AI code review is a model reading a change and commenting on it. It runs as a bot on the pull request, or in a chat you paste the diff into (the lines the change adds and removes). What it checks depends on what you give it. Anthropic’s Code Review docs (2026) say Claude’s reviewer “focuses on correctness” by default. OpenAI’s Review GitHub pull requests with Codex (2026) says Codex flags only P0 and P1 issues on GitHub, its two highest priorities.
To review design, names and tests as well, give the reviewer a standard. The checklist below has one line for each of the 51 tenets in the Prickles canon, grouped by pillar. Each line links to the tenet’s page, with the full rule, its evidence and a short note to paste into the review. The same list works whether a person or an agent does the reviewing.
Where AI code review fits
A change passes several checks before it merges, and AI review is one of them. The review note on P7 Short-Lived Branches with AI+Human Review gives the order in two sentences: “AI does the first review pass. Humans approve.”
Before the pull request opens, AI6 Self-Review Pass has the agent critique and fix its own draft against its tests, spec and brief. AI6’s rule cites the reason for that grounding. In Large Language Models Cannot Self-Correct Reasoning Yet (ICLR 2024), Jie Huang and colleagues found that models “struggle to self-correct their responses without external feedback”.
Next come the linter and CI. A linter checks code against rules without running it, and CI runs the build on every push. TA1 Linter as Law makes every quality gate an error, and TA2 Duplication Detection runs a copy-paste detector on every commit. A change that breaks either fails before anyone reads it.
Then the AI reviewer reads what’s left. A person decides whether it merges. Copilot code review and Claude Code Review both leave that decision to a person by default. GitHub’s Using GitHub Copilot code review on GitHub (2026) says Copilot’s reviews “do not count toward required approvals” unless it’s set up to approve. Anthropic’s page says Claude’s findings “don’t approve or block your PR”.
What the linter checks before review
Some tenets set a number a linter can count. The Prickles packs make each one an error, so a change over the limit fails the build. A reviewer who checks those numbers again is reading what the build already reported. Anthropic’s Code Review page says as much. Among the things to tell its reviewer to skip, it lists “anything your CI already enforces like linting or spellcheck”.
Here is what the TypeScript pack’s ESLint config checks for eight of the tenets, and what it leaves for the reviewer. The Python, Java and PHP packs cap complexity and parameters at the same numbers with their own linters.
| Tenet | The TypeScript pack’s linter checks | Left for the reviewer |
|---|---|---|
| S2 Cyclomatic Caps | Complexity 3, nesting 3 deep and 10 statements a function | Whether the change raises a cap or adds an eslint-disable comment |
| S3 Parameter Object | 3 parameters a function | Whether the object that replaces a fourth parameter names a real group |
| F1 Single Responsibility Principle | 15 lines a function and 150 a file | Whether each function has one job |
| F5 Self-Documenting Code | Comments at the end of a line of code, and TODO and FIXME comments | A comment on a line of its own that says what the code does |
| S5 Inline-First | Any number but -1, 0 and 1 outside a named constant | Whether the constant’s name says what the number means |
| T2 No Escape Hatches | any written in the code, and @ts-ignore comments |
as casts, plus an any that comes from a library call such as JSON.parse |
| F3 Don’t Repeat Yourself and TA2 | Repeated strings and identical functions, and copied blocks through jscpd in CI | Two pieces of code that do the same thing in different words |
TA2’s own review note hands the last row to a model: “An LLM review pass on the change set for Type-4.” A Type-4 duplicate does the same thing as other code but is written differently, so it doesn’t look like a copy.
Everything else in a review takes judgement. Google’s reviewer guide, What to look for in a code review (2019), works through design, functionality, complexity, tests, naming and comments. One line of its summary says what F4 Simplicity says:
The developer isn’t implementing things they might need in the future but don’t know they need now.
The AI code review checklist
Each line below quotes a tenet’s rule as the canon states it in release 4.0.0, cut to the part a reviewer checks. Each link opens the tenet’s page, with its full rule, evidence and review note. In the lines below, AC stands for acceptance criteria, E2E for end-to-end, SDLC for the software development life cycle, QA for quality assurance and PR for pull request. jscpd is a copy-paste detector.
You won’t need every pillar for every change. The Process and AI Collaboration lines concern how a change was made, so check those against the pull request itself, such as its size, tests and history.
Foundations (F1 to F6)
- F1 Single Responsibility Principle: One obvious job per function, class, or module. When a second concern shows up in the same unit, peel it out so each piece has its own reason to change.
- F2 Intention-Revealing Names: Use names that answer why this exists and what it does in the domain vocabulary.
- F3 Don’t Repeat Yourself: Give every fact, rule and decision in the system one home.
- F4 Simplicity: Build for the requirement at hand, not the speculative future.
- F5 Self-Documenting Code: Do not add comments that explain what the code does.
- F6 Encapsulation: Hide the inside of your code. Callers should only call methods: never read or change fields directly.
Architecture (A1 to A11)
- A1 Screaming Architecture: Make the top-level folder structure the architecture diagram.
- A2 Three-Tier Hoisting: Default to local. A new helper lives next to the consumer until a second consumer earns the move.
- A3 Stable Dependencies Principle: Make volatile code depend on stable code, never the reverse.
- A4 Common Closure Principle: Group code by axis of change. What changes for the same reason goes in the same component, and what changes for different reasons goes in different components.
- A5 Thin Handlers: Keep every handler thin. At each boundary (HTTP, queue, CLI, websocket, Server Action) the handler parses input, dispatches one call and returns a response.
- A6 Bounded Contexts: Give each bounded context one model and its own top-level folder, and lint any import that crosses between contexts.
- A7 Coupling First: Pick the architecture with the weaker coupling.
- A8 Push to the Source of Truth: Push the work toward the source of truth. The component that owns the data runs the logic.
- A9 Liskov Substitution Principle: A subtype must work wherever its supertype is promised. No surprise throws. No weakened guarantees.
- A10 Interface Segregation Principle: Build interfaces around clients, not implementations. No caller depends on operations it never uses.
- A11 Dependency Inversion Principle: Depend on abstractions. High-level policy doesn’t depend on volatile detail.
Style (S2 to S5)
- S2 Cyclomatic Caps: Cap cyclomatic complexity per function. The number is editorial. Pick one and put it in the linter.
- S3 Parameter Object: Cap function parameters at three. The fourth parameter is the missing struct.
- S4 Guard Clauses: When a function’s preconditions are nested as conditionals, lift each one to a guarded early return at the top of the function.
- S5 Inline-First: Inline first. Extract when a name is needed. A binding justifies itself when its name carries information the expression does not.
Types & Schema (T1 to T4)
- T1 Domain-Driven Types: Brand the primitives. A user ID is not a string.
- T2 No Escape Hatches: Forbid the four escape hatches by default:
any,unknownwithout narrowing,ascasts on anything other thanas const, and@ts-ignore/@ts-expect-error. - T3 Type Guards: Where data crosses a runtime boundary (
JSON.parse,localStorage,postMessage, route input, error catch, library boundary), validate the shape with a guard before the value flows further. - T4 Schema Sovereignty: Give each schema exactly one owning application, and let only that application write to it.
Process (P1 to P8)
- P1 Test-First Development: Write the failing test before the implementation.
- P2 Spec-First Execution: Read the brief twice. Write the AC as bullets a reviewer can verify as met or unmet, not “works correctly” but “rejects an empty cart with 400 and the message cart cannot be empty”.
- P3 Definition of Done: Don’t call work done until every row of the checklist passes: coverage, lint, type-check, format, dup-check, AC verification and accessibility, each machine-checked and signed off.
- P4 Continuous Quality Feedback: Lint while you write. Type-check on save. Run the test against the file you just edited.
- P5 Shift-Left Quality: Move every check earlier in the SDLC. From QA into PR. From PR into CI. From CI into the editor.
- P6 Leave it Better Than You Found it: Tidy the line you touched. The unit is small: rename one variable, split one function, delete one dead import.
- P7 Short-Lived Branches with AI+Human Review: Cut a branch for a single piece of work and keep it under a day where you can.
- P8 Architecture Decision Records: Record each architecturally significant decision as the next numbered file in
docs/adr/, in the same pull request as the change.
Testing (TS1 to TS8)
- TS1 Living Documentation: If a behaviour matters, write a test that proves it.
- TS2 Specification by Example: Write the scenario in the language a stakeholder uses.
- TS3 Parameterised Scenarios: Replace near-duplicate scenarios with one step definition that takes
{string}placeholders. - TS4 Real-Dependency E2E: Run E2E tests against the real dependencies the production system touches: real database, real message queue, real API surface.
- TS5 Structural Assertions: Locate by structure, not content. Reach for
getByRolefirst, fall back togetByLabelText, and only thengetByText. - TS6 Behaviour Testing: Test through the public surface only.
- TS7 Test Isolation: Test only the unit you wrote. Replace or trust everything outside it: no DB, no network, no clock, no filesystem, no env.
- TS8 One Concept Per Test: One concept per test. The test name is the spec for the test. If the name needs an
and, split the test.
AI Collaboration (AI1 to AI6)
- AI1 The Intern Pattern: Treat the agent as an over-eager intern. Run the loop in four moves: plan, approve, execute, review.
- AI2 Persistent Brief: Keep the agent’s brief in the repo, not in the prompt.
- AI3 Working Context: Treat the agent’s context window as a precious, finite resource: Anthropic’s own phrase.
- AI4 Verifiable Specs: Write specs as executable contracts the agent can run and grade itself against, not passive documentation it has to interpret.
- AI5 Verify the API: Check the current docs or source before you write any call to a framework, library or SDK.
- AI6 Self-Review Pass: Draft, then critique what you drafted, then fix it, before you hand anything back.
Tooling (TA1 and TA2)
- TA1 Linter as Law: Make every quality gate an error, never a warning.
- TA2 Duplication Detection: Run jscpd in CI for every commit.
Patterns (PT1 and PT3)
- PT1 Patterns Are Vocabulary: If a class implements a known pattern, name the pattern in the identifier.
- PT3 Every Pattern Has a Failure Mode: Before introducing a pattern, read its Consequences (or Cons) section in GoF / Refactoring Guru / the canonical reference. Name the cost out loud in the same paragraph as the choice.
How to paste a tenet’s review note
- Open the tenet’s page from its line in the checklist.
- Find the box headed Copy a note and link, below the tenet’s opinion.
- Press Copy for paste. It copies the note, a blank line and the page’s full address.
- Paste it as a comment on the line it applies to, or into the AI chat with the diff.
Pick a tenet on the home page and its preview panel shows the same box. Its description on the site reads: “Grab this short comment and drop it into a PR comment or an LLM chat to prompt the right change.” Each note states the rule in plain words. Its address takes a reader to the reasoning and the evidence.
Every tenet has a note except T1 Domain-Driven Types. For T1, write the comment yourself and link the page.
A worked example of AI code review
An agent opens a pull request that adds a discount code to a shop’s cart. It adds src/orders/discount-handler.ts:
import { loadCart, saveCart } from "./cart-store";
const WELCOME_CODE = "WELCOME10";
const WELCOME_RATE = 0.1;
const BAD_REQUEST = 400;
export const discountHandler = async (request: Request): Promise<Response> => {
const body = await request.json();
const cart = await loadCart(body.cartId);
if (body.code !== WELCOME_CODE) {
return Response.json({ error: "Unknown code" }, { status: BAD_REQUEST });
}
const total = cart.total * (1 - WELCOME_RATE);
await saveCart({ ...cart, total, codes: [...cart.codes, body.code] });
return Response.json({ total });
};
It also adds this passing test:
it("applies WELCOME10 and stores the code on the cart", async () => {
const response = await discountHandler(postJson({ cartId: "cart-1", code: "WELCOME10" }));
expect(await response.json()).toEqual({ total: 90 });
expect(storedCart("cart-1")?.codes).toEqual(["WELCOME10"]);
});
Both files pass the TypeScript pack’s lint rules and type-check cleanly. Reviewed against three lines of the checklist, each line finds something.
A5 Thin Handlers
Here the handler checks the code, works out the discount and saves the cart, so the only way to test the discount rule is through an HTTP request. A5’s note, pasted as a comment on the handler, says so:
Entry points dispatch, they don't decide. The handler at the framework boundary parses input, calls a single domain function, and shapes the response. Business rules, persistence, branching all belong in functions one level in. When the transport changes, the domain doesn't move: only the handler does.
https://prickles.org/tenet/thin-handlers/A5
T3 Type Guards
In TypeScript’s types, request.json() resolves to any, so nothing checks body.cartId or body.code. The linter doesn’t object either. Sent a body with no cart ID, the handler still answers with a 200 and a discounted total. T3’s note goes on the line that reads the body:
Prove the type, don't assert it. Anywhere data crosses a runtime boundary (JSON.parse, localStorage, postMessage, route input, error catch, library boundary), validate the shape with a type predicate, a discriminated-union narrowing, or a schema (Zod / Valibot / io-ts). `as` is a lie you tell the compiler.
https://prickles.org/tenet/type-guards/T3
TS8 One Concept Per Test
This test’s name has an “and” in it. It checks two outcomes, the total and the stored code, so TS8’s note goes on the test:
One concept per test. If the test name needs an 'and', split it. Multiple Acts hide multiple causes behind a single failure message. One Act, one outcome, one test. Use it.each for parametrised assertions when the same concept needs many inputs: that's one concept, many examples, not many concepts.
https://prickles.org/tenet/one-concept-per-test/TS8
What else the checklist finds
Other lines find more. T1 asks you to brand the cart ID as its own type, and here it’s a plain string. T1 has no note, so that comment comes from the reviewer.
After the fixes
Now redeemCode holds the code check and the arithmetic, isRedeemRequest guards the body and the test splits in two. The handler parses the input, dispatches one call and shapes the response:
import { redeemCode } from "./redeem-code";
import { isRedeemRequest } from "./redeem-request";
const BAD_REQUEST = 400;
const badRequest = (error: string): Response => Response.json({ error }, { status: BAD_REQUEST });
export const discountHandler = async (request: Request): Promise<Response> => {
const body: unknown = await request.json();
if (!isRedeemRequest(body)) {
return badRequest("Send a cart ID and a code");
}
const result = await redeemCode(body);
return result.accepted ? Response.json({ total: result.total }) : badRequest(result.reason);
};
Sent the same body with no cart ID, it now answers with a 400.
An AI code review prompt
For a review in a chat, paste the diff with whichever checklist lines apply, and ask for each finding in the same shape:
Review the diff below against these rules. For each finding, give the rule's ID, the file and line, and one sentence on what breaks the rule. Don't report anything the linter or the type checker already catches. If no rule is broken, say so.
Rules:
- A5 Thin Handlers. Keep every handler thin. At each boundary (HTTP, queue, CLI, websocket, Server Action) the handler parses input, dispatches one call and returns a response. Nothing else.
- T3 Type Guards. Where data crosses a runtime boundary (`JSON.parse`, `localStorage`, `postMessage`, route input, error catch, library boundary), validate the shape with a guard before the value flows further.
- TS8 One Concept Per Test. One concept per test. The test name is the spec for the test. If the name needs an `and`, split the test.
Diff:
<paste the diff here>
Asking for the rule’s ID ties each finding to a line you can look up. The packs’ review file opens with the same request: “Report a change that breaks any tenet below as a review finding, and name the tenet’s ID.” The prompt’s last instruction gives the model a way to report nothing.
Paste only the pillars the change touches. All 51 full rule lines come to just under 10,000 characters, the size of the packs’ review file. AI3 Working Context asks you to keep what the model reads short.
Copilot code review and the other review bots
A review bot on the pull request reads rule files from the repository, and each reads a different set. The table follows each vendor’s docs as they read on 12 October 2026.
| Review bot | What it reads | The Prickles 3.0.0 file it gets |
|---|---|---|
| Copilot code review | .github/copilot-instructions.md, path-specific .github/instructions files, AGENTS.md, CLAUDE.md, GEMINI.md, REVIEW.md and skills in .github/skills |
The Copilot pack’s .github/copilot-instructions.md, holding every rule line |
| Claude Code Review | CLAUDE.md at every level, and REVIEW.md at the repository root |
The Claude Code pack’s optional REVIEW.md |
| Cursor Bugbot | .cursor/BUGBOT.md at the root and in folders above a changed file, plus Team Rules and learned rules, but not .cursor/rules |
The Cursor pack’s optional .cursor/BUGBOT.md |
| Codex code review | AGENTS.md, applying the rules under a ## Code Review Rules heading |
AGENTS.md in the Codex and universal packs, ending with that heading |
Release 3.0.0 adds REVIEW.md and .cursor/BUGBOT.md because neither reviewer reads the packs’ rule files. Each holds every tenet’s rule line under its pillar heading, in under 10,000 characters with no frontmatter. Keep the file if you use that reviewer and delete it if you don’t.
Each bot has its own limits:
- Claude Code Review is a research preview on Team and Enterprise plans. It flags a new breach of
CLAUDE.mdas a nit, and passesREVIEW.mdto the agents that find and check its findings. The local/code-reviewcommand doesn’t readREVIEW.md. - Bugbot cuts each rule at 30,000 characters and caps all the rules in one review at 100,000. Comment
bugbot run verbose=trueon a pull request to see which rules it included. - Copilot code review reads its files from the pull request’s head branch, so a pull request can change the rules that review it. GitHub advises keeping each instruction file to about 1,000 lines. GitHub Copilot instructions covers its files in more detail.
- Codex flags only P0 and P1 issues, so a naming or test finding may never reach the pull request.
Prickles hasn’t yet checked a Claude Code Review or Bugbot run against these files. The pack guides on the implementation page follow each vendor’s docs as they read on 12 October 2026. For a review bot that isn’t in the table, check its docs for the file it reads and put the rule lines there.
