Skip to content
Back to skills

Code Review

ASecurity

Review code changes adversarially. Use when the user says "review", "code review", or "review story {X}". Also run by the lw-story-reviewer subagent after every /dev-story, as the independent review.

  • 2 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 19, 2026
ai-agentsrustgoswiftbashsqlcode-reviewgitapisecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 23, 2026

npx -y skills add rterakedis/leanwheel-skills --skill code-review --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Code Review?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Code Review
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/rterakedis-code-review/badge)](https://www.skillsdirectory.com/skills/rterakedis-code-review)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: code-review
description: Review code changes adversarially. Use when the user says "review", "code review", or "review story {X}". Also run by the lw-story-reviewer subagent after every /dev-story, as the independent review.
---

# Code Review Skill

**Goal:** Find real bugs, missing behavior, AC violations. Assume problems exist — your job is to find them. Zero findings requires explicit justification ("re-analyzed, clean because…"), not silence.

**Independence (DD-75).** You did not write this diff, and you do not inherit the author's view of it. The diff and the story's contract are the evidence. The author's own account — the story's `## Dev Agent Record` (Debug Log, Completion Notes) — is read only *after* your passes, to check its claims (e.g. the recorded sabotage checks), never to decide what to look at. A claim the diff contradicts — a test that doesn't exist, a guard that isn't there — is a `patch` finding against the verification record (add the missing check, or correct the record), never a note. If you did write this diff in this same context (a flywheel's no-subagent fallback), say so in the report and omit `--standalone` from the ledger line.

## Step 1 — Find the Diff

Check in order (stop when found):
0. A `REVIEW HANDOFF` (from dev-story)? The diff is `git diff {BASE}` — everything since the base, committed or not — **plus** new files, which `git diff` omits while untracked: `git ls-files --others --exclude-standard`. The story is `STORY`.
1. Explicit argument (PR, branch, commit, story file)?
2. Story with `Status: review` in `docs/epics/`?
3. Current branch not main/master?
4. Ask: "What do you want to review?"

If empty diff: stop. If >3000 lines: warn, offer to chunk by file.

## Step 2 — Load Context

- If story file identified: read it up to its author record — `sed '/^## Dev Agent Record/q' {story}` — and leave the rest for after the passes (see Independence).
- Read CLAUDE.md if exists.
- **If `docs/setup/swift/` exists** (Apple platform project): read `docs/setup/swift/anti-patterns.md` and `docs/setup/swift/state-management.md` before beginning passes — use them as the rejection criteria for Pass A and Pass C. Also read `docs/setup/swift/core-data-cloudkit.md` if present and the diff touches the data model, persistence, or sync; `docs/setup/swift/demo-data-and-copy.md` if the diff touches seed/preview data or copy on a regulated surface; `docs/setup/swift/xcode-footguns.md` if the diff touches the `.xcodeproj`. Also read `docs/setup/swift/iphone-duo.md` if present and the diff touches layout geometry, toolbar/tab-bar construction, or sheet presentation; read `docs/setup/swift/ipados-specific.md` if present and the diff touches navigation, split view, or multi-window code; read `docs/setup/swift/macos-specific.md` if present and the diff touches menus, windows, settings, or toolbar code.
- **If `docs/setup/web/` exists** (web/SSG project) and the diff touches templates, markup, or styles: read `docs/setup/web/anti-patterns.md` and `docs/setup/web/css-design-system.md` — use them as rejection criteria.
- **If the diff touches user-visible UI** and `docs/ux/DESIGN.md` exists: read its frontmatter token block (and the story's `### Design Contract` if present) — required input for Pass E.
- Confirm: "Reviewing {N} files, {+/-lines}. Story context: {yes/no}."

## Step 3 — Six Review Passes

Work each pass independently. Look for what's *missing* (absent behavior, unhandled path) as hard as what's *wrong*.

**Pass A: Blind Correctness** — read the diff as if you wrote none of it
- Logic errors: off-by-one, wrong operator, inverted condition, stale state
- Null/zero/empty dereference: unchecked optional, missing guard before index
- Unchecked return values: ignored errors, discarded Results/Optionals
- Resource leaks: unclosed file handles, connections, or goroutines without cleanup
- Concurrency: shared mutable state without synchronization, TOCTOU races
- Error handling: swallowed errors, generic catch blocks that hide root cause
- Type coercion / implicit conversion producing unexpected values
- Dead code or unreachable branches that indicate a logic mistake

**Pass B: Security & Data** — adversarial user mindset
- Injection: SQL/command/HTML built with string concatenation from user input
- Auth: missing authentication check, missing authorization/ownership check (IDOR)
- Secrets: hardcoded keys, tokens, passwords in source or committed config
- Data exposure: API response leaks fields the caller shouldn't see; PII in logs
- Input validation: missing size/type/range checks at trust boundaries
- Session/token: insecure storage (localStorage), missing expiry, no rotation
- Rate limiting absent on auth, registration, reset, or expensive endpoints
- **Deep security (conditional):** if the story's Dev Notes has a non-blank `### Security Sensitivity`, also run the matching categories from `skills/security-review/SKILL.md`.

**Pass C: Edge Case & Regression** — break it with inputs
- Boundary values: empty collection, zero, negative, max int, nil/null
- Missing error paths: what happens when the external call fails, returns empty, or times out?
- Callers outside the diff: does this change break existing call sites?
- Unchecked assumptions baked into the new code (e.g., "list is always non-empty")
- Regression: does the change touch behavior relied on elsewhere?
- **Serialization-shaped stories** (backup, export/import, sync, any serializer): round-trip tests must assert the at-risk fields **by name**, never entity counts, and the field set must have been enumerated against the live schema — a curated subset silently drops data every round-trip. Source of the rule: `create-story` → Behavior Contract.

**Pass D: Acceptance Audit** (only if story loaded)
- Each AC: implemented? fully? or only the happy path?
- ACs that contradict each other or the architecture
- Constraints in Dev Notes that were ignored
- Files the story said would be touched that weren't (missing implementation)
- Invariants left `[ ]` UNVERIFIED in `### Invariant Verification` — each is a finding

**Pass E: Design Compliance** (only if the diff touches user-visible UI and `docs/ux/DESIGN.md` or a `### Design Contract` exists)
- Hardcoded colors/spacing/type values where a DESIGN.md token exists (near-miss hex counts)
- Required states from EXPERIENCE.md missing: empty, loading, error, offline
- Dark mode: load-bearing surfaces missing the second appearance
- Platform checklist: Apple — tap targets, Dynamic Type, safe areas, SF Symbols; web — semantic elements, focus visibility, `alt`, heading hierarchy, off-token `!important`/inline styles
- New component that near-duplicates one in `docs/ux/components-built.md`
- **Automation contract** (Apple projects with `docs/setup/swift/testability.md`): an interactive element or dynamic list row shipped with **no** `.accessibilityIdentifier`; an identifier that **differs from the name the story's Design Contract assigned** (HIGH — a silently renamed identifier breaks every flow, eval, and screenshot addressing it, and the break surfaces later as a red test in someone else's story); an identifier not matching `{feature}-{element}-{role}` kebab-case (MEDIUM); a **new screen with no deep-link route** (MEDIUM — unreachable by `/design-verify` and by every future flow). Skip on projects without the testability foundation — route those to `/swift-audit` instead of filing per-diff findings.
- Unresolved findings in the story's `### Design Verification` section
- **Never accept "the string/control is in the source" as evidence a UI change is reachable** — presence ≠ renders; require rendered evidence (see `dev-story` verification step).
- **Dark patterns** (check the diff against EXPERIENCE.md's `## Engagement & Persuasion`, if present): pre-checked paid or consent opt-ins; smart defaults that pre-select the higher-cost/higher-commitment choice; fake or endowed progress indicators; countdown/urgency with no real deadline; guilt-decline (confirmshaming) copy; decoy pricing. Flag any lever in the diff that isn't backed by an honest entry in that section — shipping user-hostile behavior is HIGH severity (it erodes trust and is a support/churn liability, not just a style nit).

**Pass F: Over-Engineering** — hunt complexity and code that shouldn't exist (correctness/security belong to Passes A–D; do not duplicate them here). One tagged line per finding:
- `delete:` — dead code or a speculative feature; replacement is nothing.
- `stdlib:` — a hand-rolled thing the standard library already ships (name it).
- `native:` — a dependency or code doing what the platform/framework already does (name the feature).
- `yagni:` — an abstraction with one implementation, a config nobody sets, a layer with one caller.
- `shrink:` — same logic, fewer lines (show the shorter form).

Boundary: a single smoke test / assert-based self-check is the minimum — never flag it for deletion; and never simplify away validation, security, or accessibility. End the pass with `net: −N lines possible` or `Lean already.` if there's nothing to cut.

## Step 4 — Triage and Severity

For each finding assign both a category and severity:

**Category:**
- `decision-needed` — ambiguous; needs user input before a fix is possible
- `patch` — clear bug; unambiguous fix exists
- `fix-now` — outside the story's ACs but small and obviously right; apply it instead of deferring
- `defer` — pre-existing issue not introduced by this diff
- `dismiss` — confirmed false positive (state reason)

**`fix-now` ceiling** — a finding qualifies only when **all** hold:
- small (≤ ~10 lines, one file) and adjacent to the diff already under review;
- provably safe — covered by an existing test, or by one written in the same pass;
- introduces no new dependency, schema change, public API change, or user-visible copy change;
- describable in one line of the commit message.

Anything failing a condition is `defer`, unchanged. Do not stretch the ceiling to avoid logging — `fix-now` is a shorter door, not a wider one.

**Severity** (for `patch` and `decision-needed`):
- `HIGH` — data loss, auth bypass, secret exposure, injection, crash in main path
- `MEDIUM` — incorrect behavior under reachable conditions, missing error handling, IDOR
- `LOW` — edge case with low probability, best-practice gap, missing guard on unlikely path

**Pass F (over-engineering) findings** route as `patch` when the cut is clear (usually LOW/MEDIUM), `fix-now` when the cut is clear but outside the ACs and within the ceiling below, or `defer` / `decision-needed` when removing code needs a judgment call. They are cleanups, not correctness bugs — never HIGH.

Merge duplicates. Drop `dismiss`. If zero remain: write `Clean review — no patches, fix-nows, or deferred items.` and justify briefly why each pass came up empty.

## Step 5 — Report and Act

**Write findings:** If story loaded, write to `### Review Findings`:
- If clean review: write `Clean review — no patches, fix-nows, or deferred items.` plus one-sentence justification per pass.
- Otherwise, order by severity (HIGH first), then category:
  - `- [ ] [HIGH/MEDIUM/LOW] [Decision] {title} — {detail}`
  - `- [ ] [HIGH/MEDIUM/LOW] [Patch] {title} [{file}:{line}]`
  - `- [x] [Fix-Now] {title} [{file}:{line}]`
  - `- [ ] [Defer] {title} — pre-existing ({file}:{line})`

Every `fix-now` is recorded here even though it's already applied — the line is what makes it reviewable rather than invisible.

**Summary:** "Review complete. {D} decision, {P} patches, {F} fix-now, {W} deferred."

**Resolve decisions:** All at once, wait for answers, record, convert to patch/fix-now/defer/dismiss.

**Auto-patch:** Apply immediately, mark `[x]`. If can't auto-apply, leave `[ ]`. Apply `fix-now` items the same way — but if one turns out to need more than the ceiling allows, revert it and re-triage as `defer`.

**Verify green:** If any patch or fix-now changed code and a toolchain is present, **run a real build + test** before marking the review done (quiet invocation + `tee` to `.leanwheel/logs/`, per dev-story → Build & Test Gate or the project's `## Quiet commands`). A patch is only resolved once the toolchain confirms it compiles and tests stay green — never close a review on a fix verified by reading alone. If red, fix and re-run, or leave the finding `[ ]` and set Status `in-progress`. If the re-run is **blocked** (toolchain busy, simulator held by another session, environment failure), the review is not done: set Status `in-progress` and report the blocker — a blocked verify is treated exactly like a red one. Skip only on a clean review or when no toolchain exists (state which). If `docs/evals/` exists, also run `bash scripts/evals.sh --epic {n} --quiet` (per evals → RUN) — a failing eval is a regression and blocks closing just like a red build.

**A new gate is not done until it has been shown to fail** — scoped to tests, evals, and assertions written during this review's patches. Apply dev-story → Build & Test Gate's fail-first rule (`scripts/sabotage.sh --name {TestName} -- {filtered gate cmd}` preferred; record the discriminating check in Completion Notes). A gate only ever observed green is unverified (DD-11).

**Eval Scorecard:** Emit a structured pass/fail line — this is just turning the passes you already ran into scored output, **no extra model calls**. Execute **SCORE** from `skills/evals/SKILL.md`: one verdict per applicable dimension and an overall gate. Append to `### Review Findings`:

```
RUBRIC: correctness PASS · edge-cases PASS · ac-coverage PASS · design PASS · simplicity PASS · security n/a → GATE PASS
```

The `simplicity` dimension scores Pass F (it's just reporting a pass already run — no extra model calls). A dimension is FAIL if any unresolved `[ ]` finding maps to it; GATE is PASS only when every applicable dimension is PASS. The gate feeds the flywheel checkpoint and the ledger.

**Verify-green gate rule:** GATE PASS additionally requires that Verify green actually ran green this session (or was legitimately skipped: no code changed, or no toolchain — state which). A red or blocked verify caps the outcome at Status `in-progress` — there is no qualified PASS ("PASS pending verify") in any report field or ledger line; `scripts/ledger.sh` refuses one mechanically.

**Pull deferred forward:** If any `[ ] [Defer]`, execute **LOG-AND-SCHEDULE** from `skills/deferred/SKILL.md` for each deferred item (title = finding title, detail = finding detail, source = file:line). `[Fix-Now]` items never go to the log — they're already fixed, and that intake rejects them.

**Update epic context:** Before updating status, check for discoveries made during review or fix application that future stories/epics should know about. Look for:
- Constraints or invariants uncovered while fixing bugs
- Schema, API, or integration details that differed from assumptions
- Patterns established or broken during patches
- Any "we learned X the hard way" that isn't obvious from the code

If a story file was loaded, identify its epic (e.g., `epic-2` from `epic-2-story-3.md`). Check whether `docs/epics/epic-<n>-context.md` exists. If discoveries exist, append them under a `## Story {id} Learnings` heading. If no discoveries worth capturing, skip silently. (These project-canonical learnings are promoted into `docs/architecture.md` at the epic boundary by `/epic-flywheel`.)

If the context file does **not** exist, do not create it — a stub newer than `prd.md`/`architecture.md` would satisfy create-story's timestamp check and silently replace the full distillation with an empty file. Instead surface `EPIC CONTEXT MISSING: docs/epics/epic-<n>-context.md` in the review output so the orchestrator/user regenerates it via create-story's Generate Cache section.

**Flag guidance drift:** If, while using `docs/setup/swift/` or `docs/setup/web/` as rejection criteria (Step 2), you found that the *guidance itself* is wrong, stale, or contradicted by what the codebase consistently and intentionally does (not a one-off bug — that's a finding), execute **DRIFT** from `skills/docs-sync/SKILL.md` with the contradiction. It emits a `/refresh-swift|web` advisory and never mutates `docs/setup/*` (external-sourced canon). Skip silently if the guidance held.

**Update component inventory:** If the diff introduced a new reusable UI component (a view/element designed to be used in more than one place), append a row to `docs/ux/components-built.md` — create the file with this header if missing:

```markdown
# Components Built
*Auto-maintained by /code-review. /create-story injects this into UI stories — reuse these, never create near-duplicates.*

| Component | File | Purpose | Variants/Props |
|---|---|---|---|
```

One-off layout subviews don't qualify; only components future stories should reuse. Skip silently if no new reusable component shipped.

**Ledger:** Append one `code-review` line via `scripts/ledger.sh` (never hand-write the JSON — the script owns the schema, normalizes the model name, stamps the timestamp, and no-ops if `docs/metrics/` is absent):
`bash scripts/ledger.sh code-review --story {id} --model {model} --build-test green|red|blocked|n/a --rubric-gate PASS|FAIL|n/a --evals P/T --patched {n} --decisions {n} --deferred {n} [--standalone] [--notes "≤300 chars"]`
Pass `--standalone` for every review except a same-context one (see Independence). Notes are a one-line pointer — review detail lives in `### Review Findings`, not the ledger. The script enforces the verify-green gate rule below and refuses a qualified PASS.

**Update status** (in the YAML frontmatter — the source of truth; never as a `**Status:**` body line):
- All resolved: `status: done` → **CLOSE-ISSUE**
- Unresolved patches: `status: in-progress` → **TRANSITION**

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.