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:
commit
86797abb99
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.skill
|
||||
__pycache__/
|
||||
.DS_Store
|
||||
67
CLAUDE.md
Normal file
67
CLAUDE.md
Normal 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
141
a-review/SKILL.md
Normal 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.
|
||||
21
a-review/references/review-prompt.md
Normal file
21
a-review/references/review-prompt.md
Normal 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.
|
||||
155
a-review/scripts/assemble_context.py
Normal file
155
a-review/scripts/assemble_context.py
Normal 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()
|
||||
70
sessions/a-review-skill.md
Normal file
70
sessions/a-review-skill.md
Normal 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)
|
||||
Loading…
Reference in New Issue
Block a user