团队规模从三个人涨到十几个人后我的合并请求积压量迎来了峰值。没人真想用“已阅”糊弄 review但排期摆在那每个人打开 PR 只能匆匆扫一眼有没有冲突就 Approve。更常见的是代码合进去一周后线上报警时才有人发现当初漏掉了一个参数边界。这种循环重复多了我开始把代码审查当工程来做搭了一套 open-code-review 流水线用开源工具做静态扫描用大模型做初筛把人工评审拉回到真正需要经验与判断力的地方。这篇文章会写清楚这套体系为什么值得搭、怎么用最少的成本跑起来、提示词怎么设计、以及真实踩过的坑。内容包括工具选型、GitHub Actions 配置、Semgrep 规则、AI 评审提示词和团队落地机制适合正在被 PR 积压困扰的中小团队、独立项目维护者以及想给开源仓库提升审校质量的人。如果你是第一次接触这类方案跟着章节顺序读即可如果你已经跑过部分自动检查可以直接跳到第 3 章和第 4 章看提示词和坑点。1. 为什么需要一套开放、可复用的代码审查体系1.1 人工 review 在快节奏迭代下的四个真实瓶颈先说结论不是大家不愿意 review而是传统的人工审查模式在持续交付的节奏下天然有几个扛不住的地方。第一个问题是时间错配。审查是一件需要连续思考的事但大部分人的时间是碎片化的。项目排期一紧张PR 会被拖到最后一刻才看打开时脑子里还想着自己的需求很难进入状态最后只能点一个“看起来没问题”。第二个问题是标准漂移。团队里每个人对“什么样的代码可以合入”的理解不一样有人在意命名有人只看功能是否跑通同一个 PR 在不同 reviewer 那里可能得到完全相反的结论。这种不一致会慢慢消耗作者对审查的信任最后变成“Review 就是走个流程”。第三个问题是知识孤岛。老同事知道某个模块为什么长成这样但这条经验通常只存在于聊天记录里。等他休假或者离职新人面对同一块代码时只能靠猜。第四个问题是流程空转。一旦默认 Approve 成为风气审查质量会快速下滑而下滑之后又没有人愿意花时间改进形成恶性循环。1.2 “开放”与“可复用”背后的工程思路open-code-review 想解决的其实就是上面四个问题。它把隐藏在每个人脑子里的审查标准、零散在 IDE 里的 Lint 提示、以及事后才想起来的安全检查统一成一套可见、可改、可复制的资产。“开放”的含义不是说非要去建一个公开社区而是规则对团队内所有人透明。一个新人加入仓库打开 .github 目录和 reviews 规则文件就能看到“什么情况必须阻塞、什么情况只是建议”不需要靠犯错去摸索。对外部贡献者也是一样的道理一个开源项目只要把审查标准写清楚外部 PR 的通过率会明显提升。“可复用”则意味着这套东西不只服务于一个仓库。我可以在多个项目里复用同一套流水线配置、同一组静态规则、同一个评审提示词遇到新仓库时复制过去就能跑成本非常低。1.3 我期望这套体系做到的事目标可以拆成三层。第一层是机器能判断的比如密钥扫描、危险函数调用、格式规范全部交给静态工具结果直接呈现在 PR 上。第二层是需要语义理解的比如某个改动是不是引入了空指针、有没有越权风险、异常处理是否合理交给大模型做初筛。第三层才是留给人的架构方案、业务正确性、未来的可维护性这些需要真正的上下文和经验。设定这个分层之后人工每次 review 的时间可以从“二十分钟找茬”降到“五分钟做关键判断”而且不再那么容易遗漏明显问题。这套体系不是为了替代人而是把人的精力留到最有价值的部分。2. 从零搭建流水线先把静态扫描和 AI 初筛串起来2.1 先定红线再选工具我见过很多人搭审查体系失败不是因为工具不好而是因为一开始就想着“把所有问题都拦下来”结果规则太多、噪音太大团队直接放弃。比较稳妥的做法是先定义红线也就是哪些问题一旦出现就必须阻塞合并。我最早定义的红线只有四条安全漏洞和密钥泄露、会直接导致生产事故的正确性问题、破坏公共接口或数据结构的改动、无法回滚的数据变更。其他问题包括代码风格、命名、注释全部交给格式化工具和 linter不要让人在这上面纠结。红线的原则是“少而硬”宁缺毋滥。2.2 开源工具怎么选我的组合方式与理由关于工具我梳理过几类工具定位优点缺点Semgrep静态安全扫描与反模式匹配规则可以用代码写团队容易自维护对深层语义分析能力有限SonarQube综合质量门禁指标全适合大型团队部署和维护偏重Reviewdog把检查结果发布为 review 评论与 CI 集成体验好本身不产生检查结果需要搭配其他工具Danger JS用代码描述提交规范灵活可检查 PR 描述、体积、文件变更需要写 JS 逻辑LLM 评审脚本语义理解与逻辑审查能发现静态工具漏掉的真实问题有误报需要设计提示词和输出格式自托管开源模型数据不出内网的 AI 评审隐私可控需要 GPU 资源效果依赖模型选择最终我选了 GitHub Actions Semgrep Reviewdog 一个自写的 LLM 评审脚本。这里要说清楚为什么。GitHub Actions 是因为团队代码已经托管在上面不需要额外维护 JenkinsSemgrep 是因为规则能以 YAML 代码形式进仓库审查标准能版本化Reviewdog 负责把结果贴到 PR 的 diff 上省掉“自己解析输出、自己调 API 发评论”的重复工作LLM 评审脚本则处理静态工具覆盖不到的语义问题。这四层各干各的互不干扰每一层都可以单独替换。2.3 最小可用流水线的 GitHub Actions 配置一份最小化的 workflow 长这样name: open-code-review on: pull_request: types: [opened, synchronize, reopened] pull_request_review_comment: types: [created] concurrency: group: code-review-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: false permissions: contents: read pull-requests: write jobs: semgrep: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 with: fetch-depth: 0 - uses: returntocorp/semgrep-actionv2 with: config: semgrep-rules/ publish_token: ${{ secrets.SEMGREP_PUBLISH_TOKEN }}简单解释几个关键配置。types: [opened, synchronize, reopened]表示只在 PR 创建和后续提交更新时触发不在每次 commit 都跑省资源。concurrency防止连续 push 时排队堆积但cancel-in-progress: false保证正在跑的扫描不会被打断避免出现“检查结果还没发出来任务已经被取消”的情况。permissions里的pull-requests: write是给 Reviewdog 和 AI 评论脚本用的如果没有这一步机器人没有权限在 PR 上留评论。2.4 把评审意见沉淀成静态规则Semgrep 规则示例静态扫描能发挥多大价值取决于规则集有没有跟上团队踩过的坑。Semgrep 的好处是规则本身就是代码可以直接放仓库里做版本管理。举个例子之前团队在接入第三方支付时经常有人把回调签名校验写成“只比对参数不为空”我就沉淀了一条规则rules: - id: insecure-signature-check patterns: - pattern: | if $SIGN $EXPECTED or $SIGN ! $EXPECTED: ... message: 回调校验不能仅判断参数是否存在或值不相等 必须使用签名算法对完整参数做一致性校验。 languages: [python] severity: ERROR这类规则的价值在于把一次人为失误变成永久的自动化防线。以后不管是谁提交这类代码CI 都会直接标红。建议团队在每次 code review 讨论中只要有“这类问题以前踩过”的感觉就顺手沉淀成规则。坚持半年规则库会有几十条效果非常可观。2.5 AI 评审的触发策略与运行边界AI 评审脚本不应该在每次 commit 都跑否则成本高、噪音也大。我是在 workflow 里加了一个单独的 job触发条件改成“PR 打开或同步时”并且设置if: github.event.pull_request.draft false草稿 PR 不跑等作者标记 ready 再跑。运行时还要注意几个边界。第一个是只分析 diff不分析全仓库代码否则模型上下文会被撑爆输出质量也会下降。第二个是过滤文件类型锁文件、构建产物、纯文档改动直接跳过。第三个是给脚本设置超时时间避免 LLM API 长时间无响应阻塞整个流水线。AI 评审结论默认不阻塞合并只作为参考直到团队对它的准确率有信心之后再考虑提高门槛。3. 提示词与审查规则让 AI 输出可执行的结论3.1 审查范围三原则增量、上下文与过滤很多 AI 审查工具跑起来效果差问题不在模型而在没有约束好“审查范围”。我总结出三个原则。第一只审查增量。让 AI 只看这次 PR 新增和修改的行不要评价历史代码否则它会对着一段没改过的老代码长篇大论那不是在帮人 review是在制造噪音。第二提供必要上下文。diff 本身缺少“这个文件是干嘛的”“这个函数被谁调用”这类信息所以我会在 prompt 里附上 PR 描述、涉及的模块名称以及被调用的关键接口。第三做文件级过滤。.lock、.min.js、自动生成的 API 客户端代码不送审节省 token 也减少误报。3.2 一套可以直接复用的 Prompt 模板我使用的 prompt 结构如下你可以根据团队语言和审查重点调整你是一名资深代码审查员正在审查一次合并请求。 任务 只分析下面 diff 中新增和修改的行不要评价未改动的历史代码。 审查重点 1. 安全风险注入、越权、密钥泄露、危险反序列化、签名校验缺失。 2. 正确性问题空指针、并发、资源泄漏、死循环、边界条件未处理。 3. 可维护性问题极度重复的代码、被忽略的异常、明显错误的错误处理。 4. 兼容性问题是否破坏已有接口、是否改变数据结构且无迁移方案。 输出格式 ## 阻塞问题必须修复后才能合并 - [文件:行号] 问题描述 | 修复建议 ## 建议问题不阻塞合并 - [文件:行号] 问题描述 | 修复建议 ## 总结3 行以内 如果不存在阻塞问题请以“未发现阻塞问题”开头。 以下是 PR 信息 PR 描述{pr_description} 以下是文件 diff {diff}这段提示词里最关键的是“输出格式”那一节。没有格式约束的模型回复会是一大段散文脚本很难解析。固定成“阻塞问题/建议问题/总结”三个区块之后后处理脚本可以直接按标题切分把冒烟问题发布成评论把建议问题沉淀成一个单独列表。另外PR 描述这个变量值得保留它能明显减少“AI 看不懂业务意图”的误报。3.3 分级结论与机器人评论格式为了让 review 结果容易阅读我给机器人定义了四种结论状态阻塞、建议、无问题、无法分析。在 PR 评论区显示出来的效果类似下面这样## AI 评审结果 ### 阻塞问题2 - [src/api/auth.py:42] 签名校验只比较了值是否相等缺少完整性校验 | 建议使用 HMAC 对请求参数整体签名 - [src/db/migrate.py:158] 数据迁移未添加事务回滚 | 缺少回滚会在中途失败时留下半迁移状态 ### 建议问题3 - [src/utils/format.py:20] datetime 拼接字符串可以换成标准格式化 - [tests/test_api.py:87] mock 数据缺少边界值用例 ### 总结 存在 2 个需要修复的阻塞问题主要集中在鉴权与数据迁移。我一般在脚本里把结论控制在 3 到 8 条超过这个数量的输出我会直接截断因为超过 8 条的评论没人会认真看。宁可漏掉一些边缘问题也不要把重要结论淹没在噪音里。3.4 反复出现的三类噪音及规避方法第一类是对锁文件挑刺。package-lock.json、poetry.lock 这类文件里经常有外部依赖 URL模型有时候会当“可疑注入”报出来。解决办法是在送审前过滤。第二类是对测试代码上纲上线。测试代码里为了构造极端场景经常会有一些违反了生产代码规范的写法模型照着规范一本正经地提建议看起来很专业实际上是在浪费时间。第三类是“顺手点评老代码”。如果 prompt 里没有强调“只审查增量”模型会在 diff 附近的历史代码上发挥想象力这时候需要在 prompt 中显式声明禁止。4. 踩坑记录从上下文爆掉到权限泄漏4.1 大 PR 把上下文窗口撑爆的处理第一次跑通流水线没几天我就遇到了一个大麻烦一个重构 PR 改了 40 多个文件diff 加起来超过 10 万字符直接把上下文窗口撑满了。初始策略是“全量 diff 送入”结果脚本直接报错。解决办法是把 diff 按文件拆分成多个子任务提交给模型并行处理再在最后阶段把各子任务的结论汇总成一份总评论。这里要注意拆分之后模型会丢失“跨文件接口变化”的上下文所以我会额外提取 PR 里改动的函数签名变化列表放进每个子任务的 prompt 里让 AI 至少知道这个函数被谁调用了。如果 PR 实在太大超过 1200 行 diff我会直接让 AI 只做安全类检查放弃可维护性类建议因为大 PR 本身就是一场事故。4.2 限流与超时把 AI 服务当成外部依赖来设计LLM API 不是本地函数它是外部服务具备网络延迟、限流、超时等所有外部服务的特征。第一次接入时我直接用同步请求结果遇到一次上游抖动整个 CI 任务卡了 8 分钟才超时所有 PR 的检查都挂上“失败”标。正确的设计思路是把它当作消息队列来对待请求设置 60 秒超时连接失败时按指数退避重试第一次等 2 秒第二次等 4 秒最多重试 3 次。同时控制并发数同一个 PR 的多个子任务并发度不要超过 4。为了不让 AI 评审影响合并流程的可用性我在 workflow 里把 AI 审查的结果设为软门禁超时或失败只会在评论里标注“AI 评审暂不可用”不会阻塞合并等待服务恢复后可以手动重跑该 job。4.3 误报太多团队开始无视机器人上线第一周AI 在 30 个 PR 里报出了 120 个问题我一看觉得很有成就感结果团队的反馈是“这些问题大部分不用改”。第二周我观察到一个可怕的现象大家看到 AI 评论直接点“Resolve conversation”连内容都不读了。用脚投票如果一个工具 90% 的结论是噪音那它关于另外 10% 的真实意见也不会被人在意。解决办法是调整阈值。我在 prompt 里把“可维护性问题”的严重级别整体降一档只保留安全漏洞、空指针、并发问题和资源泄漏作为阻塞级。同时加了一个 post-processing 脚本重复出现三次以上且被 reviewer 标记为“不是问题”的规则会被自动加入黑名单触发一个提示“这条建议历史上被人工评审驳回 N 次”让模型在生成该类型建议时更谨慎或者直接不再生成。这个过程持续了两周AI 评审的“被接受率”从不到 10% 提升到大约 30%虽然还是不高但至少大家会认真浏览它说的问题了。4.4 机器人权限不能随心所欲有一次我在日志里发现审查机器人使用了一个拥有仓库写权限的 Personal Access Token这让我惊出一身冷汗。如果这个 token 泄露攻击者可以直接向仓库推送代码。审查机器人本质上是一个“在 PR 评论里写内容”的工具它需要的权限理论上只有pull-requests: write和contents: read完全不需要对代码库的写权限。我后来把机器人全部改成了 GitHub App而不是 Personal Access Token。GitHub App 的好处是权限按需分配且只能访问被授权的仓库token 还有自动过期机制比个人 token 安全得多。另一个容易忽略的点是审查脚本不要去 clone 整个仓库只需要checkout当前 PR 的合并提交即可减少密钥暴露面。5. 工具之外的最后一公里让审查成为团队习惯5.1 把“合并门槛”从人治改成规则工具链搭好之后如果不定义合并标准它很快就会被绕过。我给团队定的合并门槛很明确CI 必须全绿Semgrep 扫描不能有 ERROR 级问题AI 评审的阻塞问题需要被处理修复或人工明确解释原因人工 reviewer 必须有至少一个 approve。这四件事都满足才允许点下 Merge。关键点是“AI 阻塞问题可以被人工驳回”。如果团队认为 AI 的判断在特定场景下不成立正确的做法不是无视而是在评论里“驳回”并说明理由。理由会被记录下来成为后续调 prompt 的依据。这样既给了 AI 意见一定权重又保留了人的最终决定权。5.2 用 PR 体积约束保住审查质量有一个很容易被忽视、但影响极大的因素是 PR 体积。当 PR 改动超过 400 行时人工审查的深度会急剧下降AI 审查的疲劳度也会上升这是生理和注意力决定的不是意志力能改变的。我后来在 Danger JS 里加了一个检查单 PR 超过 400 行且没有拆分的流水线会贴一条警告评论要求作者解释为什么这个 PR 只能一次提交。这条规则的价值不仅在于提升可审查性还在于倒逼团队拆需求。一个需求的代码量超过 400 行通常说明需求本身可以被拆成更小的批次这是提高整个团队交付节奏的一个重要习惯。5.3 让知识沉淀从 review 意见到仓库资产运行半年之后我翻看了历史 review 记录发现大量问题是重复的。比如“异步函数里直接调用外部 HTTP 接口没有超时”“时间字段被当成字符串存储”等。这类问题每出现一次就会占用一次人工审查时间但我之前没有把它们变成自动规则。后来我形成了一个工作流每周花二十分钟把上一周 review 里重复出现的问题整理成两条路径能量化为 Semgrep 规则的直接提交规则文件不能量化的写进团队文档并且补充进 AI prompt 的审查重点。把 review 意见当成“需求”把规则文件当成“产品”循环迭代这套体系的价值才会越来越大。5.4 开源项目如何吸引更多有效评审者如果你维护的是开源仓库open-code-review 的思路还有额外一层收益。开源项目通常不缺用户但缺能稳定投入的 reviewer。把审查标准、规则集、AI 评审方式公开之后外部贡献者提交 PR 之前会自己对照检查减少了 maintainer 的大量重复劳动。另一个实践是给“review 流程”本身写文档像对待提交代码的规范一样对待审查规范。我见过不少项目把模棱两可的信息全部压在 maintainer 脑子里外部 reviewer 根本不知道该按什么标准审最后只能放弃。把“哪些问题会阻塞合并、哪些只是建议”写清楚反而会吸引更多愿意认真参与的人。这套流水线维护了半年多我最深的体会是工具能砍掉的是人最不情愿做的那部分重复劳动而真正决定代码质量的仍旧是人的判断。自动化审查的价值不在于让机器人点评每一行代码而在于把人的时间还给架构、业务和风险讨论。如果你也被 PR 积压和形式化 review 困扰可以先从最小的组合跑起来哪怕只是 Semgrep 加 Reviewdog 加一个最简单的 LLM 评论脚本先让流水线运转再按自己的节奏优化远好过把方案设计到完美才开始。