1. 项目概述这不是一个工具而是一套可落地的开源代码评审工作流“open-code-review”这个名字乍一听像某个新发布的 CLI 工具或者某家大厂刚开源的 AI 代码审查平台。但实际翻遍 GitHub、PyPI、NPM 和主流技术社区你会发现——它没有官方仓库、没有安装包、没有文档首页甚至没有一个统一的 logo。它不是产品而是正在快速凝聚共识的一类实践范式用开源可验证的方式把大模型驱动的代码评审能力从 IDE 插件和 SaaS 界面里解放出来塞进开发者每天敲git commit之后、git push之前那个最自然的间隙里。我从去年底开始在三个不同规模的团队中推动这件事一个 8 人初创团队用它替代了原本每周两小时的线下 PR 同步会一个 40 人中台部门把它嵌入 CI 流水线在合并前自动触发多模型交叉比对还有一个外包交付团队直接把它做成客户验收前的“代码健康报告生成器”附在交付包里作为技术信任背书。它们用的不是同一套代码但核心逻辑惊人一致不依赖闭源 API、不绑定特定模型、不绕过本地开发环境、不增加额外审批节点。关键词里的 “open-code-review” 是目标形态“code review” 是问题域“LLM Agent” 是能力载体“CLI” 是交付界面“NPE”Null Pointer Exception则是我们反复验证时最常被漏掉的典型缺陷类型——它恰好暴露了传统静态扫描和纯人工评审的双重盲区。而那些热搜词比如 “codex cli”、“trae cli”、“claude code cli”本质上都是开发者在混沌中摸索这个范式的脚印有人想调用 Codex 的旧接口有人在折腾 Trae 的本地 embedding 服务还有人卡在飞书机器人权限配置上……这些不是碎片而是同一张拼图的不同边角。这篇文章不教你如何安装某个叫open-code-review的命令行工具因为它目前不存在而是带你亲手搭出属于你团队的那套流程。它会覆盖为什么必须放弃“一键接入大模型 API”的捷径哪些环节必须本地化、哪些可以云化如何让 LLM 不只是说“这里有 bug”而是能准确定位到src/utils/date.ts:42并给出可 patch 的修复建议以及最关键的一点——当你的 junior 开发者第一次看到模型指出他写的if (user?.profile?.name)可能因user.profile为 null 而崩溃时该怎么让他信服而不是回一句“这 IDE 早就标红了”。适合谁读如果你是技术负责人正被“AI 代码审查到底该自建还是买 SaaS”困扰如果你是资深工程师厌倦了在 ChatGPT 窗口里粘贴 300 行代码再手动整理反馈如果你是 DevOps 工程师发现现有 SonarQube 规则越来越难覆盖 TypeScript 的联合类型边界甚至如果你是刚转行的前端正试图理解为什么agent、LLM、embedding这些词总在同一个上下文里出现——这篇文章的每一步都来自真实产线上的锤炼不是理论推演更不是 Demo 演示。2. 核心设计思路为什么拒绝“开箱即用”坚持“手搓工作流”2.1 本质矛盾SaaS 化代码审查工具的三大不可解瓶颈市面上所有标榜“AI 代码审查”的 SaaS 产品无论宣传多么炫酷最终都撞在三堵墙上。我带团队试过 7 款主流工具包括两个头部厂商的私有化部署版本结论很明确它们解决的是“有没有 AI”而不是“AI 是否真正融入开发者的肌肉记忆”。第一堵墙是上下文断层。SaaS 工具要求你上传代码片段或 PR 链接但它永远看不到你本地node_modules的实际版本、.env.local里的调试开关、甚至tsconfig.json中skipLibCheck: true这个关键配置。去年我们有个紧急修复模型在 SaaS 界面里反复提示“useEffect依赖数组缺失”而实际代码里早已用eslint-plugin-react-hooks的exhaustive-deps规则锁死——问题根本不在代码而在工具无法读取 ESLint 配置。它只能基于通用规则做判断而你的项目有且仅有一套真实规则。第二堵墙是反馈延迟与动作割裂。最典型的场景开发者写完代码IDE 里点“提交”SaaS 工具的反馈要等 15~45 秒取决于网络和队列然后弹窗显示“发现 3 处潜在问题”但点击“查看详情”却跳转到另一个 Web 页面里面没有“一键修复”按钮也没有“忽略此问题并记住我的选择”选项。结果就是——92% 的工程师选择直接关掉弹窗继续点“推送”。不是他们不重视质量而是这个动作打断了他们脑内“写-测-提”的流畅节奏。第三堵墙是责任归属模糊。当 SaaS 工具标记某段代码“可能存在安全风险”但团队安全规范明确要求“所有 SQL 查询必须通过 ORM 层”而工具却因为没看到你封装的db.query()方法误判原生mysql2调用为高危。这时谁来负责是工具厂商是安全团队还是写代码的工程师我们曾因此在一次线上事故复盘会上争论了 40 分钟最后发现根源是工具把knex(users).where(id, req.params.id)识别为“未参数化查询”——它压根没解析 Knex 的 query builder AST。提示这三个问题恰恰是open-code-review范式要根治的靶心。它不追求“更快的 API 响应”而追求“零感知的上下文继承”不堆砌“更多检测规则”而专注“每个反馈都能立刻转化为一行git add -p的操作”。2.2 架构选型逻辑为什么 CLI 是唯一合理的入口点很多人看到“CLI”就想到“命令行很反人类”但恰恰相反——CLI 是当前技术栈里唯一能同时满足“零侵入”、“全上下文”、“可审计”、“可复现”四大要求的界面。零侵入不需要修改 IDE 配置、不强制安装插件、不劫持git commit钩子初期可选。开发者只需在终端输入ocr review --pr 123它就自动拉取 GitHub PR 的 diff连同本地package.json、tsconfig.json、.eslintrc.js一起打包分析。整个过程不改变任何现有工作流。全上下文CLI 进程天然运行在开发者本地环境。它能直接读取process.env、执行npx tsc --noEmit --watch获取实时类型信息、调用git show HEAD:src/config.ts获取历史配置。去年我们处理一个 React Server Components 的 hydration 错误模型需要知道NEXT_PUBLIC_API_URL在构建时是否被注入这个值只有在本地next build时才确定——SaaS 工具永远拿不到。可审计每次执行ocr review都会生成一个review-report-20240521-1423.json文件里面包含完整的输入 diff、调用的模型名称、temperature 参数、原始输出文本、以及最终提炼的结构化问题列表。法务团队要求“所有 AI 辅助决策必须留痕”这个 JSON 就是证据。可复现当某次评审漏掉了一个 NPE你可以用完全相同的命令、相同的 commit hash、相同的模型版本重新跑一遍精准定位是 prompt 写错了还是 embedding 向量没更新。而 SaaS 工具的“重试”按钮背后可能是另一台服务器、另一个缓存状态、甚至另一个模型微调版本。注意这里说的 CLI 不是curl https://api.xxx.com/review的简单封装。真正的open-code-reviewCLI 必须具备“本地推理能力”或“本地代理能力”。比如它应该能自动检测你本地是否安装了 Ollama并优先调用ollama run deepseek-coder:6.7b如果没有则降级到curl调用你私有部署的 vLLM 服务再不行才 fallback 到 OpenAI API。这种弹性是任何 SaaS 工具无法提供的。2.3 模型层设计为什么必须区分 LLM、Agent、Embedding且不能混用热搜词里反复出现的 “agent 和 llm 和 ai模型 有什么区别”、“deepseek是属于哪个”暴露出一个普遍误解把 LLM 当成万能黑盒以为换个模型名就能解决所有问题。实际上在代码评审场景里这三个角色分工明确强行合并只会让效果断崖下跌。LLM大语言模型是“分析师”它负责阅读代码、理解意图、识别模式。比如输入一段 TypeScript 的mapStateToProps函数它要能判断出“这里把user.id直接赋给 prop但user可能为 undefined”并生成自然语言描述。DeepSeek-Coder、CodeLlama、Qwen2.5-Coder 都属于这一层。它们强在代码理解弱在长期记忆和工具调用。Agent智能体是“项目经理”它不直接写代码而是规划任务、调用工具、验证结果。比如当 LLM 指出“data.items.map()可能因data.items为 null 报错”Agent 就要① 调用grep -r data.items src/查找所有调用点② 对每个调用点用ast-grep检查data的初始化位置③ 如果发现data来自fetch响应就调用curl请求 mock API 获取真实响应结构④ 最后汇总所有证据决定是否标记为高危。LangChain、LlamaIndex 的 Agent 框架就是干这个的。Embedding嵌入模型是“档案管理员”它把代码片段、文档、历史 issue 转换成向量存入本地向量库如 ChromaDB。当 LLM 看到axios.get(/api/users)Embedding 模型会从向量库中召回“过去 3 个月所有关于/api/users接口的错误日志、性能监控截图、以及相关 PR 的评审意见”把这些上下文喂给 LLM让它判断“这次调用是否复现了已知的超时问题”。BGE-M3、text-embedding-3-small 就是这类模型。实操心得我们最初犯的最大错误就是用 DeepSeek-Coder 7B 直接处理 2000 行 diff。结果它花了 83 秒输出一堆泛泛而谈的“建议添加类型注解”却漏掉了最关键的res.data?.items?.[0]?.name的 NPE 风险。后来拆解成先用 BGE-M3 对 diff 做语义切片只保留涉及res.data的 12 行再用 Agent 调用ast-grep提取所有?.操作符位置最后把这 12 行 3 个位置坐标喂给 DeepSeek-Coder 1.5B轻量版。整个过程 4.2 秒精准定位 2 处 NPE并给出res.data?.items?.[0]?.name ?? 的修复建议。模型不是越大越好而是越专越准。3. 核心环节实现从零搭建可运行的 open-code-review 工作流3.1 环境准备最小可行依赖与版本锁定策略别急着pip install open-code-review——目前没有这个包。我们要从最基础的组件开始组装。整个工作流的核心依赖只有 4 个全部开源、可审计、无闭源 SDKOllamav0.3.1本地模型运行时。它解决了“模型下载、GPU 分配、HTTP API 暴露”三大痛点。我们不用自己写 CUDA 调度Ollama 已经做好了。重点必须用--gpu all启动否则 7B 模型推理速度会从 12 tokens/s 降到 1.8 tokens/s。ast-grepv0.25.0代码结构化搜索工具。它不是正则而是基于 AST 的精确匹配。比如搜索?.操作符正则会匹配到注释里的// foo?.bar而 ast-grep 只匹配真实语法节点。安装命令brew install ast-grepMac或cargo install ast-grepLinux/Windows。ChromaDBv0.4.24轻量向量数据库。它不依赖外部服务单文件存储./chroma.db启动即用。我们用它存两类向量① 项目 README.md 的 embedding② 所有 closed PR 的 diff embedding。这样当新 PR 出现时能快速召回“类似变更的历史评审意见”。jqv1.6JSON 处理瑞士军刀。所有模型输出、API 响应、配置文件最终都要变成结构化 JSON。jq是唯一能可靠处理嵌套数组、条件过滤、字段重命名的命令行工具。别用 Python 脚本替代它会拖慢整个流水线。提示版本锁定不是为了“稳定”而是为了“可复现”。我们在requirements.txt里写死ollama0.3.1是因为 v0.3.2 修复了一个 Windows 下 GPU 内存泄漏 bug但同时引入了新的 context length 计算偏差。如果团队成员版本不一致同样的ocr review命令可能在 A 机器上成功在 B 机器上因 context overflow 失败。所以make setup脚本的第一行必须是curl -fsSL https://ollama.com/install.sh | sh -s -- -v 0.3.1。3.2 CLI 主程序用 Bash 实现的极简但健壮的调度器很多人觉得 CLI 必须用 Go 或 Rust 写才专业但我们用 Bash 实现了核心调度器原因很实在Bash 是唯一能无缝调用git、curl、jq、ollama的语言且无需编译、无需安装 runtime。以下是ocr命令的核心骨架已脱敏可直接复制使用#!/usr/bin/env bash # File: /usr/local/bin/ocr set -euo pipefail # 关键开启严格模式任何命令失败立即退出 # 解析参数 PR_ID MODELdeepseek-coder:6.7b while [[ $# -gt 0 ]]; do case $1 in --pr) PR_ID$2 shift 2 ;; --model) MODEL$2 shift 2 ;; *) echo Usage: ocr --pr PR_NUMBER [--model MODEL_NAME] exit 1 ;; esac done # 步骤1拉取 PR diff带完整上下文 echo Fetching PR #$PR_ID diff... PR_DIFF$(curl -s -H Authorization: token $GITHUB_TOKEN \ https://api.github.com/repos/$GITHUB_REPO/pulls/$PR_ID | \ jq -r .diff_url | xargs curl -s) # 步骤2提取关键代码片段用 ast-grep 精准定位 echo ✂️ Extracting critical code sections... CRITICAL_SECTIONS$(echo $PR_DIFF | \ ast-grep --lang ts --pattern $A?.[$B] --json | \ jq -r map({file: .file, line: .start.line, code: .snippet})) # 步骤3生成 embedding 并召回历史相似 PR echo Building context from vector DB... HISTORY_CONTEXT$(echo $CRITICAL_SECTIONS | \ python3 -c import sys, json, chromadb client chromadb.PersistentClient(path./chroma.db) col client.get_or_create_collection(pr_history) # ... embedding logic ... print(json.dumps(context)) ) # 步骤4构造 prompt 并调用本地模型 PROMPT$(cat EOF 你是一名资深 TypeScript 工程师正在评审以下代码变更 \\\ $PR_DIFF \\\ 关键风险点 $(echo $CRITICAL_SECTIONS | jq -r .[] | - \(.file):\(.line) \(.code)) 历史参考 $(echo $HISTORY_CONTEXT | jq -r .[] | - \(.title): \(.summary)) 请严格按以下格式输出 ### NPE 风险 - [ ] file.ts:123 user?.profile?.name → user.profile 可能为 null - [x] utils.ts:45 data?.items?.[0]?.id → data.items 已在第 22 行校验非空 ### 类型安全建议 - [ ] 添加 | null 到 getUserProfile() 返回类型 EOF ) # 步骤5调用 Ollama API自动检测本地服务是否运行 if ! nc -z localhost 11434; then echo ⚠️ Ollama not running. Starting now... nohup ollama serve /dev/null 21 sleep 3 fi echo Running LLM analysis... RESPONSE$(curl -s -X POST http://localhost:11434/api/chat \ -H Content-Type: application/json \ -d {\model\:\$MODEL\,\messages\:[{\role\:\user\,\content\:\$PROMPT\}]}) # 步骤6结构化解析输出用 jq 提取 markdown 列表 echo $RESPONSE | jq -r .message.content | \ grep -E ^\s*-\s*\[.\]\s* | \ sed s/^\s*-\s*\[\(.\)\]\s*/\1 / review-report-$(date %Y%m%d-%H%M).md echo ✅ Report saved to review-report-$(date %Y%m%d-%H%M).md实操心得这段 Bash 看似简单但每一行都踩过坑。比如set -euo pipefail没有它当curl失败时脚本会继续执行导致后续jq处理空字符串报错最终生成一个空报告。再比如nc -z localhost 11434检测 Ollama我们试过用ps aux | grep ollama但 Docker 容器里进程名不同检测失效。而端口检测100% 可靠。还有grep -E ^\s*-\s*\[.\]\s*这行正则专门为了匹配模型输出的 markdown 列表它能过滤掉 LLM 生成的解释性文字只留下可执行的检查项。这些细节才是工作流真正“稳”的原因。3.3 Prompt 工程让 LLM 从“胡说八道”到“精准指路”的关键配方Prompt 不是“多写几句话”而是定义 LLM 的角色、约束它的输出格式、并提供足够多的领域知识锚点。我们测试了 37 个不同 prompt 版本最终收敛到一个 5 层结构缺一不可角色定义层Role Definition你是一名有 8 年经验的 TypeScript 工程师专注于金融级系统开发。你从不猜测只基于代码事实和项目规范发言。为什么有效避免 LLM 用“可能”、“建议”、“通常”等模糊词汇。在金融系统里“可能为空”必须变成“此处未做 null check运行时必 crash”。输入约束层Input Constraints你只能看到以下内容① PR 的完整 diff② 该 diff 中所有?.操作符的具体位置和上下文代码③ 过去 30 天内所有 closed PR 的标题和摘要。禁止访问任何外部文档、API 或互联网。为什么有效强制 LLM 放弃“我知道 React 18 的 rules of hooks”这类通用知识专注当前代码。我们发现去掉这行LLM 会 42% 的概率给出“请升级 React”这种无关建议。输出格式层Output Format严格按以下 JSON Schema 输出不得添加任何额外字段、注释或说明 { npe_risks: [{ file: string, line: number, code: string, fix_suggestion: string }], type_suggestions: [...] }为什么有效JSON 是唯一能被jq100% 可靠解析的格式。Markdown 列表会被换行、缩进、emoji 破坏结构纯文本会被标点符号干扰。只有 JSON能让下游自动化脚本比如自动生成 patch稳定运行。领域知识锚点层Domain Anchors本项目规范① 所有 API 响应必须用zodschema 校验②user对象在进入业务逻辑前必须经过validateUser()函数③utils/date.ts中的parseDate()函数返回Date | null。为什么有效这些是 LLM 无法从 diff 里推断的隐含规则。没有它模型会把parseDate(input)的返回值默认当成Date从而漏掉null分支。负向示例层Negative Examples错误示范禁止“考虑添加类型注解” → 太模糊不指明文件和行号“这里可能有性能问题” → 未提供 benchmark 数据支撑正确示范必须“src/api/user.ts:87res.data?.items?.[0]?.name→ res.data.items 未校验运行时抛 NPE建议改为res.data?.items?.[0]?.name ?? ”为什么有效LLM 对“错误示范”的学习效率远高于“正确示范”。我们实测加入负向示例后模糊建议率从 68% 降到 9%。注意这个 prompt 不是写完就扔进代码里。它必须和模型版本强绑定。比如 DeepSeek-Coder 6.7B 的最佳 temperature 是 0.3而 Qwen2.5-Coder 7B 需要 0.1。我们用prompt_registry.json统一管理{ deepseek-coder:6.7b: { temperature: 0.3, top_p: 0.85, prompt: ... }, qwen2.5-coder:7b: { temperature: 0.1, top_p: 0.95, prompt: ... } }3.4 NPE 检测专项优化从“发现”到“修复”的闭环设计NPENull Pointer Exception是open-code-review的首攻目标因为它是静态分析最难覆盖、人工评审最容易忽略、但线上故障率最高的缺陷类型。我们的方案不是“让 LLM 更努力地看代码”而是构建一个三层漏斗逐级收窄风险范围第一层AST 静态筛选精度 99.2%速度 0.03s用ast-grep直接匹配 TypeScript 的可选链表达式?.和空值合并运算符??。命令ast-grep --lang ts --pattern $A?.[$B] --json | jq -r .[] | \(.file):\(.start.line) \(.snippet)这一步能精准抓出所有obj?.prop形式排除// obj?.prop这类注释干扰。我们统计过一个中型项目平均每次 PR 产生 12.7 个?.节点其中 83% 是安全的因为前面有if (obj)校验剩下 17% 需要深度分析。第二层数据流追踪精度 88.5%速度 1.2s对第一层筛选出的每个?.节点用ast-grep反向追踪obj的来源# 追踪 user?.profile?.name 中的 user 来源 ast-grep --lang ts --pattern const user $A --json | \ jq -r .[] | select(.file src/api/user.ts) | \(.start.line)如果user来自fetch响应就标记为高风险如果来自useStateUser(initialUser)且initialUser是非 null 对象则标记为低风险。这一步依赖项目约定所以我们在ocr config里允许用户定义safe_initializers: [useState, useReducer]。第三层LLM 语义验证精度 94.1%速度 2.8s把前两层的结果文件、行号、上下文代码、数据流路径喂给 LLM并在 prompt 里强调请只关注以下三点① obj 的初始化是否可能为 null② obj 的赋值是否经过异步操作如 await fetch③ obj 的使用是否在 try/catch 块内。其他任何问题一律不反馈。这样LLM 的注意力被强制聚焦在 NPE 的三个必要条件上避免它发散到“命名不规范”、“缺少注释”等无关问题。实操心得这个三层漏斗的设计源于我们踩过的一个大坑。早期我们直接让 LLM 分析整个 diff结果它花了 22 秒输出 17 条建议其中 15 条是“建议添加 JSDoc”只有 2 条提到 NPE还都错了。后来改成漏斗式总耗时降到 4.3 秒NPE 检出率从 31% 提升到 94%且 0 条误报。精准永远比“全面”重要。最终ocr review --pr 123的输出里NPE 风险会以这种格式呈现### NPE 高危3处 - src/pages/UserList.tsx:142 users?.[0]?.name → users 来自 useEffect 中的 fetch未校验长度运行时抛错 ✅ 修复建议{users?.length ? users[0].name : 暂无用户} - src/utils/api.ts:88 res.data?.items?.[0]?.id → res.data.items 未做空值校验已在 PR #119 修复过同类问题 历史参考PR #119 “修复 /api/items 接口空数组 NPE”4. 常见问题与排查技巧实录那些文档里不会写的血泪教训4.1 模型输出不稳定先检查这 3 个隐藏开关LLM 输出“忽好忽坏”90% 的情况不是模型问题而是环境变量没设对。我们整理了最常被忽略的 3 个配置OLLAMA_NUM_GPU必须显式设置即使你只有一块 GPU也必须运行export OLLAMA_NUM_GPU1。Ollama 默认不启用 GPU 加速即使检测到 CUDA。我们有个团队在 AWS g4dn.xlarge1x T4上跑了 3 天一直以为模型太小直到发现ollama list显示STATUS: downloading而nvidia-smi显示 GPU 利用率为 0。加了这行环境变量推理速度从 4.2s 降到 0.8s。OLLAMA_NO_CUDA必须为 false这个变量默认是true很多教程没提导致你明明装了 CUDAOllama 却用 CPU 跑。检查方法ollama show deepseek-coder:6.7b | grep cuda如果输出cuda: false立刻执行export OLLAMA_NO_CUDAfalse。OLLAMA_KEEP_ALIVE必须设为5m默认值是5s意味着模型加载后 5 秒没请求就卸载。而ocr review的完整流程拉 diff → AST 分析 → embedding → LLM 调用通常要 6~8 秒。结果就是每次调用都重新加载模型白白浪费 3.2 秒。设为5m模型常驻内存后续调用秒级响应。提示把这些 export 命令写进~/.bashrc是下策。上策是写进ocr脚本开头export OLLAMA_NUM_GPU1 export OLLAMA_NO_CUDAfalse export OLLAMA_KEEP_ALIVE5m4.2 为什么ast-grep总是找不到我的代码模式ast-grep的 pattern 语法和正则完全不同新手最容易犯两个错误错误1用正则思维写 pattern想匹配user?.profile?.name写成user\?\.\[profile\]\?\.\[name\]。这是错的ast-grep的?.是语法节点不是字符。正确写法是ast-grep --lang ts --pattern $A?.[$B]?.[$C]$A、$B、$C是占位符代表任意标识符。错误2忽略语言模式language modeast-grep默认是 JavaScript 模式但 TypeScript 有额外语法如as const、satisfies。必须加--lang ts否则user as User这样的断言会被解析失败。我们有个 PR 因为漏了--lang ts导致ast-grep没识别出data as ApiResponse进而认为data.items是any类型漏掉了 NPE。实操技巧用ast-grep --lang ts --debug查看 AST 结构。比如对const user api.getUser();运行ast-grep --lang ts --debug --pattern const $A $B();它会输出完整的 AST 树让你看清$B对应的是api.getUser还是api.getUser()节点。4.3 ChromaDB 向量召回不准试试这 2 个调优参数向量召回不准不是模型问题而是距离度量方式没选对。ChromaDB 默认用cosine距离但在代码场景里l2欧氏距离效果更好因为代码语义的差异更接近“绝对偏差”而非“方向夹角”。参数1distance_functionl2创建 collection 时必须指定client.create_collection( namepr_history, embedding_functionembedding_func, metadata{hnsw:space: l2} # 关键 )参数2n_results5不要贪多我们测试过n_results1到20发现5是最佳平衡点。n_results1可能召回一个不相关的 PR比如都改了package.jsonn_results20会把噪声全拉进来稀释真正相关的信号。5个结果足够覆盖“相同接口”、“相同模块”、“相同错误类型”三个维度。注意向量库必须定期更新。我们用cron每天凌晨 2 点执行# 更新 PR 历史向量 git log --merges --oneline -n 50 | while read line; do PR_ID$(echo $line | awk {print $2} | sed s/#//) if [ -n $PR_ID ]; then ocr embed-pr --pr $PR_ID fi done4.4 如何让 junior 开发者真正接受 AI 评审最大的阻力从来不是技术而是人心。我们总结出 3 条铁律永远不替代人工只辅助人工ocr review的输出里每一条建议后面都必须跟一句 人工确认请检查 src/api/user.ts 第 88 行确认 res.data.items 是否可能为空。我们甚至在ocr脚本里硬编码了一行echo 请打开 VS Code跳转到上述文件和行号手动验证。目的只有一个让开发者保持“决策者”身份AI 只是“情报员”。首次使用必须由 Tech Lead 亲自演示我们规定任何新成员入职第一周Tech Lead 必须用他的电脑现场运行ocr review --pr 1并把输出和 IDE 里的实际代码并排展示。重点演示“看模型指出这里可能 NPE我们点进去果然res.data是any类型而items字段没做校验——这就是它为什么能发现而 ESLint 不能。” 眼见为实胜过千言万语。建立“AI 评审贡献榜”每周五邮件