|
| 1 | +# Code Review Agent |
| 2 | + |
| 3 | +> 基于 tRPC-Agent Skill 体系的自动代码评审 Agent 原型。输入 git diff / PR patch / 本地变更,输出结构化 findings,并将审查任务、拦截记录、监控摘要、结果写入 SQLite,支持评测、监控、回放。 |
| 4 | +
|
| 5 | +## 架构设计 |
| 6 | + |
| 7 | +这个示例的目标不是让 LLM 直接评论代码,而是把可复用 Skill、沙箱执行、Filter 治理、结构化结果、持久化存储和监控审计串成一条可验证的代码评审链路。输入可以是 unified diff、PR patch 或本地 git 工作区变更;输出是结构化 findings、人工复核项、Filter 拦截摘要、沙箱执行摘要、监控指标和双格式报告。 |
| 8 | + |
| 9 | +整体采用六层流水线。L1 输入解析层负责把 `--diff-file`、`--repo-path` 或 fixture 解析成 ChangeSet,提取文件、hunk、上下文和候选行号。L2 Skill 加载层通过 `skill_load` 读取 `skills/code-review/SKILL.md`、规则文档和脚本清单,让规则和执行入口从 Agent 编排中解耦。L3 Filter 治理层在任何脚本进入沙箱前做前置决策,拦截高风险脚本、禁止路径、非白名单网络访问和超预算执行,并把 `deny` / `needs_human_review` 写入数据库和报告。L4 沙箱执行层默认走 Container 或 Cube/E2B runtime,本地只作为开发与 dry-run fallback;每次执行都带超时、输出大小限制、环境变量白名单、脱敏和失败记录。L5 去重结构化层按 `(file, line, category)` 合并重复诊断,并根据置信度把结果分入 findings、warnings 或 needs_human_review。L6 存储输出层把 task、input diff、sandbox run、filter block、finding、monitor summary 和最终 report 写入 SQLite,并生成 `review_report.json` 与 `review_report.md`。 |
| 10 | + |
| 11 | +数据流是单向的:CLI 输入先变成 ChangeSet,再加载 code-review Skill 和规则;脚本清单先过 Filter,允许后才进入沙箱执行;沙箱产出的原始诊断统一进入去重和分流;最终结果落库并渲染报告。异常分支也走同一条审计链:Filter 拒绝不会进入沙箱,但会记录拦截原因;沙箱超时、失败或输出截断不会让任务崩溃,而是记录失败并继续生成可查询报告;dry-run 使用 fake runner 跑同一套规则路径,保证没有真实模型 API Key 时也能验证解析、沙箱记录、落库和报告链路。 |
| 12 | + |
| 13 | +数据库围绕 `review_task` 建模,其他表都通过 `task_id` 关联。`input_diff` 保存输入摘要,`sandbox_run` 保存 runtime、脚本、状态、耗时、退出码、输出大小、超时和脱敏数量,`finding` 保存结构化问题和 bucket,`filter_block` 保存治理拦截,`monitor_summary` 保存耗时、调用次数、拦截次数、severity 分布和异常类型分布,`review_report` 保存最终报告路径与摘要。这样按 task id 可以回放一次完整评审。 |
| 14 | + |
| 15 | +Skill 本身只负责声明和承载可复用评审能力:`SKILL.md` 声明入口、规则列表和沙箱策略,`rules/` 覆盖安全风险、异步错误、资源泄漏、测试缺失、敏感信息泄漏、数据库连接或事务生命周期等类别,`scripts/` 提供 diff 解析、规则检查、去重和脱敏。Agent 编排层只负责加载 Skill、执行治理、调度沙箱、落库和渲染报告。 |
| 16 | + |
| 17 | +监控审计贯穿全链路。每次 review 都记录总耗时、沙箱耗时、工具调用次数、Filter 拦截次数、finding 数量、严重级别分布和异常类型分布;这些字段同时进入数据库和报告,便于后续评测、监控和问题回放。 |
| 18 | + |
| 19 | +### 目录结构 |
| 20 | + |
| 21 | +``` |
| 22 | +skills_code_review_agent/ |
| 23 | +├── .env # 运行配置(LLM 等,安全默认,不提交密钥) |
| 24 | +├── .env.example # 配置模板 |
| 25 | +├── run_agent.py # CLI 薄入口(load_dotenv → 调用 agent.agent.main) |
| 26 | +├── README.md |
| 27 | +├── agent/ # 所有内部模块 |
| 28 | +│ ├── __init__.py |
| 29 | +│ ├── agent.py # 编排管线(CLI 解析 + 六层流水线) |
| 30 | +│ ├── db/ # Phase-0 存储层(SQLite + ORM) |
| 31 | +│ ├── filters/ # Filter 治理(高危/禁路径/网络/预算) |
| 32 | +│ ├── llm/ # 可选真实 LLM 二次研判(默认关闭) |
| 33 | +│ ├── sandbox/ # 沙箱运行时(local/container/cube + 透明回退) |
| 34 | +│ └── telemetry/ # OTel 埋点 |
| 35 | +├── skills/ # SDK skill 定义(FsSkillRepository 按 base_dir 加载) |
| 36 | +│ └── code-review/ |
| 37 | +│ ├── SKILL.md |
| 38 | +│ ├── rules/ # 6 类规则文件 (security/async/resource/tests/sensitive/db) |
| 39 | +│ └── scripts/ # 静态分析脚本(run_checks/dedupe/mask_secrets/parse_diff) |
| 40 | +├── tests/ # 8 个测试文件,175 用例 |
| 41 | +├── examples/ # 生成的报告产物 |
| 42 | +└── docs/ # 各阶段概述文档 |
| 43 | +``` |
| 44 | + |
| 45 | +--- |
| 46 | + |
| 47 | +## 1. 运行环境 |
| 48 | + |
| 49 | +必须使用隔离 venv(原因见下),不要用系统 Python: |
| 50 | + |
| 51 | +```bash |
| 52 | +# 隔离 venv(Windows 示例路径) |
| 53 | +VENV="C:/Users/douzhenyu/.workbuddy/binaries/python/envs/cr_agent/Scripts/python.exe" |
| 54 | + |
| 55 | +cd E:/agent/trpc-agent-python/examples/skills_code_review_agent |
| 56 | +``` |
| 57 | + |
| 58 | +**关键依赖约束**: |
| 59 | + |
| 60 | +- 必须安装 `python-magic-bin`(不是 `python-magic`)。Windows 上缺 `magic1.dll` 会导致 SDK `import code_executors` 直接 segfault。 |
| 61 | +- SDK 存储为 async 优先,所有存储 / 沙箱调用都要 `await`。 |
| 62 | +- **路径用真实文件系统路径**,别用 Git Bash 的 `/tmp`(aiosqlite 在 Windows 上认不出,会静默失败)。 |
| 63 | + |
| 64 | +依赖清单(核心):`SQLAlchemy` `aiosqlite` `pydantic` `opentelemetry-sdk` `tzlocal` `PyYAML` `jinja2` `python-magic-bin` `docker`(可选,容器运行时预留)。 |
| 65 | + |
| 66 | +--- |
| 67 | + |
| 68 | +## 2. CLI 参数 |
| 69 | + |
| 70 | +| 参数 | 必填 | 说明 | 默认 | |
| 71 | +|------|------|------|------| |
| 72 | +| `--diff-file PATH` | 三选一(互斥) | 解析一个 unified diff 文件 | — | |
| 73 | +| `--repo-path PATH` | 三选一 | 在仓库里跑 `git diff HEAD`(暂存 + 未暂存) | — | |
| 74 | +| `--fixture NAME` | 三选一 | 内置样本:`clean` / `security` | — | |
| 75 | +| `--skill-dir PATH` | 否 | code-review skill 目录 | `skills/code-review` | |
| 76 | +| `--db-path PATH` | 否 | SQLite 库路径 | `cr_agent.db` | |
| 77 | +| `--mode dry-run\|real` | 否 | 执行模式 | `dry-run` | |
| 78 | +| `--dry-run` | 否 | `--mode dry-run` 快捷 flag | — | |
| 79 | +| `--output-dir DIR` | 否 | `review_report.json` + `.md` 输出目录 | `.` | |
| 80 | +| `--telemetry` | 否 | 开启 OTel,span 打到 stdout | 关 | |
| 81 | +| `--print-changeset` | 否 | 额外打印解析后的 ChangeSet JSON | 关 | |
| 82 | +| `--require-sandbox` | 否 | real 模式强制使用 SKILL.md 声明的 `default_runtime`(如 `container`);若不可用直接报错,不静默回退 `local` | 关 | |
| 83 | +| `--enable-llm` | 否 | 开启真实 LLM 二次研判(也可用 `.env` 的 `LLM_ENABLED`) | 关 | |
| 84 | +| `--llm-env PATH` | 否 | 指定 `.env` 文件路径(默认项目根 `.env`) | — | |
| 85 | + |
| 86 | +输入三选一是 **required** 的——不传任何一个会直接报错。 |
| 87 | + |
| 88 | +--- |
| 89 | + |
| 90 | +## 3. 两种模式 |
| 91 | + |
| 92 | +| | dry-run | real | |
| 93 | +|---|---|---| |
| 94 | +| 执行方式 | 进程内直接 `import run_checks` | 按 `default_runtime` 选后端(先试 `container`,不可用时透明回退 `local`)| |
| 95 | +| 安全边界 | 不生效(没进沙箱) | 超时 / 输出上限 / env 白名单 / 脱敏全生效 | |
| 96 | +| Filter 治理 | 生效(deny 仍拦截) | 生效 | |
| 97 | +| 是否需要 Key | 否 | 否 | |
| 98 | +| 用途 | CI 预览、快速验证、评测 | 生产评审(**独立容器沙箱**,更隔离、更安全) | |
| 99 | + |
| 100 | +> **独立沙箱(PRD 强制)**:`real` 模式 + `default_runtime: container` 时,检查脚本在**全新 docker 容器**内执行——`network_mode=none`(无网络)、不继承宿主机环境变量、每次评审起一个容器用完即毁。这正是 PRD §6.2 要求的生产隔离方案,已在本机(docker 已装)实证:整条链路在容器内跑通、`sandbox_run.runtime` 记录为 `container`、密钥脱敏生效。 |
| 101 | +> 加 `--require-sandbox` 可在生产环境**强制**该契约:请求 `container` 但不可用就直接报错,不静默降级到 `local`(local 仅作 dry-run / 开发 fallback,PRD 明确"不能作为生产方案")。 |
| 102 | +
|
| 103 | +> 评审核心为**确定性静态分析引擎**(规则 + AST),**默认不调用任何 LLM / 模型 API**,因此两条路径默认都不需要 API Key。 |
| 104 | +> 所谓 “real” 仅表示 “在隔离沙箱里执行” vs “直接进程内执行”。 |
| 105 | +> 如需真实模型二次研判,见 [§4.5 LLM 二次研判(可选)](#45-llm-二次研判可选);未配置时自动降级为 no-op,链路不受影响。 |
| 106 | +
|
| 107 | +--- |
| 108 | + |
| 109 | +## 4. 配置改在哪 |
| 110 | + |
| 111 | +**沙箱安全边界** → `skills/code-review/SKILL.md` 的 `sandbox:` frontmatter: |
| 112 | + |
| 113 | +```yaml |
| 114 | +sandbox: |
| 115 | + default_runtime: container # real 模式优先用容器;docker 不可用自动回退 local |
| 116 | + fallback: local |
| 117 | + timeout_s: 30 # 超时秒数,超了降级为空 |
| 118 | + max_output_bytes: 1048576 # 1MB 输出上限,超出截断 |
| 119 | + env_whitelist: [PATH, HOME, LANG] # 只透传这些环境变量进沙箱 |
| 120 | +``` |
| 121 | +
|
| 122 | +**检测规则** → `skills/code-review/rules/*.md`(6 类:security / async_errors / resource_leak / missing_tests / sensitive_info / db_lifecycle)。改规则直接改这些 md,无需动代码。 |
| 123 | + |
| 124 | +**脱敏正则** → `skills/code-review/scripts/mask_secrets.py`(AWS / OpenAI / GitHub / password / 私钥 / 连接串 + 熵检测)。 |
| 125 | + |
| 126 | +--- |
| 127 | + |
| 128 | +## 4.5 LLM 二次研判(可选) |
| 129 | + |
| 130 | +在 dedupe 之后、落库之前,可用**真实 LLM** 对低置信度的 `needs_human_review` 档位做二次研判: |
| 131 | + |
| 132 | +- **real(确认)** → 更新置信度、给 `source` 打 `llm` 标、把模型解释追加进 `recommendation`,并按新置信度重新分桶(≥0.8 进 findings / ≥0.6 进 warnings)。 |
| 133 | +- **false_positive(误报)** → 直接丢弃。 |
| 134 | +- **调用失败 / 无 Key / 未启用** → 自动降级为 no-op,原 findings 原样落库。 |
| 135 | + |
| 136 | +### 配置(`.env`) |
| 137 | + |
| 138 | +复制 `.env.example` 为 `.env`,按需填写: |
| 139 | + |
| 140 | +```bash |
| 141 | +LLM_ENABLED=true # 或 CLI --enable-llm 临时开启 |
| 142 | +LLM_API_KEY=sk-... # 留空 = 禁用真实模型 |
| 143 | +LLM_BASE_URL=https://api.openai.com/v1 # 任意 OpenAI 兼容端点(Azure/本地vLLM/内部网关) |
| 144 | +LLM_MODEL=gpt-4o-mini |
| 145 | +LLM_TEMPERATURE=0.1 |
| 146 | +LLM_MAX_TOKENS=1024 |
| 147 | +LLM_TIMEOUT=30 |
| 148 | +``` |
| 149 | + |
| 150 | +- 任意 OpenAI 兼容端点都能用:OpenAI、Azure OpenAI、**DeepSeek**(`https://api.deepseek.com`,`LLM_MODEL=deepseek-chat`)、本地 vLLM、内部 LLM 网关,只改 `LLM_BASE_URL` / `LLM_API_KEY`。 |
| 151 | +- **无 Key 时完全不调用模型**,解析 / 沙箱 / 落库链路(含 `--dry-run`)照常工作,所有测试可无 Key 跑通。 |
| 152 | +- 喂给模型的 diff 会先经 `mask_secrets` 脱敏,密钥不会外泄。 |
| 153 | +- **已实证**(DeepSeek `deepseek-chat`):低置信度 `needs_human_review` 项(规则 SEC005 `verify=False`,置信度 0.5)被模型研判为 `real`、置信度 0.95 → 提级进 `findings` 桶、`source` 标 `rule+llm`、解释写入 `recommendation`;高/中置信度项不受影响。验证 fixture 为 `llm_probe`。 |
| 154 | +- 真实网络路径有门控集成测试 `tests/test_phase7_llm_live.py`:`CR_LLM_LIVE=1` 且有 `LLM_API_KEY` 时才跑,避免污染无 Key 的 CI。 |
| 155 | + |
| 156 | +### 启用方式 |
| 157 | + |
| 158 | +```bash |
| 159 | +# 方式一:.env 里 LLM_ENABLED=true 后直接跑 |
| 160 | +$VENV run_agent.py --diff-file ./my_change.diff --mode real --db-path ./cr.db |
| 161 | +
|
| 162 | +# 方式二:CLI 临时开启(覆盖 .env) |
| 163 | +$VENV run_agent.py --fixture security --enable-llm --llm-env ./path/to/.env |
| 164 | +``` |
| 165 | + |
| 166 | +--- |
| 167 | + |
| 168 | +## 5. 真实环境用法 |
| 169 | + |
| 170 | +```bash |
| 171 | +# (A) 本地 diff 文件 → 生产沙箱模式,报告输出到 ./reports |
| 172 | +$VENV run_agent.py \ |
| 173 | + --diff-file ./my_change.diff \ |
| 174 | + --mode real \ |
| 175 | + --output-dir ./reports \ |
| 176 | + --db-path ./cr_prod.db |
| 177 | +
|
| 178 | +# (B) 审一个 Git 仓库的未提交改动(CI 门禁,dry-run 够用) |
| 179 | +$VENV run_agent.py \ |
| 180 | + --repo-path /path/to/repo \ |
| 181 | + --dry-run \ |
| 182 | + --telemetry \ |
| 183 | + --db-path ./cr_ci.db |
| 184 | +
|
| 185 | +# (C) 评测 / 演示用内置样本 |
| 186 | +$VENV run_agent.py --fixture security --dry-run --output-dir ./out |
| 187 | +``` |
| 188 | + |
| 189 | +跑完会生成: |
| 190 | + |
| 191 | +- `./reports/review_report.json` — 八段式结构化报告(机器可读) |
| 192 | +- `./reports/review_report.md` — 八段式报告(人读) |
| 193 | +- `cr_prod.db` 里写入 `review_task` / `input_diff` / `finding` / `sandbox_run` / `filter_block` / `monitor_summary` / `review_report` |
| 194 | + |
| 195 | +### 按 task_id 查询完整记录 |
| 196 | + |
| 197 | +```python |
| 198 | +import sqlite3 |
| 199 | +c = sqlite3.connect("cr_prod.db"); c.row_factory = sqlite3.Row |
| 200 | +t = c.execute("SELECT * FROM review_task WHERE id=?", (task_id,)).fetchone() |
| 201 | +findings = c.execute("SELECT * FROM finding WHERE task_id=?", (task_id,)).fetchall() |
| 202 | +# ... sandbox_run / filter_block / monitor_summary / review_report 同理 |
| 203 | +``` |
| 204 | + |
| 205 | +--- |
| 206 | + |
| 207 | +## 6. 示例产物 |
| 208 | + |
| 209 | +- [`examples/review_report.json`](./examples/review_report.json) — 一份真实的八段式报告样例(fixture=security 跑出)。 |
| 210 | + |
| 211 | +--- |
| 212 | + |
| 213 | +## 7. 已知边界 |
| 214 | + |
| 215 | +- **容器隔离(独立沙箱,已实证)**:`ContainerRuntime` 是 SDK `ContainerClient` 的真实适配(docker 进程隔离,`network_mode=none`、不继承宿主机 env),`CubeRuntime` 是远程 Cube/E2B 的真实适配;两者在 docker/cube 不可用时抛 `RuntimeUnavailable`,由 agent 透明回退到 `LocalRuntime`(或 `--require-sandbox` 下直接报错)。`sandbox_run.runtime` 记录实际落地的后端(`local`/`container`/`cube`)。 |
| 216 | + - 本机 docker 已装,已端到端实证:整条 real 链路在容器内跑通、检出 + 脱敏生效、`runtime=container` 正确落库(见 `tests/test_phase3_container_sandbox.py`,docker 不可用时自动 skip)。 |
| 217 | + - 绕开 SDK `ContainerClient` 的 stdin 执行路径 bug(其 `_exec_run_with_stdin` 有 `self.container.container.id` 笔误 + 关闭 socket 后复用连接池报错);本 runtime 改用 base64 把脚本与输入写进容器文件、再以容器内 `python x.py < _input.json` 重定向执行,全程走稳健的 no-stdin `exec_run` 分支。SDK 该笔误已就地修复(`trpc_agent_sdk/code_executors/container/_container_cli.py`)。 |
| 218 | +- **并发**:多任务建议每条评审一个 `task_id`;不要多个进程写同一个 `.db` 文件。 |
| 219 | +- **Windows 路径**:所有 `--db-path` / `--output-dir` / `--diff-file` 用正斜杠或盘符绝对路径。 |
| 220 | + |
| 221 | +--- |
| 222 | + |
| 223 | +## 8. 测试 |
| 224 | + |
| 225 | +```bash |
| 226 | +# 全量回归(8 个阶段 + LLM + 容器沙箱) |
| 227 | +for p in phase0_foundation phase1_input_skill phase2_rules_engine \ |
| 228 | + phase3_sandbox_filter phase3_container_sandbox telemetry \ |
| 229 | + phase4_dedupe phase5_orchestration phase7_llm cr_agent; do |
| 230 | + $VENV examples/skills_code_review_agent/tests/test_${p}.py |
| 231 | +done |
| 232 | +``` |
| 233 | + |
| 234 | +当前累计 **178 用例 / 0 失败**(含 docker 门控的容器独立沙箱测试;无 docker 时该测试自动 skip)。 |
0 commit comments