代码审查与批量修复流水线
系列第 8 篇 · 前置:第 1 篇 → 第 2 篇→ 第 3 篇→ 第 4 篇→ 第 5 篇→ 第 6 篇→ 第 7 篇
第 5 篇讲了审查验证模式:多维度查→去重→逐个验证。这篇把它工程化:确认的问题自动分类,简单的直接修、跑测试验证,修坏了回退;复杂的只报告。
这是系列的最后一篇正文,会用到前面所有知识:parallel、pipeline、schema、haiku 分层、错误处理。
一、整体流程
markdown
四维度并行审查(Bug/安全/性能/风格)
↓
去重(纯JS)
↓
逐个对抗验证(pipeline)
↓
确认的问题分类
├── 简单问题(格式/命名/明显笔误)→ 自动修复 → 跑测试 → 失败回退
└── 复杂问题(逻辑/架构/安全) → 写进报告,人来处理
↓
生成审查报告
和第 5 篇的区别:第 5 篇只做到"确认真问题",这篇接上"修"和"验"。
二、定义修复 Agent
yaml
---
name: code-fixer
description: 修复简单的代码问题(格式、命名、明显笔误),只改指定问题,不重构。
tools: Read, Write, Bash
---
你是一个代码修复员。给你一个文件和一个具体问题,你只修这个问题,不改其他任何东西。
规则:
1. 只修指定的问题,不要顺手重构
2. 修完说明改了哪几行
3. 如果问题涉及逻辑改动(不是简单笔误),不要修,返回"SKIP: 需要人工确认"
4. 不要修改测试文件
给它 Read、Write、Bash(跑测试用),但 Prompt 里严格限制只改指定问题。
三、完整 Workflow
javascript
export const meta = {
name: 'code-review-fix',
description: '四维度审查 → 验证 → 自动修复简单问题 → 测试验证 → 报告',
phases: [
{ title: '四维审查' },
{ title: '验证问题' },
{ title: '自动修复' },
{ title: '生成报告' },
],
}
const TARGET = args.target || 'src/'
const TEST_CMD = args.testCmd || 'npm test'
// ===== 阶段1:四维度并行审查 =====
phase('四维审查')
log(`审查目标:${TARGET}`)
const DIMENSIONS = [
{ key: 'bugs', prompt: '查逻辑错误、空指针、await缺失、条件判断错误' },
{ key: 'security', prompt: '查注入风险、硬编码密钥、权限缺失' },
{ key: 'performance', prompt: '查不必要的串行、内存泄漏、重复计算' },
{ key: 'style', prompt: '查命名不一致、未使用变量、缺少注释的复杂逻辑' },
]
const allFindings = await parallel(
DIMENSIONS.map(d => () =>
agent(
`审查 ${TARGET}。${d.prompt}。
每条问题输出:文件路径、行号、问题描述、是否简单可修(true/false)。`,
{
label: `审查:${d.key}`,
phase: '四维审查',
model: 'haiku',
schema: {
type: 'object',
properties: {
issues: {
type: 'array',
items: {
type: 'object',
properties: {
file: { type: 'string' },
line: { type: 'number' },
description: { type: 'string' },
autoFixable: { type: 'boolean' },
},
required: ['file', 'description', 'autoFixable'],
},
},
},
required: ['issues'],
},
}
).then(result => result.issues.map(i => ({ ...i, dimension: d.key })))
)
)
// 去重(纯JS)
const seen = new Set()
const unique = allFindings.flat().filter(i => {
const key = `${i.file}:${i.description.slice(0, 50)}`
if (seen.has(key)) return false
seen.add(key)
return true
})
log(`发现 ${allFindings.flat().length} 条,去重后 ${unique.length} 条`)
// ===== 阶段2:对抗验证 =====
phase('验证问题')
const verified = await pipeline(
unique,
(issue) => agent(
`读 ${issue.file}。有人报告第${issue.line || '?'}行有问题:
"${issue.description}"
请验证。读文件确认。
- 确实是问题 → 输出 CONFIRMED
- 误报 → 输出 FALSE
- 不确定 → 输出 FALSE(严格标准)`,
{ label: `验证:${issue.file}`, phase: '验证问题' }
).then(verdict => verdict.includes('CONFIRMED') ? issue : null)
)
const realIssues = verified.filter(Boolean)
const autoFixable = realIssues.filter(i => i.autoFixable)
const needHuman = realIssues.filter(i => !i.autoFixable)
log(`确认 ${realIssues.length} 条:${autoFixable.length} 条可自动修,${needHuman.length} 条需人工`)
// ===== 阶段3:自动修复 + 测试验证 =====
phase('自动修复')
const fixResults = []
for (const issue of autoFixable) {
log(`修复:${issue.file} - ${issue.description.slice(0, 40)}`)
const fix = await agent(
`修复 ${issue.file} 中的问题:${issue.description}。只改这一处。`,
{ agentType: 'code-fixer', label: `修复:${issue.file}`, phase: '自动修复' }
)
if (fix.includes('SKIP')) {
fixResults.push({ issue, status: 'skipped', reason: fix })
needHuman.push(issue)
continue
}
// 跑测试验证
const test = await agent(
`运行 ${TEST_CMD},告诉我测试是否通过。只报告通过/失败和失败原因。`,
{ label: '跑测试', phase: '自动修复' }
)
if (test.includes('失败') || test.includes('FAIL')) {
// 回退:让Agent恢复文件
await agent(
`刚才修改 ${issue.file} 导致测试失败,请恢复这个文件到修改前的状态。
失败原因:${test.slice(0, 300)}`,
{ label: '回退', phase: '自动修复' }
)
fixResults.push({ issue, status: 'reverted', reason: test.slice(0, 200) })
needHuman.push(issue)
} else {
fixResults.push({ issue, status: 'fixed', detail: fix.slice(0, 200) })
}
}
// ===== 阶段4:生成报告 =====
phase('生成报告')
const report = await agent(
`生成代码审查报告。
审查目标:${TARGET}
自动修复结果:
${JSON.stringify(fixResults, null, 2)}
需要人工处理的问题:
${JSON.stringify(needHuman, null, 2)}
格式:
1. 修复汇总(修了几个、回退几个、跳过几个)
2. 需要人工处理的问题列表(文件、问题、建议)
3. 总体评价`,
{ phase: '生成报告' }
)
return {
target: TARGET,
summary: {
found: unique.length,
confirmed: realIssues.length,
autoFixed: fixResults.filter(r => r.status === 'fixed').length,
reverted: fixResults.filter(r => r.status === 'reverted').length,
needHuman: needHuman.length,
},
report,
}
四、关键设计
1. 为什么修复用 for 循环不用 pipeline
修复必须串行。两个 Agent 同时改同一个文件会冲突。而且每修一个要跑测试,测试失败要回退,有前后依赖。pipeline 适合各走各的,这里不行。
2. 为什么修完立刻跑测试
自动修复最大的风险是"修了一个问题,引入两个新问题"。每修一个就跑测试,一旦失败立刻回退,不会累积错误。比全修完再测安全得多。
3. 为什么回退让 Agent 做而不是 git checkout
两种方式都行。如果项目在 Git 管理下,更安全的做法是修之前先 commit,回退直接 git checkout。让 Agent 恢复文件是不依赖 Git 的方案,但不如 Git 可靠。建议:
javascript
// 更安全的做法:修复前先提交
await agent('git add -A && git commit -m "checkpoint before auto-fix"')
// 回退
await agent(`git checkout ${issue.file}`)
4. 为什么 autoFixable 让审查 Agent 判断
审查 Agent 看了代码,它最清楚这个问题是"少了个等号"还是"架构有问题"。加一个 autoFixable 布尔字段,比在修复阶段再判断省一次 Agent 调用。
5. 安全边界
这个流程不适合直接在主分支跑。建议:
- 新建分支跑
- 修复前先 commit checkpoint
- 自动修复的结果必须人工 review 后再合并
- 安全问题(security 维度)一律不自动修,只报告
五、运行方式
bash
# 审查 src 目录,用 npm test 验证
运行 workflows/code-review-fix.js,目标是 src/,测试命令是 npm test
# 审查单个文件
运行 workflows/code-review-fix.js,目标是 lib/utils.js,测试命令是 npm test
跑完你会得到:
- 自动修复了几个、回退了几个
- 需要人工处理的问题列表
- 完整报告
六、从这个系列学到了什么
8篇下来,核心就三件事:
1. 定义 Agent 就是定义岗位
name、description、tools、Prompt。description 决定它什么时候被调用,tools 决定它能做什么,Prompt 决定它怎么做。权限收窄、规则具体、给示例,这三条做到了 Agent 就靠谱。
2. Workflow 就是用代码调度 Agent
四样 JS 走天下:await agent()、模板字符串、parallel、pipeline。需要所有人结果用 parallel,各干各的用 pipeline,不知道跑多少轮用 while。能 JS 处理的别派 Agent,找问题用 haiku。
3. 工程化的关键是验证和回退
单个 Agent 写得再好也会出错。多维度查、交叉验证、修完跑测试、失败回退,这些机制比"把 Prompt 写得更完美"可靠得多。
小结
sql
代码审查流水线 = 查 → 去重 → 验证 → 分类 → 修 → 测 → 回退
简单问题自动修,复杂问题交给人
每修一个跑一次测试,失败立刻回退
在独立分支跑,修复前commit checkpoint