[控场AI]
· 9 分钟阅读· 4,880 字

AI 代码 Agent 第一个 PR 前:逐 Hunk 评分的安全闸门

AI 代码 Agent 第一个 PR 前:逐 Hunk 评分的安全闸门

一套用评分脚本和「review包」把AI生成补丁关进闸门的可操作流程

本文针对新手在不熟悉代码仓库时直接使用AI Agent生成PR的风险,提出了一套以「闸门」为核心理念的审查流程。核心做法包括:在隔离草稿分支上运行Agent、用四条约束写死prompt、用本地bash脚本按文件数/行数/封锁路径三项硬指标给diff评分、以及由人工逐行亲写REVIEW_PACKET.md。流程强调尺寸评分通过不等于语义通过,每个hunk都必须能被亲口讲清楚,测试命令必须是真实执行过的,回滚命令必须是当天能理解的。对于涉及生产数据、迁移、发布或共享分支的场景,流程明确要求直接跳过而非强行套用。

AI 编程助手能在几分钟内生成一个覆盖面很广的补丁,但流畅的模型回复并不等于安全的代码。尤其当你刚加入某个仓库、还没有建立起对高风险路径的认知时,直接把 Agent 生成的 diff 开成 Pull Request,等于让人替你的盲目信任买单。

本文把这个场景提炼成一套可操作的流程:在请求人工 review 之前,对每一个变更块(hunk)逐一评分,并用一份你能亲自为之辩护的「review 包」作为交付物。核心理念只有一句——这是一道闸门,不是一场演示。

Score Every Hunk Before Your First Agent PR

先守住边界

整个流程的第一原则是:不要让 Agent 直接在你真正的 PR 分支上运行。做法是另开一个草稿分支(scratch branch),让 Agent 在隔离环境中跑,再把结果以三样东西带回来——一个 diff、一份测试记录,以及一行回滚命令(revert line)。

同时要把敏感信息挡在提示词之外:密钥、token、生产配置都不应该出现在 prompt 里。更重要的是,在这一轮里明确禁止 Agent 碰某些路径:

  • CI 工作流文件(.github/workflows/)
  • 锁文件(lockfile)
  • 数据库迁移文件(migrations)

这些路径的改动需要资深工程师审查,而不是新手在上手第一小时的猜测。把它们列入黑名单,是风险控制的第一层防线。

「草稿分支隔离」的底层逻辑来自 Git 的分支模型:任何在独立分支上发生的提交,都不会自动影响主线历史。这意味着即便 Agent 生成了完全错误的代码,你只需要删掉草稿分支,主分支的状态丝毫不受影响。相比之下,如果直接在功能分支上运行 Agent,一旦需要回退,就不得不处理混杂了人工提交和 Agent 提交的历史,git revert 的粒度会变得很难控制。更危险的情形是 Agent 顺手修改了 CI 工作流:.github/workflows/ 下的 YAML 文件一旦被推送到远端,往往会立即触发一次流水线运行,后果可能在你来得及 review 之前就已发生。锁文件和迁移文件则属于另一类风险——它们改动容易、回滚代价极高,前者会影响整个团队的依赖环境,后者一旦在生产数据库上执行就无法简单撤销。把这三类路径列入黑名单,是把「代价不对称」的风险在源头截断。

把提示词写成四条约束

不要给 Agent 讲故事式的长段描述,而是把 prompt 压缩成四条简短约束:工单目标、允许修改的文件、禁止触碰的路径、测试命令。任何跳过其中一条的回复都应被拒绝。

Ticket: reject empty names in parseName.
Allowed: src/parseName.js src/parseName.test.js
Forbidden: .github migrations package-lock.json .env
Test: npm test -- src/parseName.test.js
Output: a patch only. Do not commit.

这段 prompt 要和 review 包一起存进草稿笔记。一旦你改了 prompt,笔记也要同步更新——后来的审查者应当能看到你当初给出的确切要求。约束清晰的 prompt 本身就是降低补丁范围失控的手段。

用本地脚本给 diff 评分

在调用任何模型之前,先把评分脚本准备好。原文给出的 bash 脚本并非一个已执行的 benchmark,而是一份可改造的「新手闸门」提案。它检查三类硬指标:

  • 文件数量是否超过阈值(默认 8 个)
  • 变更行数是否超限(默认 200 行)
  • diff 中是否出现被封锁的路径(workflows、migrations、lock 文件、.env、credentials)
#!/usr/bin/env bash
set -euo pipefail
BASE="${1:-origin/main}"
MAX_FILES="${MAX_FILES:-8}"
MAX_LINES="${MAX_LINES:-200}"

脚本中 `set -euo pipefail` 这行 Bash 选项组合值得单独说明:`-e` 表示任意命令以非零状态退出时脚本立即中止;`-u` 表示引用未定义变量时报错而非静默替换为空字符串;`-o pipefail` 表示管道中任意一段失败即视为整条管道失败。三者合用的效果是:脚本不会在中间某步悄悄出错后继续执行并返回一个虚假的「成功」退出码。这对「闸门脚本」尤其重要——如果统计文件数的 `git diff` 命令本身因为分支名拼错而失败,没有这组选项的脚本仍会继续跑完并输出「PASS」,你会误以为检查通过。`BASE` 变量默认为 `origin/main`,在本地对比时需要确保 `origin/main` 是最新拉取的远端状态,否则行数和文件数的统计基准会偏低,导致超限的 diff 被误判为通过。
# ...统计文件数、增删行数,并对超限或命中封锁路径的情况标记 FAIL

脚本还会校验 REVIEW_PACKET.md 是否存在,以及其中是否包含 Revert 行、Test 行、以及「Unknown hunks: 0」这一行。只要退出码非零,就不要开 PR。

运行方式很简单:

chmod +x scripts/score-agent-diff.sh
MAX_FILES=8 MAX_LINES=200 ./scripts/score-agent-diff.sh origin/main
git diff --stat origin/main...HEAD

但原文反复强调一点:即便脚本通过,也要亲眼读 git diff --stat 的输出。一次重命名可能掩盖一次大规模重写,脚本的尺寸检查未必能发现。闸门的最后一道还是你自己的眼睛。

review 包必须自己写

REVIEW_PACKET.md 要由你亲手逐行写出,绝不能接受 Agent 代写的「笔记」充当你自己的判断。一个最小模板是:

Ticket: ISSUE-123
Test: npm test -- workspace/foo.test.js
Revert: git revert <merge-sha>
Unknown hunks: 0
Risk: parser change only, no schema edit

这里有两条铁律。其一,Test 行写的命令必须是你真实跑过的;如果没跑,就写「Test not run」然后停下——没跑测试就是一次失败的闸门,而不是无关紧要的小瑕疵。其二,Revert 行必须是你今天就能理解的命令;如果回滚需要配合数据回填(data backfill),那就直接终止这个 PR,上手第一小时不该碰这类改动。

八步流程与决策表

原文把整套动作浓缩成八步:抄下工单验收标准 → 建草稿分支 → 在隔离服务器上启动 Agent(不带密钥)→ 限定 prompt 文件范围 → 拉回补丁跑评分脚本 → 跑测试并记录真实结果 → 逐块解释后才把 unknown hunks 设为 0 → 脚本退出码为 0 时才开 PR。

如果 Agent 动了你没点名的文件,或者它的摘要提到了 diff 里不存在的文件,立刻停下——这类不一致说明 review 包还没准备好。

决策表则把常见信号映射到动作:

信号评分你的动作
文件数 ≤ 8通过继续到测试环节
变更行数 > 200失败拆分补丁或丢弃
diff 含封锁路径失败重置草稿分支
测试不是你亲自跑的失败跑一遍,否则别开 PR
unknown hunk 数 > 0失败重读或删掉该块
无法解释的 revert 行失败写出真正的回滚或停止
新增依赖或改 workflow失败把工单交给资深工程师

一个关键警示:尺寸评分通过,不代表语义评分通过。你仍然要按顺序读完每个 hunk,读不懂的代码在你能把它讲清楚之前都算失败。

逐块审查的样例

假设工单要求 parser 拒绝空名字,而 Agent 既改了 parseName,又顺手重排了 README。此时 parser 这一块与工单相关,可以保留;README 那一块标记为 unknown,然后删掉。

- if (!name) return null;
+ if (!name || !name.trim()) throw new Error("empty name");

这段 diff 只是示意,不是你仓库的真实结果。审查时固定一套问题:这一块对用户可见行为改了什么?如果答案需要靠猜,就标记 unknown。要特别警惕散落在无关文件里的格式化噪声、工单从未要求的新 helper,以及藏在注释背后的行为变更。最后把 Agent 的摘要与 --name-only 的 diff 列表逐一对齐:摘要里的每个文件都该出现在列表中,列表里的每个文件都该出现在你的笔记里。

「hunk」是 git diff 输出的最小审查单元:每个以 @@ 开头的区块就是一个 hunk,它包含了变更行的上下文(默认前后各三行)以及实际的增删内容。逐块审查的意义在于,模型生成的补丁有时会把多个逻辑上无关的修改混入同一个 diff,仅看文件名或总行数无法发现这种「搭便车」现象。一个典型模式是:Agent 在修复目标逻辑的同时,顺手对整个文件做了代码风格统一(如把所有单引号改成双引号),这些格式变更会把真正的逻辑改动淹没在大量噪声行里,使 reviewer 的注意力被分散。git diff --stat 给出的是文件级摘要,而 git diff -U3 或直接读补丁文件才能看到 hunk 级别的细节。把每个 hunk 对应到工单中的具体验收条件,是判断「相关」还是「unknown」的唯一可靠标准。

诚实对待工具与免费额度

这篇流程源于 MonkeyCode 的产品推广内容(原文已明确披露)。文中提到它提供「免费模型访问」和「免费服务器选项」两项可用性声明,但没有点名任何具体模型,也没有说明 token 配额、硬件规格、运行时长或可用性数字。原文的态度很克制:这些事实未经验证,运行当天请以项目官方文档为准,文档与文章冲突时信文档。

免费服务器只适合用来跑嘈杂的第一轮,私有测试夹具和凭据都要留在本地;带回来的应该是补丁,而不是一整套运行环境。免费模型额度能让第二次 prompt 成本更低,但重试前必须换一条约束,否则只是重复制造噪声。

什么情况下别用这套流程

原文给出了明确的禁用边界:工单涉及生产数据、你还读不懂该语言、改动属于发布或迁移、需要有保障的配额或支持合同、diff 已经在共享分支上——以上任何一种情况都应跳过这套上手流程。如果团队本就禁止使用外部编程 Agent,更不能把「免费层」当作绕过政策的借口,先问你的 lead。

归根到底,你的第一个 PR 应该小到能当面讲清楚。评分脚本是你能亮出来的防御,模型只负责起草那份被你评分过的补丁。闸门守得住,信任才建立得起来。

#AI 代码审查#AI 编程 Agent#Pull Request 评分#代码 diff 审查#AI 生成代码安全#MonkeyCode#code review 流程
分享:

相关推荐