Initial a-review skill: model-agnostic independent code review

Skill that sends git diffs to an independent AI model (Gemini CLI, Ollama)
for security, bug, performance, and convention review. Assembles a context
packet from CLAUDE.md + session notes + expanded diff so the reviewer
evaluates intent vs. implementation.

Includes context assembler script, structured review prompt template,
and review-ready CLAUDE.md with key patterns documented.
This commit is contained in:
Travis Herbranson 2026-04-27 20:32:56 -04:00
commit 86797abb99
6 changed files with 457 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.skill
__pycache__/
.DS_Store

67
CLAUDE.md Normal file
View File

@ -0,0 +1,67 @@
# a-review — Agent Code Review Skill
Claude Code skill that sends code changes to an independent AI model for
review. Model-agnostic — supports Gemini CLI, Ollama, or any text-in/text-out
backend.
## Architecture
```
a-review-skill/
a-review/
SKILL.md — Skill definition (triggers, workflow)
references/
review-prompt.md — Prompt template sent to the reviewer
scripts/
assemble_context.py — Builds context packet from project files
```
## Key Patterns
**Context packet.** The reviewer gets four pieces of information assembled
by `scripts/assemble_context.py`: CLAUDE.md (architecture + conventions),
latest session notes (intent), expanded diff (changes in surrounding context),
and diff stat (scope summary). This mirrors how a human reviewer works —
read the PR description, understand conventions, then evaluate the diff.
**Model-agnostic backend.** The skill doesn't call the reviewer directly.
It builds the context packet and pipes it to whatever backend is configured.
Gemini CLI uses `gemini -p` with `--approval-mode plan` (read-only). Ollama
uses the HTTP generate API. New backends only need a way to accept text in
and return text out.
**Read-only reviewer.** The reviewer model runs in plan/read-only mode. It
cannot modify files. Its job is to produce findings, not fixes. The authoring
agent (Claude Code) decides what to act on.
**Complements zero-check, doesn't replace it.** zero-check is deterministic
(lint, SAST, tests, deps). a-review is subjective (security patterns, logic
gaps, convention violations, intent-vs-implementation). They run in sequence:
zero-check first, a-review after.
## Development
```bash
# Test the context assembler
cd /path/to/any/git/project
python /path/to/a-review-skill/a-review/scripts/assemble_context.py
# Run a review manually with Gemini
python scripts/assemble_context.py | \
GEMINI_CLI_TRUST_WORKSPACE=true gemini -p "$(cat references/review-prompt.md)" \
--approval-mode plan
# Package as .skill file
cd a-review && zip -r ../a-review.skill SKILL.md references/ scripts/
```
## Session Notes
After each coding session, append a session entry to
`sessions/a-review-skill.md` in this project folder.
See that file for the format convention and existing entries.
Granularity: one entry per logical change. A new function, a new route,
a new template, a new config setting, or a bug fix each get their own
entry. Don't combine multiple changes into one entry. If it would be a
separate item in a code review, it's a separate entry here.

141
a-review/SKILL.md Normal file
View File

@ -0,0 +1,141 @@
---
name: a-review
description: >
Run an independent AI code review after coding sessions. Sends the git diff,
CLAUDE.md, and session notes to a second model (Gemini CLI, Ollama, or any
text-in/text-out backend) for security, bug, performance, and convention
review. Use this skill after completing code changes, after zero-check passes,
when the user asks for a review, or when you want a second opinion on code
quality. Also triggers on "review my changes", "get a second opinion",
"run a-review", or "check this with gemini/ollama".
---
# a-review — Agent Code Review
Send code changes to an independent AI for review. The reviewer gets project
context (architecture, conventions, intent) alongside the diff so it can
evaluate what was done against what was intended.
This is not a linter or SAST tool — that's what zero-check does. This is a
subjective second opinion from a different model: "did you miss something
a linter wouldn't catch?"
## When to Run
- After a coding session with substantive changes
- After zero-check passes (review complements validation)
- When the user asks for a review or second opinion
- Before committing changes to a shared branch
Do not run on trivial changes (typo fixes, comment edits, formatting-only).
## Workflow
### Step 1: Collect the diff
```bash
# Default: changes since last commit
git diff HEAD~1
# Or staged changes if nothing committed yet
git diff --cached
# Expanded context for reviewer (10 lines surrounding each change)
git diff -U10 HEAD~1
```
Use `HEAD~1` by default. If the user says "review staged changes" or
there are no commits yet, use `--cached`.
### Step 2: Assemble the context packet
The reviewer needs four things:
1. **CLAUDE.md** — read from the project root. This gives the reviewer
the architecture, key patterns, and conventions to review against.
If CLAUDE.md doesn't exist, skip this and note it in the output.
2. **Session notes** — read the latest session entry from
`sessions/<project-slug>.md`. This tells the reviewer what was
intended, so it can compare intent vs. implementation.
If no session notes exist, skip this.
3. **Expanded diff**`git diff -U10 HEAD~1` so the reviewer sees
each change in its surrounding context (the function it lives in,
the class it belongs to).
4. **Diff stat**`git diff --stat HEAD~1` for a quick summary of
what files changed and how much.
Run the assembler script to combine these:
```bash
python scripts/assemble_context.py
```
This reads the project, builds the context packet, and prints it to
stdout. Pipe it to the reviewer.
### Step 3: Send to the reviewer
Check for a `.a-review.yml` config file in the project root. If it
exists, use the configured backend. If not, default to Gemini CLI.
**Gemini CLI:**
```bash
python scripts/assemble_context.py | \
GEMINI_CLI_TRUST_WORKSPACE=true \
gemini -p "$(cat references/review-prompt.md)" \
--approval-mode plan
```
**Ollama:**
```bash
CONTEXT=$(python scripts/assemble_context.py)
curl -s http://localhost:11434/api/generate \
-d "{\"model\": \"codellama\", \"prompt\": \"$(cat references/review-prompt.md)\n\n$CONTEXT\", \"stream\": false}" \
| jq -r '.response'
```
The `--approval-mode plan` flag for Gemini is important — it keeps
the reviewer in read-only mode so it can't modify files.
### Step 4: Save the output
Save the review to `reviews/a-review-YYYY-MM-DD.md` in the project.
Create the `reviews/` directory if it doesn't exist.
If a review already exists for today, suffix with `-2`, `-3`, etc.
Add a header to the saved file:
```markdown
# a-review — YYYY-MM-DD
**Model:** <model name/version>
**Diff:** HEAD~1 (N files, M lines changed)
**Context:** CLAUDE.md + session notes included
---
<reviewer output here>
```
### Step 5: Surface findings
Read the saved review and summarize the findings for the user. Group
by severity if the reviewer used severity labels. Highlight anything
marked critical or security-related.
If the reviewer found actionable issues, offer to fix them.
## Reference Files
| File | Purpose |
|------|---------|
| `references/review-prompt.md` | The prompt template sent to the reviewer |
| `scripts/assemble_context.py` | Builds the context packet from project files |
Load `references/review-prompt.md` when building the review command.
Load `scripts/assemble_context.py` when you need to understand or
modify the context assembly logic.

View File

@ -0,0 +1,21 @@
You are reviewing code changes to a software project. You will receive a context packet containing the project's architecture documentation (CLAUDE.md), the developer's session notes describing what they intended, and the actual diff of what changed.
Review the changes for:
1. **Security vulnerabilities** — injection, path traversal, auth bypass, data exposure, hardcoded secrets, unsafe deserialization
2. **Bugs and logic errors** — null/None handling, off-by-one, race conditions, missing error handling, unreachable code, incorrect conditionals
3. **Performance issues** — unnecessary loops, missing caching, repeated I/O, N+1 queries, unbounded data structures
4. **Convention violations** — patterns defined in CLAUDE.md that the changes don't follow (e.g., "all writes go through the cache layer" but a new write bypasses it)
5. **Intent vs. implementation** — compare what the session notes say was intended against what the diff actually does. Flag gaps, unfinished work, or deviations from stated intent.
For each finding:
- State the severity: [CRITICAL], [WARNING], or [INFO]
- Name the file and approximate location
- Explain what the issue is and why it matters
- Suggest a fix if one is obvious
Skip stylistic preferences (naming conventions, comment style, blank lines) unless they violate documented conventions in CLAUDE.md. Focus on correctness and safety.
Output your findings as structured markdown with severity headers. End with a brief summary of overall code quality and any patterns you noticed.
If the changes look clean and you have no findings, say so explicitly — don't manufacture issues to fill space.

View File

@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""Assemble the context packet for a-review.
Reads CLAUDE.md, latest session notes, expanded diff, and diff stat
from the current git repository. Outputs a structured context document
to stdout for piping to the reviewer model.
Usage:
python scripts/assemble_context.py # diff against HEAD~1
python scripts/assemble_context.py --staged # diff of staged changes
python scripts/assemble_context.py --context 15 # 15 lines of context
"""
from __future__ import annotations
import argparse
import subprocess
import sys
from pathlib import Path
def _run(cmd: list[str], cwd: Path | None = None) -> str:
"""Run a command and return stdout, or empty string on failure."""
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
cwd=cwd,
check=False,
)
return result.stdout.strip()
except FileNotFoundError:
return ""
def _find_project_root() -> Path:
"""Find the git repository root."""
root = _run(["git", "rev-parse", "--show-toplevel"])
if root:
return Path(root)
return Path.cwd()
def _read_file_if_exists(path: Path) -> str:
"""Read a file if it exists, return empty string otherwise."""
if path.exists():
return path.read_text()
return ""
def _find_latest_session_entry(project_root: Path) -> str:
"""Find and return the latest session notes entry."""
sessions_dir = project_root / "sessions"
if not sessions_dir.exists():
return ""
# Find session note files
session_files = sorted(sessions_dir.glob("*.md"), key=lambda f: f.stat().st_mtime, reverse=True)
if not session_files:
return ""
content = session_files[0].read_text()
# Extract the last ## Session entry
sections = content.split("\n## Session")
if len(sections) < 2:
return content # Return full file if no session headers
last_section = "## Session" + sections[-1]
return last_section.strip()
def assemble(staged: bool = False, context_lines: int = 10) -> str:
"""Assemble the full context packet."""
root = _find_project_root()
parts: list[str] = []
# Header
project_name = root.name
parts.append(f"# Code Review Context: {project_name}")
parts.append("")
# CLAUDE.md
claude_md = _read_file_if_exists(root / "CLAUDE.md")
if claude_md:
parts.append("## Project Documentation (CLAUDE.md)")
parts.append("")
parts.append(claude_md)
parts.append("")
else:
parts.append("## Project Documentation")
parts.append("No CLAUDE.md found in project root.")
parts.append("")
# Session notes
session = _find_latest_session_entry(root)
if session:
parts.append("## Latest Session Notes")
parts.append("")
parts.append(session)
parts.append("")
else:
parts.append("## Session Notes")
parts.append("No session notes found.")
parts.append("")
# Diff stat
diff_ref = ["--cached"] if staged else ["HEAD~1"]
stat = _run(["git", "diff", "--stat", *diff_ref], cwd=root)
if stat:
parts.append("## Diff Summary")
parts.append("```")
parts.append(stat)
parts.append("```")
parts.append("")
# Expanded diff (with surrounding context)
expanded = _run(
["git", "diff", f"-U{context_lines}", *diff_ref],
cwd=root,
)
if expanded:
parts.append(f"## Expanded Diff ({context_lines} lines context)")
parts.append("```diff")
parts.append(expanded)
parts.append("```")
parts.append("")
# Standard diff
diff = _run(["git", "diff", *diff_ref], cwd=root)
if diff:
parts.append("## Diff")
parts.append("```diff")
parts.append(diff)
parts.append("```")
elif not expanded:
parts.append("## Diff")
parts.append("No changes found.")
return "\n".join(parts)
def main() -> None:
parser = argparse.ArgumentParser(description="Assemble a-review context packet")
parser.add_argument("--staged", action="store_true", help="Review staged changes instead of HEAD~1")
parser.add_argument("--context", type=int, default=10, help="Lines of surrounding context (default: 10)")
args = parser.parse_args()
output = assemble(staged=args.staged, context_lines=args.context)
sys.stdout.write(output)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,70 @@
# a-review Skill — Session Notes
Session log for the a-review-skill project. Each session is a dated
heading with a list of changes. Each change has a label, narrative, and
four structured fields (What, Why, How, Touches).
---
## Session — 2026-04-27
### SKILL.md Definition
Wrote the skill definition with trigger description, five-step workflow
(collect diff, assemble context, send to reviewer, save output, surface
findings), and reference file pointers. Triggers automatically after
coding sessions and on manual requests. Supports Gemini CLI and Ollama
backends with examples for both.
- **What:** SKILL.md with model-agnostic review workflow
- **Why:** Need a reusable skill that runs independent code review from
any AI backend, not just Gemini CLI one-offs
- **How:** Five-step workflow: git diff, assemble_context.py, pipe to
backend with review-prompt.md, save to reviews/ folder, summarize
findings. Backend selection via .a-review.yml config or default to
Gemini CLI.
- **Touches:** a-review/SKILL.md (new)
### Review Prompt Template
Wrote the structured prompt sent to the reviewer model. Covers five
review areas (security, bugs, performance, conventions, intent-vs-
implementation) with severity levels and output format guidance.
Explicitly tells the reviewer to skip stylistic preferences and not
manufacture findings.
- **What:** Review prompt template at references/review-prompt.md
- **Why:** Structured prompt produces consistent, actionable findings
across different models
- **How:** Markdown document with numbered review areas, severity
definitions ([CRITICAL], [WARNING], [INFO]), and output format
template. Includes instruction to compare session notes intent
against diff implementation.
- **Touches:** a-review/references/review-prompt.md (new)
### Context Packet Assembler
Python script that collects CLAUDE.md, latest session notes, expanded
diff (with surrounding context), and diff stat from the current git repo.
Outputs a structured markdown document to stdout for piping to the
reviewer.
- **What:** `assemble_context.py` CLI script with --staged and --context options
- **Why:** Reviewer needs project context alongside the diff to produce
meaningful findings — a naked diff review misses convention violations
and intent mismatches
- **How:** Uses subprocess to call git commands. Finds project root via
`git rev-parse --show-toplevel`. Reads CLAUDE.md and latest session
entry from sessions/ directory. Builds markdown document with labeled
sections. Supports --staged for uncommitted changes and --context N
for surrounding line count (default 10).
- **Touches:** a-review/scripts/assemble_context.py (new)
### Project Documentation
Added CLAUDE.md with key patterns documenting the context packet approach,
model-agnostic backend design, read-only reviewer constraint, and the
complementary relationship with zero-check.
- **What:** CLAUDE.md with architecture and key patterns
- **Why:** Future sessions need to understand design decisions, especially
the separation between context assembly, prompt template, and backend
- **How:** Follows the review-ready CLAUDE.md convention with file tree,
key patterns, dev workflow, and session notes pointer.
- **Touches:** CLAUDE.md (new)