本教程面向希望将 Claude Code 从「交互式终端工具」升级为「可编程自动化引擎」的开发者。你将掌握三大核心能力:Headless 模式的完整参数体系、Agent SDK 的 TypeScript 子进程集成、以及 Hooks 生命周期事件的实战配置。适合有一定 Claude Code 使用基础、需要在 CI/CD 管道、批量脚本或多 Agent 编排场景中自动化运行的中高级开发者。
学习目标: - 理解 Claude Code 的三种非交互执行模式及其适用场景 - 使用 Agent SDK 构建可编程的 Claude Code 子进程调用 - 配置 Hooks 实现工具调用的前后拦截与自动化 - 完成一个多仓库批量代码审查的端到端自动化项目
| 条件 | 说明 |
|---|---|
| Claude Code CLI | 已安装并完成 OAuth 认证(claude 命令可用) |
| Node.js 18+ | Agent SDK 运行环境 |
| TypeScript | 示例代码使用 TS,可按需降级为 JS |
| 基础 Shell | 了解管道、重定向、退出码等概念 |
| Claude Code 使用经验 | 至少在交互模式下完成过一个项目 |
Claude Code 提供三种脱离 TUI(终端用户界面)的执行方式,对应不同自动化需求:
┌─────────────────────────────────────────────────────────┐
│ Claude Code 非交互执行模式 │
├──────────────┬──────────────┬───────────────────────────┤
│ Print 模式 │ Stream 模式 │ Agent SDK 模式 │
│ claude -p │ --output- │ @anthropic-ai/ │
│ │ format json │ claude-agent-sdk │
├──────────────┼──────────────┼───────────────────────────┤
│ 单次执行 │ 流式输出 │ 可编程子进程 │
│ 文本/JSON输出 │ 事件驱动 │ 完整事件监听 │
│ 适合脚本 │ 适合实时监控 │ 适合应用集成 │
│ 最简集成 │ 中等复杂度 │ 最高灵活度 │
└──────────────┴──────────────┴───────────────────────────┘
-p)完整参数Print 模式是最轻量的非交互方式,一行命令即可获取 Claude 的输出:
# 基础用法:直接输出文本
claude -p "解释这个函数的作用"
# 从 stdin 读取输入(管道)
cat src/auth.ts | claude -p "审查这段代码的安全性问题"
# 输出为 JSON 格式
claude -p "列出所有 TODO 注释" --output-format json
# 流式 JSON(实时获取事件)
claude -p "重构这个模块" --output-format stream-json
# 限制执行轮次(防止失控)
claude -p "修复所有 lint 错误" --max-turns 15
# 指定允许的工具
claude -p "检查测试覆盖率" --allowedTools "Bash,Read,Glob,Grep"
# 跳过权限确认(仅限 CI/容器环境)
claude -p "运行全部测试" --dangerously-skip-permissions
# 组合使用:CI 场景的典型模式
claude -p "审查 PR 的变更内容并给出改进建议" \
--output-format json \
--max-turns 10 \
--allowedTools "Read,Grep,Glob" \
--dangerously-skip-permissions
关键参数详解:
| 参数 | 作用 | 推荐值 |
|---|---|---|
-p / --print |
非交互模式,处理完自动退出 | 必需 |
--output-format |
text(默认)、json、stream-json |
脚本用 json,监控用 stream-json |
--max-turns |
限制 agent 循环的最大轮次 | CI 场景建议 5-20 |
--allowedTools |
白名单允许的工具列表 | 按需最小化授权 |
--dangerously-skip-permissions |
跳过所有权限确认 | 仅限受控环境 |
--model |
指定使用的模型 | 按成本/质量权衡 |
--resume |
恢复上一次对话 | 批量任务续跑 |
JSON 输出(--output-format json):
claude -p "列出项目中的 TypeScript 文件" --output-format json
返回结构:
{
"result": "项目中共有 47 个 TypeScript 文件...",
"cost_usd": 0.0034,
"duration_ms": 5230,
"num_turns": 2,
"session_id": "abc123-def456"
}
流式 JSON(--output-format stream-json):
claude -p "重构 auth 模块" --output-format stream-json
实时输出事件流:
{"type":"assistant","subtype":"text","text":"我来分析 auth 模块的结构..."}
{"type":"tool_use","tool":"Read","input":{"file_path":"/src/auth/login.ts"}}
{"type":"tool_result","tool":"Read","output":"..."}
{"type":"assistant","subtype":"text","text":"发现以下可改进的点..."}
流式模式允许你在脚本中实时捕获每个工具调用和文本输出,适用于: - 实时监控长时间运行的任务 - 构建自定义进度仪表盘 - 在工具调用间注入逻辑
#!/bin/bash
# batch-review.sh - 批量审查多个文件
set -euo pipefail
FILES=("src/auth.ts" "src/api.ts" "src/db.ts")
RESULTS_DIR="./review-results"
mkdir -p "$RESULTS_DIR"
for file in "${FILES[@]}"; do
echo "🔍 审查: $file"
result=$(claude -p "审查这个文件的安全性和代码质量,输出改进建议:$(cat "$file")" \
--output-format json \
--max-turns 5)
# 提取结果文本
summary=$(echo "$result" | jq -r '.result')
cost=$(echo "$result" | jq -r '.cost_usd')
echo "$summary" > "$RESULTS_DIR/$(basename "$file" .ts).md"
echo " ✅ 完成 (成本: \$$cost)"
done
echo "📊 所有审查结果已保存到 $RESULTS_DIR/"
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 输出被截断 | 默认 --max-turns 太低 |
增大到 10-20,或设为 0(无限制) |
| CI 中卡住等待权限 | 未跳过权限确认 | 使用 --dangerously-skip-permissions + Docker 沙箱 |
| API 费用超预期 | Agent 循环次数失控 | 始终设置 --max-turns 上限 |
| JSON 解析失败 | 输出包含非 JSON 内容 | 确认使用 --output-format json |
| stdin 内容丢失 | 管道与 -p 文本冲突 |
使用 cat file \| claude -p "指令" 格式 |
Agent SDK(@anthropic-ai/claude-agent-sdk)是官方提供的 TypeScript 库,它以子进程方式调用 Claude Code CLI,提供类型安全的事件流接口:
graph TD
A[你的 Node.js 应用] -->|import SDK| B[ClaudeAgent SDK]
B -->|spawn 子进程| C[Claude Code CLI]
C -->|执行工具调用| D[文件系统 / Shell / MCP]
D -->|返回结果| C
C -->|事件流 JSONL| B
B -->|类型化事件| A
style A fill:#4A90D9,color:#fff
style B fill:#7B68EE,color:#fff
style C fill:#FF6B6B,color:#fff
style D fill:#51CF66,color:#fff
核心概念: - Host-Guest 模型:你的应用是 Host,Claude Code 是被管理的 Guest 子进程 - 事件驱动:所有交互通过事件流完成,不依赖标准输入输出 - 工具隔离:子进程继承 CLI 的所有工具能力,但权限可独立控制
# 安装 SDK
npm install @anthropic-ai/claude-agent-sdk
# 确保 Claude Code CLI 已安装并认证
claude --version
最小可用示例:
// minimal-agent.ts
import { ClaudeCode } from '@anthropic-ai/claude-agent-sdk';
async function main() {
const agent = new ClaudeCode({
prompt: '列出当前目录下的所有 TypeScript 文件,统计总行数',
options: {
outputFormat: 'json',
maxTurns: 5,
},
});
const result = await agent.run();
console.log('结果:', result.result);
console.log('成本:', result.cost_usd);
console.log('轮次:', result.num_turns);
}
main().catch(console.error);
运行:
npx tsx minimal-agent.ts
对于长时间运行的任务,流式监听是关键能力:
// stream-agent.ts
import { ClaudeCode } from '@anthropic-ai/claude-agent-sdk';
async function streamReview() {
const agent = new ClaudeCode({
prompt: '审查 src/ 目录下所有文件的代码质量',
options: {
outputFormat: 'stream-json',
maxTurns: 20,
allowedTools: ['Read', 'Grep', 'Glob'],
},
});
// 监听各类事件
agent.on('assistant', (event) => {
// Claude 输出的文本
process.stdout.write(event.text);
});
agent.on('tool_use', (event) => {
// 工具被调用
console.log(`\n🔧 使用工具: ${event.tool}`);
if (event.tool === 'Read') {
console.log(` 📄 读取: ${event.input.file_path}`);
}
});
agent.on('tool_result', (event) => {
// 工具返回结果
const preview = typeof event.output === 'string'
? event.output.slice(0, 100) + '...'
: JSON.stringify(event.output).slice(0, 100);
console.log(` 📋 结果: ${preview}`);
});
agent.on('error', (event) => {
console.error('❌ 错误:', event.message);
});
const result = await agent.run();
console.log('\n✅ 完成');
console.log(`总成本: $${result.cost_usd}`);
console.log(`总轮次: ${result.num_turns}`);
}
streamReview().catch(console.error);
这是 Agent SDK 的杀手级应用——同时运行多个 Claude Code 实例并行处理任务:
// parallel-agents.ts
import { ClaudeCode } from '@anthropic-ai/claude-agent-sdk';
interface ReviewResult {
file: string;
summary: string;
issues: string[];
cost: number;
}
async function reviewFile(filePath: string): Promise<ReviewResult> {
const agent = new ClaudeCode({
prompt: `审查文件 ${filePath} 的代码质量。关注:
1. 安全漏洞
2. 性能问题
3. 代码可维护性
以 JSON 格式输出: {"summary": "...", "issues": ["..."]}`,
options: {
outputFormat: 'json',
maxTurns: 8,
allowedTools: ['Read', 'Grep', 'Glob'],
},
});
const result = await agent.run();
try {
const parsed = JSON.parse(result.result);
return {
file: filePath,
summary: parsed.summary || result.result,
issues: parsed.issues || [],
cost: result.cost_usd,
};
} catch {
return {
file: filePath,
summary: result.result,
issues: [],
cost: result.cost_usd,
};
}
}
async function parallelReview() {
const files = [
'src/auth/login.ts',
'src/auth/register.ts',
'src/api/users.ts',
'src/api/products.ts',
'src/db/connection.ts',
];
console.log(`🚀 启动 ${files.length} 个并行审查 Agent...\n`);
// Promise.allSettled 确保单个失败不影响整体
const results = await Promise.allSettled(
files.map(file => reviewFile(file))
);
// 汇总报告
let totalCost = 0;
let totalIssues = 0;
results.forEach((r, i) => {
if (r.status === 'fulfilled') {
const { file, summary, issues, cost } = r.value;
totalCost += cost;
totalIssues += issues.length;
console.log(`\n📄 ${file}`);
console.log(` 摘要: ${summary.slice(0, 100)}`);
console.log(` 问题数: ${issues.length}`);
issues.forEach(issue => console.log(` ⚠️ ${issue}`));
} else {
console.log(`\n❌ ${files[i]} 审查失败: ${r.reason}`);
}
});
console.log(`\n${'='.repeat(50)}`);
console.log(`📊 汇总: ${files.length} 文件 | ${totalIssues} 问题 | $${totalCost.toFixed(4)}`);
}
parallelReview().catch(console.error);
graph LR
A[主进程] -->|spawn| B1[Agent 1: auth/login.ts]
A -->|spawn| B2[Agent 2: auth/register.ts]
A -->|spawn| B3[Agent 3: api/users.ts]
A -->|spawn| B4[Agent 4: api/products.ts]
A -->|spawn| B5[Agent 5: db/connection.ts]
B1 -->|结果| C[汇总报告]
B2 -->|结果| C
B3 -->|结果| C
B4 -->|结果| C
B5 -->|结果| C
style A fill:#4A90D9,color:#fff
style C fill:#51CF66,color:#fff
// advanced-config.ts
import { ClaudeCode } from '@anthropic-ai/claude-agent-sdk';
const agent = new ClaudeCode({
prompt: '执行全面的安全审计',
options: {
// 输出格式
outputFormat: 'stream-json',
// 执行控制
maxTurns: 30,
model: 'claude-sonnet-4-6', // 选择模型(成本优化)
// 工具控制
allowedTools: ['Read', 'Grep', 'Glob', 'Bash'],
// 权限控制
dangerouslySkipPermissions: false,
// 恢复会话
resume: 'previous-session-id',
// 系统提示(项目级)
systemPrompt: '你是一个安全审计专家,专注于 OWASP Top 10',
},
});
// 超时控制
const TIMEOUT_MS = 120_000; // 2 分钟
const timeout = setTimeout(() => {
console.error('⏰ 超时,终止 Agent');
agent.abort();
}, TIMEOUT_MS);
try {
const result = await agent.run();
console.log('完成:', result.result);
} finally {
clearTimeout(timeout);
}
Hooks 是嵌入 Claude Code 执行生命周期的用户自定义 Shell 命令。它们在特定事件触发时自动执行,无需修改 Claude Code 本身:
sequenceDiagram
participant U as 用户
participant CC as Claude Code
participant H as Hooks 层
participant FS as 文件系统
U->>CC: 发送请求
CC->>H: PreToolUse 事件
H-->>CC: 允许 / 阻止 / 修改
alt Hook 允许
CC->>FS: 执行工具调用
FS-->>CC: 返回结果
CC->>H: PostToolUse 事件
H-->>CC: 处理完毕
else Hook 阻止
CC-->>U: 工具被拦截
end
CC->>H: Stop 事件
H-->>CC: 通知处理
CC-->>U: 返回最终结果
核心事件类型:
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
PreToolUse |
工具执行前 | 拦截危险操作、注入上下文 |
PostToolUse |
工具执行后 | 自动格式化、日志记录 |
Notification |
通知触发时 | Slack/邮件通知、CI 状态更新 |
Stop |
Agent 停止时 | 最终汇总、报告生成 |
SessionStart |
会话开始时 | 加载配置、环境检查 |
Hooks 在 settings.json 中配置(项目级 .claude/settings.json 或全局 ~/.claude/settings.json):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "cat"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write $CLAUDE_FILE_PATH 2>/dev/null || true"
}
]
}
],
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "curl -s -X POST \"$SLACK_WEBHOOK\" -H 'Content-type: application/json' --data '{\"text\":\"🔔 Claude Code 任务完成\"}'"
}
]
}
]
}
}
每个 Hook 通过 stdin 接收 JSON 输入,通过 stdout 返回 JSON 输出:
PreToolUse 输入(stdin):
{
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf node_modules"
},
"session_id": "abc-123"
}
PreToolUse 输出(stdout):
{
"decision": "block",
"reason": "禁止执行 rm -rf 命令"
}
决策值:
- approve — 允许执行(默认行为)
- block — 阻止执行并返回原因
- 不输出 JSON — 默认允许
PostToolUse 输入(stdin):
{
"tool_name": "Edit",
"tool_input": {
"file_path": "/src/auth.ts",
"old_string": "...",
"new_string": "..."
},
"tool_output": "File edited successfully",
"session_id": "abc-123"
}
Hook 1:阻止危险 Shell 命令
#!/bin/bash
# hooks/block-dangerous.sh
# 读取 stdin 的 JSON 输入
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# 危险命令黑名单
DANGEROUS_PATTERNS=(
"rm -rf /"
"rm -rf ~"
"DROP TABLE"
":(){ :|:& };:"
"dd if=/dev/zero"
"mkfs"
"> /dev/sda"
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qi "$pattern"; then
# 输出 block 决策
jq -n --arg reason "🚫 检测到危险命令模式: $pattern" \
'{decision: "block", reason: $reason}'
exit 0
fi
done
# 默认允许
echo '{"decision": "approve"}'
Hook 2:自动格式化编辑过的文件
#!/bin/bash
# hooks/auto-format.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# 根据文件扩展名选择格式化工具
case "$FILE_PATH" in
*.ts|*.tsx|*.js|*.jsx)
npx prettier --write "$FILE_PATH" 2>/dev/null
npx eslint --fix "$FILE_PATH" 2>/dev/null
;;
*.py)
black "$FILE_PATH" 2>/dev/null
isort "$FILE_PATH" 2>/dev/null
;;
*.go)
gofmt -w "$FILE_PATH" 2>/dev/null
;;
esac
Hook 3:保护敏感文件
#!/bin/bash
# hooks/protect-secrets.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# 敏感文件保护列表
PROTECTED_FILES=(
".env"
".env.production"
"credentials.json"
"service-account-key.json"
"id_rsa"
)
BASENAME=$(basename "$FILE_PATH")
for protected in "${PROTECTED_FILES[@]}"; do
if [ "$BASENAME" = "$protected" ] || echo "$FILE_PATH" | grep -q "$protected"; then
jq -n --arg file "$FILE_PATH" \
'{decision: "block", reason: "🔒 禁止修改敏感文件: \($file)"}'
exit 0
fi
done
echo '{"decision": "approve"}'
完整 settings.json 配置:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/block-dangerous.sh"
}
]
},
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/protect-secrets.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/auto-format.sh"
}
]
}
]
}
}
我们将构建一个完整的自动化系统,它能: 1. 扫描多个 Git 仓库 2. 检测最近的变更文件 3. 为每个文件启动独立的审查 Agent 4. 汇总生成 Markdown 报告 5. 通过 Hook 自动发送通知
graph TB
subgraph "批量审查系统"
A[主控脚本<br/>batch-review.ts] --> B[扫描仓库列表]
B --> C{遍历仓库}
C --> D[git pull & git diff]
D --> E[提取变更文件]
E --> F[启动并行 Agent]
F --> G1[Agent 1<br/>审查文件A]
F --> G2[Agent 2<br/>审查文件B]
F --> G3[Agent 3<br/>审查文件C]
G1 --> H[汇总结果]
G2 --> H
G3 --> H
H --> I[生成 Markdown 报告]
I --> J[Hook: 发送通知]
end
style A fill:#4A90D9,color:#fff
style H fill:#51CF66,color:#fff
style J fill:#FFD93D,color:#333
项目结构:
batch-review/
├── src/
│ ├── main.ts # 主入口
│ ├── scanner.ts # 仓库扫描器
│ ├── reviewer.ts # Agent 审查器
│ └── reporter.ts # 报告生成器
├── .claude/
│ ├── hooks/
│ │ └── notify.sh # 通知 Hook
│ └── settings.json # Hooks 配置
├── package.json
└── tsconfig.json
package.json:
{
"name": "batch-review",
"version": "1.0.0",
"type": "module",
"scripts": {
"review": "tsx src/main.ts",
"review:dry": "tsx src/main.ts --dry-run"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "latest"
},
"devDependencies": {
"tsx": "^4.7.0",
"typescript": "^5.4.0"
}
}
src/scanner.ts — 仓库扫描器:
import { execSync } from 'child_process';
import path from 'path';
export interface RepoChange {
repo: string;
file: string;
status: 'added' | 'modified' | 'deleted';
diff: string;
}
export interface RepoConfig {
path: string;
branch?: string;
since?: string; // git log --since 格式
}
export function scanRepo(config: RepoConfig): RepoChange[] {
const { path: repoPath, branch = 'main', since = '1 day ago' } = config;
try {
// 拉取最新代码
execSync(`git -C "${repoPath}" pull origin ${branch}`, { encoding: 'utf-8' });
// 获取变更文件列表
const changedFiles = execSync(
`git -C "${repoPath}" diff --name-status HEAD~5..HEAD`,
{ encoding: 'utf-8' }
).trim().split('\n').filter(Boolean);
return changedFiles.map(line => {
const [status, ...fileParts] = line.split('\t');
const file = fileParts.join('\t');
// 只审查代码文件
if (!file.match(/\.(ts|tsx|js|jsx|py|go|rs|java)$/)) {
return null;
}
// 获取 diff 内容
let diff = '';
try {
diff = execSync(
`git -C "${repoPath}" diff HEAD~5..HEAD -- "${file}"`,
{ encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }
);
} catch { /* diff 太大或文件已删除 */ }
return {
repo: path.basename(repoPath),
file,
status: status as RepoChange['status'],
diff: diff.slice(0, 5000), // 限制 diff 大小
};
}).filter(Boolean) as RepoChange[];
} catch (error) {
console.error(`扫描 ${repoPath} 失败:`, error);
return [];
}
}
src/reviewer.ts — Agent 审查器:
import { ClaudeCode } from '@anthropic-ai/claude-agent-sdk';
import { RepoChange } from './scanner.js';
export interface ReviewResult {
repo: string;
file: string;
approved: boolean;
summary: string;
issues: Array<{
severity: 'critical' | 'warning' | 'info';
line?: number;
message: string;
}>;
cost: number;
}
const REVIEW_PROMPT = `你是一个严格的代码审查专家。审查以下代码变更,关注:
1. **安全漏洞** — SQL 注入、XSS、认证绕过、敏感信息泄露
2. **逻辑错误** — 边界条件、空指针、竞态条件
3. **性能问题** — N+1 查询、内存泄漏、不必要的循环
4. **可维护性** — 命名规范、代码重复、过度复杂
以以下 JSON 格式输出(不要输出其他内容):
{
"approved": true/false,
"summary": "一句话总结",
"issues": [
{"severity": "critical|warning|info", "line": 42, "message": "描述"}
]
}`;
export async function reviewChange(change: RepoChange): Promise<ReviewResult> {
const prompt = `${REVIEW_PROMPT}\n\n仓库: ${change.repo}\n文件: ${change.file}\n变更状态: ${change.status}\n\nDiff:\n\`\`\`diff\n${change.diff}\n\`\`\``;
const agent = new ClaudeCode({
prompt,
options: {
outputFormat: 'json',
maxTurns: 5,
model: 'claude-sonnet-4-6', // 成本优化
allowedTools: [], // 审查不需要工具
},
});
const result = await agent.run();
try {
// 尝试从结果中提取 JSON
const text = result.result;
const jsonMatch = text.match(/\{[\s\S]*\}/);
if (jsonMatch) {
const parsed = JSON.parse(jsonMatch[0]);
return {
repo: change.repo,
file: change.file,
approved: parsed.approved ?? true,
summary: parsed.summary ?? '',
issues: parsed.issues ?? [],
cost: result.cost_usd,
};
}
} catch { /* JSON 解析失败 */ }
return {
repo: change.repo,
file: change.file,
approved: true,
summary: result.result,
issues: [],
cost: result.cost_usd,
};
}
export async function reviewChangesParallel(
changes: RepoChange[],
concurrency: number = 3
): Promise<ReviewResult[]> {
const results: ReviewResult[] = [];
// 分批并行执行
for (let i = 0; i < changes.length; i += concurrency) {
const batch = changes.slice(i, i + concurrency);
const batchResults = await Promise.allSettled(
batch.map(change => reviewChange(change))
);
batchResults.forEach((r, j) => {
if (r.status === 'fulfilled') {
results.push(r.value);
} else {
console.error(`审查失败 ${batch[j].repo}/${batch[j].file}:`, r.reason);
}
});
console.log(`📊 进度: ${Math.min(i + concurrency, changes.length)}/${changes.length}`);
}
return results;
}
src/reporter.ts — 报告生成器:
import fs from 'fs';
import { ReviewResult } from './reviewer.js';
export function generateReport(results: ReviewResult[]): string {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const totalCost = results.reduce((sum, r) => sum + r.cost, 0);
const approved = results.filter(r => r.approved);
const rejected = results.filter(r => !r.approved);
const criticalIssues = results.flatMap(r =>
r.issues.filter(i => i.severity === 'critical')
);
let report = `# 🤖 批量代码审查报告\n\n`;
report += `**生成时间**: ${new Date().toLocaleString('zh-CN')}\n`;
report += `**审查文件**: ${results.length} 个\n`;
report += `**通过**: ${approved.length} | **需修改**: ${rejected.length}\n`;
report += `**严重问题**: ${criticalIssues.length} 个\n`;
report += `**总成本**: $${totalCost.toFixed(4)}\n\n`;
// 严重问题优先
if (criticalIssues.length > 0) {
report += `## 🚨 严重问题\n\n`;
criticalIssues.forEach(issue => {
const file = results.find(r =>
r.issues.some(i => i.message === issue.message)
);
report += `- **${file?.repo}/${file?.file}**`;
if (issue.line) report += `:${issue.line}`;
report += ` — ${issue.message}\n`;
});
report += '\n';
}
// 按仓库分组
const byRepo = groupBy(results, r => r.repo);
for (const [repo, repoResults] of byRepo) {
report += `## 📦 ${repo}\n\n`;
repoResults.forEach(r => {
const icon = r.approved ? '✅' : '❌';
report += `### ${icon} ${r.file}\n\n`;
report += `${r.summary}\n\n`;
if (r.issues.length > 0) {
report += `| 严重度 | 行号 | 描述 |\n`;
report += `|--------|------|------|\n`;
r.issues.forEach(i => {
const sevIcon = i.severity === 'critical' ? '🔴' :
i.severity === 'warning' ? '🟡' : '🔵';
report += `| ${sevIcon} ${i.severity} | ${i.line ?? '-'} | ${i.message} |\n`;
});
report += '\n';
}
});
}
// 保存报告
const reportPath = `./reports/review-${timestamp}.md`;
fs.mkdirSync('./reports', { recursive: true });
fs.writeFileSync(reportPath, report);
console.log(`\n📄 报告已保存: ${reportPath}`);
return report;
}
function groupBy<T>(arr: T[], key: (item: T) => string): Map<string, T[]> {
const map = new Map<string, T[]>();
arr.forEach(item => {
const k = key(item);
if (!map.has(k)) map.set(k, []);
map.get(k)!.push(item);
});
return map;
}
src/main.ts — 主入口:
import { scanRepo, RepoConfig } from './scanner.js';
import { reviewChangesParallel } from './reviewer.js';
import { generateReport } from './reporter.js';
// 仓库配置列表
const REPOS: RepoConfig[] = [
{ path: '../backend-api', branch: 'main' },
{ path: '../frontend-app', branch: 'develop' },
{ path: '../shared-libs', branch: 'main' },
];
async function main() {
const dryRun = process.argv.includes('--dry-run');
console.log('🔍 开始扫描仓库变更...\n');
// 第一步:扫描所有仓库
const allChanges = REPOS.flatMap(config => {
const changes = scanRepo(config);
console.log(` ${config.path}: ${changes.length} 个变更文件`);
return changes;
});
if (allChanges.length === 0) {
console.log('✅ 没有检测到代码变更');
return;
}
console.log(`\n📊 共 ${allChanges.length} 个文件需要审查\n`);
if (dryRun) {
allChanges.forEach(c => {
console.log(` [${c.status}] ${c.repo}/${c.file}`);
});
return;
}
// 第二步:并行审查
console.log('🚀 启动并行审查...\n');
const results = await reviewChangesParallel(allChanges, 3);
// 第三步:生成报告
console.log('\n📝 生成审查报告...');
generateReport(results);
// 第四步:输出统计
const rejected = results.filter(r => !r.approved);
if (rejected.length > 0) {
console.log(`\n⚠️ ${rejected.length} 个文件需要修改:`);
rejected.forEach(r => console.log(` ❌ ${r.repo}/${r.file}`));
process.exit(1); // CI 中触发失败
}
console.log('\n✅ 所有文件审查通过!');
}
main().catch(error => {
console.error('💥 批量审查失败:', error);
process.exit(1);
});
通知 Hook(.claude/hooks/notify.sh):
#!/bin/bash
# 当 Stop 事件触发时发送通知
INPUT=$(cat)
EVENT_TYPE=$(echo "$INPUT" | jq -r '.stop_hook_message // empty')
if [ -n "$SLACK_WEBHOOK" ]; then
curl -s -X POST "$SLACK_WEBHOOK" \
-H 'Content-type: application/json' \
--data "{\"text\":\"🤖 批量审查完成: $EVENT_TYPE\"}" \
> /dev/null 2>&1
fi
# 可选:发送邮件
if [ -n "$MAIL_TO" ]; then
echo "批量代码审查已完成。请查看报告。" | \
mail -s "代码审查报告 $(date +%Y-%m-%d)" "$MAIL_TO"
fi
# 安装依赖
npm install
# 试运行(只列出变更文件,不启动 Agent)
npm run review:dry
# 正式运行
npm run review
# 作为定时任务(crontab)
# 每天早上 9 点自动审查
0 9 * * * cd /path/to/batch-review && npm run review >> logs/review.log 2>&1
预期输出:
🔍 开始扫描仓库变更...
../backend-api: 5 个变更文件
../frontend-app: 3 个变更文件
../shared-libs: 2 个变更文件
📊 共 10 个文件需要审查
🚀 启动并行审查...
📊 进度: 3/10
📊 进度: 6/10
📊 进度: 9/10
📊 进度: 10/10
📝 生成审查报告...
📄 报告已保存: ./reports/review-2026-06-05T09-00-00Z.md
⚠️ 2 个文件需要修改:
❌ backend-api/src/auth/login.ts
❌ backend-api/src/api/admin.ts
# .github/workflows/claude-review.yml
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Claude Code
run: npm install -g @anthropic-ai/claude-code
- name: Run Claude Review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
# 获取 PR 变更文件
CHANGED_FILES=$(git diff --name-only origin/main...HEAD | grep -E '\.(ts|tsx|js|jsx)$' | head -20)
if [ -z "$CHANGED_FILES" ]; then
echo "没有代码文件变更"
exit 0
fi
# 对每个文件运行审查
for file in $CHANGED_FILES; do
echo "审查: $file"
claude -p "审查这个文件的变更,关注安全性和代码质量。只报告问题,不要夸赞。
文件: $file
$(cat "$file")" \
--output-format json \
--max-turns 5 \
--allowedTools "Read" \
--dangerously-skip-permissions > "review-$(basename "$file").json"
done
# 合并结果为 PR 评论
node scripts/post-review-comment.js
// cost-control.ts — 三级成本控制
// 级别 1:限制执行轮次
const LEVEL_1 = {
maxTurns: 5, // 最少轮次
model: 'claude-haiku-4-5', // 最便宜的模型
};
// 级别 2:限制工具和输出
const LEVEL_2 = {
maxTurns: 10,
model: 'claude-sonnet-4-6',
allowedTools: ['Read', 'Grep', 'Glob'], // 只读工具
};
// 级别 3:完整能力
const LEVEL_3 = {
maxTurns: 30,
model: 'claude-sonnet-4-6',
allowedTools: ['Read', 'Grep', 'Glob', 'Bash', 'Edit'],
};
function selectLevel(fileCount: number, budgetUsd: number) {
// 按文件数和预算自动选择级别
if (fileCount > 20 || budgetUsd < 0.5) return LEVEL_1;
if (fileCount > 5 || budgetUsd < 2.0) return LEVEL_2;
return LEVEL_3;
}
在 CI 环境中,--dangerously-skip-permissions 听起来可怕,但配合以下策略是安全的:
# Dockerfile.ci — 最小权限容器
FROM node:20-slim
# 只安装必要工具
RUN apt-get update && apt-get install -y --no-install-recommends \
git && rm -rf /var/lib/apt/lists/*
# 非 root 用户
RUN useradd -m -s /bin/bash claude
USER claude
WORKDIR /home/claude/app
# 只读挂载代码(Docker run 时加 --read-only)
COPY --chown=claude:claude . .
# 环境变量通过 CI secrets 注入
ENV ANTHROPIC_API_KEY=""
# 运行:只读文件系统 + 网络 API only
docker run --rm \
--read-only \
--tmpfs /tmp \
-e ANTHROPIC_API_KEY="$API_KEY" \
-v "$(pwd)":/app:ro \
claude-review:latest \
claude -p "审查代码" \
--max-turns 10 \
--dangerously-skip-permissions \
--output-format json
# 开启详细日志
DEBUG=claude-code:* claude -p "测试" --output-format stream-json
# 查看 SDK 子进程通信
TRACE=1 tsx your-agent-script.ts
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
ECONNREFUSED |
CLI 未安装或认证过期 | 运行 claude 交互模式重新认证 |
Max turns exceeded |
达到轮次上限 | 增大 --max-turns 或简化任务 |
Tool not allowed |
工具不在白名单 | 检查 --allowedTools 配置 |
Hook timeout |
Hook 脚本执行超时 | 优化脚本性能,避免阻塞操作 |
JSON parse error |
输出格式不匹配 | 确认 --output-format json 已设置 |
Rate limit |
API 调用频率过高 | 添加请求间隔,控制并发数 |
Session not found |
resume 的会话不存在 | 不使用 --resume 或确认会话 ID |
✅ 并发控制:3-5 个 Agent 并行,避免过多导致 API 限流
✅ 模型选择:审查用 Haiku,复杂重构用 Sonnet
✅ 工具最小化:只开放任务必需的工具
✅ 输入裁剪:diff 限制 5000 字符,文件过大只发片段
✅ 缓存复用:相同输入缓存 SDK 结果,避免重复调用
✅ 超时保护:所有 Agent 调用设置 2-5 分钟超时
✅ 增量执行:只审查变更文件,不全量扫描
| 维度 | 评估 |
|---|---|
| 适用训练营类型 | AI 工具链训练营、DevOps 自动化训练营、团队工程效能训练营 |
| 学员基础要求 | TypeScript 基础 + Shell 脚本 + Claude Code 交互模式使用经验 |
| 预计教学时长 | 4-6 小时(含实操) |
| 实操占比 | 70%(5 个渐进式练习:Print 模式 → 流式监听 → Hook 配置 → 多 Agent 并行 → 完整项目) |
| 硬件要求 | 每人需要 Claude Code API 额度(建议 $10/人) |
| 难度等级 | ⭐⭐⭐⭐(中高级,适合有 Claude Code 基础的开发者) |
| 最佳配套课程 | Round 2(Skills 开发)+ Round 3(MCP 服务器)可形成完整工具链 |
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
