job51-gitlab-cr-node-jt-1 2.4.1 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/GITLAB_CR_NODE_TECHNICAL_DOCS.md +1359 -0
- package/index.js +760 -716
- package/package.json +1 -1
|
@@ -0,0 +1,1359 @@
|
|
|
1
|
+
# GitLab Code Review AI Tool 技术文档
|
|
2
|
+
|
|
3
|
+
**项目名称**: job51-gitlab-cr-node
|
|
4
|
+
**当前版本**: 2.4.1
|
|
5
|
+
**作者**: tao.jing
|
|
6
|
+
**最后更新**: 2026-04-15
|
|
7
|
+
**项目地址**: https://gitdev.51job.com/51jobweb/ai-agent
|
|
8
|
+
**当前分支**: jt-test-skill
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 目录
|
|
13
|
+
|
|
14
|
+
1. [项目概述](#1-项目概述)
|
|
15
|
+
- [1.1 项目定位](#11-项目定位)
|
|
16
|
+
- [1.2 技术栈](#12-技术栈)
|
|
17
|
+
- [1.3 核心功能](#13-核心功能)
|
|
18
|
+
- [1.4 完整端到端流程图](#14-完整端到端流程图)
|
|
19
|
+
2. [系统架构](#2-系统架构)
|
|
20
|
+
3. [核心模块设计](#3-核心模块设计)
|
|
21
|
+
4. [关键算法与流程](#4-关键算法与流程)
|
|
22
|
+
5. [数据结构定义](#5-数据结构定义)
|
|
23
|
+
6. [配置与部署](#6-配置与部署)
|
|
24
|
+
- [6.0 CI 镜像分析](#60-ci-镜像分析)
|
|
25
|
+
- [6.1 环境变量](#61-环境变量)
|
|
26
|
+
7. [GitLab CI/CD 配置](#7-gitlab-cicd-配置)
|
|
27
|
+
8. [API 参考](#8-api-参考)
|
|
28
|
+
9. [代码审查规则体系](#9-代码审查规则体系)
|
|
29
|
+
10. [错误处理与容错机制](#10-错误处理与容错机制)
|
|
30
|
+
11. [性能优化策略](#11-性能优化策略)
|
|
31
|
+
12. [扩展与维护](#12-扩展与维护)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 1. 项目概述
|
|
36
|
+
|
|
37
|
+
### 1.1 项目定位
|
|
38
|
+
|
|
39
|
+
GitLab Code Review AI Tool 是一个基于 Claude AI 的自动化代码审查工具,专为 GitLab 合并请求(Merge Request)设计。该工具能够:
|
|
40
|
+
|
|
41
|
+
- 自动获取 MR 的代码变更(diff)
|
|
42
|
+
- 使用 Claude AI 对每个变更块进行深度代码审查
|
|
43
|
+
- 将审查结果以评论形式发布到 GitLab MR 的对应代码行
|
|
44
|
+
- 支持并发控制和增量审查
|
|
45
|
+
|
|
46
|
+
### 1.2 技术栈
|
|
47
|
+
|
|
48
|
+
| 组件 | 技术选型 | 版本 |
|
|
49
|
+
|------|----------|------|
|
|
50
|
+
| 运行时 | Node.js | 10+ |
|
|
51
|
+
| HTTP 客户端 | Axios | ^1.13.3 |
|
|
52
|
+
| 开发工具 | nodemon | ^3.0.1 |
|
|
53
|
+
| AI 引擎 | Claude CLI | 最新 |
|
|
54
|
+
|
|
55
|
+
### 1.3 核心功能
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
59
|
+
│ GitLab MR Code Review │
|
|
60
|
+
├─────────────────────────────────────────────────────────────┤
|
|
61
|
+
│ 1. 获取 MR Diffs ──→ 2. 拆分变更块 ──→ 3. AI 审查每个块 │
|
|
62
|
+
│ ↓ ↓ ↓ │
|
|
63
|
+
│ GitLab API 解析 diff 头 Claude 本地调用 │
|
|
64
|
+
│ ↓ ↓ ↓ │
|
|
65
|
+
│ 4. 发布评论 ←───────────────────────────────────── │
|
|
66
|
+
│ ↓ │
|
|
67
|
+
│ GitLab Discussion API (行级评论/一般讨论) │
|
|
68
|
+
└─────────────────────────────────────────────────────────────┘
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 1.4 完整端到端流程图
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
75
|
+
│ GitLab AI 代码审查完整流程 │
|
|
76
|
+
├─────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
|
77
|
+
│ │
|
|
78
|
+
│ ┌──────────────┐ │
|
|
79
|
+
│ │ 开发者提交 │ │
|
|
80
|
+
│ │ 代码 Push │ │
|
|
81
|
+
│ └──────┬───────┘ │
|
|
82
|
+
│ │ │
|
|
83
|
+
│ ▼ │
|
|
84
|
+
│ ┌──────────────┐ │
|
|
85
|
+
│ │ 创建 MR/PR │ │
|
|
86
|
+
│ └──────┬───────┘ │
|
|
87
|
+
│ │ │
|
|
88
|
+
│ ▼ │
|
|
89
|
+
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
|
90
|
+
│ │ GitLab CI Pipeline 触发 │ │
|
|
91
|
+
│ │ 触发条件: │ │
|
|
92
|
+
│ │ - CI_PIPELINE_SOURCE == "web" (手动触发) │ │
|
|
93
|
+
│ │ - CI_PIPELINE_SOURCE == "merge_request_event" (MR 事件) │ │
|
|
94
|
+
│ └──────────────────────────┬──────────────────────────────────────────────┘ │
|
|
95
|
+
│ │ │
|
|
96
|
+
│ ▼ │
|
|
97
|
+
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
|
98
|
+
│ │ Stage: review - AI 代码审查任务 │ │
|
|
99
|
+
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
|
|
100
|
+
│ │ │ 1. 检查流水线来源 │ │ │
|
|
101
|
+
│ │ │ - web → 直接执行 │ │ │
|
|
102
|
+
│ │ │ - merge_request_event → 检查未解决评论 │ │ │
|
|
103
|
+
│ │ └─────────────────────────────┬─────────────────────────────────────┘ │ │
|
|
104
|
+
│ │ │ │ │
|
|
105
|
+
│ │ ▼ │ │
|
|
106
|
+
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
|
|
107
|
+
│ │ │ 2. 未解决评论检测 (仅 MR 事件) │ │ │
|
|
108
|
+
│ │ │ GET /projects/:id/merge_requests/:iid/notes │ │ │
|
|
109
|
+
│ │ │ 过滤:type=DiffNote && author="AI 审查 - 乌萨奇" && resolved=false │ │ │
|
|
110
|
+
│ │ └─────────────────────────────┬─────────────────────────────────────┘ │ │
|
|
111
|
+
│ │ │ │ │
|
|
112
|
+
│ │ ┌───────────┴───────────┐ │ │
|
|
113
|
+
│ │ │ │ │ │
|
|
114
|
+
│ │ 有未解决评论 无未解决评论 │ │
|
|
115
|
+
│ │ (UNRESOLVED > 0) (UNRESOLVED = 0) │ │
|
|
116
|
+
│ │ │ │ │ │
|
|
117
|
+
│ │ ▼ ▼ │ │
|
|
118
|
+
│ │ ┌───────────────────────────┐ ┌───────────────────────────────────┐ │ │
|
|
119
|
+
│ │ │ 跳过审查 (exit 0) │ │ 继续执行审查 │ │ │
|
|
120
|
+
│ │ │ 避免重复评论 │ │ │ │ │
|
|
121
|
+
│ │ └───────────────────────────┘ │ │ │ │
|
|
122
|
+
│ │ └───────────┬───────────────────────┘ │ │
|
|
123
|
+
│ │ │ │ │
|
|
124
|
+
│ │ ▼ │ │
|
|
125
|
+
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
|
|
126
|
+
│ │ │ 3. 安装依赖 │ │ │
|
|
127
|
+
│ │ │ npm install -g job51-gitlab-cr-node-skill-prompt-optimize │ │ │
|
|
128
|
+
│ │ │ cp -r .claude $CI_PROJECT_DIR/ │ │ │
|
|
129
|
+
│ │ └─────────────────────────────┬─────────────────────────────────────┘ │ │
|
|
130
|
+
│ │ │ │ │
|
|
131
|
+
│ │ ▼ │ │
|
|
132
|
+
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
|
|
133
|
+
│ │ │ 4. 执行 gitlab-cr 命令 │ │ │
|
|
134
|
+
│ │ └─────────────────────────────┬─────────────────────────────────────┘ │ │
|
|
135
|
+
│ └────────────────────────────────│────────────────────────────────────────┘ │
|
|
136
|
+
│ │ │
|
|
137
|
+
│ ▼ │
|
|
138
|
+
│ ┌─────────────────────────────────────────────────────────────────────────────────────────────────┐ │
|
|
139
|
+
│ │ GitLabCodeReviewer 执行流程 │ │
|
|
140
|
+
│ │ ┌─────────────────────────────────────────────────────────────────────────────────────────┐ │ │
|
|
141
|
+
│ │ │ 4.1 获取 MR Diffs │ │ │
|
|
142
|
+
│ │ │ GET /projects/:id/merge_requests/:iid}/diffs │ │ │
|
|
143
|
+
│ │ │ 分页获取所有 diff 块 (per_page=20) │ │ │
|
|
144
|
+
│ │ └─────────────────────────────────┬───────────────────────────────────────────────────────┘ │ │
|
|
145
|
+
│ │ │ │ │
|
|
146
|
+
│ │ ▼ │ │
|
|
147
|
+
│ │ ┌─────────────────────────────────────────────────────────────────────────────────────────┐ │ │
|
|
148
|
+
│ │ │ 4.2 拆分变更块 │ │ │
|
|
149
|
+
│ │ │ 按 @@ ... @@ 分割 diff │ │ │
|
|
150
|
+
│ │ │ 提取行号信息 (old_start, new_start, old_count, new_count) │ │ │
|
|
151
|
+
│ │ └─────────────────────────────────┬───────────────────────────────────────────────────────┘ │ │
|
|
152
|
+
│ │ │ │ │
|
|
153
|
+
│ │ ▼ │ │
|
|
154
|
+
│ │ ┌─────────────────────────────────────────────────────────────────────────────────────────┐ │ │
|
|
155
|
+
│ │ │ 4.3 线程池并发处理 (maxConcurrency=3) │ │ │
|
|
156
|
+
│ │ │ ┌───────────────────────────────────────────────────────────────────────────────┐ │ │ │
|
|
157
|
+
│ │ │ │ 对每个 diff block 执行: │ │ │ │
|
|
158
|
+
│ │ │ │ a. 创建临时文件 (带元数据) │ │ │ │
|
|
159
|
+
│ │ │ │ b. 调用 Claude AI 审查 │ │ │ │
|
|
160
|
+
│ │ │ │ c. 解析 REPORT 和 LINE_INFO │ │ │ │
|
|
161
|
+
│ │ │ │ d. 清理临时文件 │ │ │ │
|
|
162
|
+
│ │ │ └───────────────────────────────────────────────────────────────────────────────┘ │ │ │
|
|
163
|
+
│ │ └─────────────────────────────────┬───────────────────────────────────────────────────────┘ │ │
|
|
164
|
+
│ │ │ │ │
|
|
165
|
+
│ │ ▼ │ │
|
|
166
|
+
│ │ ┌─────────────────────────────────────────────────────────────────────────────────────────┐ │ │
|
|
167
|
+
│ │ │ 4.4 AI 审查结果验证 (三级判断) │ │ │
|
|
168
|
+
│ │ │ ┌───────────────────────────────────────────────────────────────────────────────┐ │ │ │
|
|
169
|
+
│ │ │ │ 步骤 1: LINE_INFO 标签检查 │ │ │ │
|
|
170
|
+
│ │ │ │ 条件:hasLineInfoTag && hasNonEmptyLineInfo │ │ │ │
|
|
171
|
+
│ │ │ │ - 失败 → 返回空格式,跳过评论 │ │ │ │
|
|
172
|
+
│ │ │ │ - 通过 → 进入步骤 2 │ │ │ │
|
|
173
|
+
│ │ │ └───────────────────────────────────────────────────────────────────────────────┘ │ │ │
|
|
174
|
+
│ │ │ ┌───────────────────────────────────────────────────────────────────────────────┐ │ │ │
|
|
175
|
+
│ │ │ │ 步骤 2: 严重问题关键词检查 │ │ │ │
|
|
176
|
+
│ │ │ │ 条件:includes('严重问题') │ │ │ │
|
|
177
|
+
│ │ │ │ - 失败 → 无严重问题,跳过评论 │ │ │ │
|
|
178
|
+
│ │ │ │ - 通过 → 进入步骤 3 │ │ │ │
|
|
179
|
+
│ │ │ └───────────────────────────────────────────────────────────────────────────────┘ │ │ │
|
|
180
|
+
│ │ │ ┌───────────────────────────────────────────────────────────────────────────────┐ │ │ │
|
|
181
|
+
│ │ │ │ 步骤 3: 标题格式检查 │ │ │ │
|
|
182
|
+
│ │ │ │ 条件:includes('🤖 AI 代码审查结果') │ │ │ │
|
|
183
|
+
│ │ │ │ - 失败 → 重试 (最多 5 次) │ │ │ │
|
|
184
|
+
│ │ │ │ - 通过 → 接受结果 │ │ │ │
|
|
185
|
+
│ │ │ └───────────────────────────────────────────────────────────────────────────────┘ │ │ │
|
|
186
|
+
│ │ └─────────────────────────────────┬───────────────────────────────────────────────────────┘ │ │
|
|
187
|
+
│ │ │ │ │
|
|
188
|
+
│ │ ▼ │ │
|
|
189
|
+
│ │ ┌─────────────────────────────────────────────────────────────────────────────────────────┐ │ │
|
|
190
|
+
│ │ │ 4.5 评论发布决策 │ │ │
|
|
191
|
+
│ │ │ 条件:reportContent.includes('严重问题') │ │ │
|
|
192
|
+
│ │ │ - 不包含 → 跳过评论发布 │ │ │
|
|
193
|
+
│ │ │ - 包含 → 调用 postSingleCommentToGitLab │ │ │
|
|
194
|
+
│ │ └─────────────────────────────────┬───────────────────────────────────────────────────────┘ │ │
|
|
195
|
+
│ │ │ │ │
|
|
196
|
+
│ │ ▼ │ │
|
|
197
|
+
│ │ ┌─────────────────────────────────────────────────────────────────────────────────────────┐ │ │
|
|
198
|
+
│ │ │ 4.6 GitLab API 调用 │ │ │
|
|
199
|
+
│ │ │ a. 获取 MR 版本信息 (base_sha, head_sha, start_sha) │ │ │
|
|
200
|
+
│ │ │ b. 确定 targetLine │ │ │
|
|
201
|
+
│ │ │ - 优先:LINE_INFO 解析行号 (验证范围) │ │ │
|
|
202
|
+
│ │ │ - 后备:diff 块起始行号 │ │ │
|
|
203
|
+
│ │ │ c. 选择评论类型 │ │ │
|
|
204
|
+
│ │ │ - 有 targetLine → Diff 评论 (行级) │ │ │
|
|
205
|
+
│ │ │ - 无 targetLine → 一般讨论 (文件级) │ │ │
|
|
206
|
+
│ │ │ d. 调用 API + 降级处理 │ │ │
|
|
207
|
+
│ │ │ - Diff 评论失败 → 自动降级为一般讨论 │ │ │
|
|
208
|
+
│ │ └─────────────────────────────────────────────────────────────────────────────────────────┘ │ │
|
|
209
|
+
│ └─────────────────────────────────────────────────────────────────────────────────────────────────┘ │
|
|
210
|
+
│ │
|
|
211
|
+
│ ▼ │
|
|
212
|
+
│ ┌─────────────────────────────────────────────────────────────────────────────────────────────────┐ │
|
|
213
|
+
│ │ 5. 审查结束处理 │ │
|
|
214
|
+
│ │ ┌───────────────────────────────────────────────────────────────────────────────────────┐ │ │
|
|
215
|
+
│ │ │ 仅 MR 事件:发表评论提醒用户 │ │ │
|
|
216
|
+
│ │ │ POST /projects/:id/merge_requests/:iid/notes │ │ │
|
|
217
|
+
│ │ │ 内容:"本轮 ai 代码审查已结束,请 resolve 所有评论后..." │ │ │
|
|
218
|
+
│ │ └───────────────────────────────────────────────────────────────────────────────────────┘ │ │
|
|
219
|
+
│ └─────────────────────────────────────────────────────────────────────────────────────────────────┘ │
|
|
220
|
+
│ │
|
|
221
|
+
│ ▼ │
|
|
222
|
+
│ ┌─────────────────────────────────────────────────────────────────────────────────────────────────┐ │
|
|
223
|
+
│ │ 6. 开发者处理评论 │ │
|
|
224
|
+
│ │ - 查看 GitLab MR 中的 AI 审查评论 │ │
|
|
225
|
+
│ │ - 修复问题并提交代码 │ │
|
|
226
|
+
│ │ - resolve 已解决的评论 │ │
|
|
227
|
+
│ │ - 手动点击"Run again"开启新一轮审查 (如有需要) │ │
|
|
228
|
+
│ └─────────────────────────────────────────────────────────────────────────────────────────────────┘ │
|
|
229
|
+
│ │
|
|
230
|
+
└─────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 2. 系统架构
|
|
236
|
+
|
|
237
|
+
### 2.1 整体架构图
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
241
|
+
│ 环境变量配置层 │
|
|
242
|
+
│ CI_API_V4_URL │ GITLAB_ACCESS_TOKEN │ CI_PROJECT_ID │ ... │
|
|
243
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
244
|
+
↓
|
|
245
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
246
|
+
│ GitLabCodeReviewer 类 │
|
|
247
|
+
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
|
248
|
+
│ │ GitLabAPIClient │ │ reviewDiffWith │ │ postCommentsTo │ │
|
|
249
|
+
│ │ (GitLab 通信) │ │ ClaudeUsingFile │ │ GitLab │ │
|
|
250
|
+
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
|
251
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
252
|
+
↓
|
|
253
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
254
|
+
│ AI 审查层 │
|
|
255
|
+
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
256
|
+
│ │ Claude CLI (spawn 子进程) │ │
|
|
257
|
+
│ │ ┌───────────────┐ ┌───────────────┐ ┌─────────────┐ │ │
|
|
258
|
+
│ │ │ simple-code- │ │ code-review- │ │ SKILL.md │ │ │
|
|
259
|
+
│ │ │ review 技能 │ │ rules.md │ │ 模板 │ │ │
|
|
260
|
+
│ │ └───────────────┘ └───────────────┘ └─────────────┘ │ │
|
|
261
|
+
│ └─────────────────────────────────────────────────────────┘ │
|
|
262
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### 2.2 模块依赖关系
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
index.js (主入口)
|
|
269
|
+
├── utils.js (工具模块)
|
|
270
|
+
│ ├── GitLabAPIClient (GitLab API 封装)
|
|
271
|
+
│ ├── debugLog (日志函数)
|
|
272
|
+
│ └── extractReportContent (报告提取)
|
|
273
|
+
├── .claude/skills/simple-code-review/SKILL.md (审查技能定义)
|
|
274
|
+
└── .claude/rules/code-review-rules.md (审查规则)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## 3. 核心模块设计
|
|
280
|
+
|
|
281
|
+
### 3.1 GitLabCodeReviewer 类
|
|
282
|
+
|
|
283
|
+
**位置**: `index.js:8-634`
|
|
284
|
+
|
|
285
|
+
**构造函数**:
|
|
286
|
+
```javascript
|
|
287
|
+
constructor(gitlabToken, gitlabUrl = null)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**核心职责**:
|
|
291
|
+
1. 初始化 GitLab API 客户端
|
|
292
|
+
2. 获取 MR 的 diff 信息
|
|
293
|
+
3. 调用 AI 进行代码审查
|
|
294
|
+
4. 将审查结果发布到 GitLab
|
|
295
|
+
|
|
296
|
+
### 3.2 GitLabAPIClient 类
|
|
297
|
+
|
|
298
|
+
**位置**: `utils.js:31-68`
|
|
299
|
+
|
|
300
|
+
**封装的 GitLab API 调用**:
|
|
301
|
+
```javascript
|
|
302
|
+
class GitLabAPIClient {
|
|
303
|
+
constructor(gitlabToken, gitlabUrl)
|
|
304
|
+
async callGitLabAPI(endpoint, options)
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**API 端点**:
|
|
309
|
+
| 端点 | 方法 | 用途 |
|
|
310
|
+
|------|------|------|
|
|
311
|
+
| `/projects/{id}/merge_requests/{iid}/diffs` | GET | 获取 MR 变更 |
|
|
312
|
+
| `/projects/{id}/merge_requests/{iid}/versions` | GET | 获取版本信息 |
|
|
313
|
+
| `/projects/{id}/merge_requests/{iid}/discussions` | POST | 发布评论 |
|
|
314
|
+
|
|
315
|
+
### 3.3 AI 审查模块
|
|
316
|
+
|
|
317
|
+
**审查流程**:
|
|
318
|
+
```
|
|
319
|
+
1. 创建临时 diff 文件 (带元数据)
|
|
320
|
+
↓
|
|
321
|
+
2. 调用 Claude CLI (--tools default -p)
|
|
322
|
+
↓
|
|
323
|
+
3. 解析 REPORT 和 LINE_INFO 标签
|
|
324
|
+
↓
|
|
325
|
+
4. 验证结果 (严重问题检查 + 标题验证)
|
|
326
|
+
↓
|
|
327
|
+
5. 重试机制 (最多 5 次)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**重试策略** (`index.js:55-325`):
|
|
331
|
+
- 最大重试次数:5 次
|
|
332
|
+
- 递增等待时间:`1000 * attempts` 毫秒
|
|
333
|
+
- 验证条件:
|
|
334
|
+
1. LINE_INFO 标签存在且非空
|
|
335
|
+
2. 包含"严重问题"关键词时,标题必须为"🤖 AI 代码审查结果"
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## 4. 关键算法与流程
|
|
340
|
+
|
|
341
|
+
### 4.1 严重问题判断逻辑(核心)
|
|
342
|
+
|
|
343
|
+
**位置**: `index.js:55-133`
|
|
344
|
+
|
|
345
|
+
**判断流程图**:
|
|
346
|
+
```
|
|
347
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
348
|
+
│ AI 审查结果验证流程 │
|
|
349
|
+
├─────────────────────────────────────────────────────────────┤
|
|
350
|
+
│ │
|
|
351
|
+
│ 步骤 1: 提取 LINE_INFO 标签 │
|
|
352
|
+
│ ┌───────────────────────────────────────────────────────┐ │
|
|
353
|
+
│ │ const lineInfoMatch = claudeResult.match( │ │
|
|
354
|
+
│ │ /<LINE_INFO>\s*\[([^\]]*)\]\s*<\/LINE_INFO>/ │ │
|
|
355
|
+
│ │ ); │ │
|
|
356
|
+
│ └───────────────────────────────────────────────────────┘ │
|
|
357
|
+
│ ↓ │
|
|
358
|
+
│ 步骤 2: 判断 LINE_INFO 是否为空 │
|
|
359
|
+
│ ┌───────────────────────────────────────────────────────┐ │
|
|
360
|
+
│ │ 条件:!hasLineInfoTag || !hasNonEmptyLineInfo │ │
|
|
361
|
+
│ │ 结果 → 无问题,返回标准空格式 │ │
|
|
362
|
+
│ │ reportContent: '<REPORT>\n## 🤖 AI 代码审查结果\n\n</REPORT>'│ │
|
|
363
|
+
│ │ lineInfo: '[]' │ │
|
|
364
|
+
│ └───────────────────────────────────────────────────────┘ │
|
|
365
|
+
│ ↓ (LINE_INFO 非空) │
|
|
366
|
+
│ 步骤 3: 检查是否包含"严重问题"关键词 │
|
|
367
|
+
│ ┌───────────────────────────────────────────────────────┐ │
|
|
368
|
+
│ │ const hasSeriousProblem = │ │
|
|
369
|
+
│ │ claudeResult.includes('严重问题') │ │
|
|
370
|
+
│ │ │ │
|
|
371
|
+
│ │ 如果 false → 无严重问题 │ │
|
|
372
|
+
│ │ 返回:{ reportContent: claudeResult, │ │
|
|
373
|
+
│ │ lineInfo: lineInfoContent } │ │
|
|
374
|
+
│ └───────────────────────────────────────────────────────┘ │
|
|
375
|
+
│ ↓ (有严重问题) │
|
|
376
|
+
│ 步骤 4: 检查标题是否符合要求 │
|
|
377
|
+
│ ┌───────────────────────────────────────────────────────┐ │
|
|
378
|
+
│ │ const hasValidTitle = claudeResult.includes( │ │
|
|
379
|
+
│ │ '🤖 AI 代码审查结果' │ │
|
|
380
|
+
│ │ ); │ │
|
|
381
|
+
│ │ │ │
|
|
382
|
+
│ │ 如果 true → 接受结果,返回 extractReportContent() │ │
|
|
383
|
+
│ │ 如果 false → 重试 (最多 5 次) │ │
|
|
384
|
+
│ └───────────────────────────────────────────────────────┘ │
|
|
385
|
+
└─────────────────────────────────────────────────────────────┘
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**三级判断条件**:
|
|
389
|
+
|
|
390
|
+
| 判断步骤 | 检查内容 | 判断条件 | 通过时动作 | 失败时动作 |
|
|
391
|
+
|----------|----------|----------|------------|------------|
|
|
392
|
+
| **步骤 1** | LINE_INFO 标签存在性 | `hasLineInfoTag && hasNonEmptyLineInfo` | 进入步骤 2 | 返回空格式 |
|
|
393
|
+
| **步骤 2** | 严重问题关键词 | `claudeResult.includes('严重问题')` | 进入步骤 3 | 返回原报告 |
|
|
394
|
+
| **步骤 3** | 标题格式正确性 | `claudeResult.includes('🤖 AI 代码审查结果')` | 接受结果 | 重试 (最多 5 次) |
|
|
395
|
+
|
|
396
|
+
**重试策略详情**:
|
|
397
|
+
```javascript
|
|
398
|
+
// 递增等待时间:1s, 2s, 3s, 4s, 5s
|
|
399
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
|
|
400
|
+
|
|
401
|
+
// 达到最大重试次数后的处理
|
|
402
|
+
if (attempts >= maxAttempts) {
|
|
403
|
+
debugLog(`【决策】已达到最大重试次数 ${maxAttempts},返回最后一次结果`);
|
|
404
|
+
return extractReportContent(claudeResult); // 强制返回最后一次结果
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**决策日志输出** (便于调试):
|
|
409
|
+
```
|
|
410
|
+
[DEBUG] ... LINE_INFO 检查结果:hasLineInfoTag=true, hasNonEmptyLineInfo=true, lineInfoContent=[{...}]
|
|
411
|
+
[DEBUG] ... 严重问题检查:hasSeriousProblem=true
|
|
412
|
+
[DEBUG] ... 标题检查:hasValidTitle=true
|
|
413
|
+
[DEBUG] ... 【决策】报告包含严重问题且标题正确,接受结果 (尝试 1)
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### 4.2 评论发布决策逻辑
|
|
417
|
+
|
|
418
|
+
**位置**: `index.js:173-183`
|
|
419
|
+
|
|
420
|
+
**发布条件**:
|
|
421
|
+
```javascript
|
|
422
|
+
// 只有包含"严重问题"的审查结果才会发布评论
|
|
423
|
+
if (blockObj.review_result &&
|
|
424
|
+
blockObj.review_result.reportContent &&
|
|
425
|
+
blockObj.review_result.reportContent.includes('严重问题')) {
|
|
426
|
+
await this.postSingleCommentToGitLab(...);
|
|
427
|
+
} else {
|
|
428
|
+
debugLog(`该块不包含严重问题,跳过评论发布`);
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**决策流程图**:
|
|
433
|
+
```
|
|
434
|
+
┌─────────────────────┐
|
|
435
|
+
│ 获取审查结果 │
|
|
436
|
+
└──────────┬──────────┘
|
|
437
|
+
↓
|
|
438
|
+
┌─────────────────────┐
|
|
439
|
+
│ review_result 存在? │
|
|
440
|
+
└──────────┬──────────┘
|
|
441
|
+
Yes │ No
|
|
442
|
+
↓ └──────────────┐
|
|
443
|
+
┌─────────────────────┐ │
|
|
444
|
+
│ reportContent 存在?│ │
|
|
445
|
+
└──────────┬──────────┘ │
|
|
446
|
+
Yes │ No │
|
|
447
|
+
↓ └───────────────┤
|
|
448
|
+
┌─────────────────────┐ │
|
|
449
|
+
│ 包含"严重问题"? │ │
|
|
450
|
+
└──────────┬──────────┘ │
|
|
451
|
+
Yes │ No │
|
|
452
|
+
↓ └───────────────┤
|
|
453
|
+
┌─────────────────────┐ │
|
|
454
|
+
│ 发布评论到 GitLab │ │
|
|
455
|
+
└──────────┬──────────┘ │
|
|
456
|
+
│ │
|
|
457
|
+
└─────────┬─────────┘
|
|
458
|
+
↓
|
|
459
|
+
┌─────────────────────┐
|
|
460
|
+
│ 跳过评论发布 │
|
|
461
|
+
│ (记录调试日志) │
|
|
462
|
+
└─────────────────────┘
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### 4.3 Diff 块拆分算法
|
|
466
|
+
|
|
467
|
+
**位置**: `index.js:380-428`
|
|
468
|
+
|
|
469
|
+
**算法描述**:
|
|
470
|
+
```javascript
|
|
471
|
+
getDiffBlocks(diffObj) {
|
|
472
|
+
// 1. 使用正则分割 diff 块
|
|
473
|
+
const regex = /(?=@@\s-\d+(?:,\d+)?\s\+\d+(?:,\d+)?\s@@)/g;
|
|
474
|
+
const diffBlocks = diffObj.diff.split(regex);
|
|
475
|
+
|
|
476
|
+
// 2. 解析每个块的头信息
|
|
477
|
+
const headerRegex = /@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
478
|
+
|
|
479
|
+
// 3. 提取行号信息
|
|
480
|
+
return diffBlocks.map(block => ({
|
|
481
|
+
diff: block,
|
|
482
|
+
line_info: { old_start, old_count, new_start, new_count }
|
|
483
|
+
}));
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
**行号计算规则**:
|
|
488
|
+
| 行前缀 | 行号计算 | 说明 |
|
|
489
|
+
|--------|----------|------|
|
|
490
|
+
| `+` | `current_line++` | 新增代码,计入行号 |
|
|
491
|
+
| 空格 | `current_line++` | 上下文代码,计入行号 |
|
|
492
|
+
| `-` | 不变 | 删除代码,不计入行号 |
|
|
493
|
+
|
|
494
|
+
### 4.4 评论发布核心逻辑(详细)
|
|
495
|
+
|
|
496
|
+
**位置**: `index.js:497-589`
|
|
497
|
+
|
|
498
|
+
**完整发布流程**:
|
|
499
|
+
```
|
|
500
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
501
|
+
│ 评论发布完整流程 │
|
|
502
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
503
|
+
│ │
|
|
504
|
+
│ 步骤 1: 获取 MR 版本信息 (SHA 值) │
|
|
505
|
+
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
506
|
+
│ │ versionInfo = getMergeRequestVersions() │ │
|
|
507
|
+
│ │ - base_sha: 基础提交 SHA │ │
|
|
508
|
+
│ │ - head_sha: 最新提交 SHA │ │
|
|
509
|
+
│ │ - start_sha: 起始提交 SHA │ │
|
|
510
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
511
|
+
│ ↓ │
|
|
512
|
+
│ 步骤 2: 确定目标行号 (targetLine) │
|
|
513
|
+
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
514
|
+
│ │ 优先级 1: LINE_INFO 标签解析的行号 (最精确) │ │
|
|
515
|
+
│ │ 优先级 2: diff 块起始行号 (后备方案) │ │
|
|
516
|
+
│ │ │ │
|
|
517
|
+
│ │ 验证:parsedLineInfo.new_line 必须在 [new_start, new_end] 范围内 │ │
|
|
518
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
519
|
+
│ ↓ │
|
|
520
|
+
│ 步骤 3: 选择评论类型 │
|
|
521
|
+
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
522
|
+
│ │ 有 targetLine → Diff 评论 (行级评论,定位到具体代码行) │ │
|
|
523
|
+
│ │ 无 targetLine → 一般讨论 (文件级评论) │ │
|
|
524
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
525
|
+
│ ↓ │
|
|
526
|
+
│ 步骤 4: 调用 GitLab API 发布评论 │
|
|
527
|
+
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
528
|
+
│ │ Diff 评论失败 → 自动降级为一般讨论 │ │
|
|
529
|
+
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
530
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**targetLine 确定逻辑**:
|
|
534
|
+
```javascript
|
|
535
|
+
// 第一层:根据 diff 块第一行字符类型确定基础 targetLine
|
|
536
|
+
let targetLine = lineInfo.firstLineFirstChar === '+'
|
|
537
|
+
? { new_line: lineInfo.new_start, new_path: diff_info.new_path }
|
|
538
|
+
: lineInfo.firstLineFirstChar === '-'
|
|
539
|
+
? { old_line: lineInfo.old_start, old_path: diff_info.old_path }
|
|
540
|
+
: { new_line: lineInfo.new_start, new_path: diff_info.new_path };
|
|
541
|
+
|
|
542
|
+
// 第二层:尝试从 LINE_INFO 标签解析更精确的行号
|
|
543
|
+
const parsedLineInfo = this.parseLineInfoFromReviewResult(lineInfoTag);
|
|
544
|
+
if (parsedLineInfo) {
|
|
545
|
+
// 验证行号是否在 diff 块范围内
|
|
546
|
+
const newStart = lineInfo.new_start;
|
|
547
|
+
const newCount = lineInfo.new_count || 0;
|
|
548
|
+
const newEnd = newStart + newCount - 1;
|
|
549
|
+
|
|
550
|
+
if (parsedLineInfo.new_line >= newStart && parsedLineInfo.new_line <= newEnd) {
|
|
551
|
+
// 行号有效,使用解析结果
|
|
552
|
+
targetLine = parsedLineInfo;
|
|
553
|
+
} else {
|
|
554
|
+
// 行号越界,使用 diff 块起始行号
|
|
555
|
+
debugLog(`解析的行号 ${parsedLineInfo.new_line} 不在 diff 块范围 [${newStart}, ${newEnd}] 内`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
**GitLab API Payload 结构**:
|
|
561
|
+
```javascript
|
|
562
|
+
// Diff 评论 Payload
|
|
563
|
+
const payload = {
|
|
564
|
+
body: reviewContent, // 审查报告内容
|
|
565
|
+
position: {
|
|
566
|
+
position_type: 'text', // 文本位置类型
|
|
567
|
+
base_sha: baseSha, // 基础提交 SHA
|
|
568
|
+
head_sha: headSha, // 最新提交 SHA
|
|
569
|
+
start_sha: startSha, // 起始提交 SHA
|
|
570
|
+
new_line: 45, // 目标行号 (new_line 或 old_line 二选一)
|
|
571
|
+
new_path: 'file.java' // 文件路径
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
// 一般讨论 Payload
|
|
576
|
+
const discussionData = {
|
|
577
|
+
body: `**代码审核评论 - 文件:${file_path}**\n\n${review_result}`
|
|
578
|
+
};
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
**降级策略**:
|
|
582
|
+
```javascript
|
|
583
|
+
try {
|
|
584
|
+
await this.createDiffDiscussion(projectId, mergeRequestIid, payload);
|
|
585
|
+
debugLog(`评论已发布到文件的变更区域`);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
// Diff 评论失败 → 降级为一般讨论
|
|
588
|
+
debugLog(`GitLab API 错误详情:${JSON.stringify(error.response?.data)}`);
|
|
589
|
+
await this.createGeneralDiscussion(...);
|
|
590
|
+
debugLog(`评论已发布 (作为一般讨论)`);
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
**评论类型对比**:
|
|
595
|
+
| 评论类型 | 触发条件 | API 端点 | 特点 |
|
|
596
|
+
|----------|----------|----------|------|
|
|
597
|
+
| Diff 评论 (行级) | 有 targetLine | POST /discussions (带 position) | 显示在代码变更区域,可被回复 |
|
|
598
|
+
| 一般讨论 (文件级) | 无 targetLine 或 API 失败 | POST /discussions (不带 position) | 显示在 MR 讨论区,作为普通评论 |
|
|
599
|
+
|
|
600
|
+
### 4.5 线程池并发控制
|
|
601
|
+
|
|
602
|
+
**位置**: `index.js:335-373`
|
|
603
|
+
|
|
604
|
+
**并发策略**:
|
|
605
|
+
```javascript
|
|
606
|
+
async processWithThreadPool(tasks, processor, maxConcurrency = 3) {
|
|
607
|
+
const executing = [];
|
|
608
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
609
|
+
const promise = processor(tasks[i])
|
|
610
|
+
.then(result => {
|
|
611
|
+
executing.splice(executing.indexOf(promise), 1);
|
|
612
|
+
return result;
|
|
613
|
+
});
|
|
614
|
+
executing.push(promise);
|
|
615
|
+
|
|
616
|
+
if (executing.length >= maxConcurrency) {
|
|
617
|
+
await Promise.race(executing); // 等待至少一个完成
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
await Promise.all(executing);
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
**配置方式**:
|
|
625
|
+
```bash
|
|
626
|
+
export GITLAB_CR_CONCURRENCY=5 # 默认 3
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### 4.6 行号解析算法
|
|
630
|
+
|
|
631
|
+
**位置**: `index.js:435-470`
|
|
632
|
+
|
|
633
|
+
**LINE_INFO 解析函数**:
|
|
634
|
+
```javascript
|
|
635
|
+
parseLineInfoFromReviewResult(lineInfoTag) {
|
|
636
|
+
// 1. 从标签中提取 JSON 内容
|
|
637
|
+
const jsonContent = lineInfoTag
|
|
638
|
+
.replace(/<LINE_INFO>\s*/g, '')
|
|
639
|
+
.replace(/\s*<\/LINE_INFO>/g, '')
|
|
640
|
+
.trim();
|
|
641
|
+
|
|
642
|
+
// 2. 空数组处理 → 使用后备方案
|
|
643
|
+
if (jsonContent === '[]') {
|
|
644
|
+
debugLog('LINE_INFO 为空数组,使用 diff 块起始行号作为后备方案');
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// 3. 解析 JSON 数组
|
|
649
|
+
const lineInfoArray = JSON.parse(jsonContent);
|
|
650
|
+
|
|
651
|
+
// 4. 返回第一个问题的行号信息
|
|
652
|
+
if (Array.isArray(lineInfoArray) && lineInfoArray.length > 0) {
|
|
653
|
+
const lineInfo = lineInfoArray[0];
|
|
654
|
+
return {
|
|
655
|
+
new_path: lineInfo.new_path,
|
|
656
|
+
new_line: lineInfo.new_line
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return null; // 解析失败,使用后备方案
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
**GitLab API 约束**:
|
|
665
|
+
| 约束项 | 说明 |
|
|
666
|
+
|--------|------|
|
|
667
|
+
| new_line / old_line 互斥 | 只能传递其中一个,不能同时存在 |
|
|
668
|
+
| 新增代码 (`+`) | 使用 `new_line` + `new_path` |
|
|
669
|
+
| 删除代码 (`-`) | 使用 `old_line` + `old_path` |
|
|
670
|
+
| 上下文代码 | 使用 `new_line` + `new_path` |
|
|
671
|
+
|
|
672
|
+
**行号验证逻辑**:
|
|
673
|
+
```
|
|
674
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
675
|
+
│ 行号验证流程 │
|
|
676
|
+
├─────────────────────────────────────────────────────────────┤
|
|
677
|
+
│ │
|
|
678
|
+
│ 1. 从 LINE_INFO 解析行号 → parsedLineInfo.new_line │
|
|
679
|
+
│ ↓ │
|
|
680
|
+
│ 2. 计算 diff 块范围 │
|
|
681
|
+
│ new_start = line_info.new_start │
|
|
682
|
+
│ new_end = new_start + new_count - 1 │
|
|
683
|
+
│ ↓ │
|
|
684
|
+
│ 3. 验证:parsedLineInfo.new_line ∈ [new_start, new_end] │
|
|
685
|
+
│ ↓ │
|
|
686
|
+
│ 4. 判断: │
|
|
687
|
+
│ - 在范围内 → 使用解析行号 (精确) │
|
|
688
|
+
│ - 越界 → 使用 diff 块起始行号 (后备) │
|
|
689
|
+
└─────────────────────────────────────────────────────────────┘
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### 4.7 评论发布策略
|
|
693
|
+
|
|
694
|
+
**增量发布模式** (`index.js:174-183`):
|
|
695
|
+
```javascript
|
|
696
|
+
// 只有包含严重问题才发布评论
|
|
697
|
+
if (review_result.includes('严重问题')) {
|
|
698
|
+
await this.postSingleCommentToGitLab(...);
|
|
699
|
+
} else {
|
|
700
|
+
debugLog('该块不包含严重问题,跳过评论发布');
|
|
701
|
+
}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
**行级评论定位** (`index.js:529-552`):
|
|
705
|
+
```javascript
|
|
706
|
+
// 优先使用 LINE_INFO 解析的行号
|
|
707
|
+
const parsedLineInfo = this.parseLineInfoFromReviewResult(lineInfoTag);
|
|
708
|
+
if (parsedLineInfo.new_line 在 diff 块范围内) {
|
|
709
|
+
targetLine = parsedLineInfo;
|
|
710
|
+
} else {
|
|
711
|
+
targetLine = diff_block_start_line; // 后备方案
|
|
712
|
+
}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
### 4.8 完整流程图:从 AI 审查到评论发布
|
|
716
|
+
|
|
717
|
+
```
|
|
718
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
719
|
+
│ 完整审查与评论发布流程 │
|
|
720
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
721
|
+
│ │
|
|
722
|
+
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
723
|
+
│ │ 阶段 1: AI 审查 │ │
|
|
724
|
+
│ │ 1.1 调用 Claude CLI 审核 diff 块 │ │
|
|
725
|
+
│ │ 1.2 获取审查结果 (包含 REPORT 和 LINE_INFO 标签) │ │
|
|
726
|
+
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
727
|
+
│ ↓ │
|
|
728
|
+
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
729
|
+
│ │ 阶段 2: 严重问题判断 (三级验证) │ │
|
|
730
|
+
│ │ 2.1 LINE_INFO 标签检查 │ │
|
|
731
|
+
│ │ - 无标签或空数组 → 无问题,返回空格式,跳过评论 │ │
|
|
732
|
+
│ │ - 有行号 → 进入下一步 │ │
|
|
733
|
+
│ │ 2.2 严重问题关键词检查 │ │
|
|
734
|
+
│ │ - 不包含"严重问题" → 无严重问题,跳过评论 │ │
|
|
735
|
+
│ │ - 包含"严重问题" → 进入下一步 │ │
|
|
736
|
+
│ │ 2.3 标题格式检查 │ │
|
|
737
|
+
│ │ - 包含"🤖 AI 代码审查结果" → 接受结果 │ │
|
|
738
|
+
│ │ - 不包含 → 重试 (最多 5 次) │ │
|
|
739
|
+
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
740
|
+
│ ↓ │
|
|
741
|
+
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
742
|
+
│ │ 阶段 3: 评论发布决策 │ │
|
|
743
|
+
│ │ 3.1 检查 review_result.reportContent 是否包含"严重问题" │ │
|
|
744
|
+
│ │ - 不包含 → 跳过评论发布 │ │
|
|
745
|
+
│ │ - 包含 → 调用 postSingleCommentToGitLab │ │
|
|
746
|
+
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
747
|
+
│ ↓ │
|
|
748
|
+
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
749
|
+
│ │ 阶段 4: GitLab API 调用 │ │
|
|
750
|
+
│ │ 4.1 获取 MR 版本信息 (base_sha, head_sha, start_sha) │ │
|
|
751
|
+
│ │ 4.2 确定 targetLine │ │
|
|
752
|
+
│ │ - 优先:LINE_INFO 解析行号 (验证范围) │ │
|
|
753
|
+
│ │ - 后备:diff 块起始行号 │ │
|
|
754
|
+
│ │ 4.3 选择评论类型 │ │
|
|
755
|
+
│ │ - 有 targetLine → Diff 评论 (行级) │ │
|
|
756
|
+
│ │ - 无 targetLine → 一般讨论 (文件级) │ │
|
|
757
|
+
│ │ 4.4 调用 API + 降级处理 │ │
|
|
758
|
+
│ │ - Diff 评论失败 → 自动降级为一般讨论 │ │
|
|
759
|
+
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
760
|
+
│ │
|
|
761
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
**关键决策点速查表**:
|
|
765
|
+
|
|
766
|
+
| 决策点 | 位置 | 判断条件 | 通过 → | 失败 → |
|
|
767
|
+
|--------|------|----------|--------|--------|
|
|
768
|
+
| LINE_INFO 检查 | `index.js:81-94` | `hasLineInfoTag && hasNonEmptyLineInfo` | 继续检查 | 返回空格式 |
|
|
769
|
+
| 严重问题检查 | `index.js:98-106` | `includes('严重问题')` | 继续检查 | 返回原报告 |
|
|
770
|
+
| 标题格式检查 | `index.js:109-123` | `includes('🤖 AI 代码审查结果')` | 接受结果 | 重试 |
|
|
771
|
+
| 评论发布检查 | `index.js:174-183` | `reportContent.includes('严重问题')` | 发布评论 | 跳过 |
|
|
772
|
+
| 行号验证 | `index.js:546-551` | `new_line ∈ [new_start, new_end]` | 使用解析行号 | 使用起始行号 |
|
|
773
|
+
| API 降级 | `index.js:570-579` | `createDiffDiscussion` 成功 | Diff 评论 | 一般讨论 |
|
|
774
|
+
|
|
775
|
+
---
|
|
776
|
+
|
|
777
|
+
## 5. 数据结构定义
|
|
778
|
+
|
|
779
|
+
### 5.1 Diff 对象结构
|
|
780
|
+
|
|
781
|
+
```javascript
|
|
782
|
+
{
|
|
783
|
+
diff: string, // unified diff 内容
|
|
784
|
+
new_path: string, // 新文件路径
|
|
785
|
+
old_path: string, // 旧文件路径
|
|
786
|
+
a_mode: string, // 旧文件权限
|
|
787
|
+
b_mode: string, // 新文件权限
|
|
788
|
+
new_file: boolean, // 是否新增文件
|
|
789
|
+
renamed_file: boolean, // 是否重命名
|
|
790
|
+
deleted_file: boolean, // 是否删除
|
|
791
|
+
generated_file: boolean,// 是否生成文件
|
|
792
|
+
block_index: number, // 块索引
|
|
793
|
+
line_info: {
|
|
794
|
+
old_start: number,
|
|
795
|
+
old_count: number,
|
|
796
|
+
new_start: number,
|
|
797
|
+
new_count: number,
|
|
798
|
+
firstLineFirstChar: string // '+', '-', 或 ' '
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
### 5.2 审查结果结构
|
|
804
|
+
|
|
805
|
+
```javascript
|
|
806
|
+
{
|
|
807
|
+
reportContent: string, // <REPORT>...</REPORT> 内容
|
|
808
|
+
lineInfo: string // <LINE_INFO>[...]</LINE_INFO> 内容
|
|
809
|
+
}
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
### 5.3 LINE_INFO 格式
|
|
813
|
+
|
|
814
|
+
```json
|
|
815
|
+
[
|
|
816
|
+
{
|
|
817
|
+
"new_path": "src/main/java/com/example/UserService.java",
|
|
818
|
+
"new_line": 45,
|
|
819
|
+
"old_path": "src/main/java/com/example/UserService.java",
|
|
820
|
+
"old_line": 43
|
|
821
|
+
}
|
|
822
|
+
]
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
---
|
|
826
|
+
|
|
827
|
+
## 6. 配置与部署
|
|
828
|
+
|
|
829
|
+
### 6.0 CI 镜像分析
|
|
830
|
+
|
|
831
|
+
**镜像名称**: `harbor.51job.com/codereview/ai-ide-cli:20251227`
|
|
832
|
+
|
|
833
|
+
**镜像基本信息**:
|
|
834
|
+
| 属性 | 值 |
|
|
835
|
+
|------|-----|
|
|
836
|
+
| 镜像 ID | sha256:618b55c7e9c256838de774c06e575d5c5efaca0cde9a836692866b3e51d0b85e |
|
|
837
|
+
| 架构 | amd64 |
|
|
838
|
+
| 大小 | 462.8 MB (压缩后) / 1.76 GB (解压后) |
|
|
839
|
+
| 构建时间 | 2025-12-27 |
|
|
840
|
+
| 最后标记时间 | 2026-03-30 |
|
|
841
|
+
|
|
842
|
+
**基础环境**:
|
|
843
|
+
| 组件 | 版本 | 说明 |
|
|
844
|
+
|------|------|------|
|
|
845
|
+
| 操作系统 | Debian/Ubuntu 系 | 基于 Linux 的容器环境 |
|
|
846
|
+
| Node.js | v18.20.8 | LTS 版本 |
|
|
847
|
+
| npm | 10.8.2 | Node 包管理器 |
|
|
848
|
+
| Yarn | 1.22.22 | 通过 corepack 管理 |
|
|
849
|
+
| Git | 2.39.5 | 版本控制工具 |
|
|
850
|
+
| curl | 7.88.1 | HTTP 客户端工具 |
|
|
851
|
+
|
|
852
|
+
**预装工具**:
|
|
853
|
+
| 工具 | 版本 | 用途 |
|
|
854
|
+
|------|------|------|
|
|
855
|
+
| @anthropic-ai/claude-code | 2.0.76 | Claude CLI 代码审查工具 |
|
|
856
|
+
| corepack | 0.32.0 | Node.js 包管理器代理 |
|
|
857
|
+
|
|
858
|
+
**环境变量配置**:
|
|
859
|
+
| 变量名 | 默认值 | 说明 |
|
|
860
|
+
|--------|--------|------|
|
|
861
|
+
| `NODE_VERSION` | 18.20.8 | Node.js 版本号 |
|
|
862
|
+
| `YARN_VERSION` | 1.22.22 | Yarn 版本号 |
|
|
863
|
+
| `ANTHROPIC_BASE_URL` | https://dashscope.aliyuncs.com/apps/anthropic | AI API 地址 |
|
|
864
|
+
| `API_TIMEOUT_MS` | 3000000 | API 超时时间 (50 分钟) |
|
|
865
|
+
| `ANTHROPIC_AUTH_TOKEN` | (空) | AI API 认证 Token |
|
|
866
|
+
| `ANTHROPIC_MODEL` | (空) | 主 AI 模型 |
|
|
867
|
+
| `ANTHROPIC_SMALL_FAST_MODEL` | (空) | 快速 AI 模型 |
|
|
868
|
+
|
|
869
|
+
**curl 支持库**:
|
|
870
|
+
- OpenSSL 3.0.16 (HTTPS/TLS 支持)
|
|
871
|
+
- zlib 1.2.13 (压缩)
|
|
872
|
+
- brotli 1.0.9 (压缩)
|
|
873
|
+
- zstd 1.5.4 (压缩)
|
|
874
|
+
- libidn2 2.3.3 (国际化域名)
|
|
875
|
+
- libssh2 1.10.0 (SSH 支持)
|
|
876
|
+
- nghttp2 1.52.0 (HTTP/2 支持)
|
|
877
|
+
- OpenLDAP 2.5.13 (LDAP 支持)
|
|
878
|
+
|
|
879
|
+
**镜像用途**:
|
|
880
|
+
该镜像是专为 AI 代码审查流程定制的容器环境,预装了:
|
|
881
|
+
1. Node.js 18 运行时环境
|
|
882
|
+
2. Claude Code CLI 工具 (2.0.76 版本)
|
|
883
|
+
3. Git、curl 等必要的开发工具
|
|
884
|
+
4. 配置了灵积 AI API 的基础 URL
|
|
885
|
+
|
|
886
|
+
### 6.1 环境变量
|
|
887
|
+
|
|
888
|
+
| 变量名 | 必填 | 默认值 | 说明 |
|
|
889
|
+
|--------|------|--------|------|
|
|
890
|
+
| `CI_API_V4_URL` | 是 | - | GitLab API v4 地址 |
|
|
891
|
+
| `GITLAB_ACCESS_TOKEN` | 是 | - | GitLab 访问令牌 |
|
|
892
|
+
| `CI_PROJECT_ID` | 是 | - | GitLab 项目 ID |
|
|
893
|
+
| `CI_MERGE_REQUEST_IID` | 是 | - | 合并请求 IID |
|
|
894
|
+
| `GITLAB_CR_CONCURRENCY` | 否 | 3 | 最大并发审查数 |
|
|
895
|
+
| `ANTHROPIC_MODEL` | 否 | qwen3.5-plus | AI 模型配置 |
|
|
896
|
+
|
|
897
|
+
### 6.2 安装方式
|
|
898
|
+
|
|
899
|
+
**全局安装**:
|
|
900
|
+
```bash
|
|
901
|
+
npm install -g job51-gitlab-cr-node
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
**本地开发**:
|
|
905
|
+
```bash
|
|
906
|
+
npm install
|
|
907
|
+
npm run dev # 使用 nodemon 热加载
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
### 6.3 GitLab CI/CD 集成
|
|
911
|
+
|
|
912
|
+
**配置文件**: `.gitlab-ci.yml`
|
|
913
|
+
|
|
914
|
+
**完整配置**:
|
|
915
|
+
```yaml
|
|
916
|
+
stages:
|
|
917
|
+
- ".pre"
|
|
918
|
+
- review
|
|
919
|
+
- ".post"
|
|
920
|
+
|
|
921
|
+
review:
|
|
922
|
+
stage: review
|
|
923
|
+
image: harbor.51job.com/codereview/ai-ide-cli:20251227
|
|
924
|
+
tags:
|
|
925
|
+
- ai
|
|
926
|
+
rules:
|
|
927
|
+
- if: $CI_PIPELINE_SOURCE == "web"
|
|
928
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
929
|
+
variables:
|
|
930
|
+
ANTHROPIC_MODEL: qwen3.5-plus
|
|
931
|
+
ANTHROPIC_SMALL_FAST_MODEL: qwen3.5-plus
|
|
932
|
+
script:
|
|
933
|
+
- npm install -g job51-gitlab-cr-node-skill-prompt-optimize
|
|
934
|
+
- cp -r $(npm root -g)/job51-gitlab-cr-node-skill-prompt-optimize/.claude $CI_PROJECT_DIR/
|
|
935
|
+
- export GITLAB_CR_PROJECT_DIR=$CI_PROJECT_DIR
|
|
936
|
+
- gitlab-cr
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
**CI 触发条件**:
|
|
940
|
+
| 触发源 | 条件 | 说明 |
|
|
941
|
+
|--------|------|------|
|
|
942
|
+
| web | `$CI_PIPELINE_SOURCE == "web"` | 手动触发 |
|
|
943
|
+
| merge_request_event | `$CI_PIPELINE_SOURCE == "merge_request_event"` | MR 事件自动触发 |
|
|
944
|
+
|
|
945
|
+
**智能审查逻辑**:
|
|
946
|
+
```
|
|
947
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
948
|
+
│ CI 审查流程控制 │
|
|
949
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
950
|
+
│ │
|
|
951
|
+
│ 1. 检查流水线来源 │
|
|
952
|
+
│ - web → 直接执行审查 │
|
|
953
|
+
│ - merge_request_event → 进入步骤 2 │
|
|
954
|
+
│ ↓ │
|
|
955
|
+
│ 2. 获取 MR 所有评论/笔记 │
|
|
956
|
+
│ GET /projects/:id/merge_requests/:iid/notes │
|
|
957
|
+
│ ↓ │
|
|
958
|
+
│ 3. 统计未解决的 AI 审查评论 │
|
|
959
|
+
│ 条件:type=DiffNote && author="AI 审查 - 乌萨奇" && resolved=false │
|
|
960
|
+
│ ↓ │
|
|
961
|
+
│ 4. 判断: │
|
|
962
|
+
│ - 有未解决评论 (>0) → 跳过审查,避免重复 │
|
|
963
|
+
│ - 无未解决评论 (=0) → 执行审查 │
|
|
964
|
+
│ ↓ │
|
|
965
|
+
│ 5. 审查结束后发表评论 │
|
|
966
|
+
│ 提醒用户 resolve 所有评论后手动重新运行流水线 │
|
|
967
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
**未解决评论检测脚本**:
|
|
971
|
+
```bash
|
|
972
|
+
# 获取 MR 的所有评论
|
|
973
|
+
DISCUSSIONS=$(curl --silent --header "PRIVATE-TOKEN: glpat-xxx" \
|
|
974
|
+
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes")
|
|
975
|
+
|
|
976
|
+
# 统计未解决的 AI 审查评论数量
|
|
977
|
+
UNRESOLVED_COUNT=$(echo "$DISCUSSIONS" | node -e "
|
|
978
|
+
const data = JSON.parse(input);
|
|
979
|
+
const unresolved = data.filter(item =>
|
|
980
|
+
item.type === 'DiffNote' &&
|
|
981
|
+
item.author &&
|
|
982
|
+
item.author.name === 'AI 审查 - 乌萨奇' &&
|
|
983
|
+
item.resolved === false
|
|
984
|
+
);
|
|
985
|
+
console.log(unresolved.length);
|
|
986
|
+
")
|
|
987
|
+
|
|
988
|
+
if [ "$UNRESOLVED_COUNT" -gt 0 ]; then
|
|
989
|
+
echo "存在未解决的 AI 审查评论,跳过审查"
|
|
990
|
+
exit 0
|
|
991
|
+
fi
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
**审查结束提醒**:
|
|
995
|
+
```bash
|
|
996
|
+
# 审查完成后发表评论(仅 MR 事件)
|
|
997
|
+
curl --request POST \
|
|
998
|
+
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \
|
|
999
|
+
--data-urlencode 'body=本轮 ai 代码审查已结束,请 resolve 所有评论后,
|
|
1000
|
+
并在 Merge request pipeline 右侧手动点击"Run again"以开启新一轮的评审'
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
### 6.4 项目目录结构
|
|
1004
|
+
|
|
1005
|
+
```
|
|
1006
|
+
cr-node/
|
|
1007
|
+
├── index.js # 主入口,GitLabCodeReviewer 类
|
|
1008
|
+
├── utils.js # 工具函数(GitLabAPIClient, debugLog)
|
|
1009
|
+
├── example.js # 使用示例
|
|
1010
|
+
├── package.json # 项目配置
|
|
1011
|
+
├── mr-review-template.md # 审查模板(旧版)
|
|
1012
|
+
├── docs/
|
|
1013
|
+
│ └── GITLAB_CR_NODE_TECHNICAL_DOCS.md # 技术文档
|
|
1014
|
+
├── .claude/
|
|
1015
|
+
│ ├── rules/
|
|
1016
|
+
│ │ └── code-review-rules.md # 代码审查规则(19 条强制规则)
|
|
1017
|
+
│ └── skills/
|
|
1018
|
+
│ └── simple-code-review/
|
|
1019
|
+
│ └── SKILL.md # 代码审查技能定义
|
|
1020
|
+
└── node_modules/
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
### 6.5 依赖要求
|
|
1024
|
+
|
|
1025
|
+
| 依赖 | 版本 | 用途 |
|
|
1026
|
+
|------|------|------|
|
|
1027
|
+
| Node.js | 10+ | 运行时环境 |
|
|
1028
|
+
| axios | ^1.13.3 | HTTP 客户端 |
|
|
1029
|
+
| nodemon | ^3.0.1 | 开发热加载 |
|
|
1030
|
+
| Claude CLI | 最新 | AI 审查引擎 |
|
|
1031
|
+
|
|
1032
|
+
---
|
|
1033
|
+
|
|
1034
|
+
## 7. GitLab CI/CD 配置
|
|
1035
|
+
|
|
1036
|
+
**配置文件路径**: `D:\code\ai-agent\code-review\.gitlab-ci.yml`
|
|
1037
|
+
|
|
1038
|
+
### 7.1 流水线阶段
|
|
1039
|
+
|
|
1040
|
+
```yaml
|
|
1041
|
+
stages:
|
|
1042
|
+
- ".pre"
|
|
1043
|
+
- review # AI 代码审查阶段
|
|
1044
|
+
- ".post"
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
### 7.2 review 任务配置
|
|
1048
|
+
|
|
1049
|
+
| 配置项 | 值 | 说明 |
|
|
1050
|
+
|--------|-----|------|
|
|
1051
|
+
| `stage` | review | 审查阶段 |
|
|
1052
|
+
| `image` | harbor.51job.com/codereview/ai-ide-cli:20251227 | 自定义 AI 镜像 |
|
|
1053
|
+
| `tags` | ai | AI Runner 标签 |
|
|
1054
|
+
| `ANTHROPIC_MODEL` | qwen3.5-plus | AI 模型 |
|
|
1055
|
+
|
|
1056
|
+
### 7.3 触发规则
|
|
1057
|
+
|
|
1058
|
+
| 触发源 | 条件 | 说明 |
|
|
1059
|
+
|--------|------|------|
|
|
1060
|
+
| web | `$CI_PIPELINE_SOURCE == "web"` | 手动触发 |
|
|
1061
|
+
| merge_request_event | `$CI_PIPELINE_SOURCE == "merge_request_event"` | MR 事件自动触发 |
|
|
1062
|
+
|
|
1063
|
+
### 7.4 执行脚本
|
|
1064
|
+
|
|
1065
|
+
```bash
|
|
1066
|
+
# 1. 安装全局包
|
|
1067
|
+
npm install -g job51-gitlab-cr-node-skill-prompt-optimize
|
|
1068
|
+
|
|
1069
|
+
# 2. 复制.claude 配置到项目目录
|
|
1070
|
+
cp -r $(npm root -g)/job51-gitlab-cr-node-skill-prompt-optimize/.claude $CI_PROJECT_DIR/
|
|
1071
|
+
|
|
1072
|
+
# 3. 设置项目目录环境变量
|
|
1073
|
+
export GITLAB_CR_PROJECT_DIR=$CI_PROJECT_DIR
|
|
1074
|
+
|
|
1075
|
+
# 4. 执行代码审查
|
|
1076
|
+
gitlab-cr
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
### 7.5 智能防重复审查机制
|
|
1080
|
+
|
|
1081
|
+
**流程**:
|
|
1082
|
+
```
|
|
1083
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
1084
|
+
│ 防重复审查流程 │
|
|
1085
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
1086
|
+
│ │
|
|
1087
|
+
│ 1. 检查流水线来源 │
|
|
1088
|
+
│ - web → 直接执行审查 │
|
|
1089
|
+
│ - merge_request_event → 进入步骤 2 │
|
|
1090
|
+
│ ↓ │
|
|
1091
|
+
│ 2. 获取 MR 所有评论/笔记 (Notes API) │
|
|
1092
|
+
│ GET /projects/:id/merge_requests/:iid/notes │
|
|
1093
|
+
│ ↓ │
|
|
1094
|
+
│ 3. 统计未解决的 AI 审查评论 │
|
|
1095
|
+
│ 过滤条件: │
|
|
1096
|
+
│ - type === 'DiffNote' (差异评论) │
|
|
1097
|
+
│ - author.name === 'AI 审查 - 乌萨奇' │
|
|
1098
|
+
│ - resolved === false (未解决) │
|
|
1099
|
+
│ ↓ │
|
|
1100
|
+
│ 4. 判断: │
|
|
1101
|
+
│ - UNRESOLVED_COUNT > 0 → 跳过审查 (exit 0) │
|
|
1102
|
+
│ - UNRESOLVED_COUNT = 0 → 执行审查 │
|
|
1103
|
+
│ ↓ │
|
|
1104
|
+
│ 5. 审查结束后发表评论 │
|
|
1105
|
+
│ 提醒用户 resolve 所有评论后手动重新运行流水线 │
|
|
1106
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1109
|
+
**检测脚本**:
|
|
1110
|
+
```bash
|
|
1111
|
+
# 获取 MR 的所有评论
|
|
1112
|
+
DISCUSSIONS=$(curl --silent --header "PRIVATE-TOKEN: glpat-xxx" \
|
|
1113
|
+
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes")
|
|
1114
|
+
|
|
1115
|
+
# Node.js 解析 JSON 统计未解决评论
|
|
1116
|
+
UNRESOLVED_COUNT=$(echo "$DISCUSSIONS" | node -e "
|
|
1117
|
+
const data = JSON.parse(input);
|
|
1118
|
+
const unresolved = data.filter(item =>
|
|
1119
|
+
item.type === 'DiffNote' &&
|
|
1120
|
+
item.author &&
|
|
1121
|
+
item.author.name === 'AI 审查 - 乌萨奇' &&
|
|
1122
|
+
item.resolved === false
|
|
1123
|
+
);
|
|
1124
|
+
console.log(unresolved.length);
|
|
1125
|
+
")
|
|
1126
|
+
|
|
1127
|
+
# 有未解决评论则跳过
|
|
1128
|
+
if [ "$UNRESOLVED_COUNT" -gt 0 ]; then
|
|
1129
|
+
echo "存在未解决的 AI 审查评论,跳过审查"
|
|
1130
|
+
exit 0
|
|
1131
|
+
fi
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
### 7.6 审查结束提醒
|
|
1135
|
+
|
|
1136
|
+
审查完成后自动发表评论(仅 MR 事件):
|
|
1137
|
+
```bash
|
|
1138
|
+
curl --request POST \
|
|
1139
|
+
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \
|
|
1140
|
+
--data-urlencode 'body=本轮 ai 代码审查已结束,请 resolve 所有评论后,
|
|
1141
|
+
并在 Merge request pipeline 右侧手动点击"Run again"以开启新一轮的评审'
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
**提醒内容**:
|
|
1145
|
+
> 本轮 ai 代码审查已结束,请 resolve 所有评论后,并在上方 Merge request pipeline 右侧手动点击"Run again"以开启新一轮的评审,防止多次 push 代码引发 ci 的邮件打扰
|
|
1146
|
+
|
|
1147
|
+
---
|
|
1148
|
+
|
|
1149
|
+
## 8. API 参考
|
|
1150
|
+
|
|
1151
|
+
### 8.1 GitLabCodeReviewer 公共方法
|
|
1152
|
+
|
|
1153
|
+
| 方法 | 参数 | 返回值 | 说明 |
|
|
1154
|
+
|------|------|--------|------|
|
|
1155
|
+
| `getMergeRequestDiffs` | projectId, mergeRequestIid | `Promise<Array>` | 获取 MR 所有 diff |
|
|
1156
|
+
| `reviewMergeRequest` | projectId, mergeRequestIid, maxConcurrency | `Promise<Array>` | 审查整个 MR |
|
|
1157
|
+
| `postSingleCommentToGitLab` | projectId, mergeRequestIid, result | `Promise<void>` | 发布单条评论 |
|
|
1158
|
+
| `getMergeRequestVersions` | projectId, mergeRequestIid | `Promise<Object>` | 获取 MR 版本信息 |
|
|
1159
|
+
|
|
1160
|
+
### 8.2 GitLab API 端点
|
|
1161
|
+
|
|
1162
|
+
```javascript
|
|
1163
|
+
// 获取 Diffs
|
|
1164
|
+
GET /projects/{projectId}/merge_requests/{iid}/diffs?per_page=20&page=1
|
|
1165
|
+
|
|
1166
|
+
// 获取 Versions
|
|
1167
|
+
GET /projects/{projectId}/merge_requests/{iid}/versions
|
|
1168
|
+
|
|
1169
|
+
// 发布 Discussion
|
|
1170
|
+
POST /projects/{projectId}/merge_requests/{iid}/discussions
|
|
1171
|
+
{
|
|
1172
|
+
"body": "评论内容",
|
|
1173
|
+
"position": {
|
|
1174
|
+
"position_type": "text",
|
|
1175
|
+
"base_sha": "...",
|
|
1176
|
+
"head_sha": "...",
|
|
1177
|
+
"start_sha": "...",
|
|
1178
|
+
"new_line": 45,
|
|
1179
|
+
"new_path": "file.java"
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
```
|
|
1183
|
+
|
|
1184
|
+
---
|
|
1185
|
+
|
|
1186
|
+
## 9. 代码审查规则体系
|
|
1187
|
+
|
|
1188
|
+
### 9.1 规则文件结构
|
|
1189
|
+
|
|
1190
|
+
```
|
|
1191
|
+
.claude/
|
|
1192
|
+
├── rules/
|
|
1193
|
+
│ └── code-review-rules.md # 核心审查规则 (19 条强制规则)
|
|
1194
|
+
└── skills/
|
|
1195
|
+
└── simple-code-review/
|
|
1196
|
+
└── SKILL.md # 技能执行模板
|
|
1197
|
+
```
|
|
1198
|
+
|
|
1199
|
+
### 9.2 核心审查规则摘要
|
|
1200
|
+
|
|
1201
|
+
**最高优先级规则**:
|
|
1202
|
+
|
|
1203
|
+
| 规则编号 | 规则名称 | 描述 |
|
|
1204
|
+
|----------|----------|------|
|
|
1205
|
+
| 规则 0 | 已安全处理禁止报告 | 已有判空/异常处理的代码不得报告 |
|
|
1206
|
+
| 规则 1 | 审查范围限制 | 只审查 diff 块内新增代码 |
|
|
1207
|
+
| 规则 2 | 同一问题只报告一次 | 多处调用的同一问题合并报告 |
|
|
1208
|
+
| 规则 3 | 禁止报告位置 | import/类定义/方法签名不得报告 |
|
|
1209
|
+
| 规则 4 | 深度分析精准报告 | 必须 Read 相关方法实现后确认 |
|
|
1210
|
+
|
|
1211
|
+
### 9.3 输出格式要求
|
|
1212
|
+
|
|
1213
|
+
```markdown
|
|
1214
|
+
<REPORT>
|
|
1215
|
+
## 🤖 AI 代码审查结果
|
|
1216
|
+
|
|
1217
|
+
### 🔴 严重问题
|
|
1218
|
+
|
|
1219
|
+
1️⃣ [问题描述]<br/>
|
|
1220
|
+
**文件及行号**: [path:line]<br/>
|
|
1221
|
+
**修改建议**: [示例代码]
|
|
1222
|
+
|
|
1223
|
+
</REPORT>
|
|
1224
|
+
|
|
1225
|
+
<LINE_INFO>
|
|
1226
|
+
[{"new_path":"path","new_line":42}]
|
|
1227
|
+
</LINE_INFO>
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
---
|
|
1231
|
+
|
|
1232
|
+
## 10. 错误处理与容错机制
|
|
1233
|
+
|
|
1234
|
+
### 10.1 AI 审查失败处理
|
|
1235
|
+
|
|
1236
|
+
```javascript
|
|
1237
|
+
try {
|
|
1238
|
+
claudeResult = await runClaudeCommand(prompt);
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
// 递增等待时间后重试
|
|
1241
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
|
|
1242
|
+
}
|
|
1243
|
+
```
|
|
1244
|
+
|
|
1245
|
+
### 10.2 GitLab API 失败降级
|
|
1246
|
+
|
|
1247
|
+
```javascript
|
|
1248
|
+
try {
|
|
1249
|
+
await this.createDiffDiscussion(...); // 尝试行级评论
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
await this.createGeneralDiscussion(...); // 降级为一般讨论
|
|
1252
|
+
}
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1255
|
+
### 10.3 临时文件清理
|
|
1256
|
+
|
|
1257
|
+
```javascript
|
|
1258
|
+
finally {
|
|
1259
|
+
if (fs.existsSync(tmpFileName)) {
|
|
1260
|
+
fs.unlinkSync(tmpFileName);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
---
|
|
1266
|
+
|
|
1267
|
+
## 11. 性能优化策略
|
|
1268
|
+
|
|
1269
|
+
### 11.1 并发控制
|
|
1270
|
+
|
|
1271
|
+
- 默认并发数:3
|
|
1272
|
+
- 可配置范围:1-10
|
|
1273
|
+
- 线程池复用,避免频繁创建销毁
|
|
1274
|
+
|
|
1275
|
+
### 11.2 分页获取 Diffs
|
|
1276
|
+
|
|
1277
|
+
```javascript
|
|
1278
|
+
do {
|
|
1279
|
+
const url = `.../diffs?per_page=20&page=${page}`;
|
|
1280
|
+
allDiffs = allDiffs.concat(data);
|
|
1281
|
+
page++;
|
|
1282
|
+
} while (data.length > 0);
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
### 11.3 增量审查
|
|
1286
|
+
|
|
1287
|
+
- 只审查有"严重问题"的变更块
|
|
1288
|
+
- 无问题的块跳过评论发布
|
|
1289
|
+
- 减少无效 API 调用
|
|
1290
|
+
|
|
1291
|
+
---
|
|
1292
|
+
|
|
1293
|
+
## 12. 扩展与维护
|
|
1294
|
+
|
|
1295
|
+
### 12.1 扩展点
|
|
1296
|
+
|
|
1297
|
+
| 扩展点 | 文件位置 | 扩展方式 |
|
|
1298
|
+
|--------|----------|----------|
|
|
1299
|
+
| 审查规则 | `.claude/rules/code-review-rules.md` | 修改/追加规则条目 |
|
|
1300
|
+
| 输出模板 | `.claude/skills/simple-code-review/SKILL.md` | 调整模板格式 |
|
|
1301
|
+
| GitLab 集成 | `utils.js:GitLabAPIClient` | 添加新 API 端点 |
|
|
1302
|
+
| 并发策略 | `index.js:processWithThreadPool` | 调整调度算法 |
|
|
1303
|
+
|
|
1304
|
+
### 12.2 调试技巧
|
|
1305
|
+
|
|
1306
|
+
**开启详细日志**:
|
|
1307
|
+
```javascript
|
|
1308
|
+
// 所有日志都通过 debugLog 输出
|
|
1309
|
+
debugLog('消息内容');
|
|
1310
|
+
// 输出格式:[DEBUG] 2026-04-14T10:00:00.000Z 消息内容
|
|
1311
|
+
```
|
|
1312
|
+
|
|
1313
|
+
**本地测试**:
|
|
1314
|
+
```bash
|
|
1315
|
+
# 使用 example.js 测试
|
|
1316
|
+
npm run example
|
|
1317
|
+
|
|
1318
|
+
# 或使用 nodemon 热加载
|
|
1319
|
+
npm run dev
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
### 12.3 版本历史
|
|
1323
|
+
|
|
1324
|
+
| 版本 | 日期 | 变更说明 |
|
|
1325
|
+
|------|------|----------|
|
|
1326
|
+
| 2.4.1 | 2026-04-08 | 修复 AI 审查结果为空时的返回值 |
|
|
1327
|
+
### 12.3 版本历史
|
|
1328
|
+
|
|
1329
|
+
| 版本 | 日期 | 变更说明 |
|
|
1330
|
+
|------|------|----------|
|
|
1331
|
+
| 2.4.1 | 2026-04-08 | 修复 AI 审查结果为空时的返回值 |
|
|
1332
|
+
| 2.4.0 | 2026-03-25 | 更新 MR 审查模板 |
|
|
1333
|
+
| 2.3.8 | 2026-03-25 | 优化 AI 审核结果处理逻辑 |
|
|
1334
|
+
|
|
1335
|
+
---
|
|
1336
|
+
|
|
1337
|
+
## 附录
|
|
1338
|
+
|
|
1339
|
+
### A. 文件清单
|
|
1340
|
+
|
|
1341
|
+
| 文件 | 行数 | 职责 |
|
|
1342
|
+
|------|------|------|
|
|
1343
|
+
| `index.js` | 717 | 主入口,审查逻辑 |
|
|
1344
|
+
| `utils.js` | 75 | 工具函数,GitLab 客户端 |
|
|
1345
|
+
| `example.js` | 30 | 使用示例 |
|
|
1346
|
+
| `package.json` | 28 | 项目配置 |
|
|
1347
|
+
| `.gitlab-ci.yml` | 70 | GitLab CI 配置 |
|
|
1348
|
+
| `.claude/rules/code-review-rules.md` | 200+ | 审查规则 |
|
|
1349
|
+
| `.claude/skills/simple-code-review/SKILL.md` | 53 | 技能定义 |
|
|
1350
|
+
| `docs/GITLAB_CR_NODE_TECHNICAL_DOCS.md` | 1100+ | 技术文档 |
|
|
1351
|
+
|
|
1352
|
+
### B. 联系方式
|
|
1353
|
+
|
|
1354
|
+
- 作者:tao.jing
|
|
1355
|
+
- 项目仓库:(内部)
|
|
1356
|
+
|
|
1357
|
+
---
|
|
1358
|
+
|
|
1359
|
+
*文档生成时间:2026-04-14*
|