Code Review Agent Blueprint
Complete code review agent that reads file trees, runs linters, checks patterns, and suggests refactors. Ready-to-run with file system access and Git integration.
Code Review Agent
An AI agent that reviews code like a senior engineer. It reads the file tree, understands context across files, runs linters, checks for anti-patterns, and suggests concrete refactors with before/after code.
Note:
Run this agent on PRs, pre-commit hooks, or as a standalone code quality tool. It reads local files — no API key needed for the file system tools, only for the LLM.
Agent File Structure
Setup
Install Dependencies
Install the OpenAI client plus linter tools the agent will invoke.
pip install openai ruff pylint
Create config.json
Configure the agent. project_root tells the agent which directory to review. ignore_patterns skips generated files and dependencies.
{
"openai_api_key": "sk-...",
"model": "gpt-4o",
"max_iterations": 8,
"project_root": "./my-project",
"ignore_patterns": ["node_modules", ".git", "__pycache__", "*.pyc", ".next", "dist", "build", "*.min.js"],
"max_file_size_kb": 200
}
Note:
The agent reads files from your local filesystem. Only point it at code you trust the agent to access. Don't run on production servers.
Verify
Review a single file to test the setup.
python agent.py --path "./src/utils.py"
The agent should output findings with file locations and severity levels.
System Prompt
You are a senior code reviewer. Review code for bugs, anti-patterns, performance
issues, security vulnerabilities, and style violations. Follow this protocol:
1. THOUGHT: What aspects of the code need review?
2. ACTION: Read files, run linters, check Git diffs
3. Categorize findings as BUG, SECURITY, PERF, STYLE, or REFACTOR
4. For each finding, provide: file path, line range, severity (critical/high/medium/low),
description, current code (snippet), suggested fix (snippet)
5. FINAL_REVIEW: Summarize all findings grouped by category
Rules:
- Flag real issues, not nitpicks. Prefer fewer high-signal findings over many low-value ones.
- If you identify a pattern (e.g., same issue in 5 files), flag it once and note the pattern.
- Suggest specific fixes with code, not vague advice.
- Acknowledge code that is well-written. Don't fabricate issues.
- For security findings, explain the attack vector, not just "this is unsafe."
Tool Definitions
Agent Tools
Values: path: string
Values: path: string, recursive?: bool
Values: path: string, linter?: 'ruff' | 'eslint'
Values: path?: string (optional, default: project_root)
Values: pattern: string, file_pattern?: string (default *.py)
Tool Implementation
# tools.py
import os
import re
import subprocess
import json
PROJECT_ROOT = None
IGNORE_PATTERNS = []
MAX_FILE_SIZE_KB = 200
def _should_ignore(path):
for pattern in IGNORE_PATTERNS:
if pattern.endswith("/"):
if path.startswith(pattern):
return True
elif path.endswith(pattern.lstrip("*")) or pattern in path:
return True
return False
def read_file(path):
full = os.path.join(PROJECT_ROOT, path)
if not os.path.exists(full):
return f"ERROR: File not found: {path}"
size_kb = os.path.getsize(full) / 1024
if size_kb > MAX_FILE_SIZE_KB:
return f"WARNING: File is {size_kb:.0f}KB, exceeds {MAX_FILE_SIZE_KB}KB limit. First 200 lines:"
with open(full, "r") as f:
lines = f.readlines()
numbered = "".join(f"{i+1:4d}| {l}" for i, l in enumerate(lines[:300]))
return numbered
def list_directory(path, recursive=False):
full = os.path.join(PROJECT_ROOT, path)
if not os.path.exists(full):
return f"ERROR: Directory not found: {path}"
items = []
for root, dirs, files in os.walk(full):
dirs[:] = [d for d in dirs if not _should_ignore(os.path.join(root, d))]
for f in files:
fpath = os.path.join(root, f)
if _should_ignore(fpath):
continue
rel = os.path.relpath(fpath, PROJECT_ROOT)
size_kb = os.path.getsize(fpath) / 1024
items.append(f"{rel} ({size_kb:.0f}KB)")
if not recursive:
break
return "\n".join(items) if items else f"Directory {path} is empty"
def run_linter(path, linter="ruff"):
full = os.path.join(PROJECT_ROOT, path)
cmd = ["ruff", "check", full, "--output-format=text"] if linter == "ruff" else ["npx", "eslint", full]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
return result.stdout or "No issues found."
except FileNotFoundError:
return f"Linter '{linter}' not installed. Run: pip install ruff"
def git_diff(path=None):
import subprocess
cmd = ["git", "diff", "--unified=3"]
if path:
cmd.append(path)
result = subprocess.run(cmd, cwd=PROJECT_ROOT, capture_output=True, text=True)
return result.stdout[:5000] if result.stdout else "No changes detected."
def search_pattern(pattern, file_pattern="*.py"):
matches = []
import fnmatch
for root, dirs, files in os.walk(PROJECT_ROOT):
dirs[:] = [d for d in dirs if not _should_ignore(d)]
for f in files:
if not fnmatch.fnmatch(f, file_pattern):
continue
full = os.path.join(root, f)
if _should_ignore(full):
continue
rel = os.path.relpath(full, PROJECT_ROOT)
with open(full, "r") as fh:
for i, line in enumerate(fh, 1):
if re.search(pattern, line):
matches.append(f"{rel}:{i}: {line.rstrip()}")
if len(matches) > 50:
matches.append("... (truncated, showing first 50 matches)")
break
return "\n".join(matches) if matches else f"No matches for pattern: {pattern}"
Agent Initialization
# agent.py
import json
import argparse
import importlib
from openai import OpenAI
import tools as agent_tools
TOOL_SCHEMAS = [
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read the full contents of a file",
"parameters": {
"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"]
}
}
},
{
"type": "function",
"function": {
"name": "list_directory",
"description": "List files and subdirectories in a directory",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string"},
"recursive": {"type": "boolean", "default": False}
},
"required": ["path"]
}
}
},
{
"type": "function",
"function": {
"name": "run_linter",
"description": "Run a linter (ruff or eslint) on a file or directory",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string"},
"linter": {"type": "string", "enum": ["ruff", "eslint"]}
},
"required": ["path"]
}
}
},
{
"type": "function",
"function": {
"name": "git_diff",
"description": "Show staged and unstaged changes as a unified diff",
"parameters": {
"type": "object",
"properties": {"path": {"type": "string"}},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "search_pattern",
"description": "Search for regex patterns across project files",
"parameters": {
"type": "object",
"properties": {
"pattern": {"type": "string"},
"file_pattern": {"type": "string", "default": "*.py"}
},
"required": ["pattern"]
}
}
}
]
SYSTEM_PROMPT = """You are a senior code reviewer. Review code for bugs, anti-patterns,
performance issues, security vulnerabilities, and style violations. Follow this protocol:
1. THOUGHT: What aspects of the code need review?
2. ACTION: Read files, run linters, check Git diffs
3. Categorize findings as BUG, SECURITY, PERF, STYLE, or REFACTOR
4. For each finding, provide: file path, line range, severity (critical/high/medium/low),
description, current code (snippet), suggested fix (snippet)
5. FINAL_REVIEW: Summarize all findings grouped by category
Rules:
- Flag real issues, not nitpicks. Prefer fewer high-signal findings over many low-value ones.
- If you identify a pattern, flag it once and note the pattern.
- Suggest specific fixes with code, not vague advice.
- Acknowledge code that is well-written. Don't fabricate issues.
- For security findings, explain the attack vector, not just 'this is unsafe.'"""
def run_agent(path, config):
client = OpenAI(api_key=config["openai_api_key"])
agent_tools.PROJECT_ROOT = config["project_root"]
agent_tools.IGNORE_PATTERNS = config.get("ignore_patterns", [])
agent_tools.MAX_FILE_SIZE_KB = config.get("max_file_size_kb", 200)
query = f"Review this code path: {path}. Read all relevant files, run linters, check for bugs, performance issues, and security vulnerabilities. Check git diff for context if available."
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": query}
]
for i in range(config.get("max_iterations", 8)):
response = client.chat.completions.create(
model=config.get("model", "gpt-4o"),
messages=messages,
tools=TOOL_SCHEMAS,
temperature=0.2
)
msg = response.choices[0].message
messages.append(msg)
if msg.content and "FINAL_REVIEW:" in msg.content:
return msg.content.split("FINAL_REVIEW:", 1)[1].strip()
if not msg.tool_calls:
messages.append({
"role": "user",
"content": "Continue your review. Use tools to read files and run linters, then provide FINAL_REVIEW."
})
continue
for tool_call in msg.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
func = getattr(agent_tools, func_name)
result = func(**func_args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
return "Agent reached max iterations."
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--path", required=True, help="File or directory to review")
parser.add_argument("--config", default="config.json")
args = parser.parse_args()
with open(args.config) as f:
config = json.load(f)
review = run_agent(args.path, config)
print(review)
Walkthrough
Reviewing src/auth.py — a file with SQL injection risk and a missing rate limiter.
Agent reads the file tree
The agent calls list_directory("src", recursive=True) to understand the project structure. It sees auth.py, database.py, middleware.py, routes.py.
Reads the target file
read_file("src/auth.py") returns the full file. The agent spots:
- f-string in SQL query (no parameterization)
- Password hashing but no salt
- No rate limiting on login endpoint
Checks related files
The agent calls read_file("src/middleware.py") to see if rate limiting is handled elsewhere. It's not. Calls search_pattern("f\".*INSERT|f\".*SELECT|f\".*DELETE") to find all raw SQL queries.
Runs linter
run_linter("src/auth.py", "ruff") confirms: no type annotations on login(), unused import, bare except clause.
Delivers FINAL_REVIEW
FINAL_REVIEW:
[CRITICAL] SECURITY — src/auth.py:42-46
SQL injection via f-string in login query. User input is concatenated directly.
Fix: Use parameterized queries.
[HIGH] SECURITY — src/auth.py:52
Password hashed without salt. Rainbow table attack vector.
Fix: Use bcrypt with auto-generated salt.
[MEDIUM] PERF — src/auth.py:18
No rate limiting on login endpoint. Brute-force attack possible.
Fix: Add rate limiter middleware or use a library like slowapi.
[LOW] STYLE — src/auth.py:1, 14, 29
Missing type annotations on public functions.
Customization
Linter Configuration
Values: path to pyproject.toml or ruff.toml
Values: path to eslint config
Values: *.py, *.js, *.ts, *.tsx
Note:
Large file handling. Files over 200KB are truncated to the first 300 lines. The agent will note this in its review. Increase max_file_size_kb if you need full-file review, but be aware this uses more tokens.
Key Takeaway
A code review agent is most useful when it catches issues humans miss: SQL injection in rarely-touched files, missing error handling in edge cases, and pattern-level bugs across multiple files. Don't use it for style nitpicks — linters already do that. Focus the agent's system prompt on high-signal findings.
Related Articles
AI Agent Blueprints & Configurations
Ready-to-run AI agent blueprints, configurations, and local setup guides. Build research agents, code reviewers, and content writers with copy-paste implementations.
Pi Coding Agent Setup Guide
Setup and configuration for Pi Coding Agent by Earendil Inc. — the minimal agent harness with TypeScript extensions, context engineering, and session trees. Powers OpenClaw under the hood.
Agent Blueprints
Ready-to-run AI agent implementations. Complete system prompts, tool definitions, and initialization code for research, code review, and content writing agents.