AI 改了代码,你怎么验收?用 Git Diff 生成一份变更审查清单 摘要AI 编程工具能帮你改代码但改完之后最关键的不是“看起来能跑”而是如何验收。本文给出一套基于 Git Diff 的审查流程和可运行 Python 脚本自动生成AI_CHANGE_REVIEW.md把改动文件、风险等级、测试建议、人工 Review 和回滚确认整理成清单。正文AI 编程工具越强开发者越容易遇到一个新问题它确实把代码改完了但我怎么知道这次改动能不能合并以前我们让 AI 写一个函数、补一段 SQL、改一个正则最多肉眼看几行代码。现在不一样了。一次任务可能会改3 个接口文件2 个类型定义1 个配置文件1 份测试文件甚至顺手改了 README、锁文件、CI 配置。OpenAI Codex 的官方更新说明里已经出现了在对话中创建、搜索、打开、fork 和管理 Codex tasks 的能力也加入了 staged、unstaged、branch、last-turn changes 等变更过滤与分支比较控制。换句话说AI 编程工具正在从“写一段代码”进入“管理一个任务的完整改动”阶段。来源https://developers.openai.com/codex/changelog这时候开发者最不该做的一件事是AI 说改好了 → 随便跑一下 → git add . → git commit → 合并真正稳的流程应该是AI 完成改动 → 先看 Git Diff → 自动生成变更审查清单 → 找出高风险文件 → 跑最小必要测试 → 人工 Review → 确认回滚方案 → 再提交这篇文章给一套可以直接落地的做法用 Git Diff 生成一份AI_CHANGE_REVIEW.md。它不替你 Review 代码。它帮你把“该 Review 什么”先列出来。一、AI 改代码最怕的不是改错而是你不知道它改了哪里很多人让 AI 改代码时关注点只放在最终回答已完成。 已修复。 已优化。 已补充测试。但真正应该看的是 Git 里发生了什么。Git 官方文档对git diff的定位很直接它可以展示工作区与暂存区、暂存区与提交、两个提交之间等不同对象的变化。也就是说AI 改完代码后git diff才是你确认真实改动范围的第一手依据。来源https://git-scm.com/docs/git-diff尤其是下面几类情况情况为什么危险AI 顺手改了无关文件可能引入额外风险AI 改了锁文件可能影响依赖版本AI 改了权限逻辑可能产生越权问题AI 改了数据库迁移可能影响线上数据AI 删除了旧兼容逻辑可能破坏调用方AI 只改代码没补测试后续回归容易漏AI 改了 CI/CD 配置可能影响部署流程还有一种情况很常见同一个文件可能同时存在 staged 和 unstaged 两种状态。Codex App 的 Review 文档也提醒过Git 可以在同一文件里同时表示 staged 和 unstaged changes这种情况下看起来像“同一个文件出现两次”这是正常 Git 行为。来源https://developers.openai.com/codex/app/review所以AI 代码验收的第一步不是问 AI你确定改好了吗而是问 Git这次到底改了哪些文件每个文件改了多少有没有明显风险二、这份审查清单要解决什么问题AI_CHANGE_REVIEW.md不是代码审查报告也不是自动测试报告。它的定位更像一个“合并前检查单”。它至少要回答 5 个问题1. 这次改了哪些文件 2. 每个文件是新增、修改、删除还是重命名 3. 哪些文件属于高风险 4. 每类文件建议跑什么验证 5. 合并前还有哪些人工确认项例如 AI 说我已经修复了用户登录失败的问题。你真正要看的不是这句话而是M src/auth/login.ts M src/middleware/permission.ts M src/types/user.ts A src/auth/login.test.ts M package-lock.json这组改动里auth、permission、package-lock.json都值得重点看。不是说它一定错而是这几个位置本来就风险更高。三、先准备一个干净的验收流程在跑脚本前建议先养成这个习惯gitstatus如果工作区已经有你自己未提交的改动先别急着让 AI 动手。更稳的做法gitcheckout-bai-fix-login-timeout让 AI 在独立分支里改。改完后再执行gitdiff--name-status HEADgitdiff--numstatHEADgitdiff--checkHEAD这里三个命令各有作用。命令作用git diff --name-status HEAD看哪些文件被新增、修改、删除、重命名git diff --numstat HEAD看每个文件增加和删除了多少行git diff --check HEAD检查空白符错误、冲突标记等问题Git 文档说明--numstat会以更适合机器读取的方式输出新增行、删除行和路径--name-only/--name-status则适合快速获取变更文件名称和状态。来源https://git-scm.com/docs/git-diff这三个命令就够我们生成第一版审查清单。四、可直接运行生成AI_CHANGE_REVIEW.md的 Python 脚本新建文件generate_ai_change_review.py粘贴下面代码#!/usr/bin/env python3from__future__importannotationsimportargparseimportsubprocessfromdataclassesimportdataclassfrompathlibimportPath HIGH_RISK_KEYWORDS(auth,permission,role,security,payment,order,checkout,refund,migration,schema,database,db,.github/workflows,dockerfile,docker-compose,nginx,package-lock.json,pnpm-lock.yaml,yarn.lock,poetry.lock,requirements.txt,.env,)TEST_HINTS{.py:运行单元测试、类型检查或关键脚本pytest / mypy / ruff,.ts:运行类型检查和测试npm test / pnpm test / tsc --noEmit,.tsx:运行组件测试、类型检查和构建pnpm test / pnpm build,.js:运行单元测试、lint 和构建npm test / npm run lint,.jsx:运行组件测试、lint 和构建npm test / npm run build,.java:运行 Maven/Gradle 测试mvn test / gradle test,.go:运行 Go 测试go test ./...,.rs:运行 Rust 测试cargo test / cargo clippy,.sql:在测试库验证迁移、回滚和数据兼容性,.md:检查文档是否和代码行为一致,.json:检查配置格式、schema、锁文件和依赖变更,.yml:检查 CI/CD、部署、权限和环境变量引用,.yaml:检查 CI/CD、部署、权限和环境变量引用,}dataclassclassChangedFile:status:strpath:stradded:str0deleted:str0propertydeftotal_changed(self)-int:try:returnint(self.added)int(self.deleted)exceptValueError:return0defrun_git(args:list[str],allow_fail:boolFalse)-tuple[int,str]:resultsubprocess.run([git,*args],textTrue,stdoutsubprocess.PIPE,stderrsubprocess.STDOUT,checkFalse,)ifresult.returncode!0andnotallow_fail:raiseSystemExit(result.stdout.strip())returnresult.returncode,result.stdout.strip()defensure_git_repo()-None:code,_run_git([rev-parse,--is-inside-work-tree],allow_failTrue)ifcode!0:raiseSystemExit(当前目录不是 Git 仓库请在项目根目录执行。)defdiff_args(base:str,staged:bool)-list[str]:args[diff,--find-renames]ifstaged:args.append(--cached)args.append(base)returnargsdefparse_name_status(raw:str)-dict[str,ChangedFile]:files:dict[str,ChangedFile]{}forlineinraw.splitlines():ifnotline.strip():continuepartsline.split(\t)statusparts[0]pathparts[-1]files[path]ChangedFile(statusstatus,pathpath)returnfilesdefattach_numstat(files:dict[str,ChangedFile],raw:str)-None:forlineinraw.splitlines():ifnotline.strip():continuepartsline.split(\t)iflen(parts)3:continueadded,deleted,pathparts[0],parts[1],parts[-1]itemfiles.get(path)ifitemisNone:itemChangedFile(status?,pathpath)files[path]item item.addedadded item.deleteddeleteddefattach_untracked(files:dict[str,ChangedFile])-None:_,rawrun_git([ls-files,--others,--exclude-standard])forpathinraw.splitlines():ifnotpath.strip():continueifpathnotinfiles:files[path]ChangedFile(status??,pathpath,added?,deleted0,)defrisk_level(item:ChangedFile)-str:path_loweritem.path.lower()ifany(keywordinpath_lowerforkeywordinHIGH_RISK_KEYWORDS):return高ifitem.total_changed250:return高ifitem.total_changed80:return中ifitem.status.startswith((D,R)):return中return低defreview_focus(item:ChangedFile)-str:pathitem.path suffixPath(path).suffix.lower()lowerpath.lower()focuses:list[str][]iftestnotinlowerandsuffixin{.py,.js,.ts,.tsx,.java,.go,.rs}:focuses.append(确认是否需要新增或更新测试)ifany(wordinlowerforwordin(auth,permission,role,login)):focuses.append(重点检查鉴权、权限边界和越权风险)ifany(wordinlowerforwordin(payment,order,refund,checkout)):focuses.append(重点检查金额、幂等、状态流转和回滚方案)ifany(wordinlowerforwordin(migration,schema,.sql)):focuses.append(重点检查迁移顺序、兼容性和回滚脚本)ifsuffixinTEST_HINTS:focuses.append(TEST_HINTS[suffix])else:focuses.append(根据文件职责补充人工 Review 和验证命令)return.join(dict.fromkeys(focuses))defmarkdown_report(base:str,staged:bool,files:list[ChangedFile],check_output:str,)-str:lines[# AI_CHANGE_REVIEW,,## 1. 基础信息,f- Diff 基准{base},f- Diff 范围{staged changesifstagedelseworking tree / current branch changes},f- 变更文件数{len(files)},,## 2. 变更总览,,| 风险 | 状态 | 文件 | 增加 | 删除 | 审查重点 |,|---|---|---|---:|---:|---|,]foriteminfiles:lines.append(f|{risk_level(item)}| {item.status} | {item.path} | f{item.added}|{item.deleted}|{review_focus(item)}|)lines.extend([,## 3. Git diff --check 结果,,])ifcheck_output:lines.extend([text,check_output,])else:lines.append(- 未发现 Git 可直接识别的空白符或冲突标记问题。)lines.extend([,## 4. 人工验收清单,,- [ ] 是否只改了本次任务要求的文件,- [ ] 是否存在未解释的大范围重构、删除或格式化,- [ ] 高风险文件是否已单独 Review,- [ ] 是否补充或更新了对应测试,- [ ] 是否跑过最小必要验证命令,- [ ] 是否检查了接口、配置、数据库、权限和日志影响,- [ ] 是否准备了回滚方式,,## 5. 给 AI 的二次审查提示词,,text,下面是 AI 生成代码后的 Git Diff 审查清单。,请不要继续改代码先只做审查。,,请按以下格式输出,1. 你认为最需要人工 Review 的 3 个文件,2. 每个高风险文件的风险原因,3. 可能遗漏的测试,4. 可能影响的调用方、配置或数据,5. 建议先运行的验证命令,6. 不确定的信息请标记为“待确认”不要编造业务规则。,,,])return\n.join(lines)defmain()-None:parserargparse.ArgumentParser(descriptionGenerate an AI code-change review checklist from Git diff.)parser.add_argument(--base,defaultHEAD,helpDiff base, such as HEAD, main, origin/main.,)parser.add_argument(--staged,actionstore_true,helpReview staged changes only.,)parser.add_argument(--output,defaultAI_CHANGE_REVIEW.md,helpOutput markdown file.,)argsparser.parse_args()ensure_git_repo()base_argsdiff_args(args.base,args.staged)_,name_statusrun_git([*base_args,--name-status])_,numstatrun_git([*base_args,--numstat])_,check_outputrun_git([*base_args,--check],allow_failTrue)filesparse_name_status(name_status)attach_numstat(files,numstat)ifnotargs.staged:attach_untracked(files)reportmarkdown_report(baseargs.base,stagedargs.staged,filessorted(files.values(),keylambdaitem:(risk_level(item),item.path),),check_outputcheck_output,)Path(args.output).write_text(report,encodingutf-8)print(f已生成{args.output})if__name____main__:main()这段脚本只依赖 Python 标准库和本机 Git。我用一个临时 Git 仓库验证过它可以识别已修改文件也能把未追踪的新文件纳入清单例如 AI 新建了auth_service.py但还没git add脚本会显示为??避免新文件被漏掉。五、运行方式在项目根目录执行python generate_ai_change_review.py--baseHEAD如果你只想审查已经暂存的改动python generate_ai_change_review.py--baseHEAD--staged如果你想比较当前分支和主分支python generate_ai_change_review.py--basemain输出文件默认是AI_CHANGE_REVIEW.md如果想换文件名python generate_ai_change_review.py\--basemain\--outputREVIEW_BEFORE_MERGE.md六、输出大概长什么样假设 AI 改完代码后项目里有这些变化M src/auth/login.ts M src/middleware/permission.ts A src/auth/login.test.ts M package-lock.json ?? scripts/debug_temp.py生成的AI_CHANGE_REVIEW.md可能类似这样# AI_CHANGE_REVIEW ## 1. 基础信息 - Diff 基准HEAD - Diff 范围working tree / current branch changes - 变更文件数5 ## 2. 变更总览 | 风险 | 状态 | 文件 | 增加 | 删除 | 审查重点 | |---|---|---|---:|---:|---| | 高 | M | src/auth/login.ts | 38 | 12 | 确认是否需要新增或更新测试重点检查鉴权、权限边界和越权风险运行类型检查和测试npm test / pnpm test / tsc --noEmit | | 高 | M | src/middleware/permission.ts | 22 | 8 | 确认是否需要新增或更新测试重点检查鉴权、权限边界和越权风险运行类型检查和测试npm test / pnpm test / tsc --noEmit | | 高 | M | package-lock.json | 120 | 40 | 检查配置格式、schema、锁文件和依赖变更 | | 低 | A | src/auth/login.test.ts | 96 | 0 | 运行类型检查和测试npm test / pnpm test / tsc --noEmit | | 低 | ?? | scripts/debug_temp.py | ? | 0 | 确认是否需要新增或更新测试运行单元测试、类型检查或关键脚本pytest / mypy / ruff | ## 3. Git diff --check 结果 - 未发现 Git 可直接识别的空白符或冲突标记问题。 ## 4. 人工验收清单 - [ ] 是否只改了本次任务要求的文件 - [ ] 是否存在未解释的大范围重构、删除或格式化 - [ ] 高风险文件是否已单独 Review - [ ] 是否补充或更新了对应测试 - [ ] 是否跑过最小必要验证命令 - [ ] 是否检查了接口、配置、数据库、权限和日志影响 - [ ] 是否准备了回滚方式这份清单最大的价值不是告诉你“代码对不对”。而是提醒你哪些文件不能一眼扫过去必须重点看。七、风险分级不要迷信但要有默认规则上面脚本里的风险分级很简单权限、鉴权、安全、支付、订单、数据库、迁移、CI/CD、依赖锁文件 → 高风险 变更行数超过一定范围 → 中高风险 删除、重命名 → 中风险 普通小文件改动 → 低风险这不是绝对标准。不同项目应该按自己的情况调整。比如一个内容站项目里Markdown 文档可能是低风险但如果你做的是文档驱动 API 平台文档变化反而可能影响 SDK、页面、接口示例和客户集成。再比如package-lock.json有时候只是正常安装依赖后的锁文件更新有时候却意味着依赖树大面积变化甚至引入安全风险。所以风险等级不是结论。它只是提醒你这里要多看一眼。 这里要单独跑测试。 这里要问清楚 AI 为什么改。 这里合并前要准备回滚。八、AI 改完代码后可以这样追问它生成AI_CHANGE_REVIEW.md后不要立刻让 AI 继续改。先让它做二次审查。可以直接把清单和核心 diff 给它然后问下面是 AI 生成代码后的 Git Diff 审查清单。 请不要继续改代码先只做审查。 请按以下格式输出 1. 你认为最需要人工 Review 的 3 个文件 2. 每个高风险文件的风险原因 3. 可能遗漏的测试 4. 可能影响的调用方、配置或数据 5. 建议先运行的验证命令 6. 不确定的信息请标记为“待确认”不要编造业务规则。这段提示词里最关键的是请不要继续改代码先只做审查。很多开发者让 AI 改完之后一发现不放心就继续说你再帮我检查一下有问题就修。结果 AI 又开始改第二轮。第二轮改动叠在第一轮上Diff 更难看问题也更难定位。更稳的做法是先冻结修改让它只分析。如果确实需要第二轮修改也要重新生成一次AI_CHANGE_REVIEW.md。九、建议把验收拆成四层AI 改代码后不能只靠一份脚本。我更建议分成四层验收。1. 文件范围验收先看gitdiff--name-status HEAD确认有没有无关文件。尤其注意.env secrets/ config/ package-lock.json pnpm-lock.yaml yarn.lock docker-compose.yml .github/workflows/ migration/ schema/ auth/ permission/ payment/ order/这些文件不是不能改而是不能“顺手改”。2. 行数和重构范围验收再看gitdiff--numstatHEAD如果一个简单 Bug 修复突然出现800 added, 600 deleted那就要问清楚为什么需要这么大范围变更 是不是格式化了整个文件 是不是把旧逻辑删掉了 是不是顺手重构了无关模块3. 语法和格式验收至少跑gitdiff--checkHEAD再按项目类型跑命令。例如pnpmtestpnpmlintpnpmbuild或者pytest ruff check.mypy.或者gotest./...这些命令不能保证业务正确但能先挡掉一批低级问题。4. 业务风险验收最后才是人工 Review。重点看是否改变接口返回结构是否破坏旧调用方是否绕过权限判断是否影响数据库字段是否遗漏并发和幂等是否改变日志内容是否把敏感信息打印出来是否缺少回滚方案。AI 能帮你整理审查点但不能替你承担上线风险。十、这套方法适合什么场景适合AI 改了多个文件AI 新增了测试AI 改了接口字段AI 调整了权限逻辑AI 补了配置AI 更新了依赖AI 做了小范围重构AI 生成了迁移脚本合并前想快速生成 Review 清单。不适合直接替代人工 Code Review单元测试集成测试安全扫描权限审计数据库迁移演练灰度发布和回滚验证。尤其是支付、权限、登录、订单、数据库迁移、CI/CD 这些位置即使脚本标了“高风险”也只是第一层提醒。真正该做的是单独 Review。十一、一个更稳的 AI 编程工作流我现在更推荐这样的顺序1. 开新分支 2. 让 AI 先输出修改计划 3. 人确认计划 4. AI 小步修改 5. 生成 AI_CHANGE_REVIEW.md 6. 让 AI 只做审查不继续改 7. 人工 Review 高风险文件 8. 跑测试和构建 9. 必要时第二轮小步修改 10. 再生成审查清单 11. 确认回滚方案 12. 提交这套流程看起来比“让 AI 一口气改完”慢一点。但它更适合真实项目。因为真实开发里最贵的不是多看两分钟 Diff。最贵的是把一个没看清楚的 AI 改动合进去等线上报错后再回头找它到底改了哪里。十二、最后总结AI 改代码以后最重要的动作不是立刻合并而是先验收。git diff是事实来源。AI 的解释只是辅助。更稳的顺序是看文件范围 → 看增删行数 → 检查高风险文件 → 跑最小测试 → 让 AI 只做审查 → 人工确认 → 再提交这份AI_CHANGE_REVIEW.md的意义就是把“我大概看了一下”变成“我知道该重点看哪里”。