1. 项目概述与核心痛点
1.1 为什么需要自动化代码评审
代码评审这件事,在所有研发团队里都是“正确但不讨好”的存在。我见过太多团队的评审状态:PR挂着三天没人理, reviewer 点开 diff 看到几百行改动直接“已阅”,偶尔认真看一次还净揪缩进和变量命名,真正的逻辑漏洞反而漏过去了。等合并上线出了事故,回头一查,Review 记录里全是 LGTM。
这不是人的问题,是流程的问题。人工评审有几个天然瓶颈:时间碎片化导致上下文丢失、人力上限决定了无法逐行检查、情绪和疲劳会影响判断一致性。而这些问题恰恰是自动化工具擅长解决的——只要规则定得够清楚,机器永远稳定,永远不会忘记,也永远不会因为“不好意思催”就放过一个高风险的改动。
我选择在 GitHub PR 阶段引入自动化代码评审,项目代号就叫 Hermes。Hermes 在希腊神话里是传递信息的信使,这个命名很直观:它负责把代码改动的关键信息精准传达到每一个参与者面前。它不是一个插件也不是一个网页服务,而是一套完整跑在 CI 流程里的自动化审查体系,从触发、分析、输出到拦截,覆盖一个 PR 从提交到合并的全生命周期。
1.2 Hermes 解决的问题清单
先说结论,Hermes 重点干四类事。第一类叫“低级错误兜底”,编译错误、拼写错误、接口签名不匹配、明显的空指针风险,这些机器比人眼快得多,而且不会漏。第二类叫“规范一致性检查”,代码风格、命名约定、错误处理模式是否和项目现有代码库保持一致,这部分是人工评审最耗费精力却最低产出的环节。第三类叫“逻辑风险预警”,识别潜在的死循环、未处理的边界条件、不安全的类型转换、可能引发性能问题的循环内嵌查询。第四类叫“评审流程加速”,自动整理 diff 摘要、标注关键变更文件、贴出相关上下文,让人工 reviewer 一打开 PR 就知道重点在哪,不用从第一行翻到最后一行。
每一类解决的都是实际项目里天天发生的真问题。我见过生产环境挂掉是因为一个 nullable 字段没判空,也见过 PR 里混进调试代码直接部署上线,还有过两个人同时改了同一个配置文件导致合并后功能静默丢失。这些事故的共性是:如果当时有一个独立于作者的自动化审查视角存在,完全可以在合并前拦住。
1.3 项目设计的边界与定位
启动 Hermes 之前,我最先做的事不是写代码,而是明确它“不管什么”。自动化评审不能也不应该替代人工评审,这是整个项目的第一原则。机器可以检查规则,但产品走向是不是对、架构取舍是不是合理、这个依赖该不该引入,这些需要业务判断和团队共识的东西,交给算法目前还是不靠谱的。所以 Hermes 的定位是“守门员+助理”,不是“裁判”。
在能力边界上我也做了妥协:初期只支持 GitHub PR,不兼容 GitLab;只处理分支 diff,不做跨 PR 的全局分析;输出方式是 PR 评论和 CI 检查项,不做独立的网页报告系统。限制范围的好处是能快速做深做透,等核心链路稳定了再横向扩展。
2. 整体架构与核心设计思路
2.1 Hermes 的工作流程全貌
Hermes 的架构并不复杂,甚至可以说不性感。它由四个模块组成:触发层、分析层、决策层、反馈层。触发层监听 GitHub Webhook 的 PR 事件,一旦有新的 commit 推送到 PR 分支,就自动拉起一次审查任务。分析层拉取代码变更数据,结合代码库元数据和项目自定义规则进行逐行扫描。决策层把分析结果按严重级别分类,筛选出真正值得报告的问题。反馈层把结论写回 GitHub,以评论、Review 和 CI 检查三种形式呈现。
这个流程说起来很短,但每个环节都有值得展开的设计决策。比如为什么用 Webhook 而不是轮询 GitHub API?因为 Webhook 是事件驱动的,PR 有更新立刻触发,实时性好,而且不消耗 API 配额。轮询的话每几分钟扫一次全仓库的 PR 列表,既慢又浪费资源。再比如为什么分析层和决策层要分开?因为扫描逻辑需要保持纯粹,只负责“发现了什么”,而决策逻辑涉及“这个问题重不重要、要不要报”,两者混在一起会导致规则越堆越乱,最后谁也看不懂。
2.2 为什么选择 GitHub Actions 作为运行载体
Hermes 的宿主环境我选的是 GitHub Actions,而不是自建服务器也不是其他 CI 系统。选择理由有三条,每一条都是实践之后才真正理解的。
第一是联动性。GitHub Actions 和 PR 是原生集成的,可以直接拿到 pull_request 事件的所有上下文,不需要额外处理 Webhook 签名验证和事件重放。第二是配置即代码。整个 Hermes 的部署不过就是一个 workflow 文件,跟着仓库走,分支策略怎么定它就怎么跑,天然适配多环境多仓库的场景。第三是成本友好。公开仓库免费跑 Actions,内部项目在配额内也不产生额外费用。相比专门维护一台跑审查服务的机器,GitHub Actions 的按需计费模式对中小团队明显更友好。
当然 Actions 也有它的局限。最典型的是执行环境是临时的,每次跑都是全新容器,没法缓存太多历史分析数据。我的解法是把历史分析结果存进一个独立的 repository,需要时通过 GitHub API 拉取。这个方案不算优雅但够用,而且不增加运维复杂度。
2.3 代码变更分析引擎的核心机制
分析引擎是 Hermes 的“大脑”,它做三件事:提取 diff 结构、构建变更上下文、执行规则匹配。
提取 diff 结构不是简单地把 PR 的 files 接口返回的数据拿来用。GitHub 的 diff 是按文件分组的,每个文件里有 hunks,每个 hunk 里有 +/- 行。Hermes 需要把这些数据解析成结构化的变更对象,记录每一行是新增还是删除,所在的函数是什么,涉及的类是什么。这个结构化的过程很关键,因为后续所有的规则匹配都是基于“变更行+上下文”而不是大段的原始文本。
构建变更上下文是我觉得整个项目里最有价值的部分。Hermes 不只是看变更行本身,还会通过语法分析找到变更行所属的函数、类、模块,并把这些上下文一并交给规则引擎。举个例子,如果有人在某个函数内部加了一行网络请求代码,Hermes 不只是看到“多了一行 HTTP 调用”,还会结合函数上下文判断这个调用是不是在循环体里、是否缺少异常处理、是否符合项目里常规的 HTTP 客户端使用方式。这种带有结构感的分析,明显优于基于正则匹配的简单扫描。
规则匹配则是把项目定义的规范和风险模式逐条套用到变更对象上。Hermes 支持三类规则:内置通用规则、基于 git 历史的学习规则、项目自定义规则。内置规则覆盖最常见的代码风险和风格问题,开箱即用;学习规则通过分析仓库历史提交中容易被 revert 或包含 bug 修复的 commit 的模式,总结出高风险写法;自定义规则则允许每个团队用简单的 DSL 或者正则,把团队自己的规范固化成自动检查项。
3. 从零搭建 Hermes 的完整实操记录
3.1 项目初始化与目录规划
Hermes 的项目结构从一开始就按“可扩展的独立模块”来设计,而不是把所有逻辑怼进一个入口文件里。我最终采用的目录结构长这样:
hermes/ ├── .github/ │ └── workflows/ │ └── code-review.yml ├── src/ │ ├── trigger/ # Webhook 事件解析 │ ├── analysis/ # diff 解析与上下文构建 │ ├── rules/ # 规则引擎与内置规则集 │ ├── decision/ # 严重级别分类与报告筛选 │ ├── feedback/ # GitHub API 交互与评论渲染 │ └── utils/ # 通用工具 ├── config/ │ ├── rules.yaml # 自定义规则配置 │ └── settings.json # 全局配置 ├── tests/ │ ├── fixtures/ # 测试用的 PR diff 样本 │ └── unit/ └── requirements.txt这个拆分不是拍脑袋定的。我是先跑了半年的“手动模拟版”,用脚本批量分析历史 PR,记录哪些检查项最有价值,然后按检查项的性质反推模块边界。最终稳定下来的模块划分,基本对应着一条数据流:事件进来,解析,分析,决策,反馈。任何一环要替换实现,不影响其他环节。
技术上我选了 Python 3.11 + PyGithub 库,主要原因是团队已有 Python 技术栈,而且 PyGithub 对 PR 相关的 API 封装得很完善。分析引擎里用到了 tree-sitter 做代码语法树解析,这点后面会展开讲。
3.2 GitHub App 还是 GitHub Actions:认证方案选型
跑自动化评审,第一件要解决的问题是“以什么身份访问仓库”。我对比过三种方案:个人访问令牌(PAT)、GitHub App、GitHub Actions 自带令牌。
个人访问令牌最简单,但问题很多:权限粒度粗,通常需要 repo 全权限;令牌有效期管理麻烦;最致命的是如果用个人令牌发评论,评论作者显示的是个人而不是机器人身份,没有独立的“Hermes 机器人”人设。GitHub App 是最正规的解法,权限可以精确到“读代码、写评论、写检查”,而且有独立的机器人身份,还可以安装到多个仓库复用。代价是配置过程繁琐,需要创建 App、生成私钥、处理安装回调、签名 JWT。
GitHub Actions 自带令牌 GITHUB_TOKEN 是个很好的折中:在 workflow 里可以直接用,不需要额外配置,权限在 workflow 里通过 permissions 关键字声明。它的限制是:如果 PR 来自 fork 仓库,默认权限是只读的,需要额外配置 pull_request_target 事件配合使用。我的方案是:内部仓库直接用 GITHUB_TOKEN,外部贡献者的 PR 则通过 label 触发人工审查兜底,避免 fork PR 的安全风险。
实际配置里,我推荐用 GitHub App 方案做生产部署,它最规范,权限也最可控。创建 App 时有几个容易踩坑的点:权限列表里需要勾选 Pull requests 的 Read and write、Checks 的 Read and write、Contents 的 Read-only;Webhook 可以不用配(如果你不用实时触发,只用 Actions 调度的话);私钥注意保存为 PEM 格式,不要提交到代码仓库。
3.3 核心流程的代码实现解析
下面这段是 Hermes 初始化审查任务的核心代码,我把关键逻辑注释在代码块里,方便直接对照:
from github import Github, Auth import yaml def run_pr_review(repo_name: str, pr_number: int, token: str): """执行一次 PR 自动化审查""" # 使用 token 认证,这里 token 来自 Actions 的 secrets auth = Auth.Token(token) g = Github(auth=auth) repo = g.get_repo(repo_name) pr = repo.get_pull(pr_number) # 1. 获取 PR 的全部变更文件,限制单次 API 调用条数防止超时 files = pr.get_files() changed_files = [] for f in files: changed_files.append({ "filename": f.filename, "status": f.status, "additions": f.additions, "deletions": f.deletions, "patch": f.patch, "contents_url": f.contents_url, }) # 2. 载入自定义规则 with open("config/rules.yaml", "r") as rule_file: rules = yaml.safe_load(rule_file) # 3. 调用分析引擎做逐文件扫描 analysis_results = analyze_changes(changed_files, rules) # 4. 决策层过滤:只保留严重级别为 WARNING 以上的结果 report_items = [] for item in analysis_results: if item.severity in ("ERROR", "WARNING"): report_items.append(item) # 5. 以 Review 形式提交回 PR if report_items: pr.create_review( body="Hermes 自动化审查发现以下问题,请人工确认:", comments=[ { "path": item["path"], "line": item["line"], "body": f"**{item['severity']}**: {item['message']}" } for item in report_items ], event="REQUEST_CHANGES" ) else: pr.create_review(body="Hermes 自动化审查未发现明显问题。", event="COMMENT") g.close()这段代码看起来不复杂,但里面的细节全是实践摔出来的。比如第 4 步的严重级别过滤,初期我把所有扫描发现都列出来,结果一个 PR 里出现五十多条评论,人工看不过来,也把真正重要的问题淹没了。后来改成只报 ERROR 和 WARNING,把 INFO 级别的内容汇总成一个摘要评论,才真正可用。
再比如第 5 步的line参数,GitHub review comment 的 line 必须对应于 diff 中的实际行号。如果你传入的行号不在 diff 的 range 内,API 会返回 422 错误。这个坑我踩过,后来统一从 patch 里解析新增行的行号范围,确保评论能准确落在变更行上。
3.4 GitHub Actions Workflow 的完整配置
有了核心逻辑之后,接进 CI 的 workflow 也不复杂。下面是我使用的完整配置文件:
name: Hermes Code Review on: pull_request: types: [opened, synchronize, reopened] pull_request_review_comment: types: [created] permissions: contents: read pull-requests: write checks: write jobs: hermes-review: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: | pip install -r requirements.txt - name: Run Hermes review env: HERMES_TOKEN: ${{ secrets.HERMES_APP_TOKEN }} HERMES_REPO: ${{ github.repository }} HERMES_PR: ${{ github.event.pull_request.number }} run: | python -m hermes.main几个值得留意的点。permissions段必须给pull-requests: write,否则你调 create_review API 时会收到 403。fetch-depth: 0是为了让 checkout 拿到完整 git 历史,这样分析引擎里如果要用 git blame 逻辑,数据才够。pull_request_review_comment的触发类型是给“有人回复 Hermes 评论后触发追问”用的,当前版本我用来实现一个简单交互:reviewer 在 Hermes 的评论下回复/ignore 规则名,Hermes 后续就不会再提那类问题。
另外,secrets.HERMES_APP_TOKEN我建议放 GitHub App 的安装 token 而不是 PAT,原因前面说过,主要是身份独立和权限可控。
3.5 用 tree-sitter 做真正的代码结构分析
Hermes 初期的分析引擎用的是正则匹配加行号定位,这是绝大多数简易审查工具的路线。跑了一周我就发现天花板:正则没法回答“这段改动是不是在循环里”“这个变量是不是在该作用域内被重复赋值”这类问题。于是我把核心分析层迁移到了 tree-sitter 上。
tree-sitter 是一个增量解析器,可以为几十种语言生成具体的语法树,并且能定位到每一个语法节点对应的行列位置。用它做代码分析的基本思路是:拿到一个文件的变更行号集合,解析全文件生成语法树,然后遍历语法树找出覆盖了变更行的最小语法节点,再沿着节点往上找父节点,直到判断出它所在的函数、类、判断分支、循环体。这样每个变更行都能带上结构上下文。
下面是我实现的一个核心函数片段:
from tree_sitter import Language, Parser import tree_sitter_python as tspython def get_changed_node_context(file_content: str, changed_lines: set, language_parser): """为变更行找到对应的语法树节点及其上下文""" parser = Parser(language_parser) tree = parser.parse(bytes(file_content, "utf8")) root = tree.root_node contexts = [] for node in root.children: # 遍历语法树中每一个函数定义节点 if node.type == "function_definition": start_line = node.start_point[0] + 1 # tree-sitter 行号从 0 开始 end_line = node.end_point[0] + 1 # 检查这个函数是否包含变更行 if any(start_line <= line <= end_line for line in changed_lines): contexts.append({ "function_name": extract_function_name(node), "start_line": start_line, "end_line": end_line, "body": extract_node_text(node, file_content) }) return contexts这段代码只是一个示例,真实实现里还需要处理嵌套函数、装饰器、lambda、类方法等边界情况。但核心思路就是这样:用语法树把“第多少行改了东西”升级成“哪个函数里发生了什么事”。
基于这个结构上下文,我实现了一个非常实用的检查项:循环内资源操作检测。逻辑很简单,遍历变更行所在的父节点链,如果发现某个节点类型是“for_statement”或“while_statement”,同时变更行本身是一次文件读写或网络请求,就报一个 WARNING,提示“文件/网络操作位于循环体内,请确认是否存在性能隐患”。这个检查项上线后,直接在一次代码评审里揪出了三个真实存在的性能问题,效果非常直观。
3.6 规则引擎的演进:从硬编码到 YAML 配置
项目早期,所有检查逻辑都写在 Python 代码里,加一条规则就意味着改代码、发 PR、部署。这种模式很原始,也不利于团队其他成员贡献规则。后来我设计了一个轻量级的规则配置体系,把检查项定义从逻辑中分离出来。
规则配置长这样:
rules: - id: "NO_PRINT_IN_PRODUCT" name: "禁止在生产代码中使用 print" severity: "WARNING" language: ["python"] pattern: | (call function: (identifier) @func (#eq? @func "print")) message: "检测到 print 调用,生产环境建议使用 logging 模块。" when: branch: "!^(release|main)$" - id: "DEBUGGER_STATEMENT" name: "禁止调试器断点" severity: "ERROR" language: ["javascript", "typescript"] pattern: "debugger" message: "代码中不应包含 debugger 断点。" - id: "LARGE_FUNCTION_CHECK" name: "函数体过大提示" severity: "INFO" language: ["python"] max_function_lines: 100 message: "函数体超过 ${max_function_lines} 行,建议拆分。"每条规则包含 id、名称、严重级别、适用语言、匹配模式和提示信息。pattern 这个字段,如果是简单文本,就做字符串匹配;如果带有 tree-sitter query 语法特征,就走结构化查询通道。这样一个团队里不是 Python 背景的同学,也能通过抄改配置来贡献检查规则,不需要碰核心代码。
配置驱动的另一个好处是可以通过when字段做分支差异化。比如上面第一条规则里,我设置了在 release 和 main 分支上不检查 print,因为那可能是临时排查线上问题打的标记,报 WARNING 会干扰合入流程,宁可人工处理。
4. 实战效果与关键产出分析
4.1 部署六个月的运行数据复盘
Hermes 在我们团队已经稳定跑了六个月,我拉了一组数据来做复盘。总共审查了 742 个 PR,平均每个 PR 的审查耗时是 40 秒到 2 分钟不等,取决于变更文件数量和复杂度。对比人工评审平均要等 4 到 24 小时才能拿到第一条有效反馈,速度提升是数量级的。
问题发现率方面,742 个 PR 中,Hermes 报了至少一个 ERROR 级别问题的有 63 个,占 8.5%;至少一个 WARNING 的有 287 个,占 38.7%。这个比例看着不算低,但要注意其中一部分是重复上报或者误报。我抽样检查了最近 100 个 PR 的报告,人工复核后计算精确率在 72% 左右,也就是说一百条警告里,大概二十八条是噪声——要么是项目里已有风格的延续,要么是规则不能理解业务意图导致的误报。
召回率我没有精确统计,因为没有“标准答案”可对照。但有一个间接指标:在 Hermes 提示过的高风险代码里,有 31 处在后续的 bug 修复 commit 中被修改。这些 bug 如果没被提前标记,大概率会在更大的代码改动里被掩盖,等到线上出问题时排查成本会高很多。
4.2 一次典型的 PR 实战演练
为了更直观地说明 Hermes 的工作方式,我拿一个真实的 PR 例子走一遍完整流程。这个 PR 的标题是“增加用户积分过期提醒功能”,改动了三个文件:积分服务的主逻辑、数据库查询方法、一个定时任务配置文件。
Hermes 在 PR 打开后的约一分钟内自动开始审查。它先拉取 diff,看到主要变更集中在积分查询逻辑里。分析引擎构建上下文时发现,在“批量查询过期用户”的方法里,新增了一段针对每个用户发送通知的逻辑。基于内置的循环内资源操作规则和网络 I/O 规则,它标记了一个 WARNING:“循环体内调用外部 HTTP 接口,请确认是否存在批量触发导致下游服务压力过大的风险。”
同时它发现数据库查询方法新增的 SQL 里,有一个字段没有加索引,结合项目里配置的数据库规范规则,报了一个 WARNING:“新查询条件字段缺少索引提示,建议在高并发场景下确认执行计划。”定时任务配置文件的改动没有触发任何规则,但在摘要评论里列入了“变更文件清单及影响面分析”。
这个 PR 的人工 reviewer 收到 Hermes 的评论后,不必通读全部 diff,直接聚焦两个 WARNING 点去判断。最终确认第一个点确实有隐患,改为批量推送方案;第二个点因为数据库量级小,评估后保留。整个评审在 PR 打开后两小时内完成,对比同项目同类规模的改动,过去至少需要一天。
4.3 误报控制与规则调优的经验
误报是自动化评审工具最容易被团队弃用的原因。我前两个月的运维工作几乎一半精力都在降误报。总结下来有三条最有效的经验。
第一条是“报告分级,评论瘦身”。不要把所有发现都塞进 review comment,那会制造信息焦虑。Hermes 把 INFO 级别的内容折叠进一个统一备注评论里,WARNING 以上才作为逐行评论卡在对应代码行上。人工 reviewer 打开 PR,第一眼看到的是 ERROR 和 WARNING,处理完再点开备注看低级别提示,主次分明。
第二条是“规则降级机制”。对于连续两周内被人工标记为“误报”的规则,Hermes 会自动触发一次复核流程——要么调整规则的匹配条件,要么把严重级别降一级。这个机制让人工反馈能持续回流到规则库里,形成迭代闭环。
第三条是“路径与分支感知”。很多检查规则在非业务目录里是噪音。比如 migrations 目录下的文件改不改索引,用不用 logging,对项目影响很小。Hermes 在规则配置里增加了paths和ignore_paths字段,规则可以限定匹配的目录。我在实际调优中,把大量规则的检查范围精确到了业务代码目录,误报率直接下降了近一半。
5. 常见问题排查与避坑指南
5.1 问题速查表
下面这个表是我在日常维护 Hermes 及配套流程时沉淀下来的,遇到问题可以直接对照处理。
| 现象 | 可能原因 | 解决方式 |
|---|---|---|
| 工作流没有触发 | Webhook 类型配置错误或 PR 事件类型不匹配 | 检查on.pull_request.types,确认包含 opened 或 synchronize |
| create_review 返回 403 | GitHub Token 权限不足 | 检查 workflow 的 permissions 配置,必须包含pull-requests: write |
| 行内评论 422 错误 | 行号不在 diff 的变更范围内 | 从patch字段中解析实际新增行号,不要用代码全文行号 |
| 规则匹配不到任何问题 | tree-sitter 语法解析失败或语言不支持 | 先单独跑解析器,确认该文件能被正确解析;检查language配置 |
| fork PR 无法评论 | GITHUB_TOKEN 在 fork PR 中默认只读 | 使用pull_request_target事件并在独立 job 中谨慎执行,或用 GitHub App token |
| 审查耗时过长 | diff 文件数过多或单文件过大 | 设置文件数上限,超过上限只审查关键文件;对大文件启用抽样分析 |
| 误报率持续偏高 | 规则过于宽松或缺少路径限制 | 按 4.3 节的方法做规则降级和路径收敛 |
5.2 排查实录:Actions 日志里最常见的三个坑
第一个坑是“checkout 不完整”。如果不用 fetch-depth: 0,默认 checkout 是浅克隆,可能拿不到目标分支的全部历史。Hermes 分析引擎里如果依赖 git blame 或者历史 diff,就会报fatal: bad object之类的错误。排查方法很简单,在 Actions 日志里搜HEAD detached,或者直接看 checkout 步骤有没有输出fatal关键字。
第二个坑是“Python 依赖装不上”。如果你把 Hermes 的 Python 包依赖写进了 requirements.txt,里头某个第三方库只有特定平台有 wheel 包,在 GitHub Actions 的 ubuntu-latest 上装起来可能很慢甚至失败。我遇到过 tree-sitter 某个语言包版本锁死导致 build 失败的案例。解决思路是不要全量锁死版本,给关键依赖设一个下限和上限范围,同时用缓存策略把 pip 依赖缓存下来,第二次跑能快不少。
第三个坑是“多仓库复用”。Hermes 部署在一个仓库后,其他仓库想复用需要复制 workflow 文件,这就产生了维护多份配置的问题。我的解法是用 GitHub 的 reusable workflow 特性,定义一个公共 workflow,其他仓库用一句话引用。这样规则更新和 bug 修复都只改一处,所有接入仓库自动生效。这个结构上稍有复杂度,但长期维护收益很大。
5.3 团队落地时容易忽略的四个细节
细节一:Hermes 不要在所有分支上启用检查。只在你的核心开发分支(开发主干或长期分支)上跑,release 分支和 hotfix 分支可以只跑 ERROR 级别的检查,降低噪音和时间成本。
细节二:给 Hermes 的评论设计一个统一的前缀标识(比如[Hermes])。团队邮件提醒和 GitHub 通知里一眼就能识别,reviewer 可以按前缀做消息过滤,避免被通知轰炸。
细节三:建议给 Hermes 配置一个“静默模式”。在项目启动初期,不要让它直接以REQUEST_CHANGES状态拦截 PR,先以普通评论的方式运行一到两周,让规则库有充分时间校准。一上来就拦截很容易触发团队对抗情绪,后面推进阻力会很大。
细节四:把 Hermes 的规则评审纳入到常规的代码评审流程里。我建议每个季度组织一次规则评审会,团队成员一起看哪些规则已经过时,哪些误报率高需要调优,哪些新问题需要新增规则覆盖。这个会不是走形式,它是让自动化评审工具持续贴近项目现实的关键机制。
6. 从 PR 审查到更大的自动化版图
6.1 Hermes 的扩展方向:不只做 PR 检查
PR 审查只是整个研发流程自动化的一个切入点。Hermes 的分析引擎既然已经能解析语法结构、构建上下文、匹配规则,那它可以复用的地方远不止 pull_request 事件。
我把 Hermes 扩展到了两个场景。第一个是问题单(Issue)自动分类。当一个 issue 被创建时,触发脚本读取标题和正文引用到的代码文件,把 issue 和最近的代码变更关联起来,自动打上“可能和某次 PR 相关”的标签。这让团队在回溯线上 bug 时,能更快定位到变化的代码块。第二个是合并后的回归监测。PR 合并后,Hermes 会对合并 commit 做一次增量分析,把分析结果存成一份“版本变更快照”。后续如果在这个版本上发现崩溃日志,可以快速比对快照和崩溃堆栈,缩小嫌疑代码范围。
这些扩展本质上没有引入新的技术,只是把同一个“代码变更理解”能力在不同的事件和时间点上复用。我认为这是做自动化工具最划算的路线:核心能力打磨到可复用,然后不断套到新的场景里。
6.2 与大语言模型结合的能力升级
最近社群热度最高的方向是自动化评审工具结合大语言模型做深度语义分析,这也是我下一步计划引入的增强能力。传统的规则引擎能识别“代码像什么”,但很难判断“代码符不符合意图”。LLM 正好补上这一环。
我的设计思路是:先让 Hermes 的规则引擎做一层快筛,把明显问题和候选问题都找出来;然后对候选问题,把 diff、相关文件内容、函数上下文一起打包成 prompt,交给 LLM 做二次判断。LLM 负责回答“这个改动意图上有没有问题”“是不是存在潜在边界情况”“和项目其他部分的风格是否一致”这类偏语义的问题,输出的内容再回到 Hermes 现有的决策和反馈管道里。
这个方案的优点在于不替换现有链路,规则引擎保证稳定性和低延迟,LLM 负责提升深层次发现能力。但也要明确它的边界:LLM 输出是不确定的,同一个 diff 多次运行的结果可能有差异,所以 LLM 分析结果应该全部标为“建议”级别,不能直接影响 CI 的通过状态。规则引擎的结论才是硬拦截。
6.3 成本控制与性能优化的实践经验
自动化评审引入后,我收到最多的团队疑问不是“有没有用”,而是“跑得贵不贵”。GitHub Actions 的运行时间直接关系成本,控制好 Actions 的账单是长期维护 Hermes 的前提。
我的经验是把审查任务拆成两级。第一级是快速预检,只跑 git diff 级别的扫描和轻量规则,在触发后 30 秒内完成,适合做 CI 的门禁检查。第二级是深度审查,才加载 tree-sitter 分析、规则引擎和可选的 LLM 扩展,每次 PR 只跑一次。从实际数据看,80% 的 PR 在预检阶段就能覆盖主要风险,深度审查不会成为频繁的耗时任务。
还有两个小的成本优化点。一个是给 workflow 加并发控制,同一个 PR 同时只有一次深度审查在跑,避免重复 push 导致的任务堆积。另一个是只对变更行涉及的规则做计算,不扫描全文件的每一条规则。比如一个 Python 文件里只改了第 100 到 105 行,Hermes 不会把整个文件的函数复杂度、命名规范全部重新算一遍,而只会对变更行和它的直接上下文跑规则。这个优化能把单次扫描时间减少 30% 到 50%。
7. 写在最后的一点个人体会
7.1 自动化评审不是目的,降低评审负担才是
回看 Hermes 这个项目的核心收获,我最大的体会是:自动化评审的终极目标不是“代替人看好每一行代码”,而是把人的注意力从重复劳动中解放出来,让人工 reviewer 专注于机器做不了的价值判断。Hermes 每天帮我挡掉 DEBUG 代码、提醒我别在循环里发请求、整理一份简洁的变更摘要,但架构方案怎么选、依赖要不要引入、产品逻辑是否成立,这些始终是需要人来拍板的。
所以如果你也想在自己的项目里引入类似 Hermes 的自动化评审,我的建议是从最小闭环开始:先只做一条规则,比如检查有没有调试断点,或是在循环内做了网络请求。把它跑通,让团队看到价值,再逐步叠加规则。不要一上来就设计一个复杂的规则引擎,那会消耗大量精力,而且大概率会因为误报问题遭到团队抵制。
7.2 工具要不断跟着项目的现实迭代
没有任何一套规则从一开始就适合你的代码库。Hermes 的规则库有三分之一的规则是部署之后根据实际误报数据新增或调整的,有几条甚至直接删除。保持迭代节奏比追求初始方案完美重要得多。我建议每个新规则都先从 INFO 级别起步,跑两周,观察误报情况,再决定是否升为 WARNING 或 ERROR。不要低估这个过程的必要性,它决定了自动化评审能否在团队里活过第一个季度。
7.3 最后分享一个效率小技巧
我给 Hermes 写了一组简单的自定义命令,reviewer 可以在评论里用/hermes ignore <规则id>让某条规则后续不再报,也可以/hermes summary强制 Hermes 重跑一次并生成一份精简摘要。这看起来是一个很轻量的交互设计,但它解决了工具落地中最常见的一个问题:当团队成员觉得通知太吵或者遗漏了某条规则时,他们有了即时反馈的入口,而不是被动地等着管理员改配置。如果你也在做类似的自动化工具,强烈建议留出类似的逃生通道。
我最后想说的是,代码评审从来不是单点工具能解决的问题,它背后是团队习惯、工程效率意识和协作文化的综合体现。Hermes 只解决其中“确定性高、重复性强”的那一部分,但它省出的时间和精力,能让团队真正专注在代码评审里最有价值的部分——讨论设计、权衡取舍、规划演进方向。这本身就是一笔非常划算的投入。