1. 为什么“敢不敢合并”成了 AI 编程时代的新瓶颈
过去一年,我身边几乎所有团队都在用 AI 写代码。从补全单行函数到生成整个模块,效率提升是实打实的。但一个奇怪的现象反复出现:代码写得越来越快,合并进主干的频率却没怎么涨。很多 PR 在评审环节卡住,不是因为代码跑不起来,而是因为没人敢拍板说“这玩意儿没问题,合吧”。
我自己就踩过这个坑。有一次让 AI 帮我重构一个订单状态机,它给出的实现逻辑清晰、测试也过了,我扫了两眼就合并了。结果上线第二天发现一个边界条件没覆盖——当订单同时触发退款和发货时,状态流转出现了死锁。问题不在于 AI 写得差,而在于我作为评审者,手里没有足够的证据去判断它到底覆盖了多少场景、改动了哪些隐含假设。
这就是标题里说的“敢不敢合并”的核心:AI 生成的代码,评审成本反而更高了。传统人工写的代码,作者脑子里有一条完整的推理链,评审时可以通过提问快速对齐。但 AI 生成的代码,作者自己可能都没完全理解每一行的意图,评审者面对的是一个“黑盒产出物”。你看到的只是一个git diff,但 diff 背后的决策依据、边界条件、潜在副作用,全都不在眼前。
所以真正的问题不是“AI 能不能写代码”,而是“AI 写完代码之后,我们用什么机制来建立信任,让合并这个动作有据可依”。我推荐的这个“有证据链的代码评审 Skill”,就是围绕这个痛点设计的。它不是一个工具,而是一套嵌入 Agent 工作流的评审方法论,核心目标是:让每一次合并决策都有可追溯的证据支撑,而不是靠感觉拍脑袋。
这篇文章适合三类人:一是正在用 AI 辅助编程但被评审环节卡住的开发者;二是负责搭建 AI Agent 工作流的技术负责人;三是对代码评审自动化感兴趣、想了解 Skill 机制怎么落地的人。我会从设计思路、核心机制、实操步骤、常见问题四个层面拆开讲,尽量把每个决策背后的“为什么”说清楚。
2. 这个代码评审 Skill 的整体设计与思路拆解
2.1 为什么不是“再写一个 Linter”,而是做 Skill
很多人第一反应是:代码评审自动化,不就是加规则、跑静态检查吗?ESLint、SonarQube 这些工具早就有了。但问题在于,传统 Linter 检查的是“代码符不符合规范”,而 AI 代码评审需要检查的是“这次改动是否可信”。这两件事的维度完全不同。
举个例子,AI 可能写出一个完全符合 ESLint 规则的函数,但它悄悄把某个异常处理从throw改成了console.log。Linter 不会报错,因为语法没问题。但这是一个语义层面的风险改动,需要评审者结合上下文判断。传统工具覆盖不了这种场景,因为它没有“意图理解”能力。
所以我选择用 Skill 的形式来做。Skill 在 Agent 架构里,本质上是一段可复用的能力封装,它可以让 Agent 在特定场景下调用一套预定义的分析流程。和普通脚本的区别在于,Skill 是有“上下文感知”的——它能读取git diff、能访问仓库历史、能调用其他 Agent 做交叉验证。这就为“证据链”提供了基础。
提示:Skill 和普通插件的核心区别在于,Skill 通常带有明确的触发条件和执行边界,它不是被动等待调用,而是可以在 Agent 工作流中主动介入。这一点在设计评审流程时非常关键。
2.2 证据链的三个层次:改动、推理、验证
我给这个 Skill 设计的核心结构是“三层证据链”。每一层解决一个不同的问题,缺一不可。
第一层是改动证据。这一层最基础,就是精确记录这次git diff到底改了什么。但注意,不是简单地把 diff 打印出来,而是要做结构化解析:改了哪些文件、哪些函数、哪些条件分支、哪些依赖引用。我实测下来,把 diff 按“语义单元”拆开之后,评审效率能提升至少一倍,因为评审者不用自己在几百行 diff 里找重点。
第二层是推理证据。这一层是核心。AI 生成代码时,它的“思考过程”往往是隐式的。这个 Skill 要求 Agent 在生成代码的同时,输出一份“改动意图说明”:为什么这么改、依据是什么、影响了哪些调用方、有没有替代方案。这份说明不是给人看的文档,而是作为评审证据的一部分被记录下来。当评审者看到 diff 时,旁边就有一份“作者自述”,对齐成本大幅降低。
第三层是验证证据。这一层解决“怎么证明它真的没问题”。包括:新增或修改的测试用例是否覆盖了改动点、原有测试是否全部通过、有没有做边界条件的补充验证。我要求这个 Skill 在提交评审前,自动跑一遍相关测试并记录结果,如果测试覆盖不足,直接标记为“证据不完整”,不允许进入合并流程。
这三层加起来,就形成了一条完整的证据链:改了什么 → 为什么这么改 → 怎么证明改对了。评审者拿到这条链,合并决策就从“我感觉没问题”变成了“证据显示没问题”。
2.3 为什么强调“敢不敢”而不是“能不能”
这里有一个微妙的心理层面的设计。技术上,“能不能合并”是一个客观判断,测试过了就能合。但实际工作中,评审者犹豫的往往是“敢不敢”——这是一种主观信任问题。AI 代码的问题在于,它看起来往往很“自信”,语法正确、格式漂亮,但可能藏着一个你没想到的坑。
这个 Skill 的设计目标之一,就是把这种主观信任转化为客观证据。当评审者看到三层证据链完整时,心理负担会显著降低。我自己的体验是,以前评审 AI 代码要花 20 分钟反复看,现在有了证据链,5 分钟就能做决策。这不是因为我看得更快了,而是因为我不需要再靠“猜”来补全信息。
3. 核心细节解析与实操要点
3.1 git diff 的结构化解析:怎么把“一堆改动”变成“可读证据”
git diff本身是行级别的,但评审需要的是语义级别的信息。这个 Skill 的第一步,就是把 diff 做结构化解析。具体怎么做?我用的方案是结合 AST(抽象语法树)分析和 diff 行映射。
先拿到 diff 的变更行范围,然后对变更文件做 AST 解析,定位到具体的函数、类、条件分支。这样就能生成一份“语义变更清单”,而不是“第 45 行到第 67 行有改动”。举个例子,原始 diff 可能显示某个文件改了 30 行,但结构化之后会告诉你:修改了calculateRefund函数的异常处理逻辑,新增了一个if (order.status === 'LOCKED')的分支判断。
# 伪代码示意:diff 结构化解析的核心逻辑 import ast import subprocess def parse_diff_semantics(file_path, diff_range): # 获取变更后的文件内容 with open(file_path, 'r') as f: source = f.read() # 解析 AST tree = ast.parse(source) # 定位变更行所在的函数和分支 changed_nodes = [] for node in ast.walk(tree): if hasattr(node, 'lineno') and diff_range[0] <= node.lineno <= diff_range[1]: changed_nodes.append({ 'type': type(node).__name__, 'name': getattr(node, 'name', 'anonymous'), 'line': node.lineno }) return changed_nodes这段逻辑的关键在于:不要试图解析所有语言,先支持团队主力语言即可。我一开始想做一个通用方案,结果发现不同语言的 AST 差异太大,维护成本极高。后来改成只支持 Python 和 TypeScript,覆盖了团队 90% 的代码,性价比最高。
注意:结构化解析的粒度要适中。太粗了等于没解析,太细了会产生大量噪音。我的经验是,定位到“函数级 + 关键分支级”就够了,不需要精确到每一行表达式。
3.2 改动意图说明的生成:让 Agent 自己“交代清楚”
这一层是整个 Skill 里最容易被忽略、但价值最高的部分。我要求 Agent 在生成代码后,必须输出一份结构化的意图说明,包含四个字段:
- 改动目标:这次改动要解决什么问题,一句话说清楚。
- 核心思路:用什么方式解决的,为什么选这个方式。
- 影响范围:涉及哪些调用方、哪些数据流、哪些外部依赖。
- 未覆盖场景:明确列出这次改动没有处理的边界情况。
最后这个“未覆盖场景”特别重要。AI 生成代码时,往往会默默忽略一些它认为不重要的边界条件。如果评审者不知道这些忽略存在,就会误以为改动是完整的。强制 Agent 列出未覆盖场景,等于把隐式假设显式化,评审者可以据此判断是否需要补充。
我实测下来,这份说明的生成质量取决于提示词的设计。如果只是简单说“解释一下你的改动”,Agent 会给出很泛泛的描述。但如果给出明确的字段结构和示例,输出质量会稳定很多。这也是 Skill 相比普通提示词的优势——它可以把最佳实践固化下来,每次调用都保持一致。
3.3 验证证据的自动化采集:测试不是万能的,但没有测试是万万不能的
验证层我设计了三道检查:
- 改动点覆盖检查:新增或修改的逻辑,是否有对应的测试用例覆盖。这个可以通过分析测试文件和源码的映射关系来判断。
- 回归测试执行:跑一遍相关模块的测试套件,记录通过率和耗时。
- 边界条件补充:对于 AI 标记为“未覆盖场景”的部分,检查是否有对应的测试或手动验证记录。
这里有个实操心得:不要让 Skill 自动生成测试用例并直接采信。我试过让 Agent 自己写测试、自己跑、自己报告通过,结果发现它写的测试往往只覆盖“快乐路径”,边界条件基本不碰。后来改成:Skill 只负责检查“有没有测试”,测试内容由人工确认或由独立的测试 Agent 生成后再交叉评审。这样虽然多了一步,但证据的可信度高了很多。
| 检查项 | 自动化程度 | 人工介入点 | 证据形式 |
|---|---|---|---|
| 改动点覆盖 | 全自动 | 无 | 覆盖率报告 |
| 回归测试 | 全自动 | 失败时介入 | 测试日志 |
| 边界条件 | 半自动 | 确认补充方案 | 验证记录 |
| 意图说明 | 全自动生成 | 评审时确认 | 结构化文档 |
3.4 Skill 的触发时机与执行边界
这个 Skill 不是每次保存代码都跑,那样太吵。我设定的触发条件是:当 Agent 完成一个完整的代码生成任务,准备提交评审时,自动触发。具体来说,就是 Agent 输出“任务完成”信号后,Skill 介入,采集三层证据,生成评审包。
执行边界也很重要。这个 Skill 只做证据采集和整理,不做合并决策。合并与否仍然由人判断。我见过一些团队试图让 Agent 自动合并低风险改动,结果出了几次事故之后就放弃了。证据链的价值在于辅助决策,而不是替代决策。至少在当前阶段,让 AI 自己决定合并自己的代码,风险还是太高。
4. 实操过程与核心环节实现
4.1 环境准备:把 Skill 挂载到 Agent 工作流里
先说前置条件。你需要一个支持 Skill 机制的 Agent 框架,比如常见的 Agent 开发框架都支持自定义 Skill 注册。我用的是基于 Python 的 Agent 框架,Skill 以独立模块的形式注册进去。
# 目录结构示意 agent_project/ ├── skills/ │ ├── code_review_evidence/ │ │ ├── __init__.py │ │ ├── diff_parser.py │ │ ├── intent_collector.py │ │ ├── verification_runner.py │ │ └── skill.yaml ├── agent_config.yaml └── main.pyskill.yaml里定义触发条件和执行入口:
name: code_review_evidence trigger: event: task_completed condition: "task.type == 'code_generation'" entry: "__init__.py:run" timeout: 300这里的关键是trigger的配置。我一开始把触发条件设得太宽,结果 Agent 每做一个小改动都触发评审流程,噪音很大。后来改成只在“完整任务完成”时触发,体验好了很多。
4.2 核心环节一:采集 diff 并生成结构化变更清单
Agent 完成任务后,Skill 首先拿到本次任务的变更范围。这里有个细节:不要直接用git diff HEAD,因为 Agent 可能做了多次提交。我的做法是记录任务开始时的 commit hash,结束时用git diff <start_hash> HEAD来获取完整变更。
import subprocess def get_task_diff(start_commit): result = subprocess.run( ['git', 'diff', f'{start_commit}', 'HEAD', '--unified=0'], capture_output=True, text=True ) return result.stdout拿到 diff 后,按文件拆分,对每个文件做 AST 解析,生成结构化清单。这一步的输出格式我设计成 JSON,方便后续处理和展示:
{ "file": "order/state_machine.py", "changes": [ { "type": "function_modified", "name": "calculate_refund", "line_range": [45, 67], "change_summary": "新增 LOCKED 状态分支判断" }, { "type": "condition_added", "name": "order.status === 'LOCKED'", "line": 52, "change_summary": "锁定状态下不允许退款" } ] }这份清单就是“改动证据”的核心。评审者不需要自己读 diff,直接看这份清单就能快速定位重点。
4.3 核心环节二:生成改动意图说明并关联到具体变更
意图说明的生成,我用的方式是让 Agent 在生成代码时同步输出。具体做法是在提示词里加入一个结构化输出要求:
INTENT_PROMPT = """ 完成代码生成后,请输出以下结构化说明: 1. 改动目标:(一句话) 2. 核心思路:(为什么选这个方案) 3. 影响范围:(列出受影响的调用方和数据流) 4. 未覆盖场景:(明确列出没有处理的边界情况) 输出格式为 JSON,字段名分别为 goal, approach, impact, uncovered。 """然后 Skill 把这份说明和结构化变更清单做关联。关联逻辑是:根据变更涉及的文件和函数名,把意图说明挂载到对应的变更条目上。这样评审者看到某个函数改动时,旁边就有对应的意图说明。
提示:意图说明的质量高度依赖提示词。我建议在提示词里给出一两个示例,Agent 的输出会稳定很多。另外,如果 Agent 输出的“未覆盖场景”为空,要警惕——大概率是它没认真想,而不是真的没有未覆盖场景。
4.4 核心环节三:自动执行验证并生成证据包
验证环节我设计了一个执行流水线:
- 根据变更清单,找到相关的测试文件。
- 执行测试套件,记录结果。
- 检查变更点的测试覆盖情况。
- 生成验证报告。
def run_verification(changed_files): # 找到相关测试 test_files = find_related_tests(changed_files) # 执行测试 test_result = subprocess.run( ['pytest', '-v'] + test_files, capture_output=True, text=True ) # 解析结果 report = { 'total': parse_total(test_result.stdout), 'passed': parse_passed(test_result.stdout), 'failed': parse_failed(test_result.stdout), 'coverage': get_coverage(changed_files) } return report最后,Skill 把三层证据打包成一个评审包,输出到指定位置。评审者打开评审包,就能看到完整的证据链。
4.5 评审包的展示与合并决策流程
评审包的展示形式我试过几种,最后发现最有效的是“变更清单 + 意图说明 + 验证报告”三栏并列的格式。评审者可以逐条查看每个变更,旁边有对应的意图和验证结果。
合并决策流程我设定为:
- 评审者打开评审包,快速浏览变更清单。
- 对每个变更点,确认意图说明是否合理。
- 检查验证报告,确认测试覆盖是否充分。
- 如果三层证据都完整,标记为“可合并”。
- 如果有证据缺失,标记为“需补充”,退回给 Agent 或人工补充。
这个流程我用了三个月,最大的感受是:评审时间从平均 20 分钟降到了 5-8 分钟,而且漏审率明显下降。因为证据链强制暴露了“未覆盖场景”,评审者不再需要靠直觉去猜哪里可能有问题。
5. 常见问题与排查技巧实录
5.1 证据链不完整怎么办:分级处理策略
实际使用中,最常见的问题是证据链不完整。比如 Agent 没输出意图说明,或者测试没跑通。我的处理策略是分级:
- 一级缺失(意图说明为空):直接退回,要求 Agent 补充。这是硬性要求,不能妥协。
- 二级缺失(测试覆盖不足):标记为“低置信度”,评审者需要额外人工验证。
- 三级缺失(回归测试失败):直接阻断合并流程,必须先修复。
这个分级机制避免了“一刀切”导致的流程僵化。有些小改动确实不需要完整的三层证据,但意图说明这一层不能省。
5.2 Agent 生成的意图说明太泛怎么办:提示词迭代技巧
我遇到过 Agent 输出的意图说明全是“优化了代码结构”“提升了可读性”这种废话。排查下来,问题出在提示词太宽松。后来我加了两条约束:
- 要求意图说明必须包含具体的函数名和条件表达式。
- 如果改动涉及逻辑变更,必须说明变更前后的行为差异。
改完之后,输出质量明显提升。另外一个小技巧是:在提示词里加入反例。比如“不要输出‘优化了代码结构’这种没有信息量的描述”,Agent 会更有针对性地输出。
5.3 测试跑得太慢拖累评审流程:增量测试策略
全量跑测试在大型项目里可能要十几分钟,评审流程等不起。我的方案是增量测试:只跑与变更文件相关的测试。具体做法是通过依赖分析,找到直接或间接引用变更文件的测试用例。
def find_related_tests(changed_files): related = set() for test_file in glob.glob('tests/**/*.py'): with open(test_file) as f: content = f.read() for changed in changed_files: module_name = changed.replace('/', '.').replace('.py', '') if module_name in content: related.add(test_file) return list(related)这个策略把测试时间从 15 分钟压到了 2-3 分钟,评审体验好了很多。当然,定期还是要跑一次全量测试作为兜底。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 意图说明为空 | 提示词未包含输出要求 | 检查提示词模板 | 加入结构化输出指令 |
| 测试覆盖率为 0 | 变更文件未被测试引用 | 检查测试映射逻辑 | 补充测试或调整映射规则 |
| diff 解析报错 | 文件语法不兼容 AST | 查看报错文件 | 降级为行级解析 |
| 评审包生成超时 | 测试执行时间过长 | 查看测试日志 | 启用增量测试 |
| 合并后出现回归 | 未覆盖场景未验证 | 回溯评审包 | 补充边界测试并更新 Skill |
5.5 几个我踩过的坑和对应的心得
第一个坑是过度依赖自动化。我一开始想让 Skill 全自动判断“是否可以合并”,结果发现它只能判断“证据是否完整”,不能判断“证据是否充分”。这两者差别很大。后来我把决策权交回给人,Skill 只负责整理证据。
第二个坑是意图说明和实际改动脱节。有几次 Agent 输出的意图说明写得很漂亮,但实际代码改动和说明对不上。排查发现是 Agent 在生成说明时“脑补”了意图,而不是基于实际改动。解决方案是:让 Agent 先输出改动清单,再基于清单生成意图说明,确保两者一致。
第三个坑是评审包信息过载。一开始我把所有证据都塞进评审包,结果评审者看不过来。后来改成分层展示:默认只显示变更清单和意图说明,验证报告折叠起来,需要时再展开。这样评审者可以快速浏览,也可以深入查看细节。
6. 我对这套机制的实际体会和后续扩展方向
用了这套证据链评审 Skill 大概半年,最大的变化不是效率数字,而是团队对 AI 代码的信任度。以前大家看到 AI 生成的 PR 会本能地警惕,现在有了证据链,评审者知道该看什么、该确认什么,心理负担小了很多。合并决策从“敢不敢”变成了“证据够不够”,这是一个质的变化。
后续我打算在这几个方向继续扩展:一是把证据链和 CI 流水线打通,让评审包自动生成并附在 PR 描述里;二是引入多 Agent 交叉评审,让一个 Agent 生成代码、另一个 Agent 专门挑刺,把“未覆盖场景”的发现率再提一提;三是把评审证据沉淀成团队知识库,每次评审的意图说明和验证记录都可以作为后续类似改动的参考。
如果你也在用 AI 写代码,并且被评审环节卡住,我建议先从“意图说明”这一层开始做。不需要一上来就搞全套证据链,先把“让 Agent 交代清楚它改了什么、为什么这么改”这件事落地,你会发现评审体验立刻不一样。