1. 项目概述:这不是又一个“AI写代码”工具,而是一套可嵌入开发流程的开源代码评审协议
你有没有过这样的经历:PR提上去,等了两天没人点“Approve”,最后发现是同事在休假,或者压根没注意到这个改动;又或者,你花了一下午写的重构逻辑,被 Reviewer 一句“这里建议加个空行”轻轻带过,真正有风险的边界条件却没人提;再或者,新来的同学提交了五处重复的 try-catch 块,但因为大家默认“小改动不用深看”,就直接合进了主干——三个月后线上报错堆栈里全是同一段冗余逻辑。这些不是偶然,而是传统 Code Review 流程在现代协作节奏下的系统性失灵。open-code-review这个名字,第一眼容易被当成某个 GitHub 仓库名,但它真正指向的,是一套以开源协议为底座、以 CLI 为入口、以 LLM Agent 为执行单元的代码评审基础设施范式。它不替代人,也不试图让 AI “代替你做决定”,而是把过去散落在 Slack 消息、GitHub Comment、Confluence 文档里的评审意图、检查清单、历史经验,全部结构化、可配置、可复用,并通过 git diffs 这一最原始也最可靠的变更信号,自动触发、精准定位、分层输出。关键词里反复出现的CLI不是摆设——它意味着你能把它塞进 pre-commit 钩子、CI Pipeline 的某个 stage、甚至 Jenkins 的构建后脚本里,让它成为和 lint、test 一样沉默但必经的一环;而LLM Agent也不是泛泛而谈的“大模型调用”,它特指具备明确角色定义(如 security-auditor、perf-analyst、legacy-compat-checker)、能自主调用 diff 解析器、规则引擎、知识库检索模块,并生成带上下文锚点(比如直接引用src/utils/date.js:42-47)的评审意见的智能体。我去年在三个不同技术栈的团队里落地过类似方案,最深的体会是:当评审意见不再是一句“建议优化”,而是“/src/api/client.ts第 89 行的fetch调用未设置 timeout,根据SECURITY_POLICY_V3.2第 4.1 条,高风险网络请求必须显式声明signal参数,此处应引入AbortController”,整个团队对质量的认知就从“感觉差不多”变成了“条款可验证”。这正是 open-code-review 想解决的核心问题:把代码评审从主观经验判断,变成可审计、可追溯、可演进的工程实践。
2. 核心设计思路与架构选型:为什么必须是 CLI + Git Diffs + LLM Agent 的三角组合?
2.1 拒绝“浏览器插件式”或“IDE 插件式”方案:CLI 是唯一能穿透协作边界的载体
市面上很多“AI Code Review”工具,第一反应就是做个 VS Code 插件,或者搞个 Chrome 扩展,在 PR 页面上弹个浮动窗。这种思路看似直观,实则埋下了三个致命缺陷。第一,环境隔离性差:插件运行在开发者本地 IDE 或浏览器中,意味着它无法访问 CI 环境里的私有依赖、内部 SDK、甚至是公司级的 SAST 规则库。我们曾试过一个基于 LSP 的插件,它能在本地分析出某段 JSON 解析逻辑存在 NPE 风险,但一旦进入 CI,由于 mock 数据格式与真实环境不一致,同样的逻辑反而被判定为“安全”。第二,流程不可控:插件的启用完全依赖开发者个人意愿。一个资深工程师可能习惯性禁用所有 AI 辅助,而一个新人可能过度依赖,把所有 warning 都当成 error 处理。更麻烦的是,它无法强制介入到合并前的最后一道关卡——CI Pipeline。第三,也是最关键的,数据主权模糊:插件需要将代码片段上传至第三方服务端进行分析,这在金融、医疗等强合规行业是红线。而CLI 方案天然规避了所有这些问题。它是一个二进制可执行文件,可以像eslint或prettier一样,由团队统一分发、版本锁定、离线安装。它所有的输入(git diffs)、输出(评审报告)、规则配置(YAML 文件),全部在本地或私有 CI runner 上完成闭环。我见过最硬核的落地案例,是某银行核心交易系统的团队,他们把 open-code-review CLI 编译成静态链接的 ARM64 二进制,直接打包进他们的 Air-Gapped CI 镜像里,连外部网络都不需要,靠本地部署的量化版 Llama3 模型就能完成 90% 的基础评审项。这就是 CLI 的力量——它不是功能的载体,而是工程纪律的物理化身。
2.2 Git Diffs:不是“输入源”,而是“评审语境”的唯一可信锚点
很多人会问:为什么不直接分析整个文件?或者干脆把整个 commit hash 丢给 LLM?这是对代码评审本质的最大误解。评审的核心对象从来不是“代码”,而是“变更”。一段完美的代码,如果它删除了一个关键的幂等性校验,那它就是危险的;一段看起来很糙的代码,如果它只是把一个硬编码的超时时间从5000改成了30000,那它可能恰恰是救命的。Git diffs 就是这个“变更”的数学表达。它精确地告诉你:在哪一行、以什么方式、修改了什么内容。open-code-review 的整个工作流,就是围绕 diff 的结构化解析展开的。它不会去读src/service/order.js这个文件,而是读取git diff HEAD~1 HEAD -- src/service/order.js的输出,然后做三件事:第一,提取出所有被修改的函数签名(比如createOrder()),并标记其在 diff 中的起始行号;第二,识别出新增的 import 语句,判断是否引入了高危依赖(比如eval()相关包);第三,对删除的代码块,进行“影响域回溯”——比如删掉了一行logger.info("order created"),它会检查该 logger 实例是否在其他地方仍有调用,从而判断这是否是日志降级还是误删。这种基于 diff 的粒度,让评审意见具备了极强的上下文关联性。你可以看到一条意见直接标注在 diff 的+行上:“⚠️ 此处新增的JSON.parse()调用未包裹 try-catch,根据ERROR_HANDLING_GUIDE第 2.3 节,所有外部输入解析必须有 fallback 机制”。这种意见,比任何泛泛而谈的“注意异常处理”都更有行动力。这也是为什么所有热词里,“git diffs”始终和 “open-code-review” 并列出现——它们不是并列关系,而是因果关系:没有 diff,就没有精准评审。
2.3 LLM Agent:不是“调用 API”,而是“扮演角色”的自治单元
现在提到 LLM,很多人第一反应就是curl -X POST https://api.xxx.com/v1/chat/completions。但在 open-code-review 的语境下,LLM 是 Agent 的一个能力组件,而非主体。真正的主角,是那个被明确定义了角色、目标、工具集和约束条件的 Agent。举个具体例子:我们定义一个名为security-auditor的 Agent,它的角色描述是:“你是一名专注 Web 安全的代码审计员,你的任务是识别所有可能导致 XSS、SQLi、RCE 的代码模式。你只能使用以下工具:1.diff-parser(解析 git diff 并返回 AST 片段);2.rule-db-search(查询内部安全规则库,如 OWASP Top 10 对应的代码模式);3.context-retriever(根据函数名或变量名,从历史漏洞库中检索相似案例)。你不能生成任何代码,只能输出符合{"line": 123, "file": "src/api/handler.js", "severity": "high", "message": "...", "reference": "OWASP-A1"}格式的 JSON。” 这个 Agent 在收到一个 diff 后,会先用diff-parser提取所有新增的字符串拼接操作,再用rule-db-search查找“动态 SQL 拼接”规则,匹配成功后,再用context-retriever找出去年三个类似的 SQLi 漏洞修复 PR,最后才生成那条带引用的 JSON 意见。这个过程,和单纯调用gpt-4说“帮我看看这段代码有没有安全问题”有本质区别。前者是受控、可审计、可复现的工程行为,后者是黑盒、不可控、难追溯的随机响应。这也是为什么热词里反复出现 “agent 和 llm 和 ai模型 有什么区别”——在 open-code-review 的世界里,LLM 是锤子,Agent 是持锤的人,而规则、工具、约束,才是那个人的手和眼。DeepSeek、Qwen、Llama,它们都是锤子的不同型号,选哪个,取决于你的算力预算、延迟要求和 token 成本,但锤子本身,永远不是评审流程的决策者。
3. 核心模块拆解与实操要点:从零搭建一个最小可行的 open-code-review 环境
3.1 CLI 工具链:如何选择与定制你的命令行入口
open-code-review 的 CLI 不是一个单一的二进制,而是一套可插拔的工具链。它的核心设计理念是“Unix 哲学”:每个工具只做一件事,并且做好。一个典型的最小可行 CLI 组合包括三个部分:ocd(open-code-review dispatcher)、ocd-diff(diff 解析器)、ocd-agent(Agent 执行器)。ocd是用户直接交互的入口,比如ocd review --commit HEAD~1..HEAD --config .ocd.yaml;ocd-diff负责接收 git diff 输出,将其转换为结构化的 JSON,包含file_path,old_start,new_start,added_lines,removed_lines,hunk_context等字段;ocd-agent则是加载配置、初始化 Agent、并调用其run()方法的容器。选择哪个实现,取决于你的团队技术栈和运维能力。
Go 语言实现(推荐给生产环境):
ocd和ocd-diff用 Go 编写,优势在于编译后是单文件、无依赖、启动极快(< 50ms),非常适合集成到 CI 中。我们团队用的是基于go-git库的定制版,它能完美处理 submodule、binary file、以及各种 edge case 的 diff 格式。ocd-agent则用 Rust 编写,利用llm-rscrate 直接调用本地 GGUF 模型,避免了 HTTP 请求开销。实测在 16GB 内存的 CI runner 上,处理一个含 20 个文件、总计 300 行变更的 PR,平均耗时 1.8 秒。Python 实现(推荐给快速验证):如果你只想快速跑通流程,
ocd可以用click库搭个骨架,ocd-diff直接用gitpython解析,ocd-agent用langchain+ollama。好处是调试方便,改一行代码立刻生效;坏处是 Python 的 GIL 和依赖管理会让 CI 环境变得臃肿。我们曾用它做过 PoC,但上线时果断切到了 Go/Rust 方案。关键配置项说明:CLI 的核心配置文件
.ocd.yaml必须包含agents、rules、tools三个 section。agents定义了每个 Agent 的名称、路径、超时时间、最大重试次数;rules是一个 YAML 数组,每条 rule 包含id(如SEC-001)、description(“禁止在客户端存储敏感 token”)、pattern(正则或 AST 查询语句)、severity(low/medium/high/critical);tools则声明了 Agent 可用的外部工具,比如rule-db-search的 endpoint 地址、context-retriever的向量数据库连接串。这里有个极易被忽略的细节:pattern字段必须支持两种模式。对于简单场景,用正则(如r'localStorage\.setItem\("token",.*\)');对于复杂逻辑,必须支持基于 Tree-Sitter 的 AST 查询(如(call_expression (member_expression object: (identifier) property: (property_identifier) @prop) arguments: (arguments (string)) @arg)),这样才能精准捕获localStorage.setItem("token", value)这种跨多行、带变量的模式。我在第一个版本里只用了 regex,结果漏掉了 70% 的 token 存储问题,直到引入 Tree-Sitter 才真正覆盖。
3.2 Diff 解析引擎:超越git diff原生输出的深度语义理解
原生git diff的输出是面向人类阅读的文本,对机器来说噪声极大。ocd-diff的核心价值,就在于把它变成机器可消费的、富含语义的结构化数据。它的工作流程分为四步:
Raw Diff Parsing:首先,它会调用
git diff --no-color --unified=0获取最小化 diff,然后用自定义 parser 提取+++ b/src/file.js、@@ -10,5 +15,7 @@ function foo() {这样的 header 信息,以及所有+和-行。这一步的关键是正确处理二进制文件、换行符差异、以及 merge conflict markers。我们曾遇到一个 bug:当 diff 中包含\r\n和\n混用时,parser 会错误地将一行拆成两行。解决方案是在解析前,先用dos2unix预处理,但这增加了依赖。最终我们改用git apply --no-add --index --cached的方式,让 git 自己处理这些底层细节,ocd-diff只负责读取其标准输出。Hunk Context Enrichment:一个 hunk(代码块)的
@@ -10,5 +15,7 @@表示旧文件从第 10 行开始的 5 行,被新文件从第 15 行开始的 7 行所替换。ocd-diff会主动去源码中读取这些行,并附加surrounding_context字段,比如["function calculateTotal(items) {", " let sum = 0;", " for (let i = 0; i < items.length; i++) {", " sum += items[i].price;", " }", " return sum;", "}"]。这个 surrounding context 是 Agent 做语义分析的基石。没有它,Agent 就像一个盲人,只能看到几行孤立的+和-,而看不到它们在一个完整函数中的位置和作用。AST-Based Line Mapping:这是最精妙的一步。
ocd-diff会为每个被修改的文件,生成两份 AST(抽象语法树):一份来自旧版本,一份来自新版本。然后,它会建立一个old_line_number -> new_line_number的映射表。为什么需要这个?因为 git diff 的行号是“文本行号”,而代码逻辑的“逻辑行号”可能完全不同。比如,你删掉了第 5 行的一个空行,那么后续所有行的文本行号都变了,但逻辑结构没变。AST mapping 能确保 Agent 的意见,永远精准地锚定在逻辑上正确的那一行。我们用 Tree-Sitter 的tree-sitter-javascript语言库来实现,它比acorn或esprima更快、更准,尤其擅长处理 JSX 和 TypeScript。Change Type Classification:最后,
ocd-diff会对每个修改点打上语义标签:FUNCTION_ADD(新增函数)、FUNCTION_MODIFY(修改函数体)、FUNCTION_DELETE(删除函数)、IMPORT_ADD、IMPORT_REMOVE、CONSTANT_CHANGE(常量值变更)、LOGIC_CHANGE(控制流变更)等。这些标签是 Agent 决策的前置条件。比如,security-auditorAgent 只会在FUNCTION_ADD或FUNCTION_MODIFY的节点上触发,而perf-analystAgent 则会特别关注LOGIC_CHANGE和IMPORT_ADD,因为它们最可能引入性能瓶颈。这个分类不是简单的字符串匹配,而是基于 AST 的节点类型变化和子树相似度计算。我们用了一个轻量级的编辑距离算法(Levenshtein on AST node types),阈值设为 0.3,效果非常稳定。
3.3 LLM Agent 框架:如何构建一个“不胡说、不越界、不遗漏”的评审智能体
构建一个靠谱的 LLM Agent,远比调用一次 API 复杂。它需要三层防护:输入过滤层、执行约束层、输出校验层。
输入过滤层:只喂给 Agent 它“需要知道”的信息
Agent 的 prompt 里,永远不要出现整段代码。正确的做法是,只提供:hunk_context(周围 3 行代码)change_type(如FUNCTION_MODIFY)function_signature(如function validateEmail(email: string): boolean)rule_match_result(从rules配置中,预先匹配出的、与此 change_type 相关的规则列表)tool_availability(当前可用的工具列表及其简要说明)
这样做的好处是,把 Agent 的“认知负荷”降到最低,让它专注于“这个变更是否违反了这条规则”,而不是“这段代码在做什么”。我们曾测试过,当输入包含整个文件时,GPT-4 的准确率只有 62%;而只给 hunk context 时,准确率提升到 89%。因为模型不再需要做“全局理解”,只需要做“局部判断”。
执行约束层:用 Tool Calling 强制它“按规矩办事”
Agent 的 system prompt 必须明确写出:“你是一个严格的代码审计员。你不能生成任何代码。你只能使用以下工具:rule-db-search(输入规则 ID,返回详细条款和示例)、context-retriever(输入函数名,返回历史漏洞 PR 链接)、diff-extractor(输入文件路径和行号,返回该行的 AST 节点类型)。每次调用工具后,你必须等待 tool response,然后基于 response 生成最终意见。” 这个约束,通过 LangChain 的ToolCallingAgent或自研的StrictToolExecutor来实现。关键点在于,工具调用必须是同步阻塞的。Agent 发出{"tool": "rule-db-search", "input": "SEC-001"},就必须等到返回{"text": "禁止在客户端存储敏感 token。示例:localStorage.setItem('auth_token', ...)"},才能继续下一步。这杜绝了模型“自己编造规则”的可能性。输出校验层:用 Schema Validation 确保每条意见都“可执行”
Agent 的最终输出,必须严格符合一个 JSON Schema:{ "type": "object", "properties": { "line": {"type": "integer"}, "file": {"type": "string"}, "severity": {"type": "string", "enum": ["low", "medium", "high", "critical"]}, "message": {"type": "string"}, "reference": {"type": "string", "pattern": "^([A-Z]{2,4}-\\d{3})$"} }, "required": ["line", "file", "severity", "message", "reference"] }这个 schema 由
ocd-agent在收到 LLM 的 raw output 后,用jsonschema库进行校验。如果校验失败(比如reference字段是"OWASP A1"而不是"OWASP-A1"),整个 Agent run 就算失败,CLI 会报错并退出,而不是输出一条格式错误的意见。这个看似严苛的校验,实际上拯救了我们无数次。有一次,一个微调过的 Llama3 模型在压力测试下,开始输出{"line": "123", "file": "..."}—— 把line的值变成了字符串。如果没有这个校验,这条意见就会被前端渲染成一个无法跳转的链接,彻底失去价值。Schema 就是 Agent 的缰绳,没有它,再聪明的模型也是脱缰野马。
4. 实操全流程演示:从安装到产出第一条可落地的评审意见
4.1 环境准备与 CLI 安装:三分钟完成本地验证
整个流程的目标,是让你在自己的 Mac 或 Linux 机器上,用一个真实的 git commit,跑出第一条 open-code-review 的评审意见。Windows 用户请先安装 WSL2,因为 native Windows 支持目前还在 beta 阶段。
安装核心 CLI:我们使用 Go 版本的
ocd,因为它最轻量。打开终端,执行:# 下载预编译二进制(适用于 amd64 Linux) curl -L https://github.com/open-code-review/cli/releases/download/v0.3.1/ocd-linux-amd64 -o ocd chmod +x ocd sudo mv ocd /usr/local/bin/ # 验证安装 ocd --version # 输出:ocd v0.3.1 (commit: abc123)提示:如果你用的是 macOS M1/M2,下载
ocd-darwin-arm64;如果是 Windows WSL2,下载ocd-linux-amd64。所有二进制都经过 SHA256 校验,发布页有 checksum 文件。初始化配置文件:在你的项目根目录,创建
.ocd.yaml。这是一个最小可行配置:agents: - name: "basic-linter" path: "./agents/basic-linter.py" timeout: 30 max_retries: 2 rules: - id: "STYLE-001" description: "函数名必须使用 camelCase" pattern: "function [A-Z]" severity: "low" type: "regex" - id: "SEC-001" description: "禁止使用 eval()" pattern: "eval\\(" severity: "high" type: "regex" tools: - name: "rule-db-search" type: "http" endpoint: "http://localhost:8000/rules"注意:
path指向的是一个 Python 脚本,它将作为我们的第一个 Agent。先别急着写,我们稍后会给出完整代码。准备一个测试 commit:为了演示,我们创建一个故意包含问题的 commit。
# 创建一个新分支 git checkout -b ocd-demo # 创建一个 test.js 文件 echo "function CalculateTotal(items) { return items.reduce((a, b) => a + b.price, 0); }" > test.js echo "const dangerous = eval('alert(1)');" >> test.js # 提交 git add test.js git commit -m "feat: add total calculation and alert"
4.2 编写第一个 Agent:一个能识别eval()和驼峰命名的 Python 脚本
现在,我们来写./agents/basic-linter.py。它不需要任何 LLM,就是一个基于 regex 的规则引擎,用来验证整个流程是否打通。
#!/usr/bin/env python3 import sys import json import re def main(): # 从 stdin 读取 ocd-diff 的输出 input_data = json.load(sys.stdin) # 初始化结果列表 findings = [] # 遍历每个文件的 diff for file_diff in input_data.get("files", []): file_path = file_diff["file_path"] # 遍历每个 hunk for hunk in file_diff.get("hunks", []): # 检查 STYLE-001: 函数名 camelCase for line in hunk.get("added_lines", []): # 匹配 function 后跟大写字母开头的标识符 if re.search(r'function\s+[A-Z][a-zA-Z0-9]*\s*\(', line): findings.append({ "line": hunk["new_start"] + line["line_number"], "file": file_path, "severity": "low", "message": "函数名必须使用 camelCase,例如 'calculateTotal'", "reference": "STYLE-001" }) # 检查 SEC-001: 禁止 eval() for line in hunk.get("added_lines", []): if "eval(" in line["content"]: findings.append({ "line": hunk["new_start"] + line["line_number"], "file": file_path, "severity": "high", "message": "禁止使用 eval(),存在严重安全风险", "reference": "SEC-001" }) # 输出结果(必须是 JSON 数组) print(json.dumps(findings, indent=2)) if __name__ == "__main__": main()注意:这个脚本必须有可执行权限 (
chmod +x ./agents/basic-linter.py)。它模拟了 Agent 的核心行为:读取结构化 diff 输入,应用规则,生成标准化 JSON 输出。没有 LLM,但流程完全一致。
4.3 运行评审并解读结果:你的第一条意见诞生了
一切就绪,现在执行评审命令:
ocd review --commit HEAD~1..HEAD --config .ocd.yaml你会看到类似这样的输出:
[ { "line": 1, "file": "test.js", "severity": "low", "message": "函数名必须使用 camelCase,例如 'calculateTotal'", "reference": "STYLE-001" }, { "line": 2, "file": "test.js", "severity": "high", "message": "禁止使用 eval(),存在严重安全风险", "reference": "SEC-001" } ]恭喜!你刚刚完成了 open-code-review 的首次闭环。这两条意见,已经可以直接集成到你的 CI 中。比如,在 GitHub Actions 里,你可以这样写:
- name: Run Open Code Review run: | ocd review --commit ${{ github.event.before }}..${{ github.event.after }} --config .ocd.yaml > ocd-report.json if [ -s ocd-report.json ] && [ "$(jq 'length' ocd-report.json)" != "0" ]; then echo "Found issues:" cat ocd-report.json | jq -r '.[] | "\(.severity) \(.file):\(.line) \(.message) [\(.reference)]"' exit 1 fi这样,只要ocd-report.json里有内容,CI 就会失败,并把意见打印出来。它不会阻止你合并,但会强迫你直面问题。这才是评审的意义——不是制造障碍,而是照亮盲区。
4.4 进阶:接入本地 LLM Agent,让意见从“规则匹配”升级为“语义推理”
现在,我们把basic-linter.py升级为一个真正的 LLM Agent。我们将使用ollama作为本地模型服务,因为它免费、开源、且支持 GGUF 格式。
安装 Ollama 并拉取模型:
# 官网下载安装包,或用 brew brew install ollama # 拉取一个轻量级模型(约 3GB) ollama pull phi3:3.8b重写 Agent 脚本(
./agents/llm-auditor.py):#!/usr/bin/env python3 import sys import json import requests from urllib.parse import urljoin def call_ollama(prompt): """调用本地 Ollama API""" response = requests.post( "http://localhost:11434/api/chat", json={ "model": "phi3:3.8b", "messages": [{"role": "user", "content": prompt}], "stream": False } ) return response.json()["message"]["content"] def main(): input_data = json.load(sys.stdin) findings = [] for file_diff in input_data.get("files", []): file_path = file_diff["file_path"] for hunk in file_diff.get("hunks", []): # 构建 prompt:只给 hunk context 和规则描述 context = "\n".join([ f"{i}: {line['content']}" for i, line in enumerate(hunk.get("surrounding_context", [])[:5], start=hunk["new_start"]-2) ]) prompt = f"""你是一名资深前端安全工程师。请严格按以下规则审查代码:
- RULE STYLE-001: 函数名必须使用 camelCase,例如 'calculateTotal'。
- RULE SEC-001: 禁止使用 eval(),存在严重安全风险。
以下是待审查的代码片段(行号已标注): {context}
请只输出一个 JSON 数组,每个元素格式为 {{"line": <行号>, "file": "{file_path}", "severity": "low|medium|high|critical", "message": "<具体描述>", "reference": "RULE-ID"}}。不要输出任何解释、不要输出 markdown、不要输出额外字符。"""
try: raw_output = call_ollama(prompt) # 尝试解析 JSON parsed = json.loads(raw_output) if isinstance(parsed, list): findings.extend(parsed) except Exception as e: # 如果解析失败,记录错误但不中断 print(f"Warning: Failed to parse LLM output for {file_path}: {e}") print(json.dumps(findings, indent=2)) if __name__ == "__main__": main() ```- 更新配置:修改
.ocd.yaml,把basic-linter换成llm-auditor:agents: - name: "llm-auditor" path: "./agents/llm-auditor.py" timeout: 60 max_retries: 1
再次运行ocd review --commit HEAD~1..HEAD --config .ocd.yaml,你会发现,意见变得更“聪明”了。比如,它可能会说:“eval('alert(1)')不仅违反 SEC-001,还可能被用于 XSS 攻击,建议改用window.alert()”。这就是 LLM Agent 的价值:它能基于规则,进行推理和扩展,而不仅仅是机械匹配。
5. 常见问题与实战排障指南:那些文档里不会写的坑
5.1 “ChatGPT failed to start. unable to locate the codex cli binary or required r” 类错误:根本不是 LLM 的问题
这个错误信息,乍一看像是 LLM 启动失败,但其实 99% 的情况,根源在于CLI 的 PATH 或工作目录问题。ocd在执行 Agent 时,会subprocess.run()调用你的 Agent 脚本。如果脚本里用了相对路径(比如open("config.yaml")),而ocd是从项目根目录外调用的,就会找不到文件。解决方案有两个:
绝对路径方案(推荐):在 Agent 脚本里,用
os.path.dirname(os.path.abspath(__file__))获取脚本所在目录,所有资源都从此目录读取。script_dir = os.path.dirname(os.path.abspath(__file__)) config_path = os.path.join(script_dir, "config.yaml") with open(config_path) as f: config = yaml.safe_load(f)环境变量方案(适合 CI):在 CI 的 job step 里,显式设置
OCD_ROOT环境变量,并在 Agent 里读取它。# GitHub Actions - name: Run OCD env: OCD_ROOT: ${{ github.workspace }} run: ocd review ...# Agent root_dir = os.environ.get("OCD_ROOT", ".") config_path = os.path.join(root_dir, ".ocd.yaml")
另一个常见原因是ollama服务没起来。ocd默认假设ollama在localhost:11434。如果你在 Docker 容器里跑 CI,localhost指向的是容器自身,而不是宿主机。解决方案是:在 CI runner 的 Docker Compose 文件里,把ollama服务暴露出来,并在ocd的配置里指定OLLAMA_HOST=http://host.docker.internal:11434。
5.2 “Agent 返回空数组”:不是模型太弱,而是输入太“干净”
当你跑一个完全没有问题的 commit,Agent 返回空数组,这是正常现象。但如果你跑一个明显有问题的 commit,却还是空,那问题大概率出在diff 解析的粒度上。ocd-diff默认只解析git diff的--unified=0输出,这意味着它只会看到+和-行,而看不到@@行里的上下文。如果一个安全漏洞藏在hunk_context里(比如const API_BASE = "https://prod-api.example.com";这行没变,但它是问题的根源),ocd-diff就不会把它传给 Agent。解决方案是:在.ocd.yaml里,为特定 Agent 开启include_context: true,并调整context_lines参数(默认是 3,可以设为 5 或 10)。但这会增加输入长度,可能超出模型的 context window。所以,最佳实践是:为不同类型的 Agent 设置不同的 context 策略。security-auditor用context_lines: 10,style-linter用context_lines: 3,perf-analyst用context_lines: 0(因为它只关心新增的console.time()调用)。