案例背景
某 SaaS 公司技术团队 50 人,每周合并约 120 个 PR。Code Review 现状:
| 痛点 | 数据 |
|---|---|
| 资深工程师 Review 占用时间 | 工作日 30%(约 12 小时/周) |
| 单 PR 平均 Review 耗时 | 18 分钟 |
| 低级问题占比(命名、格式、明显 bug) | 45% |
| 上线后发现的缺陷 | 月均 8 个,其中 3 个能在 Review 阶段拦截 |
| 新人 PR 等待 Review 时间 | 平均 6 小时 |
目标:用 AI 把低级问题自动拦截,让资深工程师专注业务逻辑与架构评审。
工具组合
| 工具 | 角色 | 选型理由 |
|---|---|---|
| Claude Code | 评审主引擎 | 200K 上下文,能吞下整个 PR diff + 上下文文件 |
| Cursor | 修复助手 | 在 IDE 内快速修复 AI 指出的问题 |
| GitHub Actions | 流水线编排 | PR 触发、调用 AI、回写评论 |
| GPT-4 | 交叉校验 | 与 Claude 互为补充,降低单一模型盲区 |
| ESLint / Prettier | 静态检查 | 先于 AI 拦截格式问题,节省 Token |
架构设计
PR 创建/更新
↓
GitHub Actions 触发
↓
[Step 1] ESLint + Prettier 静态检查
↓
[Step 2] 拉取 PR diff + 上下文文件
↓
[Step 3] Claude Code 生成评审报告(结构化 JSON)
↓
[Step 4] GPT-4 交叉校验高风险项
↓
[Step 5] 回写 GitHub Review 评论(按文件行定位)
↓
[Step 6] 若有 Critical 问题,自动申请 changes requested
↓
通知作者 + 资深工程师
实施步骤
步骤 1:用 Claude Code 设计评审 Prompt(4 小时)
这是整个系统最关键的一步。Prompt 决定评审质量。
你是一位资深代码评审工程师,拥有 15 年后端开发经验。请评审以下 PR diff。
## 评审维度(按优先级)
1. **正确性**:逻辑 bug、边界条件、空指针、并发问题
2. **安全性**:SQL 注入、XSS、权限绕过、密钥泄露
3. **性能**:N+1 查询、O(n²) 循环、不必要的同步阻塞
4. **可维护性**:命名、复杂度、重复代码、注释缺失
5. **架构**:是否符合项目分层、是否有更优雅的实现
## 输出格式(严格 JSON)
{
"summary": "一句话总体评价",
"severity": "critical | major | minor | nit | praise",
"issues": [
{
"file": "src/xxx.ts",
"line": 42,
"severity": "major",
"category": "正确性",
"message": "未处理 null 情况,当 user 为 null 时会抛 NPE",
"suggestion": "可选修复建议",
"confidence": 0.9
}
],
"praise": ["做得好的地方,给作者正向反馈"],
"overall_score": 1-10
}
## 规则
1. 只评审 diff 中变更的行,不要评论未改动的代码
2. confidence < 0.6 的问题降级为 minor
3. 不要吹毛求疵,nit 级别问题最多 3 个
4. 发现 1 个 critical 或 3 个 major,overall_score ≤ 4
5. 如果代码质量优秀,必须给出 praise,不要只挑刺
## PR 信息
- 标题:{pr_title}
- 描述:{pr_body}
- 改动文件数:{file_count}
- 增加行数:{additions}
- 删除行数:{deletions}
## Diff 内容
{diff_content}
## 上下文文件(被改动文件的完整内容)
{context_files}
关键技巧:
- 强制 JSON 输出:方便后续脚本解析与回写 GitHub
- severity 分级:critical 必须人工介入,nit 可忽略
- confidence 字段:让模型自评置信度,低于 0.6 不阻塞合并
- praise 字段:避免只挑刺,维护团队士气
步骤 2:编写 GitHub Actions 工作流(4 小时)
创建 .github/workflows/ai-review.yml:
name: AI Code Review
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
ai-review:
runs-on: ubuntu-latest
if: github.actor != 'dependabot[bot]'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Run ESLint + Prettier
id: lint
run: |
npm ci
npx eslint $(git diff --name-only origin/${{ github.base_ref }} HEAD -- '*.ts' '*.tsx') --format json > eslint.json || true
- name: Get PR Diff
id: diff
run: |
git diff origin/${{ github.base_ref }}...HEAD > pr.diff
echo "diff_size=$(wc -l < pr.diff)" >> $GITHUB_OUTPUT
- name: Run AI Review
if: steps.diff.outputs.diff_size != '0'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node scripts/ai-review.mjs
- name: Cross-check with GPT-4
if: steps.ai-review.outputs.critical_count != '0'
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: node scripts/gpt-crosscheck.mjs
步骤 3:实现 ai-review.mjs 调用 Claude(6 小时)
核心逻辑:
// scripts/ai-review.mjs(伪代码示意)
import Anthropic from '@anthropic-ai/sdk';
import { GitHub } from 'octokit';
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const gh = new GitHub({ auth: process.env.GITHUB_TOKEN });
// 1. 拉取 PR 元数据
const { data: pr } = await gh.pulls.get({
owner, repo, pull_number: prNumber
});
// 2. 拉取 diff
const diff = await gh.pulls.listFiles({ owner, repo, pull_number: prNumber });
// 3. 拉取上下文文件(diff 中被改动的文件完整内容)
const contextFiles = await Promise.all(
diff.data.map(f => gh.repos.getContent({ owner, repo, path: f.filename }))
);
// 4. 调用 Claude 评审
const response = await client.messages.create({
model: 'claude-3-5-sonnet-20261022',
max_tokens: 4096,
messages: [{
role: 'user',
content: buildReviewPrompt(pr, diff.data, contextFiles)
}]
});
// 5. 解析 JSON 评审报告
const review = JSON.parse(response.content[0].text);
// 6. 回写 GitHub Review 评论
await gh.pulls.createReview({
owner, repo, pull_number: prNumber,
body: renderReviewBody(review),
event: review.severity === 'critical' ? 'request_changes' : 'comment',
comments: review.issues.map(i => ({
path: i.file,
line: i.line,
body: `**[${i.severity}] ${i.category}**\n${i.message}\n\n建议:${i.suggestion}`
}))
});
关键技巧:
- fetch-depth: 0:必须拉取完整历史,否则
git diff拿不到 base 分支 - 上下文文件:只看 diff 不够,模型需要看完整文件才能判断改动影响
- 评论行定位:用
line而非start_line,避免多行评论定位错乱 - request_changes 谨慎用:只对 critical 级别申请 changes,否则会阻塞所有 PR
步骤 4:用 Cursor 修复 AI 指出的问题(2 小时/PR)
作者收到 AI Review 后,在 Cursor 中:
读 src/auth.ts 第 42 行,AI 指出未处理 user 为 null 的情况。
请按 AI 的建议修复,同时检查同文件其他类似问题。
Cursor 会直接定位并修复,作者只需 Review Cursor 的改动。
步骤 5:用 GPT-4 交叉校验 critical 问题(1 小时)
为避免 Claude 单一模型误报 critical,对每个 critical 问题再用 GPT-4 验证:
你是代码评审专家。请判断以下问题是否真的是 critical 级别。
代码片段:
{code_snippet}
Claude 指出的问题:
{issue_description}
请回答:
1. 这个问题是否真实存在?(yes/no/uncertain)
2. 严重程度是否被高估?(overestimated/correct/underestimated)
3. 简述理由(一句话)
只有 GPT-4 也确认 critical,才自动 request_changes。这把误报率从 15% 降到 2%。
关键 Prompt 合集
Prompt 1:让 Claude 学会"读项目规范"
读仓库根目录的 CODE_REVIEW.md(项目评审规范)和 .eslintrc.json,
把规范要点提取为 checklist,在评审时逐项检查。
如果 diff 违反了 checklist 中的某项,明确指出违反了哪条规范。
Prompt 2:让 Claude 区分"风格"与"问题"
评审时严格区分:
- 风格问题(命名、空格、引号)→ nit 级别,最多提 3 个
- 逻辑问题(bug、边界、并发)→ major 或 critical
- 架构问题(分层、耦合、抽象)→ major,且必须给出更好的实现方案
不要把风格问题升级为 major,会污染信号。
Prompt 3:让 Claude 写"建设性评论"
评论语气必须建设性,避免否定式表达:
- ❌ "这里写错了"
- ✅ "这里可以考虑用 X 方式,因为 Y"
- ❌ "这个函数太长"
- ✅ "这个函数有 80 行,建议拆分为 3 个子函数,按职责划分"
每条评论必须包含:问题 + 原因 + 建议。
效果数据
上线 6 周后的数据对比:
| 指标 | 改造前 | 改造后 | 提升 |
|---|---|---|---|
| 单 PR 平均 Review 耗时 | 18 分钟 | 4 分钟 | 4.5 倍 |
| 资深工程师 Review 占用 | 30% 工作日 | 12% 工作日 | -60% |
| 低级问题漏检率 | 22% | 4% | -82% |
| 上线后缺陷数 | 月均 8 个 | 月均 3 个 | -62% |
| 新人 PR 等待时间 | 6 小时 | 15 分钟 | 24 倍 |
| 评审一致性(不同人评分差异) | ±3 分 | ±1 分 | 提升 |
| 开发者满意度 | 6.8/10 | 8.9/10 | +31% |
经验总结
成功要点
- Prompt 是核心:花了 3 天调 Prompt,每一版都用历史 PR 回测,命中率从 40% 提升到 78%。
- 静态检查先行:ESLint/Prettier 拦截 60% 低级问题,AI 只看真正需要智能判断的部分。
- 双模型交叉校验:Claude + GPT-4 互为补充,单一模型误报率 15%,双模型降到 2%。
- 谨慎用 request_changes:只对 critical 阻塞,否则会激怒开发者。
- 必须有 praise:AI 只挑刺会让团队抵触,强制要求每个 PR 至少 1 条 praise。
踩过的坑
- diff 太大 OOM:超过 5000 行的 PR 要分文件评审,否则 Claude 会截断输出。
- 评论行号错位:GitHub Review API 的
line是 diff 中的行号,不是源文件行号,需要用patch解析。 - GPT-4 JSON 格式漂移:用
response_format: { type: 'json_object' }强制 JSON,否则会夹杂自然语言。 - Token 成本失控:每个 PR 平均消耗 $0.8,月成本约 $300,需设定预算告警。
- Bot 评论淹没人工评论:限制 AI 评论数 ≤ 15 条,超过则合并为 summary。
- 测试代码误报:在 Prompt 中明确"测试文件降级评审,只看是否覆盖主路径"。
- 生成文件误报:
.generated.ts、*.lock等文件跳过评审,在 Actions 中过滤。
不可评审的场景
AI 评审不能完全替代人工,以下场景必须人工介入:
- 跨服务的架构改动
- 涉及金额、权限、安全的核心逻辑
- 数据库 migration
- 公开 API 的破坏性变更
- 性能敏感的热路径
策略:AI 评审通过后,仍需 1 个人工 approve 才能合并,AI 只是第一道筛选。
复用建议
这套方案可迁移到:
- 前端 PR:调整 Prompt 关注 React 性能、a11y、i18n
- Go/Rust PR:调整 Prompt 关注内存安全、goroutine 生命周期
- SQL migration:单独写 Prompt 检查索引、回滚脚本
- 文档 PR:检查链接、示例代码可运行性、术语一致性
核心是把评审规范文档化(CODE_REVIEW.md),让 AI 有据可依。
工具版本
- Claude Code:v1.0.20+
- Cursor:v0.40+
- Anthropic SDK:0.40+
- Octokit:3.1+
- GitHub Actions:runner ubuntu-22.04+
- 月度 API 成本:约 $300(120 PR × $0.8 + GPT-4 校验 $200)