从 0 到 1:用 AI Agent 自动审查团队代码质量

1 阅读9分钟

痛点

我们团队 3 个项目、6 个仓库、前后端各一人。按理说每周应该做代码审查,但现实是:

  • 没人做。活都干不完,谁有空看别人的代码?
  • 做了也流于形式。随便扫两眼,"看起来没问题",完事。
  • 问题照出。低级命名、重复代码、潜在 Bug,事后才发现。

说白了,代码审查重要性都知道,但执行力几乎为零。

那能不能让 AI 来做?每天自动采集代码提交,AI 审查质量,报告推送到钉钉群。团队不用花时间,但问题有人盯着。

这就是 code-review-agent


架构:四步走

CNB API → Git Show → DeepSeek AI → 钉钉推送
 采集提交   获取Diff   AI审查代码   周报直达群
  • Step 1:从代码仓库拉取本周所有 commit 记录
  • Step 2:用 Git 获取每个 commit 的代码差异
  • Step 3:把代码差异丢给 AI,让它审查代码质量
  • Step 4:汇总成周报,推送到钉钉群

关键差异:周报只需要 commit message,代码审查需要看真正的代码变更。


Step 1:数据采集——项目映射

CNB 的仓库路径是 team/project/task-api,但报告里应该写"任务管理系统·后端"。所以需要映射:

type ProjectConfig struct {
    Name     string
    Backend  RepoInfo  // 仓库名 + 负责人
    Frontend RepoInfo
}

配置在 .env 里:

CNB_REPOS=org/repo1,org/repo2
PROJECTS_JSON={"name":"任务管理系统","backend":{"repo":"task-api","dev":"后端A"},"frontend":{"repo":"task-web","dev":"前端A"}}
PROJECTS_JSON={"name":"文档协作平台","backend":{"repo":"doc-api","dev":"后端B"},"frontend":{"repo":"doc-web","dev":"前端A"}}

采集时,把仓库名映射到项目和负责人,后面的报告才能按项目分组、按负责人 @提醒。


Step 2:获取代码差异——踩坑最多的环节

最初方案:CNB Diff API(❌ 404)

url := fmt.Sprintf("https://api.cnb.cool/%s/commit/%s/diff", repo, sha)
// 全部 404,这个 API 对第三方未开放

最终方案:Git Clone + Git Show(✅)

func FetchDiff(cfg *entities.Config, repo, sha string) (string, error) {
    repoDir := filepath.Join(os.TempDir(), "code-review-"+repo)

    // 首次:克隆仓库
    if _, err := os.Stat(repoDir); os.IsNotExist(err) {
        repoURL := fmt.Sprintf("https://cnb.cool/%s.git", repo)
        if cfg.GitUsername != "" {
            repoURL = fmt.Sprintf("https://%s:%s@cnb.cool/%s.git",
                cfg.GitUsername, cfg.GitPassword, repo)
        }
        exec.Command("git", "clone", "--depth=200", repoURL, repoDir).Run()
    } else {
        // 后续:只 fetch 更新
        exec.Command("git", "-C", repoDir, "fetch", "--all").Run()
    }

    // 获取指定 commit 的 diff
    output, _ := exec.Command("git", "-C", repoDir, "show", "--no-color", sha).CombinedOutput()
    return string(output), nil
}

关键点:

  • --depth=200:浅克隆,只拉最近 200 个 commit
  • --no-color:去掉 ANSI 颜色码,纯文本更利于 AI 解析
  • 缓存机制:仓库只克隆一次,后续只 fetch 更新

源码过滤:只审查有意义的代码

一个 commit 的 diff 里可能有 package-lock.json.env、二进制文件。这些丢给 AI 是浪费 Token:

sourceExts := map[string]bool{
    ".go": true, ".py": true, ".js": true, ".ts": true,
    ".vue": true, ".java": true, ".css": true, ".scss": true,
}

func FilterSourceCode(diff string) string {
    // 只保留源码文件的 diff 行
    // 2000 行 diff 可能只剩 500 行,Token 消耗降 75%
}

Step 3:AI 代码审查——Prompt 是灵魂

逐个审查:每个 commit 过一遍 AI

func ReviewDiff(cfg *entities.Config, diffBlock string, commitInfo string) (string, error) {
    systemPrompt := `你是代码审查助手。只输出有问题的代码,格式必须适合手机阅读。

【输出格式 - 严格遵循】

# 📋 代码审查报告

## 📊 概览
审查 X 个提交 | 质量评分 X/5

---

## ⚠️ 发现的问题

### 问题1:问题标题
**责任人:后端A**

**❌ 修改前:**
` + "```" + `语言
// 有问题的代码
` + "```" + `

**✅ 修改后:**
` + "```" + `语言
// 改进后的代码
` + "```" + `

**原因:** 简要说明问题

---

【强制规则】
-  禁止使用 Markdown 表格
-  每个问题必须有代码对比
-  每个问题必须标注责任人
-  只输出有问题的地方
-  移动端友好`

    reqBody := ChatRequest{
        Model: cfg.DeepSeekModel,
        Messages: []ChatMessage{
            {Role: "system", Content: systemPrompt},
            {Role: "user", Content: "Commit: " + commitInfo + "\n\nDiff:\n```diff\n" + diffBlock + "\n```"},
        },
    }
    // 调用 DeepSeek API ...
}

移动端友好的输出格式

这是迭代后的版本,解决了原版的问题:

  • ❌ 去掉 Markdown 表格(移动端显示乱)
  • ✅ 每个问题包含「修改前 vs 修改后」代码对比
  • ✅ 每个问题标注责任人
  • ✅ 只输出有问题的部分,正确的不写
  • ✅ 用 --- 分隔问题,适合聊天记录滚动查看

Prompt 设计的 5 条铁律

1. 格式必须写死

不加格式约束,AI 输出千奇百怪。在 Prompt 里把完整模板写出来,输出立刻可控。

2. 每个问题必须"修改前 vs 修改后"

如果 AI 只说"变量命名不规范",读者还得猜怎么改。要求给出代码对比,可操作性翻倍。

3. @负责人

审查报告的价值在于可执行。"有个变量命名不好"不如"@后端A,第 42 行变量名建议改为 userList"。

4. 只关注代码质量,不关注业务逻辑

AI 看不懂你的业务。让它审查命名、结构、可读性、重复代码、潜在 Bug。这些是 AI 真正擅长的。

5. 合并共性问题

34 个 commit 逐个审查,可能有 10 个都犯了"变量名缩写过度"。汇总时让 AI 合并同类项,报告才不会又臭又长。

汇总审查:把多份审查合并成一份周报

func GenerateSummary(cfg *entities.Config, reviews []string, commits []entities.Commit) (string, error) {
    // 构建项目统计信息
    projectInfo := buildProjectStats(commits)

    // 把所有审查结果拼接
    combinedReview := strings.Join(reviews, "\n\n---\n\n")

    // 丢给 AI 生成最终周报(格式同上)
    // ...
}

最终输出一份按项目分组的审查周报,钉钉群里直接渲染 Markdown。


Step 4:钉钉推送

上次用企业微信,这次团队用钉钉。逻辑完全一样,换了个 Webhook 格式:

func SendMarkdown(webhook, title, markdown string) error {
    msg := DingTalkMarkdownMsg{MsgType: "markdown"}
    msg.Markdown.Title = title
    msg.Markdown.Text = markdown

    jsonBody, _ := json.Marshal(msg)
    resp, _ := http.Post(webhook, "application/json", bytes.NewReader(jsonBody))
    return nil
}

💡 想用飞书、企业微信、Slack?只需要改这一个函数,其他代码不用动。


项目结构

code-review-agent/
├── main.go              # 入口 + 调度
├── entities/            # 数据结构
├── config/              # 配置加载
├── cnb/                 # CNB API 采集 + 源码过滤
├── git/                 # Git clone/show 操作
├── review/              # DeepSeek AI 审查 + 汇总
├── dingtalk/            # 钉钉推送
├── go.mod
├── .env.example
└── README.md

拆分的理由很实际:main.go 超过 600 行后,改一个地方要上下翻好久。拆成 6 个包后,改 Git 逻辑只看 git/git.go,改审查 Prompt 只看 review/review.go


定时执行

func main() {
    config.LoadDotEnv()
    cfg := config.Load()

    // 启动时立即执行一次
    runCodeReview(cfg)

    // 注册定时任务:每周三 23:11
    if cfg.CronSpec != "" {
        c := cron.New()
        c.AddFunc(cfg.CronSpec, func() {
            cfg.DateFrom = time.Now().AddDate(0, 0, -7).Format("2006-01-02")
            cfg.DateTo = time.Now().Format("2006-01-02")
            runCodeReview(cfg)
        })
        c.Start()
        select {}
    }
}

⚠️ Cron 表达式的坑

robfig/cron/v3 的格式是:分 时 日 月 周

我想设"每周三 23:00",一开始写了 0 3 23 * * 3,以为是从左到右递增。结果被解析成"每月 23 号周三凌晨 3 点"。

正确写法:0 23 * * 3


实际效果

34 个 commit 逐个审查,大约 15 分钟。其中 AI 调用是主要耗时,每个 commit 约 8-15 秒。

钉钉群里收到的报告格式:

# 📋 代码质量周报

## 📊 概览
审查 8 个提交 | 质量评分 3/5

---

## ⚠️ 问题清单

### 问题1:死代码未删除
**责任人:后端A**

**❌ 修改前:**
```html
<!-- <button class="submit">报送</button> -->

修改后:
(直接删除整行)

原因: 应删除而非注释,Git 已有历史记录


📝 改进建议

  1. 清理注释残留代码 @后端A
  2. 提取硬编码数值为常量 @前端A

可以看到:
-  适合手机滚动查看
-  每个问题有代码对比
-  @了具体负责人
-  共性问题已合并

---

## 和周报 Agent 的对比

两个 Agent 架构相似,但重点不同:

- **周报 Agent**:输入 commit message,AI 做文案整理,输出给人看"做了什么"
- **代码审查 Agent**:输入 commit message + 代码 Diff,AI 做代码审查,指出代码问题

代码审查 Agent 新增了 3 个关键能力:

1. **项目映射**:仓库名  项目名 + 负责人,报告才有归属
2. **源码过滤**:只审查 `.go` `.vue` `.ts` 等源码文件,跳过配置和锁文件
3. **汇总报告**:多份逐个审查  1 份按项目分组的周报

---

## 部署指南

### 前置准备

1. DeepSeek API Key(注册就送额度,审查一次约 0.1 元)
2. CNB Token(或其他代码平台 Token)
3. 钉钉群机器人 Webhook
4. 服务器上安装 Git

### 部署步骤

```bash
# 克隆项目
git clone https://github.com/lobster-bujiaban/code-review-agent.git
cd code-review-agent

# 配置
cp .env.example .env
# 编辑 .env,填入 API Key、仓库列表、项目映射、Webhook

# 编译
go build -o code-review-agent .

# 测试运行
./code-review-agent

.env 配置示例

DEEPSEEK_API_KEY=sk-xxxxx
CNB_TOKEN=your-token
CNB_REPOS=org/project-a/backend,org/project-a/frontend

# 项目配置(每个项目一行)
PROJECTS_JSON={"name":"项目A","backend":{"repo":"backend","dev":"后端A"},"frontend":{"repo":"frontend","dev":"前端A"}}

DINGTALK_WEBHOOK=https://oapi.dingtalk.com/robot/send?access_token=xxx

# 留空=自动最近7天,或指定日期
DATE_FROM=auto
DATE_TO=auto

# 每周三 23:00
CRON_SPEC=0 23 * * 3

systemd 部署

[Unit]
Description=Code Review Agent
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/code-review-agent
ExecStart=/opt/code-review-agent/code-review-agent
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
# 部署
sudo cp code-review-agent /opt/code-review-agent/
sudo cp .env /opt/code-review-agent/

# 启动
sudo systemctl daemon-reload
sudo systemctl enable code-review-agent
sudo systemctl start code-review-agent

# 查看日志
sudo journalctl -u code-review-agent -f

交叉编译(推荐,无需在服务器装 Go)

# Mac/Linux
GOOS=linux GOARCH=amd64 go build -o code-review-agent-linux

# Windows PowerShell
$env:GOOS="linux"; $env:GOARCH="amd64"; go build -o code-review-agent-linux

改造成你自己的

  • 换代码平台 → 改 cnb/cnb.go
  • 换 AI 模型 → 改 review/review.go 的 API 地址
  • 换推送渠道 → 改 dingtalk/dingtalk.go
  • 加审查维度 → 改 review/review.go 的 Prompt
  • 调审查频率 → 改 .envCRON_SPEC

踩过的坑

坑 1:CNB Diff API 404

API 文档里有 diff 端点,但第三方调用全部 404。浪费了 2 小时尝试各种参数。

解法:降级到 git clone + git show。多一步 Git 操作,但 100% 可靠。

教训:别信文档,先 curl 验证 API 可用性,再写代码。

坑 2:Cron 表达式字段顺序搞反

0 3 23 * * 3 → 我以为是"秒 分 时 日 月 周",实际是"分 时 日 月 周"。

解法:改成 0 23 * * 3

坑 3:AI 输出格式不可控

第一版 Prompt 只有"你是代码审查员",AI 输出五花八门。

解法:在 Prompt 里写死完整的 Markdown 模板。

教训:Prompt 不是聊天,是"编程"。你不说清楚格式,AI 就自由发挥。

坑 4:源码不过滤,Token 爆炸

一个 commit 可能改了 package-lock.json.env,这些文件 diff 动辄几千行。

解法:加了 FilterSourceCode() 函数,只保留源码文件的 diff。


总结

这个项目从想法到跑通,花了大约 两个晚上。比周报 Agent 多花了一倍时间,主要是因为 Diff 获取方案从 API 降级到 Git,以及 Prompt 迭代。

但核心架构是同一条线:

数据采集 → AI 处理 → 自动推送

下周我会写 W3 踩坑实录,把搭这两个 Agent 过程中所有踩过的坑详细拆解。

📦 源码地址

GitHub - lobster-bujiaban/code-review-agent

欢迎 Star、提 Issue、Fork 改造。


🦞 一只用 AI Agent 搭副业产线的程序员

公众号:虾哥不加班 | B站:龙虾不加班 | 掘金:龙虾不加班