让AI来帮团队code review,我是这样做的(已开源)

新入职公司不久,+1提出来想做一个CLI,让AI来帮团队进行code review,并生成对应的review报告,那就来吧。

最初,我调研了一些方案,有的方案太"重":起服务、接 webhook、连 GitLab、落数据库、跑队列,还没review,先搭了一套小型的后端系统;而有的方案太"生硬":把 AI 审查塞进 pre-commit,一旦模型慢一点、CLI 卡一下、网络抖一下,开发者的提交体验立刻崩掉。

最后,我选了一条更接地气的路:不拦提交,尽量不侵入原有的开发流程,只在提交完成后做本地 AI review

技术栈

  • 运行时:Bun >= 1.2.0。
  • 开发语言:TypeScript(ESNext、strict mode)。
  • Git 集成
  • Agent backend:Claude、Codex、Gemini 本地 CLI。
  • 配置校验:Zod。
  • 错误处理:neverthrow Result
  • 代码质量:Biome 负责 lint/format,TypeScript 负责 typecheck。
  • 测试框架:bun:test

它到底是怎么工作的

这个工具的主流程并不复杂,可以概括成 6 步:

  1. 读取当前 HEAD commit
  2. 提取本次提交涉及的文件和 diff
  3. 启动本地 agent CLI(Claude、Codex 或 Gemini)
  4. 允许 agent 直接修改工作区里的不恰当代码
  5. 如果有修改,就生成一个临时 review commit
  6. 由用户决定:应用这些修改,还是丢弃它们

项目架构:

text 复制代码
src/
  index.ts              CLI 入口
  cli.ts                参数解析和进程退出映射
  review/
    git.ts              当前 commit diff/文件收集,以及 review commit 管理
    pre-commit.ts       AI review 编排和 prompt 构造
  agents/               CLI 进程启动:runner.ts、adapters、types.ts
  config/               经过 Zod 校验的 env config
  types/                result.ts 和 errors.ts

真正的核心在 src/review/pre-commit.ts。它不是简单地"把 diff 扔给模型",而是把整个 review 行为包装成一个可控流程。

比如发给 agent 的系统指令:

ts 复制代码
const PRE_COMMIT_SYSTEM_PROMPT = [
  "Review only the committed changes provided by the user prompt.",
  "If code is clearly unsuitable, edit the affected files directly.",
  "Keep edits narrow and limited to files that are part of the reviewed commit.",
  "Do not stage files or change the git index.",
  "Do not create commits, branches, tags, or pull requests.",
  "Do not wait for interactive input.",
].join("\n");

不是在鼓励模型"自由发挥",而是在反复强调:

  • 只看当前 commit
  • 只改相关文件
  • 不要碰 git index
  • 不要自己 commit
  • 不要等待人工输入

在我看来,AI 一旦进入工程项目中,最重要的能力不是"聪明",而是守规矩

核心:让 AI 改代码,但不给它越界的机会

很多 AI coding 工具的真正风险,不是模型建议错了,而是改过头了

针对"越界修改"问题的处理思路:

在 agent 跑完之后,会再次检查工作区改动,确认 AI 只动了本次提交涉及的文件:

ts 复制代码
const outOfScopeFilePaths = changedFilePathsResult.value.filter(
  (filePath) => !source.committedFilePaths.includes(filePath),
);

if (outOfScopeFilePaths.length > 0) {
  const discardResult = discardAgentReviewChanges(options, dependencies, source.userCommitId);
  return err(
    agentError(
      `AI review modified files outside the reviewed commit: ${outOfScopeFilePaths.join(", ")}`,
      agentKind,
      -1,
    ),
  );
}

并不是"尽量不要乱改",而是:一旦越界,直接撤销。 即要么在边界内完整完成,要么回滚。

它还处理了一个很现实的问题:你的工作区可能并不干净

真实开发里,很少有人能保证每次 commit 之后工作区一尘不染。

你可能已经开始写下一段代码了,也可能在别的文件里留着一些未提交修改。

"先清空工作区再用"在我看来就是一种侵入,我没有选择这么做,而是做了两层保护。

第一层,如果本次 commit 涉及的文件已经被继续修改了,那就直接跳过 review,避免覆盖后续编辑;

第二层,如果只是其他 tracked 文件有改动,会先 stash 掉,review 结束后再恢复。

代码如下:

ts 复制代码
const stashOidResult = dependencies.stashTrackedWorkingTreeChanges(
  options.workDir,
  source.unrelatedTrackedFilePaths,
  reviewStashMessage(source.userCommitId),
);

临时 review commit 方案

如果 AI 改了代码,为什么不直接把修改留在 working tree?

因为那样很容易让状态变得模糊。你分不清哪些是自己刚写的,哪些是 AI 补的;也不好回退;更不容易记录本次 AI review 到底做了什么。

所以我的做法是:先把 AI 修改做成一个本地临时 commit,然后再让用户决定怎么处理。

创建 review commit:

ts 复制代码
const commitArgs = [
  "commit",
  "--only",
  "-m",
  "chore: apply AI review fixes",
  "-m",
  `Reviewed commit: ${userCommitId}`,
  "--",
  ...filePaths,
];

这个设计有两个好处。

第一,AI 的修改天然可审计。

你知道它改了什么,也知道它是针对哪个 user commit 产生的。

第二,应用与放弃都很干净。

接受修改时,用 git reset --mixed <userCommitId>,把 review commit 拆回未暂存工作区;

拒绝修改时,用 git reset --hard <userCommitId>,把临时 review commit 连同修改一起丢掉。

代码如下:

ts 复制代码
if (confirmation.value) {
  resetReviewCommitToUserCommit(options.workDir, userCommitId, "mixed");
} else {
  resetReviewCommitToUserCommit(options.workDir, userCommitId, "hard");
}

多 agent 支持,靠 adapter 落地

这个项目支持 claudecodexgemini 三种本地 CLI backend。

比如Codex 的命令构造代码:

ts 复制代码
return {
  command: codexPath(config),
  args: [
    "exec",
    "--dangerously-bypass-approvals-and-sandbox",
    "--json",
    "--config",
    `developer_instructions=${config.systemPrompt}`,
    "--",
    "-",
  ],
  env: {},
  stdin: config.prompt,
};

prompt 通过 stdin 传入,而不是直接拼命令参数。

这样可以避免长文本、特殊字符、-- 前缀参数等问题,CLI 行为会稳定很多。

我也没有把 Codex 的 JSONL 事件流原样暴露给用户,而是做了解析和提炼,比如:

ts 复制代码
export function parseCodexReviewComments(output: string): string | null {
  // 提取最终 agent_message,而不是把整段 jsonl 原样打印
}

这是因为我非常清楚:

终端用户需要的是"结果和关键信号",不是底层事件噪音。

少抛异常,多显式结果

统一使用 neverthrowResult<T, AppError>,并把错误分成三类:

ts 复制代码
export type AppError =
  | {
      readonly kind: "agent_error";
      readonly message: string;
      readonly agent: string;
      readonly exitCode: number;
    }
  | { readonly kind: "git_error"; readonly message: string }
  | { readonly kind: "config_error"; readonly message: string };

配置加载则用 Zod 做校验:

ts 复制代码
const ConfigSchema = z.object({
  defaultAgent: z.enum(["claude", "codex", "gemini"]).default("codex"),
  logLevel: z.enum(["trace", "debug", "info", "warn", "error", "fatal"]).default("info"),
  agentTimeoutMs: z.coerce.number().int().positive().default(600_000),
  claudePath: z.string().default("claude"),
  codexPath: z.string().default("codex"),
  geminiPath: z.string().default("gemini"),
});

与其到处 throw,不如让每一步失败都显式可见、可测试、可恢复。

不要为了KPI让工具变成负担

以前习惯了为KPI搞一些看起来很酷的东西,也方便给领导汇报,但来了新公司之后,+1明确表示好用,能用起来才是第一顺位的事情。这也是我这套CLI坚持的:AI 可以审查、可以修改、可以给意见,但它不能阻塞提交,不能偷偷扩大修改范围,不能把工作区搞乱,不能替用户做最终决定。 最后,项目已开源,附上github地址:github.com/codingWhats...

相关推荐
谷雨不太卷3 小时前
opencode的Skills
ai编程
qq_311674253 小时前
GPT5.5 token地址
ai编程
sugar__salt3 小时前
从Prompt到3D世界:用大模型精准构建你的迷你村庄
3d·ai·prompt·ai编程
peakmain93 小时前
基于 Hilt 实现 Android 网络库可插拔替换 Skill
android·架构·ai编程
jarvisuni4 小时前
《掌门日记》之GPT5.5测评报告!
人工智能·ai编程
kongba0075 小时前
EIDE如何配置,能够支持Keil/ARMClang和clangd进行文件跳转。 踩坑记录
ai编程
CoCo的编程之路5 小时前
2026 企业级 AI 编程助手全景评测:安全、规范与智能体协同
大数据·人工智能·安全·ai编程·comate·文心快码baiducomate
ZFSS5 小时前
MultiNLI 多种类自然语言推理数据集介绍
人工智能·ai·ai作画·音视频·ai编程
知彼解己5 小时前
从后端角度理解 AI Agent:理论 + Go 实战(附 MCP 服务器实现)
java·golang·ai编程