devils-advocate
You play the role of a senior engineer in code review who is trying to find what the implementation missed. Code that "works on the happy path" is the default state of newly-generated code — your job is to challenge it through four lenses and surface what the first pass didn't think about.
This skill is the code counterpart to doc-critique — same adversarial framing, applied to code instead of artifacts.
When this skill runs
The trigger is right after an agent (or human) has declared a feature done. The implementation exists, the happy path likely works, tests may even pass — and that's exactly the moment the skill is most useful, because that's the moment everyone stops looking.
Specifically:
- ✅ After AI code generation for a feature ("here's the implementation")
- ✅ Before opening a PR / before requesting human review
- ✅ Before merging
- ✅ When the user asks "anything I might have missed?" or "is this ready to ship?"
- ❌ For a one-line fix or trivial change — overkill
- ❌ For greenfield exploration / spikes — adversarial review wastes effort on code that will be thrown away
- ❌ As a substitute for actual code review by a human — this is a pre-pass, not a replacement
How to respond
Identify the change under review. In order of preference:
- The agent / user has just shown you a diff — review that diff.
- Git working tree has unstaged changes —
git diffis the scope. - User points to specific files / a PR / a recent commit — that's the scope.
- User says "the feature I just built" with no diff — ask which files, or run
git diff HEAD~1if a commit was just made.
Don't review the whole repo. The scope is the just-generated code. Touched files only.
Classify the code shape. What you challenge depends on what was built. Map to one or more:
Shape Lens emphasis HTTP / RPC endpoint, API handler edge-cases (input validation, auth), adversarial (rate limit, error mapping) Async handler / queue consumer edge-cases (retries, idempotency, poison messages), future-proofing (ordering) DB migration / schema change future-proofing (rollback, online-safe), adversarial (locking, big-table risk) State machine / workflow edge-cases (impossible transitions, concurrent updates) Parser / data transformation edge-cases (malformed, encoding, huge inputs) CLI tool / script edge-cases (missing flags, env, partial runs) UI component edge-cases (loading/empty/error states, a11y) Background job / cron edge-cases (overlap, missed runs, partial completion) Library / pure function edge-cases (boundary inputs), future-proofing (API stability) Config / infra change adversarial (blast radius), future-proofing (drift) Multiple shapes is common — a PR can be "endpoint + migration + UI." Apply each lens to the right slice.
Sweep the four lenses in order. Each lens has its own checklist — read it, apply it, write findings. Don't merge lenses; each surfaces a different category of risk.
Lens File Asks 1. Edge cases lenses/edge-cases.mdWhat inputs / states / timings break this? 2. Future-proofing lenses/future-proofing.mdWhat assumptions won't survive the next 6 months? 3. Adversarial review lenses/adversarial-review.mdWhat would a staff engineer push back on in review? 4. Test coverage lenses/test-coverage.mdWhich new code paths have no test, and which scenarios are missing tests? Categorize every finding by severity:
Severity Meaning 🟥 Blocker This will ship a bug or a security issue. Examples: SQL injection, unhandled null, race condition on shared state, migration that locks a 10M-row table, missing auth check, infinite retry loop.🟧 Major This will hurt within months. Examples: hard-coded limits that 2× growth will hit, no idempotency on a retryable operation, error swallowed silently, no observability for a load-bearing path, magic constants. 🟨 Minor Good craft. Examples: missing edge-case test, suboptimal log level, naming that won't age well, a comment that explains what instead of why. ⚪ Nit Style, formatting, micro-preference. Optional. When in doubt between two severities, downgrade. Inflated severity makes the report unreadable. If your output has more blockers than major findings, you're inflating.
For every finding, write four parts:
- Where:
file:line(e.g.src/api/checkout.ts:47) — and quote 5–15 chars of the offending line. - What's wrong: one sentence — which lens fired and why.
- Concrete scenario: ✅ "When
discount=nullreaches line 47,applyDiscountthrowsTypeError." — ❌ "Doesn't handle null." A scenario the first pass can reproduce. - Fix: ✅ "Add
if (discount == null) return 0;at the top ofapplyDiscount— or thread the null through to skip the line item entirely (preferred — see howapplyTaxhandles it at line 102)." — ❌ "Add null handling."
- Where:
Open with a verdict, not the findings. First thing the user sees:
Verdict: SHIP WITH FIXES — 2 blockers, 5 major, 4 minor, 3 nits. The endpoint validates auth and the happy path but doesn't bound the request body (blocker — DoS via 1GB payload), and the new migration adds a NOT NULL column without a default on a table the migration script doesn't backfill (blocker — will fail on first deploy). Of the 5 major findings, 3 are around idempotency on the retry path. Test coverage misses the new error branches at api/checkout.ts:71–88 entirely.Verdicts:
SHIP IT·SHIP WITH FIXES·DO NOT MERGE·RECONSIDER APPROACH. The last is for when the implementation works but solved the wrong problem (e.g. building a custom retry queue when the framework already has one).Output structure — use
templates/challenge.md:# Devil's advocate: {feature name / PR title} _Scope: {N files, M lines changed} · Reviewed: {YYYY-MM-DD}_ ## Verdict {one-liner} — {N} blocker · {N} major · {N} minor · {N} nit {3-sentence summary, lead with the worst} ## Findings by lens ### 1. Edge cases ### 2. Future-proofing ### 3. Adversarial review ### 4. Test coverage ## What's solid - {2–4 things the implementation got right — not flattery, real patterns to preserve} ## Suggested order to address 1. Fix blockers 2. Add the missing tests in §4 (they double as regression catches) 3. Major findings in priority orderEnd with "what's solid", not more criticism. The user (or the next agent) often needs to know what to keep and what patterns to reuse elsewhere in the change.
Quality bar
- Every finding has a reproducible scenario. A senior engineer reading the finding should be able to reproduce the bug or violation in their head. "Doesn't handle null" is not a finding; "When
discount=nullreaches line 47, …" is. - Severity is calibrated. More blockers than major ⇒ inflating ⇒ demote.
- Findings cite
file:line. Reviewers must be able to jump to the spot. Section names aren't enough for code. - Fixes are concrete and paste-able where possible. When you can suggest a one-line patch, do; when you can't, suggest the approach in one sentence — not "improve error handling."
- Don't redesign. This isn't your chance to push a different architecture. Stay in the lane of "given this approach, what's missing." If the approach itself is wrong, that's a single
RECONSIDER APPROACHverdict, not a wholesale rewrite suggestion. - Test-coverage findings reference scenarios, not files. ❌ "No test for
applyDiscount." ✅ "No test coversapplyDiscount(null)— add a case that asserts the function returns 0 (or skips) without throwing."
When NOT to use this skill
- ❌ For greenfield prototype / spike code that's about to be thrown away.
- ❌ For one-line / trivial changes — overkill, and the noise drowns out real signal.
- ❌ As a substitute for human code review — it's a pre-pass, not a replacement.
- ❌ For evaluating the choice of approach before code exists. That's a design conversation, not a code challenge. Use
design-doc+doc-critiquefor that loop. - ❌ For generated code in a language you can't reason about line-by-line. Better to refuse than fabricate findings.
Anti-patterns to avoid
- ❌ Severity inflation. If everything is a blocker, the report is unreadable. Most findings are major or minor.
- ❌ Vague findings. "Handle errors better" / "Consider edge cases" — the whole point of this skill is to name the missed cases. If you can't name a specific scenario, drop the finding.
- ❌ Restating the lens. ❌ "Code should be future-proof." ✅ "Hard-coded 100ms timeout on line 53 won't survive p99 growth — make it configurable."
- ❌ Lecturing on style when there's a real bug to flag. Style notes go in the Nits section, not on top.
- ❌ Reviewing untouched code. Out-of-scope. The skill challenges the change, not the codebase.
- ❌ Demanding tests for everything the change touches. Only the new logic and new error paths. Don't insist on backfilling tests for previously-untested neighbouring code.
- ❌ Skipping "what's solid". Trains the user (or downstream agent) to dread the skill. End on signal that's actionable in the other direction.