Skip to content

Commit 19ade39

Browse files
committed
feat(examples): add skills-based code review agent with sandbox and database
Implements #92 — automated code review agent with: - 6 rule scanners (security, async, resource leak, DB lifecycle, missing tests, secret info) - Filter chain for pre-execution safety interception - Local sandbox with timeout/output limits/env isolation - Deduplication and sensitive info redaction - JSON + Markdown report generation - SQLite persistence (4 tables: tasks, findings, sandbox_runs, filter_logs) - 8 test fixture diffs covering all acceptance criteria - 116 tests across 8 test modules (unit + integration + acceptance) Signed-off-by: coder-mtj <coder-mtj@users.noreply.github.com>
1 parent a547d6b commit 19ade39

42 files changed

Lines changed: 3718 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Code Review Agent
2+
3+
基于 tRPC-Agent Skills + 沙箱 + 数据库的自动化代码评审 Agent。
4+
5+
## 功能概述
6+
7+
读取 git diff → 安全检查(Filter)→ 规则扫描(6 类)→ 沙箱执行 → 去重脱敏 → 结构化报告 → 数据库存储。
8+
9+
## 快速开始
10+
11+
```bash
12+
# 安装依赖
13+
pip install -e ".[gepa]"
14+
15+
# 运行单个 diff 评审
16+
python run_review.py --diff-file fixtures/diffs/security.diff
17+
18+
# Dry-run 模式(无需 API Key)
19+
python run_review.py --diff-file fixtures/diffs/security.diff --dry-run
20+
21+
# 运行测试
22+
python -m pytest tests/ -v
23+
```
24+
25+
## 扫描规则(6 类)
26+
27+
| 类别 | 说明 | 示例 |
28+
|------|------|------|
29+
| Security | 命令注入、不安全反序列化、动态导入 | `os.system()`, `eval()`, `pickle.loads()` |
30+
| Async Error | 事件循环阻塞、缺少 await | `time.sleep()` in async, 未 await 协程 |
31+
| Resource Leak | 文件句柄未关闭、连接泄漏 | `open()` 无 with, HTTP 无 session |
32+
| DB Lifecycle | 游标/连接未关闭、事务未提交 | `cursor.execute()` 无 commit |
33+
| Missing Tests | 新函数无对应测试 | `def new_func()``test_new_func` |
34+
| Secret Info | 硬编码密钥/密码/Token | API keys, GitHub tokens, JWT |
35+
36+
## 目录结构
37+
38+
```
39+
examples/skills_code_review_agent/
40+
├── run_review.py # 入口
41+
├── pipeline/ # 评审 pipeline
42+
│ ├── diff_parser.py # Unified diff 解析
43+
│ ├── scanners.py # 6 类规则扫描器
44+
│ ├── filter_chain.py # Filter 安全拦截
45+
│ ├── sandbox.py # 沙箱执行
46+
│ ├── dedup.py # 去重降噪
47+
│ ├── redaction.py # 敏感信息脱敏
48+
│ ├── report.py # JSON/MD 报告生成
49+
│ └── telemetry.py # 监控审计
50+
├── storage/ # SQLite 持久化
51+
│ ├── schema.sql # 数据库 Schema
52+
│ ├── models.py # 数据模型
53+
│ └── dao.py # 数据访问层
54+
├── fixtures/diffs/ # 8 个测试 diff
55+
├── tests/ # 测试(116 个)
56+
└── skills/code-review/ # Code-review Skill 定义
57+
```
58+
59+
## 输出格式
60+
61+
### JSON (`review_report.json`)
62+
```json
63+
{
64+
"task_id": "review-20260707-...",
65+
"summary": { "total_findings": 6, "by_severity": {...} },
66+
"findings": [
67+
{
68+
"severity": "critical",
69+
"category": "security",
70+
"file": "handler.py",
71+
"line": 8,
72+
"title": "os.system() with unsanitized input",
73+
"evidence": "os.system(user_input)",
74+
"recommendation": "Use subprocess.run() with shell=False",
75+
"confidence": 0.9,
76+
"source": "security_scanner"
77+
}
78+
],
79+
"filter_summary": {...},
80+
"sandbox_summary": {...},
81+
"telemetry": {...}
82+
}
83+
```
84+
85+
### Markdown (`review_report.md`)
86+
- Findings 摘要和严重级别统计
87+
- 人工复核项
88+
- Filter 拦截摘要
89+
- 沙箱执行摘要
90+
- 可执行修复建议
91+
92+
## 数据库
93+
94+
SQLite 存储,4 张表:
95+
- `review_tasks` — 评审任务
96+
- `findings` — 发现的问题
97+
- `sandbox_runs` — 沙箱执行记录
98+
- `filter_logs` — Filter 拦截日志
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Code Review Agent — trpc-agent integration
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Code review agent definition — integrates with tRPC-Agent framework."""
2+
3+
from trpc_agent_sdk.agents import LlmAgent
4+
5+
6+
def create_code_review_agent(model_config: dict | None = None) -> LlmAgent:
7+
"""Create a LlmAgent configured for code review tasks.
8+
9+
The agent uses the code-review Skill for rule loading and
10+
structured output generation.
11+
12+
Args:
13+
model_config: Optional model configuration dict.
14+
15+
Returns:
16+
Configured LlmAgent instance.
17+
"""
18+
config = model_config or {
19+
"model": "fake", # Default to fake mode
20+
}
21+
22+
agent = LlmAgent(
23+
name="code_review_agent",
24+
description="Automated code review agent that analyzes diffs "
25+
"for security, quality, and best practice issues.",
26+
model=config["model"],
27+
instruction="""You are a code review agent. Your job is to:
28+
1. Load the code-review skill to understand review rules.
29+
2. Analyze the provided diff for issues.
30+
3. Output structured findings in the review format.
31+
""",
32+
)
33+
34+
return agent
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Model configuration for the code review agent."""
2+
3+
DEFAULT_MODEL_CONFIG = {
4+
"model": "fake",
5+
"temperature": 0.0,
6+
"max_tokens": 4096,
7+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""System prompts for the code review agent."""
2+
3+
SYSTEM_PROMPT = """You are an automated code review agent.
4+
Your task is to analyze code diffs for potential issues including:
5+
- Security vulnerabilities
6+
- Resource leaks
7+
- Async/await errors
8+
- Database lifecycle problems
9+
- Missing tests
10+
- Hardcoded secrets
11+
12+
For each issue found, provide:
13+
1. Severity (critical/high/medium/low/info)
14+
2. Category
15+
3. File and line number
16+
4. Evidence from the code
17+
5. A clear recommendation for fixing the issue
18+
19+
Be thorough but avoid false positives. When unsure, flag as low confidence.
20+
"""
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
diff --git a/worker.py b/worker.py
2+
index 0000000..1111111 100644
3+
--- a/worker.py
4+
+++ b/worker.py
5+
@@ -1,0 +1,16 @@
6+
+import asyncio
7+
+import time
8+
+
9+
+
10+
+async def process_items(items: list) -> list:
11+
+ results = []
12+
+ for item in items:
13+
+ time.sleep(0.1) # Blocks event loop
14+
+ results.append(item * 2)
15+
+ return results
16+
+
17+
+
18+
+def read_file():
19+
+ f = open("data.txt")
20+
+ data = f.read()
21+
+ return data # f never closed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
diff --git a/example.py b/example.py
2+
index 0000000..1111111 100644
3+
--- a/example.py
4+
+++ b/example.py
5+
@@ -5,3 +5,4 @@
6+
def greet(name: str) -> str:
7+
"""Return a friendly greeting."""
8+
- return f"Hello {name}!"
9+
+ """Greet someone by name."""
10+
+ return f"Hello, {name}!"
11+
@@ -10,2 +11,2 @@
12+
def add(a: int, b: int) -> int:
13+
- return a + b
14+
+ """Add two integers and return the sum."""
15+
+ return int(a) + int(b)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
diff --git a/db_repo.py b/db_repo.py
2+
index 0000000..1111111 100644
3+
--- a/db_repo.py
4+
+++ b/db_repo.py
5+
@@ -1,0 +1,18 @@
6+
+import sqlite3
7+
+
8+
+
9+
+def get_users() -> list:
10+
+ conn = sqlite3.connect("app.db")
11+
+ cursor = conn.cursor()
12+
+ cursor.execute("SELECT * FROM users")
13+
+ rows = cursor.fetchall()
14+
+ return rows # conn and cursor never closed
15+
+
16+
+
17+
+def insert_user(name: str) -> None:
18+
+ conn = sqlite3.connect("app.db")
19+
+ cursor = conn.cursor()
20+
+ cursor.execute("INSERT INTO users (name) VALUES (?)", (name,))
21+
+ # Missing conn.commit()
22+
+ cursor.close()
23+
+ conn.close()
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
diff --git a/service.py b/service.py
2+
index 0000000..1111111 100644
3+
--- a/service.py
4+
+++ b/service.py
5+
@@ -1,0 +1,15 @@
6+
+import os
7+
+
8+
+
9+
+def run_command_1(cmd: str) -> str:
10+
+ return os.system(cmd) # security issue
11+
+
12+
+
13+
+def run_command_2(cmd: str) -> str:
14+
+ return os.system(cmd) # same security issue, different function
15+
+
16+
+
17+
+def run_command_3(cmd: str) -> str:
18+
+ # This also calls os.system on untrusted input
19+
+ result = os.system(cmd) # same pattern again
20+
+ return str(result)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
diff --git a/math_utils.py b/math_utils.py
2+
index 0000000..1111111 100644
3+
--- a/math_utils.py
4+
+++ b/math_utils.py
5+
@@ -1,0 +1,12 @@
6+
+def calculate_average(values: list[float]) -> float:
7+
+ if not values:
8+
+ return 0.0
9+
+ return sum(values) / len(values)
10+
+
11+
+
12+
+def find_median(values: list[float]) -> float:
13+
+ sorted_vals = sorted(values)
14+
+ n = len(sorted_vals)
15+
+ if n % 2 == 1:
16+
+ return sorted_vals[n // 2]
17+
+ return (sorted_vals[n // 2 - 1] + sorted_vals[n // 2]) / 2

0 commit comments

Comments
 (0)