团队里 Code Review 规范写了十几页,评审会上还是会因为“这个应该用 XX 而不是 YY”吵起来。后来我把这套规范整理成了一份 Agent Skill,让 AI 在每次 Pull Request 提交后先按同一套规则过一遍,原本最耗精力的“背景解释”环节直接消失了。这篇文章就把完整过程拆开讲:如何把散落在文档里的 Code Review 规范转成 SKILL.md、如何设计检查清单、什么时候需要配套脚本,以及实测过程中的效果和坑。适合正在做代码质量治理、团队工具链建设,或者想让 AI 真正参与评审流程的开发者参考。
1. 为什么要把 Code Review 规范做成 Skill
1.1 从“口头规范”到“可执行技能”
大多数团队的 Code Review 规范都处于一种“明明写了,但没完全写”的状态。文档是有的,可能还挂在内网 Wiki 上,但真到评审时,没人会临时打开文档逐条核对。规范只存在于几位核心开发者的脑子里,新来的同学只能靠被 review 时候的“言传身教”慢慢悟。结果就是:同一个问题,老员工看到会皱眉,新员工看到会直接合入。
Agent Skills 的核心思路,是把一套“怎么做事的方法论”打包成一个目录结构。目录里有说明文件(通常是 SKILL.md),告诉 AI 这个技能是干什么的、什么场景该用、具体怎么执行;目录里还可以放脚本、参考文档、示例代码,作为技能的补充工具。和直接把规范塞进对话提示词最大的区别是:Skill 不是每次对话都加载的,它是按需调用的。
这有点像给 AI 一本手册,而不是在它耳边大声念一遍手册内容。手册放在它的“工具箱”里,遇到评审场景它自己知道翻出来看。这样一来,规范本身可以独立维护,不需要每次重新编辑提示词;规则复杂了也不用担心把对话上下文撑爆。
1.2 我踩过的“每次都要重新解释规范”的坑
在没有 Skill 之前,我试过两条路,都走不通。
第一条路是“人肉在对话里粘贴规范片段”。把 Code Review 规范的关键段落复制到对话里,让 AI 按规则分析 diff。短期看有效,但问题很明显:第一,规范多的时侯粘贴的内容占了大量上下文,AI 分析代码的空间被压缩;第二,粘贴的规范是静态的,团队规范更新之后,旧提示词还在被反复使用,规则版本混乱;第三,不同成员提问的措辞不一样,AI 给出的评审标准就飘忽不定,同一个改动今天说不行、明天说可以。
第二条路是给 AI 一个“万能评审提示词”,让它自由发挥。这个更不靠谱。AI 没有领域背景时,评审意见会变得非常泛化:格式问题、命名问题、缺少注释之类的低级意见一大堆,真正影响线上稳定性的逻辑缺陷反而容易被漏掉。
把 Code Review 规范写成 Skill 之后,相当于把规则从“要人记”变成“要吃透”。AI 每次评审都按同一套标准来,规则文件是团队仓库存量,改一次全员生效。这个转变才是沉淀 Skill 最大的价值。
2. Skill 文件结构:先把规范拆成机器能读的“说明书”
2.1 SKILL.md 的标准骨架
在网上能找到的描述中,Agent Skill 的核心文件一般叫 SKILL.md。不同智能体平台对字段命名大同小异,但骨架基本是固定的。
--- name: code-review-guard description: 当用户提交 Pull Request、diff 或代码片段,需要检查代码质量、遵循团队规范、评审代码时使用。 version: 1.0.0 allowed-tools: - code_reader - shell_command --- # Code Review 规范执行手册 ## 适用场景 本 Skill 用于对代码变更进行结构化评审,按团队规范给出可执行的问题列表。 ## 评审流程 1. 读取 diff 或变更内容 2. 按优先级检查问题 3. 输出结构化评审结果我见过有人上来就写几千字的规范细节,最后描述文件比项目代码还长。其实 SKILL.md 的关键不是“写满”,而是“写清楚”。“name”是这个技能的身份证,最好能做到见名知义;“description”决定 AI 在什么情况下会翻出这个技能;“version”是给团队协作留的口子,规则更新后可追溯。
2.2 description 字段的写法是头等大事
description 写得好不好,直接决定这个 Skill 会不会被 AI 在正确的时机调用。很多人图省事写“用于代码评审”,太笼统了。AI 的调用判断机制通常会扫描 description,如果描述不明确,“该调用的时候没调用”和“不该调用的时候乱调用”都会发生。
拿我自己的实践来说,description 至少要说清三件事:
- 触发场景:什么时候应该用?比如“当用户提交 PR、diff、代码片段,需要评审代码质量时”。
- 输入形式:Skill 拿到的是什么?比如“输入为 diff 文本、文件路径或变更描述”。
- 输出承诺:用它之后能拿到什么?比如“输出按优先级分类的问题列表,并附上修改建议”。
description: > 当用户要求评审代码、检查 Pull Request、或者询问“这个改动有没有问题”时使用。 输入可以是 diff 文本、文件路径或者一段变更描述。 输出为按 P0/P1/P2 分类的评审结果,每条包含问题描述、对应代码位置、修改建议。2.3 指令正文:别把文档复制粘贴进去
Skill 正文很容易犯的一个错误是“原封不动搬运规范文档”。规范文档是为人类写的,大量上下文铺垫、背景解释、历史决策说明,这些对 AI 来说都是噪音。
正文应该围绕“AI 执行评审”这个任务来重构。我通常用三段式结构:
- 先给分类体系:把规范按类别拆开,告诉 AI 先看哪些、再看哪些。
- 再给检查事项:每条规则保持独立、可判断、有明确指向。
- 最后给输出格式:规定 AI 返回的结构,避免每次输出风格不一致。
规则之间还要考虑优先级和冲突消解。比如“性能优先”和“可读性优先”可能在某个场景下矛盾,正文里就得写清楚冲突时听谁的。不写清楚,AI 只能自己猜,评审标准就又不稳定了。
3. 把 Code Review 规范写成 Skill:完整实操
3.1 第一步:盘点团队规范,分成三层
我拿到团队的规范文档后,没有直接开始写 SKILL.md,而是先做了一次“规范盘点”。把文档里所有跟评审相关的条目全部列出来,然后分三层。
第一层:高频通用规则。比如命名、空值处理、异常处理、日志输出,几乎每次评审都会用到,必须放进 SKILL.md 正文。这一类规则是 AI 评审时的“默认视角”。
第二层:架构/性能规则。比如不允许循环内查库、限制单函数长度、依赖方向控制。这些规则重要但不是每次变更都涉及,属于重点检查项。
第三层:低频复杂规范。比如“数据迁移脚本必须经过压测”“第三方 SDK 升级需要灰度方案”。这类规则场景明确、流程复杂,不适合放在正文里,更适合拆到单独的子文件或让脚本检查。
分层之后,我就知道 SKILL.md 正文的篇幅控制在哪里。不是说规则越多越好,而是该出现的时候能出现。
3.2 第二步:设计 Review 检查清单
Code Review 规范要转化成 AI 可执行的格式,检查清单是最好用的组织形式。我参考了团队原来规范文档里的检查点,做了这样一张表:
| 检查类别 | 规范要点 | 判定示例 |
|---|---|---|
| 正确性 | 空值处理必须显式 | user?.name这类可选链用法是否前后一致 |
| 安全 | 禁止硬编码密钥 | 代码中是否出现 token、password 字样 |
| 可维护性 | 新逻辑必须补充注释 | 函数超过 50 行且无注释,是否提示拆解 |
| 性能 | 禁止循环内查库 | for循环体内是否有 I/O 操作 |
| 日志 | 关键分支必须有日志 | catch块是否只吞异常无输出 |
表格做完之后,还要把它转成 SKILL.md 正文里的检查步骤。AI 不像人,看到表格不会自动知道“先查哪个、后查哪个”。所以正文里我会显式地写:
## 检查步骤 1. 先做完整性扫描:检查是否有硬编码密钥、空 catch 块、被注释的代码。 2. 再做逻辑审查:检查空值处理、边界条件、事务与锁的使用。 3. 最后做架构审查:检查是否存在循环依赖、不合理的长函数、重复代码。这一步等于告诉 AI:你先看什么,再看什么,最后看什么。没有这个顺序,AI 给的意见往往是为凑数而找问题。
3.3 第三步:把命令型规则转成“引导式提问”
这是我在实测中发现的一个关键细节:对 AI 下命令不如向 AI 发问。
规范文档里通常写的是“禁止使用全局变量”“不得吞异常”这种命令型句式,AI 在执行时往往会机械地找是否存在“全局变量”或“异常吞掉”。但真实代码太复杂,机械匹配容易误报,也容易漏报。
把命令转换成提问,AI 的思考方式会变成“我来判断这个场景是否存在风险”,准确率会高很多。举例来说:
原规则:“禁止在循环内执行数据库查询。”
转换后:“这个改动是否在循环体内部产生了数据库查询?如果有,查询结果能否提取到循环外缓存复用?”
原规则:“禁止吞异常。”
转换后:“catch 块中是否仅记录日志或完全无处理?如果吞掉异常,后续调用链失败时是否能感知到?”
提问式规则还有一个好处:它的输出天然带解释性。AI 给出问题清单时会顺着问题把推理过程说出来,而不是只丢一句“这里不符合规范”。
3.4 第四步:写出一份可直接落地的 SKILL.md 正文
综合前面的步骤,我最终交付的 SKILL.md 正文长这样(节选):
--- name: code-review-guard description: > 当用户提交代码变更、Pull Request 或 diff,要求检查代码质量、团队规范遵循情况时使用。 输入为 diff 内容、文件路径或变更描述。输出按 P0/P1/P2 分类的评审结果。 version: 1.1.0 --- # 团队 Code Review 规范 ## 角色定位 你是一位严格但不教条的资深代码评审者,基于团队规范给出结构化评审意见。 你的目标是找出会导致线上故障、安全风险、维护困难的问题,而不是对风格问题吹毛求疵。 ## 评审流程 1. 读取变更,识别变更涉及的核心模块和影响面。 2. 按优先级执行检查:P0 完整性扫描,P1 逻辑审查,P2(加速排版?删去)代码风格与可维护性。 3. 根据发现的问题生成 P0/P1/P2 分级清单。 ## 检查清单(按优先级) ### P0:必须修改 - 是否存在硬编码的密钥、密码、token? - 是否存在空的 catch 块、被吞掉的异常? - 是否存在未处理的空值解引用风险? - 事务范围是否过大或未正确提交/回滚? ### P1:建议修改 - 是否有循环内不必要的 I/O 或数据库查询? - 是否存在明显违反单一职责的超长函数? - 是否缺少对边界条件的处理(空列表、极限值、并发场景)? ### P2:可选优化 - 命名是否清晰可达意? - 是否有重复代码可以抽取复用? - 注释与函数行为是否一致? ## 输出格式 评审结果请按下述结构组织:评审结论
- 变更级别:通过 / 需修改 / 需重新评审
问题清单
[P0] 问题标题
- 位置:文件路径:行号
- 现象:具体代码片段或行为
- 建议:如何修改
[P1] 问题标题
...
## 边界 - 如果规则未覆盖的场景,只做合理推测,禁止引用不存在的团队规范。 - 当规则冲突时,安全与正确性优先于代码风格。这份正文我用了挺长时间打磨,最有价值的一段其实是“边界”部分。不写这条,AI 会在规则覆盖不到的地方自由发挥,用一些它“想象出来的团队规范”来评审,反而会让输出失去可信度。
4. 给 Skill 配上可执行的校验脚本
4.1 文本规则解决不了所有问题,脚本是第二道防线
SKILL.md 能约束 AI 的思考方式,但有些规则如果只靠 AI“读代码”来判断,速度慢且容易漏。这类规则适合写成脚本。
我举个例子:团队规范要求“禁止在代码中直接出现 console.log 调试输出”。让 AI 逐行读 diff,虽然有概率能发现,但不如一个正则匹配来得准确可靠。再比如“禁止 .env 文件被提交到仓库”,直接扫文件列表就完事。这类规则的共同特点是:判定标准简单、机械、但必须百分百精确。人看容易眼花,AI 看也有漏的可能,脚本不会。
4.2 scripts 目录的组织方式
一个带脚本的 Skill 目录,典型的组织方式是这样:
code-review-guard/ ├── SKILL.md └── scripts/ ├── check_secrets.py ├── check_todo_marks.py ├── check_diff_stats.py └── requirements.txtSKILL.md 里要在适当位置引用这些脚本,说明它们的用途和调用方式。我通常会在正文末尾加一个“可用工具”小节:
## 内置工具 - scripts/check_secrets.py:扫描变更中是否包含硬编码密钥关键词。 - scripts/check_todo_marks.py:扫描是否新增 TODO/FIXME 标记。 调用方式: ```bash python scripts/check_secrets.py <diff文件路径>调用规范也要写明白,不然 AI 不知道什么时候该调脚本。我一般会注明:“当 diff 涉及代码文件时,先运行 check_secrets.py 检查密钥泄漏;再运行 check_todo_marks.py 检查遗留标记。” ### 4.3 一个轻量检查脚本的要点 写这类脚本,克制比炫技重要。它不是一个完整的代码扫描器,它是为评审服务的“探针”。核心要求是:输入稳定、输出结构化、单次执行时间短。 拿一个密钥检测脚本举例。它接收一个 diff 文件路径,读取新增内容,用正则匹配 token、password、secret 等关键词,输出 JSON 结构化结果。代码框架大致如下: ```python #!/usr/bin/env python3 import json import re import sys from pathlib import Path SENSITIVE_PATTERN = re.compile( r"(?i)(api[_-]?key|secret|password|passwd|token)" ) def scan_diff(diff_path: str) -> list: diff = Path(diff_path).read_text(encoding="utf-8", errors="ignore") issues = [] for idx, line in enumerate(diff.splitlines(), start=1): if line.startswith("+") and SENSITIVE_PATTERN.search(line): issues.append({ "line": idx, "content": line[:200], "severity": "P0", "message": "新增内容疑似包含敏感信息,请确认是否硬编码。" }) return issues if __name__ == "__main__": result = scan_diff(sys.argv[1]) print(json.dumps(result, ensure_ascii=False, indent=2))注意几个要点:只统计新增行(以+开头),防止把删掉的敏感信息误报;匹配到疑似内容就输出 P0 级别提示,但最终是否“真的是密钥”仍需 AI 结合上下文确认。脚本给出“线索”,AI 给出“判断”,两两配合效率最高。
5. 实测:让 Agent 带着 Skill 跑一次 Code Review
5.1 准备一个故意“埋雷”的示例变更
为了验证 Skill 的实际效果,我构造了一份示例代码变更(而不是拿真实项目测试,方便控制变量)。这个变更里一共埋了六个典型问题:
| 编号 | 预埋问题 | 类型 |
|---|---|---|
| 1 | 新增内容里硬编码了密钥 | P0 安全 |
| 2 | catch 块里只写一行注释,没有日志、没有处理 | P0 正确性 |
| 3 | 循环体内新增了对某接口的调用 | P1 性能 |
| 4 | 新增变量命名含义不明确 | P2 风格 |
| 5 | 修改处没有补充异常分支 | P1 正确性 |
| 6 | 文件提交时附带 .env 文件 | P0 安全 |
模拟的 diff 内容不需要多复杂,关键是要能对应上规则项。
5.2 运行观察:AI 的实际输出
我把这段变化提交给带了 skill 的 Agent,让它执行评审。整个过程中我只发了一条指令:请对此变更进行 Code Review。
结果是,Agent 自动加载了 code-review-guard Skill,在回答中展示的方式和步骤与 SKILL.md 里定义的流程基本一致。输出结果里,六个问题中命中五个:
- 密钥硬编码被识别为 P0,并附带“建议使用环境变量注入”的修改方向。
- .env 文件提交被识别为 P0,直接引用了脚本扫描结果。
- catch 块无日志的问题被识别为 P0,AI 给出的解释是“异常被吞掉后,线上问题将无法追踪”。
- 循环内调用外部接口被识别为 P1,AI 建议将调用结果提取到循环外。
- 命名不达意、异常分支缺失本来也应该识别出来,但实际只抓到了命名问题,异常分支那一条被漏掉了。
事后回头看漏检的原因:异常分支这一规则在 SKILL.md 里的检查项是“是否缺少对边界条件的处理”,AI 判断示例代码里的场景可能不构成典型边界缺失,所以跳过了。这说明 Skill 的规则仍然需要靠实际测试来校准。
5.3 对比:带 Skill 和不带 Skill 的区别在哪里
同一份变更,不带 Skill 让 Agent 评审,收到的反馈是:
“建议增加注释说明这段逻辑的意图”“建议将函数拆分得更小”“整体代码结构清晰,存在少量可优化项”。
这些都是正确的废话。带 Skill 之后,反馈变成了按优先级排列的问题清单,每条都有位置、现象、建议,而且安全类问题排在最前面。
差异的本质不是“AI 变聪明了”,而是输入的组织方式变了。不带 Skill 时,AI 手里没有规则集,只能用通用常识评审;带 Skill 之后,AI 是对着一张“检查表”做判断题,效率自然不同。
6. 常见问题与排查技巧实录
6.1 Skill 存在,但 AI 就是没调用
这是使用 Skill 最常见的挫折。明明 SKILL.md 已经放好了,AI 回复里也没有引用它。我遇到这种情况,会按顺序排查:
- description 是否写了“触发时机”?如果描述只是“代码评审”,AI 在收到具体问题时不一定会关联上。
- 当前对话类型是否被系统限制为“不自动加载技能”?部分场景需要手动指定启用。
- 有没有多个技能同时匹配?“code-review-guard”和“通用代码检查”同时存在时,AI 可能选了另一个。
排查看下来,八成是 description 写得不够明确。我后来把触发场景细分到“PR 评审、diff 分析、变更检查”这一颗粒度,调用率立刻上来了。
6.2 规则太多,上下文被冲垮
SKILL.md 正文不是越长越好。规则太多会稀释重点,AI 在处理长文本时会给每一条规则分配相似注意力,真正重要的 P0 规则反而被平均化了。
我现在的做法是:控制 SKILL.md 正文在 200 行以内,只保留最重要的规则。其余规则拆到 scripts 里(比如静态模式扫描),或者放到单独的子文档,由 SKILL.md 在需要时引导 AI 去读。
6.3 规则之间冲突怎么办
规范文档从不同人手里汇集而来,冲突几乎不可避免。举个我遇到过的例子:性能规范要求“减少对象创建,使用对象复用”,可读性规范要求“命名清晰直白,不要复用变量名承载多重含义”。如果一段代码为了性能复用了变量,严格按“可读性”规则打回去,会显得教条。
面对这类场景,我在 SKILL.md 的“边界”小节里明确写了冲突消解原则:安全与正确性 > 性能 > 可维护性 > 代码风格。技能内的规则一旦冲突,按这个优先级让 AI 自己选。这样既保持了规则的灵活性,又不至于让 AI 在冲突中迷失。加这一句之后,评审结果里“为了规则而规则”的噪音意见明显少了。
6.4 幂等性:避免 AI 每次评审都“编造”新规则
还有一个隐蔽的问题:同一个 Skill 让 AI 评审无数次,它可能会慢慢“漂移”。比如第一次评审它严格按 P0/P1/P2 输出,第二次可能因为上次的对话上下文带偏,自己发明了一条新规则。文本型技能天生有这个问题,因为 LLM 每次生成的路径不完全一致。
我在 SKILL.md 的输出格式里做了约束:问题清单必须引用 diff 中的代码片段,用代码位置定位;如果规则未覆盖到的问题,必须标注“额外建议”,而不是伪装成团队规范。实测下来让 AI 明确“哪些是规则,哪些是推测”,推荐意见的可信度和一致性都会提高。
最后再分享一个实际体会:Skill 不是做完就完了,它跟你团队里的代码规范一样需要持续迭代。我每次评审完,会把 AI 漏检的真实问题、误报的场景回填到规则里。这个文件是团队的活文档,而不只是我个人工具链里的一串配置。如果你们团队也在为 Code Review 标准不统一发愁,不妨从最小的规则子集开始,先让 AI 带着 Skill 跑起来,再用真实评审记录去校准它。