Skip to content

Commit 5282722

Browse files
committed
feat: 完成阶段九测试与验证(8 条 fixtures + 文档 + 示例报告)
1 parent 819131e commit 5282722

12 files changed

Lines changed: 417 additions & 0 deletions
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Code Review Agent — 自动代码评审 Agent
2+
3+
基于 tRPC-Agent-Python 框架构建的自动代码评审 Agent,集成 Skills、沙箱执行、数据库存储、Filter 治理和监控审计能力,可对 git diff 进行自动化审查并输出结构化报告。
4+
5+
## 环境要求
6+
7+
- Python >= 3.10
8+
- 依赖:见 `requirements.txt``pyproject.toml`
9+
10+
## 安装
11+
12+
```bash
13+
# 从项目根目录安装
14+
pip install -e .
15+
16+
# 或直接运行(依赖已安装的情况下)
17+
cd examples/skills_code_review_agent
18+
```
19+
20+
## 快速开始
21+
22+
### 使用测试样本运行(dry-run 模式,无需 API Key)
23+
24+
```bash
25+
cd examples/skills_code_review_agent
26+
27+
# 运行无问题样本
28+
python dry_run.py --fixture 01_clean
29+
30+
# 运行安全漏洞样本
31+
python dry_run.py --fixture 02_security_leak
32+
33+
# 运行全部 8 条样本
34+
for f in 01 02 03 04 05 06 07 08; do
35+
python dry_run.py --fixture "${f}_clean"
36+
done
37+
```
38+
39+
### 审查本地 diff 文件
40+
41+
```bash
42+
python review_agent.py --diff-file /path/to/changes.diff
43+
```
44+
45+
### 审查 git 工作区变更
46+
47+
```bash
48+
python review_agent.py --repo-path /path/to/repo
49+
```
50+
51+
### 列出可用样本
52+
53+
```bash
54+
python review_agent.py --list-fixtures
55+
```
56+
57+
## 输出
58+
59+
- `review_report.json` — 结构化 JSON 报告
60+
- `review_report.md` — 可读 Markdown 报告
61+
62+
## 测试样本
63+
64+
| 样本 | 场景 | 预期 |
65+
|------|------|------|
66+
| `01_clean.py.diff` | 无问题代码 | 空 findings |
67+
| `02_security_leak.py.diff` | 硬编码密钥/密码 | severity=high |
68+
| `03_async_resource_leak.py.diff` | 未关闭 aiohttp ClientSession | 资源泄漏 |
69+
| `04_db_connection_leak.py.diff` | 数据库连接未释放 | 连接泄漏 |
70+
| `05_test_missing.py.diff` | 新增函数无测试 | warning |
71+
| `06_duplicate_finding.py.diff` | 同一问题重复出现 | 去重后只报 1 条 |
72+
| `07_sandbox_failure.py.diff` | 沙箱执行超时/失败 | 任务不崩溃 |
73+
| `08_secret_masking.py.diff` | 多种敏感信息 | 全部脱敏 |
74+
75+
## 项目结构
76+
77+
```
78+
examples/skills_code_review_agent/
79+
├── review_agent.py # 主入口
80+
├── dry_run.py # 干跑模式
81+
├── config.py # 配置管理
82+
├── cli.py # CLI 参数解析
83+
├── models.py # 数据模型
84+
├── diff_parser.py # diff 解析器
85+
├── sandbox.py # 沙箱执行器
86+
├── secret_masker.py # 敏感信息脱敏
87+
├── filters.py # Filter 治理
88+
├── filter_chain.py # Filter 编排链
89+
├── deduper.py # 去重降噪
90+
├── report_generator.py # 报告生成
91+
├── monitor.py # 监控审计
92+
├── db/ # 数据库层
93+
│ ├── schema.sql
94+
│ ├── storage.py
95+
│ └── init_db.py
96+
├── skills/code-review/ # CR Skill
97+
│ ├── SKILL.md
98+
│ ├── rules/ # 规则文档
99+
│ └── scripts/ # 沙箱脚本
100+
└── fixtures/ # 测试样本
101+
├── 01_clean.py.diff
102+
└── ...
103+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--- a/src/calculator.py
2+
+++ b/src/calculator.py
3+
@@ -0,0 +1,20 @@
4+
+"""Simple calculator module."""
5+
+
6+
+
7+
+def add(a: int, b: int) -> int:
8+
+ """Add two numbers."""
9+
+ return a + b
10+
+
11+
+
12+
+def subtract(a: int, b: int) -> int:
13+
+ """Subtract two numbers."""
14+
+ return a - b
15+
+
16+
+
17+
+def multiply(a: int, b: int) -> int:
18+
+ """Multiply two numbers."""
19+
+ return a * b
20+
+
21+
+
22+
+def divide(a: int, b: int) -> int:
23+
+ """Divide two numbers."""
24+
+ return a // b
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--- a/src/config.py
2+
+++ b/src/config.py
3+
@@ -1,12 +1,15 @@
4+
"""Application configuration."""
5+
6+
import os
7+
8+
9+
class Config:
10+
"""Application configuration."""
11+
12+
- DEBUG = os.getenv("DEBUG", "false").lower() == "true"
13+
- DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///app.db")
14+
+ DEBUG = os.getenv("DEBUG", "false").lower() == "true"
15+
+ DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///app.db")
16+
+
17+
+ # TODO: move to env var
18+
+ API_KEY = "sk-abc123def456ghi789jklmno"
19+
+ PASSWORD = "SuperSecretP@ssw0rd!"
20+
+ SECRET_TOKEN = "ghp_abcdefghijklmnopqrstuvwxyz1234567890"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--- a/src/fetcher.py
2+
+++ b/src/fetcher.py
3+
@@ -1,8 +1,12 @@
4+
"""Async data fetcher module."""
5+
6+
import aiohttp
7+
import asyncio
8+
9+
10+
async def fetch_data(url: str) -> dict:
11+
"""Fetch data from URL."""
12+
- async with aiohttp.ClientSession() as session:
13+
- async with session.get(url) as resp:
14+
- return await resp.json()
15+
+ session = aiohttp.ClientSession()
16+
+ resp = await session.get(url)
17+
+ data = await resp.json()
18+
+ return data
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--- a/src/db.py
2+
+++ b/src/db.py
3+
@@ -1,10 +1,14 @@
4+
"""Database operations module."""
5+
6+
import sqlite3
7+
8+
9+
def get_user(conn: sqlite3.Connection, user_id: int) -> dict:
10+
"""Get user by ID."""
11+
cursor = conn.cursor()
12+
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
13+
return cursor.fetchone()
14+
+
15+
+
16+
+def get_all_users():
17+
+ """Get all users - creates its own connection."""
18+
+ conn = sqlite3.connect("app.db")
19+
+ cursor = conn.cursor()
20+
+ cursor.execute("SELECT * FROM users")
21+
+ return cursor.fetchall()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--- a/src/calculator.py
2+
+++ b/src/calculator.py
3+
@@ -1,8 +1,18 @@
4+
-"""Simple calculator module."""
5+
+"""Calculator module with interest calculation."""
6+
7+
8+
def add(a: int, b: int) -> int:
9+
"""Add two numbers."""
10+
return a + b
11+
12+
13+
def subtract(a: int, b: int) -> int:
14+
"""Subtract two numbers."""
15+
return a - b
16+
+
17+
+
18+
+def calculate_interest(principal: float, rate: float, time: int) -> float:
19+
+ """Calculate compound interest.
20+
+
21+
+ This is a new function without corresponding test coverage.
22+
+ """
23+
+ return principal * (1 + rate) ** time
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--- a/src/auth.py
2+
+++ b/src/auth.py
3+
@@ -1,8 +1,18 @@
4+
"""Authentication module."""
5+
6+
7+
def login(username: str, password: str) -> bool:
8+
"""Authenticate user."""
9+
# Verify credentials
10+
if username == "admin" and password == "secret":
11+
return True
12+
return False
13+
+
14+
+
15+
+def reset_password(user_id: int, new_password: str) -> bool:
16+
+ """Reset user password."""
17+
+ # TODO: hash the password
18+
+ PASSWORD = "new_temp_pass_123"
19+
+ if len(new_password) < 8:
20+
+ PASSWORD = "new_temp_pass_123"
21+
+ return True
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--- a/src/unstable.py
2+
+++ b/src/unstable.py
3+
@@ -0,0 +1,15 @@
4+
+"""Module with operations that may fail in sandbox."""
5+
+
6+
+import subprocess
7+
+import os
8+
+
9+
+
10+
+def check_system_info():
11+
+ """Get system information - may fail in sandbox."""
12+
+ result = subprocess.run(["uname", "-a"], capture_output=True, text=True)
13+
+ return result.stdout
14+
+
15+
+
16+
+def read_large_file():
17+
+ """Read a large file that may exceed output limits."""
18+
+ return os.urandom(2 * 1024 * 1024) # 2MB of random data
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--- a/src/credentials.py
2+
+++ b/src/credentials.py
3+
@@ -1,5 +1,18 @@
4+
"""Credentials configuration."""
5+
6+
+import os
7+
8+
-# API configuration
9+
-API_KEY = os.getenv("API_KEY", "")
10+
+
11+
+class Credentials:
12+
+ """Sensitive credentials storage."""
13+
+
14+
+ # Hardcoded secrets - should use env vars
15+
+ API_KEY = "sk-abcdefghijklmnopqrstuvwxyz1234567890"
16+
+ PASSWORD = "P@ssw0rd!123"
17+
+ GITHUB_TOKEN = "ghp_abcdefghijklmnopqrstuvwxyz1234567890"
18+
+ AWS_SECRET = "AKIA1234567890ABCDEF"
19+
+
20+
+ @classmethod
21+
+ def get_db_url(cls) -> str:
22+
+ """Get database URL with embedded credentials."""
23+
+ return "postgres://admin:SuperSecret@localhost:5432/prod_db"
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"meta": {
3+
"generated_at": "2026-07-21T12:00:00.000000",
4+
"report_version": "1.0"
5+
},
6+
"task": {
7+
"id": "example-task-id-0000",
8+
"status": "completed",
9+
"input_type": "fixture",
10+
"input_summary": "02_security_leak",
11+
"created_at": "2026-07-21T12:00:00.000000",
12+
"total_duration_ms": 1500.0,
13+
"error_message": null
14+
},
15+
"summary": {
16+
"total_findings": 2,
17+
"total_warnings": 1,
18+
"total_needs_human_review": 0,
19+
"severity_distribution": {
20+
"high": 2,
21+
"medium": 1
22+
},
23+
"filter_intercept_count": 0,
24+
"sandbox_execution_count": 1
25+
},
26+
"findings": [
27+
{
28+
"id": "finding-001",
29+
"task_id": "example-task-id-0000",
30+
"severity": "high",
31+
"category": "security",
32+
"file": "src/config.py",
33+
"line": 10,
34+
"title": "Hardcoded API key detected",
35+
"evidence": "API_KEY = \"sk-***\"",
36+
"recommendation": "Move API key to environment variable: os.getenv('API_KEY')",
37+
"confidence": "high",
38+
"source": "rule",
39+
"dedup_key": "src/config.py:10:security",
40+
"created_at": "2026-07-21T12:00:00.000000"
41+
}
42+
],
43+
"warnings": [],
44+
"needs_human_review": [],
45+
"filter_intercepts": [],
46+
"sandbox_runs": [
47+
{
48+
"id": "sandbox-001",
49+
"script_name": "check_security.py",
50+
"runtime": "local",
51+
"duration_ms": 500.0,
52+
"exit_code": 0,
53+
"success": true,
54+
"error_message": null,
55+
"output_truncated": false
56+
}
57+
],
58+
"recommendations": [
59+
"Move API key to environment variable: os.getenv('API_KEY')"
60+
]
61+
}

0 commit comments

Comments
 (0)