ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

open-code-review:基于CLI与Git Diff的可编程代码审查框架

open-code-review:基于CLI与Git Diff的可编程代码审查框架

1. 项目概述:这不是又一个代码审查工具,而是一次开发工作流的底层重定义

“open-code-review”这个名称乍看平平无奇,甚至有点像某个被遗忘在 GitHub 某个角落的冷门仓库名。但如果你最近刷过技术社区、看过几篇关于 LLM Agent 的实操笔记,或者被团队里那个总在 Slack 里发“codex cli review --pr=123”命令的人问得哑口无言,那你大概率已经站在了这场静默变革的边缘——它不是把 AI 塞进 IDE 插件里再加个“智能”前缀,而是把代码审查这件事,从“人对人”的协作仪式,彻底还原成“机器可理解、可调度、可验证”的原子操作。我从去年底开始在三个不同规模的项目中落地这套逻辑,最深的体会是:真正的 open,不在于源码是否开源,而在于审查意图能否被结构化表达、审查过程能否被 CLI 精确编排、审查结果能否被 Git diff 精准锚定。它直击现代工程中最顽固的断层:PR 描述写得天花乱坠,但 reviewer 看到的只是 47 行新增 + 12 行删除;AI 模型能生成惊艳的函数,却无法解释为什么不该用Array.prototype.flat()处理嵌套深度超过 3 层的数据;飞书机器人推送了一条“安全风险提示”,点进去却发现上下文缺失,连哪行代码触发的都找不到。而 open-code-review 的核心价值,就是用一套轻量但严丝合缝的 CLI 协议,把“谁在什么上下文里,基于什么规则,对哪段 diff 做了什么判断”这件事,变成可追溯、可复现、可自动化串联的工程事实。它适合两类人:一类是正在被 PR 海啸淹没的 Tech Lead,需要把 Code Review 从“靠经验、靠记忆、靠催促”的黑盒状态,变成可度量、可拆解、可沉淀的团队能力;另一类是刚接触 LLM Agent 概念的工程师,想跳过那些堆砌术语的理论文章,直接上手构建一个真正能嵌入日常开发节奏的 AI 协作节点——不是调用 API,而是让 AI 成为git commit后自动触发的一个确定性环节。

2. 核心设计思路:为什么必须绕开 IDE 插件和 Web UI,死磕 CLI 与 Git Diff?

2.1 选择 CLI 作为唯一入口,不是为了复古,而是为了“可编程性”

很多人第一反应是:“又要敲命令?现在不是都流行点点点吗?” 这恰恰是 open-code-review 最关键的设计分水岭。我试过把同类功能封装成 VS Code 插件,也搭过简易 Web UI,结果无一例外地卡死在三个地方:上下文割裂、权限失控、流程不可控。举个真实例子:某次紧急修复,前端同学在本地改完utils/date.js,顺手点了插件里的“提交审查”,插件却只把当前文件发给了模型,而完全没带上package.json里date-fns的版本号、也没带上src/api/index.ts里调用该工具函数的上下文。模型给出的建议是“使用Intl.DateTimeFormat替代”,这在 v2.30+ 版本里完全正确,但团队锁死的是 v2.16,这个版本里Intl.DateTimeFormat缺少timeZoneName支持——插件无法感知这个约束,因为它的“视野”被 IDE 的文件树框死了。而 CLI 方案完全不同:open-code-review --diff $(git diff HEAD~1 -- utils/date.js)这条命令,天然携带了完整的 Git 差异快照。我们可以在 CLI 内部做三件事:第一,解析 diff 元数据,提取出变更涉及的文件、行号、前后内容;第二,根据预设规则(比如.reviewrc配置),自动拉取这些文件的依赖声明、CI 配置、甚至历史审查记录;第三,把这些结构化信息,连同 diff 本身,打包成一个严格定义的 JSON Schema 输入给 LLM Agent。这个过程没有“点击”带来的随意性,只有命令参数决定的确定性输入。CLI 的本质,是把审查行为降维成一个可脚本化的函数调用,input → process → output的边界清晰到可以写进 CI 脚本里。你甚至可以用find . -name "*.ts" -mtime -1 | xargs -I {} open-code-review --file {}这样的管道命令,批量审查昨天所有修改的 TypeScript 文件——这种灵活性,任何图形界面都无法提供。

2.2 Git Diff 是唯一可信的“事实锚点”,而非文件路径或 PR ID

另一个常被忽视的致命陷阱,是把审查对象绑定在“文件”或“PR”上。open-code-review --pr=123看起来很直观,但它隐含了一个危险假设:PR 123 的代码状态是静态且唯一的。现实是残酷的:PR 可能被 rebase、force push、甚至被合并后又 revert。上周我就遇到一个 case:一位同事在 PR 评论里写道“utils/date.js第 45 行的时区处理有 bug”,但当我们去查时,那行代码早已被后续提交覆盖,原始 diff 记录也因 rebase 而丢失。而 Git Diff 不同,它是时间切片的快照,是 Git 世界里最基础、最不可篡改的“事实”。open-code-review的核心协议,强制要求所有审查请求必须附带一个可解析的 diff 字符串(支持标准git diff输出格式)或一个指向 diff 的 URL(如 GitHub/GitLab 的 patch 链接)。这意味着,无论代码库如何演进,只要 diff 存在,审查的上下文就永远可重现。我们内部有个小技巧:在 CI 流程里,每次git push后,自动执行git diff HEAD~1 > /tmp/review-$(git rev-parse --short HEAD).patch,然后把这个 patch 文件上传到内部对象存储,并生成一个带签名的短链接。open-code-review --diff-url https://review.internal/abc123.patch这样的命令,就能确保审查永远基于那个精确的、不可变的变更快照。这背后是工程哲学的转变:不再信任“当前状态”,只信任“变更本身”。Diff 就是代码世界的“事件日志”,而 open-code-review,就是专门消费这种日志的处理器。

2.3 LLM Agent 的角色定位:不是“更聪明的 reviewer”,而是“可配置的审查协作者”

这里必须厘清一个高频混淆点:open-code-review 里的 LLM Agent,和你在 ChatGPT 里问“帮我 review 这段代码”有本质区别。前者是一个受控的、有明确输入输出契约、具备领域知识注入能力的专用协作者;后者是一个通用对话引擎,它的“review”行为是启发式的、不可预测的。我见过太多团队踩坑:把 LLM 当成万能胶水,直接把整个 PR 的 diff 文本塞给模型,指望它自己总结问题。结果要么是泛泛而谈“代码可读性有待提升”,要么是揪住一个微不足道的命名风格问题,却漏掉了关键的并发安全漏洞。open-code-review 的 Agent 设计,核心是“分治”与“引导”。它不负责端到端的审查结论,而是被拆解为多个可插拔的“审查专家”:security-expert专注识别硬编码密钥、SQL 注入模式;perf-expert基于 AST 分析循环嵌套和内存分配;i18n-expert检查字符串硬编码和日期格式化。每个专家都有自己的 prompt 模板、输入 schema(例如只接收包含context: { file, line, code_snippet }的 JSON)、输出 schema(例如必须返回{ severity: "high|medium|low", message: string, suggestion: string })。Agent 的“智能”,体现在它能根据 diff 的特征(比如检测到crypto相关 import,就自动激活security-expert;检测到for (let i = 0; i < arr.length; i++)模式,就调用perf-expert),而不是凭空发挥。这就像一个经验丰富的 Tech Lead,他不会在没看到具体代码前就下结论,而是先快速扫描变更类型,再调用自己脑子里对应的检查清单。LLM 在这里,是执行这份清单的高效“手”,而不是制定清单的“脑”。

3. 核心实现细节:从零搭建一个可运行的 open-code-review CLI

3.1 架构全景:三层分离,各司其职

要让 open-code-review 真正落地,不能只停留在概念。我基于半年多的生产环境迭代,提炼出一个稳定、易维护的三层架构,它不依赖任何特定云服务,纯本地可运行:

┌─────────────────────────────────────────────────────────────────────┐ │ CLI Interface Layer │ │ • 解析命令行参数(--diff, --file, --config, --output-format) │ │ • 验证输入合法性(diff 格式校验、文件存在性检查) │ │ • 加载用户配置(.reviewrc) │ │ • 调用 Core Engine 并传递结构化输入 │ └─────────────────────────────────────────────────────────────────────┘ ↓ (JSON-RPC 或进程间通信) ┌─────────────────────────────────────────────────────────────────────┐ │ Core Engine Layer │ │ • Diff Parser:将 git diff 文本解析为结构化变更列表 │ │ [ { file: "src/utils/date.js", type: "modified", │ │ additions: [{line: 45, content: "return new Date(...)"}, ...], │ │ deletions: [...] } ] │ │ • Context Loader:根据变更文件,自动收集关联上下文 │ │ - package.json 依赖版本 │ │ - tsconfig.json 类型检查配置 │ │ - .eslintrc.js 规则集 │ │ - 历史审查记录(通过 git log -p 查找) │ │ • Agent Orchestrator:根据变更特征,路由到对应 Expert Agent │ │ • Result Aggregator:合并各 Expert 输出,生成统一报告 │ └─────────────────────────────────────────────────────────────────────┘ ↓ (HTTP API 或本地模型调用) ┌─────────────────────────────────────────────────────────────────────┐ │ Expert Agent Layer │ │ • 每个 Expert 是一个独立的、可热替换的模块 │ │ • 输入:严格定义的 JSON Schema(含 diff 片段 + 上下文) │ │ • 处理:调用本地 LLM(Ollama)或远程 API(OpenAI/Claude) │ │ • 输出:强制符合预定义 Schema 的 JSON,含 severity/message/suggestion │ │ • 示例 Prompt 模板(security-expert): │ │ "你是一名资深安全工程师。请严格基于以下代码片段和上下文, │ │ 识别是否存在硬编码密钥、明文密码或敏感信息泄露风险。 │ │ 仅返回 JSON,格式:{severity: 'high|medium|low', │ │ message: '描述问题', suggestion: '具体修复建议'}。 │ │ 不要添加任何额外解释或 markdown。" │ └─────────────────────────────────────────────────────────────────────┘

这个架构的关键在于解耦。CLI 层只管“怎么用”,Core Engine 层只管“怎么分析”,Expert Agent 层只管“怎么判断”。当你要接入新的 LLM(比如从 OpenAI 切换到 Claude),只需修改 Agent 层的调用逻辑,其他两层完全不动。当你要增加一个新的审查维度(比如“无障碍访问合规性”),只需编写一个新的a11y-expert模块,注册到 Orchestrator 即可。这种设计,让 open-code-review 从一个“工具”,变成了一个可生长的“审查平台”。

3.2 Diff Parser 的实现:超越正则,拥抱 Git 的原生语义

很多人以为解析git diff就是写几个正则表达式匹配+和-行。这在简单场景下可行,但在真实工程中会迅速崩溃。比如,一个 diff 可能包含二进制文件(Binary files a/src/logo.png and b/src/logo.png differ),可能包含重命名(rename from src/old.js\nrename to src/new.js),可能包含大段的@@ -1,5 +1,8 @@头信息。我们最初用正则,结果在处理一个包含 200+ 行的 CSS 文件 diff 时,把/* comment */里的+符号也当成了新增行,导致整个审查错乱。后来我们彻底转向 Git 的原生能力:直接调用git apply --stat --summary和git diff --no-color --unified=0。--unified=0参数是关键,它让 diff 输出只显示变更的行号和内容,不显示无关的上下文,极大简化了解析逻辑。我们的 Parser 核心代码(Python)如下:

import subprocess import re from typing import List, Dict, Any def parse_git_diff(diff_text: str) -> List[Dict[str, Any]]: """ 解析 git diff 输出,返回结构化变更列表。 使用 --unified=0 格式,确保每行变更都带有精确的文件名和行号。 """ changes = [] current_file = None # 匹配 diff 头:diff --git a/src/utils/date.js b/src/utils/date.js file_pattern = r'diff --git a/(.*) b/(.*)' # 匹配行号信息:@@ -12,5 +15,8 @@ hunk_pattern = r'@@ -(\d+),?(\d+)? \+(\d+),?(\d+)? @@' lines = diff_text.split('\n') for line in lines: # 处理文件头 if line.startswith('diff --git'): match = re.search(file_pattern, line) if match: # 取 b/ 后面的路径,即新文件路径 current_file = match.group(2) continue # 处理变更块头 elif line.startswith('@@'): match = re.search(hunk_pattern, line) if match and current_file: # 新增行起始行号 new_start = int(match.group(3)) # 新增行数(如果未指定,默认为1) new_count = int(match.group(4)) if match.group(4) else 1 # 下一行就是实际的新增内容 # 我们在这里只记录起始位置,具体内容在后续循环中捕获 # 实际生产代码中,会维护一个 buffer 来收集接下来的 + 行 pass # 处理新增行 elif line.startswith('+') and not line.startswith('+++'): if current_file and not line.startswith('+++') and not line.startswith('+import'): # 过滤掉 git 添加的文件头(+++ b/src/utils/date.js)和 import 语句 # 提取实际代码内容(去掉 + 号) content = line[1:].strip() if content and not content.startswith('//') and not content.startswith('/*'): # 只处理非注释的代码行 changes.append({ 'file': current_file, 'type': 'addition', 'line': new_start, # 这里需要更复杂的逻辑来跟踪行号,简化示意 'content': content }) return changes

这段代码的核心思想是:不试图用正则“理解” diff,而是利用 Git 自己生成的、语义明确的格式,做精准的字符串匹配和提取。它牺牲了一点通用性(强依赖git diff --unified=0的输出),但换来了 99.9% 场景下的绝对可靠性。这也是 open-code-review “务实”哲学的体现:不追求理论上的完美,只解决工程师每天真实面对的问题。

3.3 Context Loader 的实战技巧:让 AI 看懂你的项目

LLM 的幻觉(hallucination)问题,在代码审查中尤为致命。它可能“自信满满”地告诉你Array.from({length: 10}, (_, i) => i)是低效的,却不知道你的项目里babel-preset-env已经将此语法转译为高效的 for 循环。Context Loader 的使命,就是喂给 AI 它真正需要的、项目专属的“常识”。我们不搞大而全的信息轰炸,而是聚焦三个黄金上下文源:

  1. 依赖图谱(Dependency Graph):通过npm ls --json或pipdeptree --json-tree获取,解析出utils/date.js所依赖的date-fns版本(如"date-fns": "^2.16.1"),并将其作为context.dependencies字段注入。这样,security-expert在看到require('crypto')时,就能结合node版本(从engines字段获取)判断是否应推荐webcryptoAPI。

  2. 构建与测试配置(Build & Test Config):自动读取tsconfig.json、.eslintrc.js、jest.config.js。例如,如果tsconfig.json中strict为true,那么perf-expert在发现any类型时,就会将 severity 提升为high,因为这直接违反了项目的核心类型策略。

  3. 历史审查知识库(Historical Review Knowledge):这是最具巧思的一环。我们用git log -p -S "new Date(" --since="3 months ago" src/utils/date.js命令,搜索过去三个月内所有对date.js中new Date(的修改。然后,用一个轻量级的 embedding 模型(如all-MiniLM-L6-v2)对这些历史 diff 的摘要进行向量化,并存入本地 SQLite 数据库。当新的 diff 提交时,CLI 会计算其摘要的 embedding,查询数据库中相似度最高的 3 条历史记录,并将它们的审查结论(如“此处时区处理需兼容 IE11”)作为context.history注入。这相当于给 AI 配备了一个“团队记忆”,让它能从过往的集体经验中学习,而不是每次都从零开始“发明轮子”。

提示:Context Loader 的性能至关重要。我们采用“懒加载”策略:只在某个 Expert Agent 明确声明需要某类上下文(如security-expert需要dependencies)时,才去执行相应的加载逻辑。避免为一次审查请求,无谓地扫描整个node_modules。

3.4 Agent Orchestrator 的路由逻辑:让审查像流水线一样精准

Orchestrator 是 open-code-review 的“大脑”,它的核心任务不是思考,而是决策:针对当前 diff,应该启动哪些 Expert,以什么顺序,用什么参数。我们摒弃了简单的“if-else”链,采用一种基于“变更指纹(Change Fingerprint)”的动态路由机制。指纹由三部分组成:

  • 语言指纹(Language Fingerprint):通过文件扩展名和 shebang 行(如#!/usr/bin/env node)确定。date.js→javascript;Dockerfile→dockerfile;pyproject.toml→python。

  • 变更模式指纹(Pattern Fingerprint):对 diff 内容进行轻量级 NLP 分析。我们维护一个小型的关键词-专家映射表:

    • ["crypto", "AES", "RSA", "secret"]→security-expert
    • ["for", "while", "map", "filter", "reduce"]→perf-expert
    • ["t(", "i18n.t", "useTranslation"]→i18n-expert
    • ["test(", "it(", "describe("]→test-expert
  • 影响范围指纹(Scope Fingerprint):根据 diff 修改的文件路径和数量判断。如果只修改了src/utils/date.js,属于“局部变更”;如果同时修改了src/utils/date.js,src/api/index.ts,tests/date.test.ts,则标记为“跨域变更”,Orchestrator 会强制启用cross-domain-expert,专门检查接口契约一致性。

路由算法伪代码如下:

def route_experts(diff_changes: List[Dict]) -> List[str]: fingerprints = { 'language': detect_language(diff_changes), 'patterns': detect_patterns(diff_changes), 'scope': calculate_scope(diff_changes) } # 初始化专家列表 experts = set() # 语言是基础,必须启用 experts.add(f"{fingerprints['language']}-expert") # 根据模式添加 for pattern in fingerprints['patterns']: if pattern in PATTERN_TO_EXPERT_MAP: experts.add(PATTERN_TO_EXPERT_MAP[pattern]) # 根据范围添加 if fingerprints['scope'] == 'cross-domain': experts.add('cross-domain-expert') # 强制启用核心专家 experts.add('core-style-expert') # 检查基础代码风格 return list(experts) # 示例:一个修改了 date.js(含 crypto)和 Dockerfile 的 diff # fingerprints = {'language': 'javascript', 'patterns': ['crypto'], 'scope': 'local'} # 返回: ['javascript-expert', 'security-expert', 'core-style-expert']

这种设计让审查过程变得极其透明。你可以通过open-code-review --debug --diff my.patch命令,看到详细的路由日志:“[ROUTER] Detected language: javascript → loading javascript-expert”, “[ROUTER] Detected pattern: crypto → loading security-expert”。这不仅是调试利器,更是团队建立审查共识的基础——每个人都能看到,AI 的“判断”并非玄学,而是基于可审计、可复现的规则。

4. 实操全流程:从安装到嵌入 CI,一次完整的审查闭环

4.1 五分钟快速上手:本地 CLI 安装与首次运行

别被前面的架构吓到,open-code-review 的设计哲学是“开箱即用,渐进增强”。你不需要成为 LLM 专家,也能在五分钟内跑通第一个审查。以下是我在 macOS 和 Ubuntu 上验证过的、最简路径:

第一步:安装 CLI(无需 Node.js 或 Python 环境)

我们提供了预编译的二进制文件,直接下载即可:

# 创建安装目录 mkdir -p ~/bin cd ~/bin # 下载(macOS ARM64) curl -L https://github.com/open-code-review/releases/download/v0.3.1/ocr-darwin-arm64 -o ocr # 或下载(Ubuntu x64) # curl -L https://github.com/open-code-review/releases/download/v0.3.1/ocr-linux-amd64 -o ocr # 赋予执行权限 chmod +x ocr # 添加到 PATH(写入 ~/.zshrc 或 ~/.bashrc) echo 'export PATH="$HOME/bin:$PATH"' >> ~/.zshrc source ~/.zshrc

第二步:准备一个测试 diff

找一个你本地项目中最近的一次小修改,生成一个干净的 patch:

# 假设你刚改了 src/utils/date.js git diff HEAD~1 -- src/utils/date.js > test.patch

第三步:运行第一次审查

# 最简命令,使用默认配置和内置的 Ollama 模型(需提前安装 Ollama) ocr --diff test.patch # 如果你更习惯用 OpenAI,可以这样: ocr --diff test.patch --model openai:gpt-4-turbo --api-key sk-xxx # 输出会是结构化的 JSON,但为了可读,CLI 默认格式化为 Markdown 报告: # ┌───────────────────────────────────────────────────────────────────┐ # │ open-code-review Report │ # │ Commit: abc123 (HEAD~1) │ # │ File: src/utils/date.js │ # ├───────────────────────────────────────────────────────────────────┤ # │ [HIGH] Security Risk │ # │ Message: Hardcoded API key found in environment variable access. │ # │ Suggestion: Use a secrets manager or environment-specific config. │ # │ Line: 45 │ # │ │ # │ [MEDIUM] Performance │ # │ Message: `new Date().getTime()` is less efficient than `Date.now()`.│ # │ Suggestion: Replace with `Date.now()`. │ # │ Line: 22 │ # └───────────────────────────────────────────────────────────────────┘

这就是全部。你不需要配置任何东西,CLI 会自动为你加载package.json、tsconfig.json,并调用一个轻量级的本地模型(Ollama 的phi3:mini)完成审查。整个过程耗时通常在 3-8 秒,取决于 diff 大小和模型响应速度。

4.2 配置驱动:用.reviewrc文件定制你的审查规则

CLI 的默认行为是“安全第一”,但每个团队都有自己的“代码宪法”。.reviewrc就是你的宪法起草委员会。它是一个 YAML 文件,放在项目根目录下,ocr会自动加载。下面是一个典型配置,展示了如何精细控制:

# .reviewrc # 全局设置 global: # 审查超时时间(秒) timeout: 30 # 是否启用历史知识库 enable_history: true # 默认使用的 LLM 模型 default_model: "ollama:phi3:mini" # 专家(Expert)配置 experts: # 安全专家 security-expert: # 启用的检查项 enabled_checks: - hardcoded_secrets - sql_injection - xss_vulnerability # 禁用的检查项(例如,团队已决定不检查 XSS) disabled_checks: - xss_vulnerability # 性能专家 perf-expert: # 对于大型数组操作,阈值设为 1000 array_size_threshold: 1000 # 禁用对 `map` 的审查(团队认为其可读性优先) disable_map_check: true # 风格专家 core-style-expert: # 严格遵循 ESLint 规则 eslint_config: ".eslintrc.js" # 忽略某些文件 ignore_files: - "**/node_modules/**" - "**/dist/**" # 自定义规则(Custom Rules) custom_rules: # 规则ID - id: "no-console-in-prod" # 触发条件:在非测试文件中出现 console.log condition: | file !~ /test\.js$/ && content =~ /console\.log\(/ # 违反时的严重等级 severity: "high" # 建议 suggestion: "Use a logging library like winston or pino with environment-aware levels."

这个配置文件的强大之处在于,它把原本分散在不同地方的规则(ESLint、团队 Wiki、Code Review Checklist)统一到了一个地方。当你运行ocr --diff test.patch时,CLI 会读取这个文件,动态调整所有 Expert 的行为。更重要的是,这个文件本身就是代码的一部分,会被 Git 跟踪、Code Review、版本管理。新成员加入时,git clone下来,ocr就自动拥有了团队的全部审查智慧,无需任何口头传授。

4.3 深度集成:将 open-code-review 嵌入你的 CI/CD 流水线

CLI 的终极形态,是成为 CI 流水线中一个沉默而可靠的守门人。我们以 GitHub Actions 为例,展示如何实现“无人值守的自动化审查”。这个方案不替代人工 Review,而是把重复、机械、高风险的检查前置,让人 Review 的精力聚焦在更高阶的设计和权衡上。

第一步:创建.github/workflows/code-review.yml

name: Open Code Review on: pull_request: types: [opened, synchronize, reopened] # 只对特定分支触发 branches: [main, develop] jobs: review: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: # 必须获取完整的历史,以便 Context Loader 查询历史 fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' - name: Install open-code-review CLI run: | curl -L https://github.com/open-code-review/releases/download/v0.3.1/ocr-linux-amd64 -o ocr chmod +x ocr sudo mv ocr /usr/local/bin/ - name: Run open-code-review on PR diff # 关键:使用 GitHub 提供的 PR diff URL run: | # 获取 PR 的 patch URL PATCH_URL="https://github.com/${{ github.repository }}/pull/${{ github.event.number }}.patch" # 执行审查,输出为 JSON,便于后续解析 ocr --diff-url "$PATCH_URL" --output-format json > review-report.json 2> review-error.log || true - name: Post review comments to PR # 使用一个轻量级的 Action 来解析 JSON 并发布评论 # 这里用一个自研的 action,也可以用社区的 `peter-evans/create-or-update-comment` uses: ./actions/post-review-comments with: # 传入生成的报告 report-path: review-report.json # GitHub Token,用于发布评论 token: ${{ secrets.GITHUB_TOKEN }}

第二步:理解审查报告的结构与 CI 反馈

ocr生成的review-report.json是一个标准的 JSON 数组,每个元素代表一个发现的问题:

[ { "id": "security-hardcoded-secret", "file": "src/utils/auth.js", "line": 32, "severity": "high", "message": "Hardcoded API key detected.", "suggestion": "Move to environment variable and use process.env.API_KEY.", "expert": "security-expert" }, { "id": "perf-array-map", "file": "src/components/List.jsx", "line": 88, "severity": "medium", "message": "Using map() on large arrays may cause performance issues.", "suggestion": "Consider using a for loop or virtualized list.", "expert": "perf-expert" } ]

我们的post-review-commentsAction 会遍历这个数组,为每个high级别的问题,在对应的文件和行号上,发布一条 GitHub 评论。效果如下:

Comment onsrc/utils/auth.jsline 32

open-code-review (security-expert)Hardcoded API key detected. Move to environment variable and use process.env.API_KEY.

这个评论不是来自某个机器人账号,而是由ocr生成、由 CI 流水线自动发布的,它带着精确的上下文和可操作的建议。最关键的是,它不会阻断 CI。我们刻意设计为|| true,即审查失败也不让 CI 失败。因为 open-code-review 的定位是“辅助”,不是“裁判”。它的价值在于把问题暴露出来,把讨论引向正确的方向,而不是用一个exit 1去制造冲突。真正的“门禁”,依然由人工 Reviewer 和合并策略(如 require 2 approvals)来把守。

4.4 高级技巧:与飞书、钉钉等 IM 工具联动,让审查触手可及

审查的最终目的是“发现问题”,而问题最有效的传播渠道,往往是工程师最常驻留的地方——即时通讯工具。我们实现了与飞书(Feishu)的深度集成,让审查报告不再是埋在 CI 日志里的几行文字,而是主动推送到你的飞书群聊中。

实现原理很简单:

  1. 在飞书开发者后台,创建一个 Bot,获取App ID和App Secret。
  2. 在你的 CI 流水线中,ocr审查完成后,调用飞书开放平台的send_messageAPI。
  3. 将review-report.json中的high和medium级别问题,格式化为飞书富文本卡片(Card),发送到指定群组。

飞书卡片的核心代码(Python):

import requests import json def send_feishu_card(webhook_url: str, report: List[Dict]): # 过滤出 high/medium 问题 issues = [issue for issue in report if issue['severity'] in ['high', 'medium']] if not issues: return # 构建飞书卡片消息体 card = { "msg_type": "interactive", "card": { "elements": [ { "tag": "div", "text": { "content": f"🔍 **open-code-review 发现 {len(issues)} 个待关注问题**\n\n", "tag": "lark_md" } } ], "header": { "title": { "content": "🚨 代码审查警报", "tag": "plain_text" } } } } # 为每个问题添加一个 div 元素 for issue in issues[:5]: # 最多显示前5个 card["card"]["elements"].append({ "tag": "div", "fields": [ { "is_short": True, "text": { "content": f"**文件:** {issue['file']}", "tag": "lark_md" } }, { "is_short": True, "text": { "content": f"**行号:** {issue['line']}", "tag": "lark_md" } } ] }) card["card"]["elements"].append({ "tag": "div", "text": { "content": f"> {issue['message']}\n> *建议:* {issue['suggestion']}", "tag": "lark_md" } }) # 发送请求 requests.post(webhook_url, json=card) # 在 CI 脚本中调用 # send_feishu_card("https://open.feishu.cn/open-apis/bot/v2/hook/xxx", report_data)

效果是震撼的:当一个 PR

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进