
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 组合包括三个部分ocdopen-code-review dispatcher、ocd-diffdiff 解析器、ocd-agentAgent 执行器。ocd是用户直接交互的入口比如ocd review --commit HEAD~1..HEAD --config .ocd.yamlocd-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用langchainollama。好处是调试方便改一行代码立刻生效坏处是 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 查询语句、severitylow/medium/high/criticaltools则声明了 Agent 可用的外部工具比如rule-db-search的 endpoint 地址、context-retriever的向量数据库连接串。这里有个极易被忽略的细节pattern字段必须支持两种模式。对于简单场景用正则如rlocalStorage\.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 --unified0获取最小化 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_MODIFYfunction_signature如function validateEmail(email: string): booleanrule_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 alert4.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(rfunction\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, indent2)) 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], starthunk[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(fWarning: Failed to parse LLM output for {file_path}: {e}) print(json.dumps(findings, indent2)) if __name__ __main__: main() 更新配置修改.ocd.yaml把basic-linter换成llm-auditoragents: - 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 容器里跑 CIlocalhost指向的是容器自身而不是宿主机。解决方案是在 CI runner 的 Docker Compose 文件里把ollama服务暴露出来并在ocd的配置里指定OLLAMA_HOSThttp://host.docker.internal:11434。5.2 “Agent 返回空数组”不是模型太弱而是输入太“干净”当你跑一个完全没有问题的 commitAgent 返回空数组这是正常现象。但如果你跑一个明显有问题的 commit却还是空那问题大概率出在diff 解析的粒度上。ocd-diff默认只解析git diff的--unified0输出这意味着它只会看到和-行而看不到行里的上下文。如果一个安全漏洞藏在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: 10style-linter用context_lines: 3perf-analyst用context_lines: 0因为它只关心新增的console.time()调用。5.3 “评审意见重复出现”不是 Agent 逻辑