1. 从“AI 能写代码”到“敢不敢合并”的真实困境
最近半年,我身边几乎每个开发团队都在用 AI 辅助写代码。Cursor、Copilot、Claude Code、Codex 这些工具轮番上阵,生成一个函数、补全一个模块、甚至从零搭出一个 CRUD 服务,都快得离谱。但有意思的是,代码产出速度上去了,合并请求(Merge Request)的通过率反而没怎么涨,有些团队甚至下降了。
原因不复杂。以前一个 MR 里三百行改动,reviewer 一行行看,心里有底。现在 AI 一口气生成八百行,逻辑看着都对,命名也规范,注释也齐全,但你就是不敢点那个 Merge 按钮。因为你不知道它有没有偷偷改掉一个边界条件,有没有在异常分支里吞掉一个错误,有没有把某个配置项的默认值从false改成了true。
这就是标题里说的那个问题:AI 写代码之后,真正难的是“敢不敢合并”。写不是瓶颈,审才是。而审的核心,不是再看一遍代码长什么样,而是要知道这段代码从哪来、改了什么、为什么这么改、有没有证据支撑它是对的。
我最近在项目里落地了一套基于 Skill 机制的代码评审方案,核心思路就是给每一次 AI 参与的改动附上一条可追溯的证据链。这篇文章就把这套东西拆开讲清楚:它解决什么问题、Skill 怎么设计、git diff 怎么用、Agent 在其中扮演什么角色、实操中踩了哪些坑。适合正在用 AI 写代码、但被 review 环节卡住的团队和个人参考。
2. 为什么传统 Code Review 在 AI 时代失效了
2.1 传统 review 的隐含前提正在崩塌
过去我们做代码评审,默认几个前提:改动量可控、作者能解释每一行、diff 是唯一的真相来源。reviewer 看 diff,结合对业务的理解,判断这段改动是否合理。这套流程运转了十几年,靠的是“人写代码有惯性”——一个人写代码,风格、思路、习惯是连续的,reviewer 能顺着这个惯性去理解。
AI 把这个惯性打断了。同一个 MR 里,可能前半段是 Claude 写的,后半段是 Copilot 补的,中间还有一段是开发者自己手改的。三种“思路”混在一起,diff 看起来是连续的,但背后的决策逻辑是断裂的。你看到一个函数被重写,不知道是 AI 觉得原来的写法不好,还是开发者故意调整了业务逻辑。
更麻烦的是,AI 生成的代码往往表面质量很高。命名规范、注释完整、异常处理看起来也很周全。这种“表面正确”会让人放松警惕,reviewer 扫一眼觉得没问题就过了。但真正的风险藏在细节里:一个>=写成了>,一个try-catch把异常吞了,一个并发场景下的竞态条件被忽略了。这些不是靠“看代码”能看出来的,需要证据。
2.2 “敢不敢合并”本质是信任问题
我观察下来,团队里对 AI 代码的信任度分三档。第一档是“完全不信”,AI 写的全部重写,等于没用。第二档是“盲目相信”,AI 写的直接合,出了问题再说。第三档是“有条件信任”,也是我认为唯一可持续的方式:你给我证据,我就敢合。
证据链要回答几个问题:这段改动对应哪个需求或 issue?AI 是基于什么上下文生成的?改动前后的行为差异有没有测试覆盖?有没有静态检查或类型检查通过?这些信息如果能在 MR 里直接看到,reviewer 的决策成本会大幅下降。
这就是 Skill 要解决的问题。Skill 不是又一个 AI 写代码工具,而是一个评审辅助层,它把 AI 生成代码过程中的上下文、diff、检查结果串成一条链,让“敢不敢合并”从主观判断变成有据可查的流程。
2.3 Skill 机制为什么适合这个场景
Skill 这个概念最近很热,从 Claude 的 Skill 到各种 Agent 框架里的 Skill 插件,本质都是把一段可复用的能力封装成标准接口。用在代码评审上,Skill 的优势在于:它可以被 Agent 调用,也可以被 CI 调用,还可以被开发者手动触发,三种入口共享同一套逻辑。
我选择 Skill 而不是写一个独立脚本,核心原因是评审逻辑需要和 AI 生成过程解耦。生成代码的 Agent 可能是 Cursor、可能是 Claude Code、可能是自研的,但评审 Skill 是统一的。不管代码从哪来,都走同一套证据链检查。这样团队不需要绑定某个 AI 工具,换工具的时候评审标准不变。
3. 证据链代码评审 Skill 的整体设计
3.1 核心思路:让每一次改动都“自带说明”
这套 Skill 的设计原则很简单:任何进入 review 环节的改动,必须附带一条可验证的证据链。证据链包含四个部分:改动来源、改动意图、改动内容、验证结果。四者缺一不可,缺了就在 MR 里标红,reviewer 一眼能看到哪里没交代清楚。
改动来源记录这段代码是 AI 生成的还是人写的,用的哪个模型、哪个版本、什么 prompt 上下文。改动意图关联到具体的 issue、需求文档或对话记录。改动内容就是 git diff,但要经过结构化处理,不是原始 diff 直接扔出来。验证结果包括单元测试、类型检查、lint、以及针对 AI 代码特别加的“行为一致性检查”。
3.2 为什么用 git diff 作为证据链的锚点
git diff 是整个证据链的锚点,因为它是唯一不可篡改的事实。AI 说的、开发者说的、issue 里写的,都可能有偏差,但 diff 是实际发生的改动。Skill 的所有分析都围绕 diff 展开:从 diff 反推改动意图,从 diff 定位风险点,从 diff 关联测试覆盖。
我试过几种 diff 处理方式。直接用git diff原始输出,信息太杂,reviewer 看起来累。用 GitHub/GitLab 的 diff 视图,又绑定了平台。最后选择的是结构化 diff:把 diff 按文件、按函数、按改动类型(新增/删除/修改)拆开,每个改动块附带上下文行和风险标记。这样 reviewer 可以按块审,而不是按行审。
3.3 Skill 的输入输出定义
Skill 的输入很明确:一个 git ref 范围(比如main..feature-branch),加上可选的元数据(issue 链接、AI 生成记录)。输出是一份结构化的评审报告,包含:
- 改动摘要:哪些文件、多少行、改动类型分布
- 风险清单:每个风险点的位置、类型、严重程度
- 证据链完整性评分:四个部分各占多少分,总分低于阈值就阻断合并
- 建议操作:哪些块需要人工重点看,哪些可以快速过
这个输出可以直接作为 MR 的评论发出去,也可以作为 CI 的门禁条件。我目前的配置是:证据链评分低于 70 分,CI 直接 fail,不允许合并。
4. 核心细节解析与实操要点
4.1 改动来源的采集:别让 AI 生成记录丢失
改动来源这块,最容易出问题。AI 生成代码的时候,上下文往往在对话窗口里,一旦关掉就没了。我的做法是在生成阶段就埋点:不管用哪个 AI 工具,生成完代码后,把 prompt、模型版本、生成时间写到一个.ai-trace.json文件里,跟代码一起提交。
这个文件不需要很复杂,几个关键字段就够:
{ "tool": "claude-code", "model": "claude-sonnet-4", "prompt_hash": "a3f8...", "generated_at": "2025-01-15T10:30:00Z", "files_touched": ["src/service/user.ts", "src/utils/validate.ts"], "human_edited": true }human_edited这个字段很重要。如果 AI 生成后开发者手动改过,reviewer 需要知道哪些部分是人工干预的。我见过太多情况,AI 生成的代码有问题,开发者改了一半,结果 MR 里看不出来哪些是 AI 的锅哪些是人的锅。
注意:
.ai-trace.json不要提交敏感信息,prompt 里如果有业务数据或密钥,只存 hash 不存原文。
4.2 改动意图的关联:从 diff 反推需求
改动意图这块,理想情况是每个 MR 都关联 issue。但实际项目里,很多改动是“顺手改的”,没有 issue。Skill 的处理方式是从 diff 反推意图:分析改动的函数名、变量名、注释变化,匹配项目里的需求文档或历史 issue。
比如 diff 里出现了calculateDiscount这个函数被修改,Skill 会去搜索项目文档里包含“折扣”“discount”的 issue,列出最相关的几个让开发者确认。这个匹配不需要很精确,目的是给 reviewer 一个上下文线索,而不是自动判定。
我实测下来,这种反推的准确率大概在六成左右,剩下的四成需要开发者手动补。但即使只补六成,reviewer 的理解成本也降了很多。关键是不要让意图字段空着,空着就在报告里标黄,提示“此改动缺少意图说明”。
4.3 结构化 diff 的生成:把八百行拆成可审的块
原始 diff 对 reviewer 不友好,尤其是 AI 生成的大块改动。Skill 会把 diff 按以下维度拆解:
| 拆分维度 | 说明 | 用途 |
|---|---|---|
| 按文件 | 每个文件一个块 | 快速定位影响范围 |
| 按函数 | 函数级改动单独成块 | 关联测试覆盖 |
| 按改动类型 | 新增/删除/修改分开 | 识别高风险操作 |
| 按风险等级 | 高/中/低标记 | 优先审高风险块 |
风险等级的判定规则我调了好几版。目前用的规则是:涉及条件判断、异常处理、并发、配置默认值的改动标为高风险;纯新增函数、纯注释、纯格式化标为低风险;其余为中风险。这套规则不完美,但比“全部一视同仁”强太多。
4.4 验证结果的收集:测试、类型、lint 一个不能少
验证结果这部分,Skill 会调用项目现有的检查工具,把结果汇总。单元测试看覆盖率和通过率,类型检查看有没有新增错误,lint 看有没有新增警告。针对 AI 代码,我额外加了一项行为一致性检查:对改动前后的函数,用同一组输入跑一遍,对比输出是否一致。不一致就标红,提示“行为可能发生变化”。
这个检查用简单的脚本就能实现,不需要复杂的测试框架。核心是给每个被修改的函数准备一组基准输入,这些输入可以从现有测试用例里提取,也可以手动构造。我一般让开发者对高风险函数手动补基准输入,低风险函数用自动提取的。
5. 实操过程与核心环节实现
5.1 环境准备与 Skill 注册
这套 Skill 我是在一个 Node.js 项目里落地的,但逻辑跟语言无关。环境准备分三步:装依赖、配检查工具、注册 Skill。
依赖主要是simple-git(读 diff)、@octokit/rest(发 MR 评论,可选)、zod(校验 trace 文件格式)。检查工具复用项目已有的:Jest 跑测试,TypeScript 做类型检查,ESLint 做 lint。
Skill 注册这块,我用的是一个轻量的 Agent 框架,把评审逻辑封装成一个reviewSkill对象,暴露analyze(refRange)方法。Agent 调用这个方法拿到报告,CI 也调用同一个方法。这样保证手动触发和自动触发走同一套逻辑,不会出现“本地看着没问题,CI 挂了”的情况。
5.2 核心流程:从 git diff 到评审报告
完整流程我拆成六步,每一步都有对应的代码模块:
- 读取 diff:
git diff main...feature --unified=5,拿带上下文的 diff。 - 解析 diff:用
parse-diff库把原始 diff 解析成结构化对象,按文件、hunk、行拆分。 - 加载 trace:读取
.ai-trace.json,校验格式,缺失就标黄。 - 风险分析:对每个 hunk 跑风险规则,打标签。
- 验证收集:并行跑测试、类型检查、lint、行为一致性检查。
- 生成报告:汇总成 Markdown 报告,发到 MR 评论,同时输出评分。
这六步里,风险分析是最需要调优的。我一开始规则写得太粗,把所有if改动都标高风险,结果报告里全是红块,reviewer 直接忽略。后来改成按上下文判断:如果if改动涉及边界值(>、>=、<、<=)或空值判断,才标高风险;普通逻辑调整标中风险。
5.3 参数计算:证据链评分怎么算
证据链评分是这套 Skill 的门禁依据,算法我调了几版,目前用的是加权求和:
| 维度 | 权重 | 评分规则 |
|---|---|---|
| 改动来源 | 25% | 有 trace 且字段完整得满分,缺失按比例扣 |
| 改动意图 | 25% | 关联 issue 得满分,反推匹配得一半,空着得零 |
| 改动内容 | 20% | 结构化 diff 完整得满分,解析失败按比例扣 |
| 验证结果 | 30% | 测试通过+类型通过+lint通过+行为一致得满分,每缺一项扣对应分 |
总分 100,低于 70 阻断合并,70 到 85 提示“建议补充证据”,85 以上放行。这个阈值可以根据团队情况调,我目前用的 70 是试了几次之后定的,太松没效果,太严开发者会绕过。
提示:评分不是目的,目的是让开发者养成“提交前补证据”的习惯。跑了一两个月之后,大部分 MR 的评分都能到 85 以上,因为开发者知道缺什么会被卡。
5.4 实操现场:一次真实的评审记录
拿最近一个真实 MR 举例。改动是给用户服务加了一个缓存层,AI 生成的代码大概 400 行,涉及 5 个文件。Skill 跑完之后的报告摘要:
- 改动来源:有 trace,模型 claude-sonnet-4,
human_edited: true - 改动意图:关联到 issue #1234“用户查询性能优化”
- 改动内容:结构化 diff 拆成 12 个块,其中 3 个高风险(缓存失效逻辑、并发写入、默认 TTL 配置)
- 验证结果:测试通过,类型通过,lint 通过,行为一致性检查发现 1 处不一致(缓存命中时的返回值类型从
User变成了User | null)
评分 78,提示“建议补充证据”。reviewer 重点看了那 3 个高风险块和 1 处不一致,发现缓存命中时返回null的情况没有处理,让开发者补了一个判断。整个过程大概 15 分钟,如果没有这份报告,reviewer 可能要花一小时逐行看,还不一定发现那个null问题。
6. 常见问题与排查技巧实录
6.1 trace 文件丢失或格式错误
这是最常见的问题。开发者用 AI 生成代码后,忘了写 trace 文件,或者写的时候字段不全。Skill 的处理是不阻断,但标黄,在报告里提示“改动来源缺失,建议补充”。如果团队要求严格,可以在 CI 里配置成阻断。
排查技巧:在项目的 pre-commit hook 里加一个检查,如果 diff 里有大段新增代码(比如超过 50 行)但没有 trace 文件,就提示开发者补。这个 hook 不需要很智能,粗粒度判断就够。
6.2 diff 解析失败:重命名和二进制文件
parse-diff对重命名和二进制文件的处理有时候会出问题。重命名文件会被拆成“删除+新增”,导致风险分析误判。我的处理是在解析前先跑git diff --name-status,识别出重命名,手动合并。二进制文件直接跳过,在报告里标注“二进制文件改动,需人工确认”。
6.3 行为一致性检查的误报
行为一致性检查有时候会误报,比如函数内部重构了但外部行为没变,检查却因为中间变量名变了而报不一致。我的处理是对比最终返回值,不对比中间过程。如果返回值一致,即使内部实现变了,也判为一致。这个调整把误报率从三成降到了一成左右。
6.4 开发者绕过 Skill 直接合并
这是管理问题,不是技术问题。我的做法是在 CI 里设门禁,评分低于阈值直接 fail,开发者想绕过就得改 CI 配置,这个动作在团队里是可见的。另外,我会定期看被绕过的 MR,分析原因,如果是 Skill 误判就调规则,如果是开发者图省事就沟通。
6.5 常见问题速查表
| 问题 | 现象 | 排查思路 | 解决方式 |
|---|---|---|---|
| trace 缺失 | 报告标黄“改动来源缺失” | 检查.ai-trace.json是否存在 | 补文件,或在 pre-commit 加提示 |
| diff 解析错乱 | 重命名文件被拆成删除+新增 | 跑git diff --name-status确认 | 解析前合并重命名 |
| 行为检查误报 | 重构代码被标不一致 | 看返回值是否真的变了 | 改为只对比返回值 |
| 评分过低 | CI fail,无法合并 | 看哪个维度扣分多 | 补对应证据,或调权重 |
| 报告太长 | reviewer 不看 | 看高风险块数量 | 调风险规则,减少误标 |
6.6 独家避坑技巧
第一个技巧:trace 文件用 hash 存 prompt,不存原文。我一开始存了原文,结果 MR 里出现了业务数据,差点出问题。后来改成只存 hash,需要复现的时候用 hash 去日志系统查。
第二个技巧:风险规则宁少勿多。规则太多,报告里全是红块,reviewer 会麻木。我目前只保留最核心的几条:边界值改动、异常处理改动、并发相关改动、配置默认值改动。其他都归为中低风险。
第三个技巧:评分阈值先松后紧。刚上线的时候设 60,让开发者适应流程,跑一个月后调到 70,再跑一个月调到 75。一步到位设太高,开发者会抵触。
第四个技巧:报告里给“建议操作”。不要只列问题,要告诉 reviewer 哪些块可以快速过,哪些块需要重点看。我一般把高风险块排在最前面,附上“建议人工确认”的标记。
7. 后续可以怎么扩展
这套 Skill 目前只覆盖了评审环节,但证据链的思路可以往前和往后延伸。往前,可以在 AI 生成阶段就强制写 trace,而不是生成完再补。往后,可以把评审报告存档,作为项目质量的历史记录,后续出问题的时候可以回溯。
我还试过把评审报告喂给另一个 AI 做二次分析,让它总结“这个 MR 最可能出问题的地方”。效果一般,因为 AI 分析 AI 代码容易陷入同样的盲区。后来改成用规则引擎做初筛,人工做终审,反而更稳。
另一个扩展方向是多 AI 协作场景。现在一个 MR 里可能混了多个 AI 工具的产出,trace 文件需要支持多条记录。我把.ai-trace.json改成了数组格式,每个元素记录一次生成。这样 reviewer 能看到“这段是 Claude 写的,那段是 Copilot 补的”,理解成本更低。
最后分享一个我在实际使用中的体会:证据链的价值不在于自动化,而在于让“不敢合并”变成“有据可合”。AI 写代码的速度已经够快了,评审环节如果还靠人肉硬扛,整个流程就会被卡住。Skill 做的是把评审需要的信息提前准备好,让 reviewer 的注意力集中在真正需要判断的地方,而不是花时间去找上下文。这套东西跑顺之后,我们团队的 MR 平均合并时间从两天降到了半天,而且合并后出问题的概率没有上升。