job51-gitlab-cr-node-jt-1 2.5.6 → 2.5.8

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.
@@ -181,13 +181,13 @@
181
181
  12. 最终输出必须以 `<REPORT>` 开始,以 `</REPORT>` 结束;
182
182
 
183
183
  13. **行号计算规范**:
184
- > - **行号定义**:行号是变更后文件中的**绝对行号**(从 1 开始计数)
184
+ > - **行号定义**:行号是**diff 块内的相对行号**(从 1 开始计数)**不是文件的绝对行号**
185
185
  > - **⚠️ 关键规则:基于 diff 块内容逐行计数**(强制执行):
186
186
  > - **禁止**读取完整文件后计算绝对行号
187
- > - **必须**基于 diff 块内容,从 `@@ ... @@` 头之后的第一行开始逐行计数
188
- > - **⚠️ 重要:忽略所有元数据行**(如 `=== File Information ===`、`=== Diff Content ===`、`New Path:` 等)
187
+ > - **必须**基于当前 diff 块内容,从 `@@ ... @@` 头之后开始逐行计数
188
+ > - **⚠️ 重要:忽略所有元数据行**(如 `=== File Information ===`、`=== Diff Content ===`、`New Path:`、`New Start:`、`New Count:` 等)
189
189
  > - 计数规则:
190
- > - **从 `@@ ... @@` 行之后的第一行开始计数**(不是从文件第一行,不是从元数据开始)
190
+ > - **从 `@@ ... @@` 行之后的第一行开始计数**(该行 = 行号 1)
191
191
  > - `+` 开头的新增代码:计入行号
192
192
  > - 空格开头的上下文代码:计入行号
193
193
  > - `-` 开头的删除代码:**不计入**行号(已删除)
@@ -210,14 +210,19 @@
210
210
  > - 示例 2:已有文件修改 `@@ -42,3 +43,5 @@`
211
211
  ```
212
212
  @@ -42,3 +43,5 @@
213
- public void method() { // 行号 = 43(上下文行,保留)
214
- + Object obj = service.getData(); // 行号 = 44
215
- + obj.toString(); // 行号 = 45 ← 问题在这,new_line = 45
213
+ public void method() { // 行号 = 1(上下文行,从 @@ 后开始计数)
214
+ + Object obj = service.getData(); // 行号 = 2
215
+ + obj.toString(); // 行号 = 3 ← 问题在这,new_line = 3
216
216
  }
217
217
  ```
218
- > - **⚠️ 验证方法**:输出前检查行号是否合理
219
- > - 对于 `@@ -0,0 +1,N @@` 的新文件,最大行号应该是 N
220
- > - 如果报告的行号超过 diff 块的 `new_count`,说明计算错误
218
+ > - **⚠️ 验证方法**(强制执行,输出前必须检查):
219
+ > 1. **查看 diff 头的 new_count 值**:
220
+ > - `@@ -0,0 +1,N @@` → 最大行号 = N
221
+ > - `@@ -old_start,old_count +new_start,new_count @@` → 最大行号 = new_count
222
+ > 2. **确保所有问题的行号 ≤ new_count**
223
+ > 3. **如果行号超出范围,说明计算错误,必须重新计算**
224
+ > - 示例:`@@ -0,0 +1,56 @@` 的 diff 块,最大行号必须是 56,**不能返回 57, 60 等**
225
+ > - 示例:`@@ -167,7 +167,6 @@` 的 diff 块,new_count=6,最大行号必须是 6
221
226
  > - **关键**:问题行号必须准确指向变更后代码在 diff 块中的位置(从 `@@` 头之后计数)
222
227
 
223
228
  14. `<REPORT>` 标签示例中的问题块数量仅用于展示格式,实际数量由 review 结果决定;
@@ -16,28 +16,15 @@ description: 代码审查技能,审查变更代码并输出 REPORT 和 LINE_IN
16
16
  3. **解析行号**:**基于 diff 块内容逐行计数**
17
17
  - **⚠️ 重要**:行号计算规则详见 @.claude/rules/code-review-rules.md 第 13 条
18
18
  - **禁止**读取完整文件后计算绝对行号
19
- - **必须**基于 diff 块内容,从 `@@ ... @@` 头之后开始逐行计数
20
- - **忽略元数据行**:忽略 `=== File Information ===`、`=== Diff Content ===`、`New Path:` 等所有元数据行
21
- - **计数起点**:从 `@@ -0,0 +1,N @@` `@@ -old,count +new,count @@` 之后的第一行开始计数(该行 = 行号 1)
22
- - **⚠️ 行号计算示例**:
23
- ```
24
- @@ -0,0 +1,56 @@
25
- +package com.job51.dev.bbs.controller; // 行号 = 1
26
- + // 行号 = 2
27
- +import java.util.List; // 行号 = 3
28
- ...
29
- +public class Test { // 行号 = 17
30
- + public void method() { // 行号 = 18
31
- + Object obj = service.getData(); // 行号 = 19
32
- + obj.toString(); // 行号 = 20 ← 问题在这,new_line = 20
33
- + }
34
- +}
35
- ```
36
- - 行号定义:行号是变更后文件中的绝对行号(从 1 开始计数)
37
- - **验证方法**:输出前检查行号是否合理
38
- - 对于 `@@ -0,0 +1,N @@` 的新文件,最大行号应该是 N
39
- - 如果报告的行号超过 diff 块的 `new_count`,说明计算错误
40
- - 例如:如果新增代码在 diff 块的第 43 行,则 `new_line = 43`
19
+ - **必须**基于当前 diff 块内容,从 `@@ ... @@` 头之后开始逐行计数
20
+ - **忽略元数据行**:忽略 `=== File Information ===`、`=== Diff Content ===`、`New Path:`、`New Start:`、`New Count:` 等所有元数据行
21
+ - **计数起点**:从 `@@ ... @@` 之后的**第一行**开始计数(该行 = 行号 1)
22
+ - **⚠️ 关键规则**:
23
+ - **行号 = 从 @@ 头之后到该行的累计行数**
24
+ - **最大行号不能超过 diff 块的 new_count 值**(例如 `@@ -0,0 +1,56 @@` 的 diff 块,最大行号必须是 56)
25
+ - **验证方法**(输出前必须检查):
26
+ - 查看 diff 头的 `+1,N` 或 `+new_start,new_count` 中的 `N` / `new_count` 值
27
+ - 确保所有问题的行号 N / new_count
41
28
 
42
29
  4. **读取上下文**:基于 New Path 读取变更后文件,涉及方法调用时追踪读取实现代码
43
30
 
@@ -1,28 +1,21 @@
1
1
  # GitLab Code Review AI Tool 技术文档
2
2
 
3
3
  **项目名称**: job51-gitlab-cr-node
4
- **当前版本**: 2.4.9
4
+ **当前版本**: 2.5.6
5
5
  **作者**: tao.jing
6
6
  **最后更新**: 2026-04-16
7
7
  **项目地址**: https://gitdev.51job.com/51jobweb/ai-agent
8
- **当前分支**: jt-test-skill
9
8
 
10
9
  ---
11
10
 
12
11
  ## 目录
13
12
 
14
13
  1. [项目概述](#1-项目概述)
15
- - [1.1 项目定位](#11-项目定位)
16
- - [1.2 技术栈](#12-技术栈)
17
- - [1.3 核心功能](#13-核心功能)
18
- - [1.4 完整端到端流程图](#14-完整端到端流程图)
19
14
  2. [系统架构](#2-系统架构)
20
15
  3. [核心模块设计](#3-核心模块设计)
21
16
  4. [关键算法与流程](#4-关键算法与流程)
22
17
  5. [数据结构定义](#5-数据结构定义)
23
18
  6. [配置与部署](#6-配置与部署)
24
- - [6.0 CI 镜像分析](#60-ci-镜像分析)
25
- - [6.1 环境变量](#61-环境变量)
26
19
  7. [GitLab CI/CD 配置](#7-gitlab-cicd-配置)
27
20
  8. [API 参考](#8-api-参考)
28
21
  9. [代码审查规则体系](#9-代码审查规则体系)
@@ -42,6 +35,7 @@ GitLab Code Review AI Tool 是一个基于 Claude AI 的自动化代码审查工
42
35
  - 使用 Claude AI 对每个变更块进行深度代码审查
43
36
  - 将审查结果以评论形式发布到 GitLab MR 的对应代码行
44
37
  - 支持并发控制和增量审查
38
+ - 内置智能防重复审查机制
45
39
 
46
40
  ### 1.2 技术栈
47
41
 
@@ -50,7 +44,7 @@ GitLab Code Review AI Tool 是一个基于 Claude AI 的自动化代码审查工
50
44
  | 运行时 | Node.js | 10+ |
51
45
  | HTTP 客户端 | Axios | ^1.13.3 |
52
46
  | 开发工具 | nodemon | ^3.0.1 |
53
- | AI 引擎 | Claude CLI | 最新 |
47
+ | AI 引擎 | Claude CLI (qwen3.5-plus) | 最新 |
54
48
 
55
49
  ### 1.3 核心功能
56
50
 
@@ -68,168 +62,6 @@ GitLab Code Review AI Tool 是一个基于 Claude AI 的自动化代码审查工
68
62
  └─────────────────────────────────────────────────────────────┘
69
63
  ```
70
64
 
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
65
  ---
234
66
 
235
67
  ## 2. 系统架构
@@ -248,6 +80,9 @@ GitLab Code Review AI Tool 是一个基于 Claude AI 的自动化代码审查工
248
80
  │ │ GitLabAPIClient │ │ reviewDiffWith │ │ postCommentsTo │ │
249
81
  │ │ (GitLab 通信) │ │ ClaudeUsingFile │ │ GitLab │ │
250
82
  │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
83
+ │ ┌─────────────────┐ │
84
+ │ │ MetricsCollector│ (性能指标收集) │
85
+ │ └─────────────────┘ │
251
86
  └─────────────────────────────────────────────────────────────────┘
252
87
 
253
88
  ┌─────────────────────────────────────────────────────────────────┐
@@ -268,8 +103,9 @@ GitLab Code Review AI Tool 是一个基于 Claude AI 的自动化代码审查工
268
103
  index.js (主入口)
269
104
  ├── utils.js (工具模块)
270
105
  │ ├── GitLabAPIClient (GitLab API 封装)
271
- │ ├── debugLog (日志函数)
272
- └── extractReportContent (报告提取)
106
+ │ ├── 日志分级系统 (debugLog/infoLog/warnLog/errorLog)
107
+ ├── extractReportContent (报告提取)
108
+ │ └── MetricsCollector (性能指标收集器)
273
109
  ├── .claude/skills/simple-code-review/SKILL.md (审查技能定义)
274
110
  └── .claude/rules/code-review-rules.md (审查规则)
275
111
  ```
@@ -280,7 +116,7 @@ index.js (主入口)
280
116
 
281
117
  ### 3.1 GitLabCodeReviewer 类
282
118
 
283
- **位置**: `index.js:8-634`
119
+ **位置**: `index.js:8-713`
284
120
 
285
121
  **构造函数**:
286
122
  ```javascript
@@ -292,10 +128,15 @@ constructor(gitlabToken, gitlabUrl = null)
292
128
  2. 获取 MR 的 diff 信息
293
129
  3. 调用 AI 进行代码审查
294
130
  4. 将审查结果发布到 GitLab
131
+ 5. 性能指标收集与统计
132
+
133
+ **新增功能** (v2.5.x):
134
+ - `_versionCache`: MR 版本信息缓存,避免重复 API 调用
135
+ - `metrics`: MetricsCollector 实例,收集性能指标
295
136
 
296
137
  ### 3.2 GitLabAPIClient 类
297
138
 
298
- **位置**: `utils.js:31-68`
139
+ **位置**: `utils.js:70-107`
299
140
 
300
141
  **封装的 GitLab API 调用**:
301
142
  ```javascript
@@ -322,27 +163,28 @@ class GitLabAPIClient {
322
163
 
323
164
  3. 解析 REPORT 和 LINE_INFO 标签
324
165
 
325
- 4. 验证结果 (严重问题检查 + 标题验证)
166
+ 4. 验证结果 (三级验证机制)
326
167
 
327
168
  5. 重试机制 (最多 5 次)
328
169
  ```
329
170
 
330
- **重试策略** (`index.js:55-325`):
171
+ **重试策略** (`index.js:192-267`):
331
172
  - 最大重试次数:5 次
332
173
  - 递增等待时间:`1000 * attempts` 毫秒
333
- - 验证条件:
174
+ - 三级验证条件:
334
175
  1. LINE_INFO 标签存在且非空
335
- 2. 包含"严重问题"关键词时,标题必须为"🤖 AI 代码审查结果"
176
+ 2. 包含"严重问题"关键词
177
+ 3. 标题为"🤖 AI 代码审查结果"
336
178
 
337
179
  ---
338
180
 
339
181
  ## 4. 关键算法与流程
340
182
 
341
- ### 4.1 严重问题判断逻辑(核心)(2.4.4 更新)
183
+ ### 4.1 严重问题判断逻辑(三级验证)
342
184
 
343
- **位置**: `index.js:55-133`
185
+ **位置**: `index.js:215-258`
344
186
 
345
- **判断流程图**:
187
+ **判断流程**:
346
188
  ```
347
189
  ┌─────────────────────────────────────────────────────────────┐
348
190
  │ AI 审查结果验证流程 │
@@ -377,7 +219,6 @@ class GitLabAPIClient {
377
219
  │ ┌───────────────────────────────────────────────────────┐ │
378
220
  │ │ const hasValidTitle = claudeResult.includes( │ │
379
221
  │ │ '🤖 AI 代码审查结果' │ │
380
- │ │ ); │ │
381
222
  │ │ │ │
382
223
  │ │ 如果 true → 接受结果,返回 extractReportContent() │ │
383
224
  │ │ 如果 false → 重试 (最多 5 次) │ │
@@ -393,33 +234,9 @@ class GitLabAPIClient {
393
234
  | **步骤 2** | 严重问题关键词 | `claudeResult.includes('严重问题')` | 进入步骤 3 | 返回空格式 |
394
235
  | **步骤 3** | 标题格式正确性 | `claudeResult.includes('🤖 AI 代码审查结果')` | 接受结果 | 重试 (最多 5 次) |
395
236
 
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
- **v2.4.4 变更**:
417
- - 无严重问题时统一返回标准空格式(之前返回原始结果)
418
- - 达到最大重试次数时返回最后一次 AI 结果(之前返回空格式)
419
-
420
237
  ### 4.2 评论发布决策逻辑
421
238
 
422
- **位置**: `index.js:173-183`
239
+ **位置**: `index.js:112-122`
423
240
 
424
241
  **发布条件**:
425
242
  ```javascript
@@ -433,42 +250,9 @@ if (blockObj.review_result &&
433
250
  }
434
251
  ```
435
252
 
436
- **决策流程图**:
437
- ```
438
- ┌─────────────────────┐
439
- │ 获取审查结果 │
440
- └──────────┬──────────┘
441
-
442
- ┌─────────────────────┐
443
- │ review_result 存在? │
444
- └──────────┬──────────┘
445
- Yes │ No
446
- ↓ └──────────────┐
447
- ┌─────────────────────┐ │
448
- │ reportContent 存在?│ │
449
- └──────────┬──────────┘ │
450
- Yes │ No │
451
- ↓ └───────────────┤
452
- ┌─────────────────────┐ │
453
- │ 包含"严重问题"? │ │
454
- └──────────┬──────────┘ │
455
- Yes │ No │
456
- ↓ └───────────────┤
457
- ┌─────────────────────┐ │
458
- │ 发布评论到 GitLab │ │
459
- └──────────┬──────────┘ │
460
- │ │
461
- └─────────┬─────────┘
462
-
463
- ┌─────────────────────┐
464
- │ 跳过评论发布 │
465
- │ (记录调试日志) │
466
- └─────────────────────┘
467
- ```
468
-
469
253
  ### 4.3 Diff 块拆分算法
470
254
 
471
- **位置**: `index.js:380-428`
255
+ **位置**: `index.js:322-375`
472
256
 
473
257
  **算法描述**:
474
258
  ```javascript
@@ -483,7 +267,7 @@ getDiffBlocks(diffObj) {
483
267
  // 3. 提取行号信息
484
268
  return diffBlocks.map(block => ({
485
269
  diff: block,
486
- line_info: { old_start, old_count, new_start, new_count }
270
+ line_info: { old_start, old_count, new_start, new_count, firstLineFirstChar }
487
271
  }));
488
272
  }
489
273
  ```
@@ -495,19 +279,19 @@ getDiffBlocks(diffObj) {
495
279
  | 空格 | `current_line++` | 上下文代码,计入行号 |
496
280
  | `-` | 不变 | 删除代码,不计入行号 |
497
281
 
498
- ### 4.4 评论发布核心逻辑(详细)(2.4.6/2.4.7 更新)
282
+ ### 4.4 评论发布核心逻辑
499
283
 
500
- **位置**: `index.js:577-665`
284
+ **位置**: `index.js:550-668`
501
285
 
502
- **完整发布流程**(v2.4.6 更新):
286
+ **完整发布流程**:
503
287
  ```
504
288
  ┌─────────────────────────────────────────────────────────────────────────┐
505
289
  │ 评论发布完整流程 │
506
290
  ├─────────────────────────────────────────────────────────────────────────┤
507
291
  │ │
508
- │ 步骤 1: 获取 MR 版本信息 (SHA 值) │
292
+ │ 步骤 1: 获取 MR 版本信息 (带缓存) │
509
293
  │ ┌───────────────────────────────────────────────────────────────────┐ │
510
- │ │ versionInfo = getMergeRequestVersions() │ │
294
+ │ │ versionInfo = getVersionInfo() │ │
511
295
  │ │ - base_sha: 基础提交 SHA │ │
512
296
  │ │ - head_sha: 最新提交 SHA │ │
513
297
  │ │ - start_sha: 起始提交 SHA │ │
@@ -528,103 +312,31 @@ getDiffBlocks(diffObj) {
528
312
  │ │ b. 构建目标行号 │ │
529
313
  │ │ - 删除代码:使用 old_line + old_path │ │
530
314
  │ │ - 新增/上下文:使用 new_line + new_path │ │
531
- │ │ c. 发布评论到对应行 │ │
532
- │ │ d. GitLab API 失败降级为一般讨论 │ │
315
+ │ │ c. 行号范围验证 (v2.4.10) │ │
316
+ │ │ d. 发布评论到对应行 │ │
317
+ │ │ e. GitLab API 失败降级为一般讨论 │ │
533
318
  │ └───────────────────────────────────────────────────────────────────┘ │
534
319
  │ ↓ │
535
320
  │ 步骤 4: 所有评论发布完成 │
536
321
  └─────────────────────────────────────────────────────────────────────────┘
537
322
  ```
538
323
 
539
- **targetLine 确定逻辑**(v2.4.7 更新):
324
+ **行号验证逻辑** (v2.4.10):
540
325
  ```javascript
541
- // v2.4.7:直接使用 AI 返回的绝对行号,不再验证范围
542
- for (let i = 0; i < allLineInfo.length; i++) {
543
- const problemInfo = allLineInfo[i];
544
-
545
- let targetLine;
546
- if (problemInfo.old_line && !problemInfo.new_line) {
547
- // 删除代码,使用 old_line
548
- targetLine = {
549
- old_path: problemInfo.old_path,
550
- old_line: problemInfo.old_line
551
- };
552
- } else if (problemInfo.new_line) {
553
- // AI 返回的是文件中的绝对行号,直接使用
554
- targetLine = {
555
- new_path: problemInfo.new_path,
556
- new_line: problemInfo.new_line
557
- };
558
- }
559
-
560
- if (!targetLine) {
561
- // 无法解析行号,使用一般讨论
562
- await this.createGeneralDiscussion(...);
563
- continue;
564
- }
565
-
566
- // 发布评论到对应行
567
- const payload = {
568
- body: singleProblemContent,
569
- position: {
570
- position_type: 'text',
571
- base_sha: baseSha,
572
- head_sha: headSha,
573
- start_sha: startSha,
574
- ...targetLine
575
- }
576
- };
326
+ // 验证 new_line 是否在 diff 块范围内 [newStart, newEnd]
327
+ if (problemInfo.new_line < newStart || problemInfo.new_line > newEnd) {
328
+ skipReason = `new_line=${problemInfo.new_line} 超出 diff 块范围 [${newStart}, ${newEnd}]`;
577
329
  }
578
- ```
579
-
580
- **v2.4.7 重要变更**:
581
- - **移除行号范围验证**:之前会验证 `parsedLineInfo.new_line ∈ [new_start, new_end]`
582
- - **直接使用绝对行号**:AI 返回的行号是文件中的绝对行号,直接使用
583
- - **原因**:AI 读取完整源文件后定位问题,返回的是文件实际行号,不是基于 `New Start` 计算的
584
-
585
- **GitLab API Payload 结构**:
586
- ```javascript
587
- // Diff 评论 Payload
588
- const payload = {
589
- body: reviewContent, // 审查报告内容
590
- position: {
591
- position_type: 'text', // 文本位置类型
592
- base_sha: baseSha, // 基础提交 SHA
593
- head_sha: headSha, // 最新提交 SHA
594
- start_sha: startSha, // 起始提交 SHA
595
- new_line: 45, // 目标行号 (new_line 或 old_line 二选一)
596
- new_path: 'file.java' // 文件路径
597
- }
598
- };
599
-
600
- // 一般讨论 Payload
601
- const discussionData = {
602
- body: `**代码审核评论 - 文件:${file_path}**\n\n${review_result}`
603
- };
604
- ```
605
330
 
606
- **降级策略**:
607
- ```javascript
608
- try {
609
- await this.createDiffDiscussion(projectId, mergeRequestIid, payload);
610
- debugLog(`评论已发布到文件的变更区域`);
611
- } catch (error) {
612
- // Diff 评论失败 → 降级为一般讨论
613
- debugLog(`GitLab API 错误详情:${JSON.stringify(error.response?.data)}`);
614
- await this.createGeneralDiscussion(...);
615
- debugLog(`评论已发布 (作为一般讨论)`);
331
+ // 删除文件特殊处理:将 AI 返回的 new_line 视为 old_line
332
+ if (isDeleteFile) {
333
+ targetLine = { old_path: problemInfo.new_path, old_line: problemInfo.new_line };
616
334
  }
617
335
  ```
618
336
 
619
- **评论类型对比**:
620
- | 评论类型 | 触发条件 | API 端点 | 特点 |
621
- |----------|----------|----------|------|
622
- | Diff 评论 (行级) | 有 targetLine | POST /discussions (带 position) | 显示在代码变更区域,可被回复 |
623
- | 一般讨论 (文件级) | 无 targetLine 或 API 失败 | POST /discussions (不带 position) | 显示在 MR 讨论区,作为普通评论 |
624
-
625
337
  ### 4.5 线程池并发控制
626
338
 
627
- **位置**: `index.js:335-373`
339
+ **位置**: `index.js:277-315`
628
340
 
629
341
  **并发策略**:
630
342
  ```javascript
@@ -651,44 +363,12 @@ async processWithThreadPool(tasks, processor, maxConcurrency = 3) {
651
363
  export GITLAB_CR_CONCURRENCY=5 # 默认 3
652
364
  ```
653
365
 
654
- ### 4.6 行号解析算法(2.4.7 更新)
655
-
656
- **行号定义**:
657
- - 行号是变更后文件中的**绝对行号**(从 1 开始计数)
658
- - 例如:如果新增代码在变更后文件的第 43 行,则 `new_line = 43`
659
-
660
- **重要变更**(v2.4.7):
661
- - **v2.4.7 之前**:使用 diff 块的 `New Start` 作为基准计算行号,并验证行号是否在 diff 块范围内
662
- - **v2.4.7 及之后**:AI 直接返回文件中的绝对行号,不再基于 `New Start` 计算,移除范围验证
663
-
664
- **原因**:
665
- - AI 会读取完整的变更后文件来分析上下文
666
- - AI 在完整源文件中定位问题,返回的是文件中的绝对行号
667
- - 使用 `New Start` 验证行号范围会导致误判(如 diff 块 `New Start=1` 但 AI 返回行号 43)
668
-
669
- **原解析函数**:
670
- ```javascript
671
- parseLineInfoFromReviewResult(lineInfoTag) {
672
- // 从标签中提取 JSON 内容
673
- const jsonContent = lineInfoTag
674
- .replace(/<LINE_INFO>\s*/g, '')
675
- .replace(/\s*<\/LINE_INFO>/g, '')
676
- .trim();
677
-
678
- if (jsonContent === '[]') {
679
- return null; // 空数组使用后备方案
680
- }
681
-
682
- const lineInfoArray = JSON.parse(jsonContent);
683
- // 返回第一个问题的行号信息
684
- return lineInfoArray[0];
685
- }
686
- ```
366
+ ### 4.6 行号解析算法
687
367
 
688
- **新增解析函数**(v2.4.6):
368
+ **解析函数**:
689
369
  ```javascript
370
+ // 解析所有问题行号 (v2.4.6)
690
371
  parseAllLineInfoFromReviewResult(lineInfoTag) {
691
- // 解析 LINE_INFO 中的所有问题行号
692
372
  const jsonContent = lineInfoTag
693
373
  .replace(/<LINE_INFO>\s*/g, '')
694
374
  .replace(/\s*<\/LINE_INFO>/g, '')
@@ -698,6 +378,12 @@ parseAllLineInfoFromReviewResult(lineInfoTag) {
698
378
 
699
379
  return JSON.parse(jsonContent);
700
380
  }
381
+
382
+ // 提取单个问题报告 (v2.4.6)
383
+ extractSingleProblemReport(fullReport, problemIndex) {
384
+ // 使用 **问题 N**:模式来切分问题
385
+ // 返回第 problemIndex 个问题的报告内容
386
+ }
701
387
  ```
702
388
 
703
389
  **GitLab API 约束**:
@@ -706,169 +392,6 @@ parseAllLineInfoFromReviewResult(lineInfoTag) {
706
392
  | new_line / old_line 互斥 | 只能传递其中一个,不能同时存在 |
707
393
  | 新增代码 (`+`) | 使用 `new_line` + `new_path` |
708
394
  | 删除代码 (`-`) | 使用 `old_line` + `old_path` |
709
- | 上下文代码 | 使用 `new_line` + `new_path` |
710
-
711
- ### 4.7 评论发布策略(2.4.6 更新)
712
-
713
- **增量发布模式** (`index.js:174-183`):
714
- ```javascript
715
- // 只有包含严重问题才发布评论
716
- if (review_result.includes('严重问题')) {
717
- await this.postSingleCommentToGitLab(...);
718
- } else {
719
- debugLog('该块不包含严重问题,跳过评论发布');
720
- }
721
- ```
722
-
723
- **多问题评论发布**(v2.4.6 新增):
724
- ```javascript
725
- // 一个 diff 块有多个问题时,为每个问题发布单独的评论
726
- for (let i = 0; i < allLineInfo.length; i++) {
727
- const problemInfo = allLineInfo[i];
728
- // 提取单个问题的报告内容
729
- const singleProblemContent = this.extractSingleProblemReport(fullReportContent, i + 1);
730
- ```
731
-
732
- **版本信息缓存**(v2.4.8 新增):
733
- ```javascript
734
- // 构造函数中初始化缓存
735
- constructor() {
736
- this._versionCache = new Map(); // 缓存 MR 版本信息
737
- }
738
-
739
- // 获取版本信息(带缓存)
740
- async getVersionInfo(projectId, mergeRequestIid) {
741
- const cacheKey = `${projectId}-${mergeRequestIid}`;
742
-
743
- // 检查缓存
744
- if (this._versionCache.has(cacheKey)) {
745
- return this._versionCache.get(cacheKey);
746
- }
747
-
748
- // 从 API 获取并缓存
749
- const versionInfo = await this.getMergeRequestVersions(projectId, mergeRequestIid);
750
- this._versionCache.set(cacheKey, versionInfo);
751
- return versionInfo;
752
- }
753
- ```
754
-
755
- **缓存优化效果**:
756
- - **优化前**: 每个问题发布评论时都调用 `getMergeRequestVersions` API
757
- - **优化后**: 同一个 MR 的版本信息只获取一次,后续直接使用缓存
758
- - **适用场景**: 一个 diff 块有多个问题时,避免重复 API 调用
759
-
760
- // 构建目标行号
761
- let targetLine;
762
- if (problemInfo.old_line && !problemInfo.new_line) {
763
- targetLine = { old_path: problemInfo.old_path, old_line: problemInfo.old_line };
764
- } else if (problemInfo.new_line) {
765
- // AI 返回的是文件中的绝对行号,直接使用
766
- targetLine = { new_path: problemInfo.new_path, new_line: problemInfo.new_line };
767
- }
768
-
769
- // 发布评论到对应行
770
- await this.createDiffDiscussion(projectId, mergeRequestIid, {
771
- body: singleProblemContent,
772
- position: { ...targetLine, ...shaInfo }
773
- });
774
- }
775
- ```
776
-
777
- **行级评论定位**(v2.4.7 更新):
778
- ```javascript
779
- // v2.4.7:直接使用 AI 返回的绝对行号,不再验证范围
780
- if (problemInfo.new_line) {
781
- // AI 返回的是文件中的绝对行号,直接使用
782
- targetLine = {
783
- new_path: problemInfo.new_path,
784
- new_line: problemInfo.new_line
785
- };
786
- }
787
- ```
788
-
789
- **报告切分方法**(v2.4.6 新增):
790
- ```javascript
791
- extractSingleProblemReport(fullReport, problemIndex) {
792
- // 使用 **问题 N**:模式来切分问题
793
- const problemBlocks = [];
794
- let currentBlock = [];
795
-
796
- for (const line of lines) {
797
- const problemMatch = line.match(/\*\*问题\s*(\d+)\*\*/);
798
- if (problemMatch) {
799
- if (currentBlock.length > 0) {
800
- problemBlocks.push(currentBlock.join('\n'));
801
- currentBlock = [];
802
- }
803
- currentBlock.push(line);
804
- } else {
805
- currentBlock.push(line);
806
- }
807
- }
808
-
809
- return problemBlocks[problemIndex - 1];
810
- }
811
- ```
812
-
813
- ### 4.8 完整流程图:从 AI 审查到评论发布
814
-
815
- ```
816
- ┌─────────────────────────────────────────────────────────────────────────┐
817
- │ 完整审查与评论发布流程 │
818
- ├─────────────────────────────────────────────────────────────────────────┤
819
- │ │
820
- │ ┌─────────────────────────────────────────────────────────────────┐ │
821
- │ │ 阶段 1: AI 审查 │ │
822
- │ │ 1.1 调用 Claude CLI 审核 diff 块 │ │
823
- │ │ 1.2 获取审查结果 (包含 REPORT 和 LINE_INFO 标签) │ │
824
- │ └─────────────────────────────────────────────────────────────────┘ │
825
- │ ↓ │
826
- │ ┌─────────────────────────────────────────────────────────────────┐ │
827
- │ │ 阶段 2: 严重问题判断 (三级验证) │ │
828
- │ │ 2.1 LINE_INFO 标签检查 │ │
829
- │ │ - 无标签或空数组 → 无问题,返回空格式,跳过评论 │ │
830
- │ │ - 有行号 → 进入下一步 │ │
831
- │ │ 2.2 严重问题关键词检查 │ │
832
- │ │ - 不包含"严重问题" → 无严重问题,跳过评论 │ │
833
- │ │ - 包含"严重问题" → 进入下一步 │ │
834
- │ │ 2.3 标题格式检查 │ │
835
- │ │ - 包含"🤖 AI 代码审查结果" → 接受结果 │ │
836
- │ │ - 不包含 → 重试 (最多 5 次) │ │
837
- │ └─────────────────────────────────────────────────────────────────┘ │
838
- │ ↓ │
839
- │ ┌─────────────────────────────────────────────────────────────────┐ │
840
- │ │ 阶段 3: 评论发布决策 │ │
841
- │ │ 3.1 检查 review_result.reportContent 是否包含"严重问题" │ │
842
- │ │ - 不包含 → 跳过评论发布 │ │
843
- │ │ - 包含 → 调用 postSingleCommentToGitLab │ │
844
- │ └─────────────────────────────────────────────────────────────────┘ │
845
- │ ↓ │
846
- │ ┌─────────────────────────────────────────────────────────────────┐ │
847
- │ │ 阶段 4: GitLab API 调用 │ │
848
- │ │ 4.1 获取 MR 版本信息 (base_sha, head_sha, start_sha) │ │
849
- │ │ 4.2 确定 targetLine │ │
850
- │ │ - 优先:LINE_INFO 解析行号 (验证范围) │ │
851
- │ │ - 后备:diff 块起始行号 │ │
852
- │ │ 4.3 选择评论类型 │ │
853
- │ │ - 有 targetLine → Diff 评论 (行级) │ │
854
- │ │ - 无 targetLine → 一般讨论 (文件级) │ │
855
- │ │ 4.4 调用 API + 降级处理 │ │
856
- │ │ - Diff 评论失败 → 自动降级为一般讨论 │ │
857
- │ └─────────────────────────────────────────────────────────────────┘ │
858
- │ │
859
- └─────────────────────────────────────────────────────────────────────────┘
860
- ```
861
-
862
- **关键决策点速查表**:
863
-
864
- | 决策点 | 位置 | 判断条件 | 通过 → | 失败 → |
865
- |--------|------|----------|--------|--------|
866
- | LINE_INFO 检查 | `index.js:81-94` | `hasLineInfoTag && hasNonEmptyLineInfo` | 继续检查 | 返回空格式 |
867
- | 严重问题检查 | `index.js:98-106` | `includes('严重问题')` | 继续检查 | 返回原报告 |
868
- | 标题格式检查 | `index.js:109-123` | `includes('🤖 AI 代码审查结果')` | 接受结果 | 重试 |
869
- | 评论发布检查 | `index.js:174-183` | `reportContent.includes('严重问题')` | 发布评论 | 跳过 |
870
- | 行号验证 | `index.js:546-551` | `new_line ∈ [new_start, new_end]` | 使用解析行号 | 使用起始行号 |
871
- | API 降级 | `index.js:570-579` | `createDiffDiscussion` 成功 | Diff 评论 | 一般讨论 |
872
395
 
873
396
  ---
874
397
 
@@ -924,63 +447,6 @@ extractSingleProblemReport(fullReport, problemIndex) {
924
447
 
925
448
  ## 6. 配置与部署
926
449
 
927
- ### 6.0 CI 镜像分析
928
-
929
- **镜像名称**: `harbor.51job.com/codereview/ai-ide-cli:20251227`
930
-
931
- **镜像基本信息**:
932
- | 属性 | 值 |
933
- |------|-----|
934
- | 镜像 ID | sha256:618b55c7e9c256838de774c06e575d5c5efaca0cde9a836692866b3e51d0b85e |
935
- | 架构 | amd64 |
936
- | 大小 | 462.8 MB (压缩后) / 1.76 GB (解压后) |
937
- | 构建时间 | 2025-12-27 |
938
- | 最后标记时间 | 2026-03-30 |
939
-
940
- **基础环境**:
941
- | 组件 | 版本 | 说明 |
942
- |------|------|------|
943
- | 操作系统 | Debian/Ubuntu 系 | 基于 Linux 的容器环境 |
944
- | Node.js | v18.20.8 | LTS 版本 |
945
- | npm | 10.8.2 | Node 包管理器 |
946
- | Yarn | 1.22.22 | 通过 corepack 管理 |
947
- | Git | 2.39.5 | 版本控制工具 |
948
- | curl | 7.88.1 | HTTP 客户端工具 |
949
-
950
- **预装工具**:
951
- | 工具 | 版本 | 用途 |
952
- |------|------|------|
953
- | @anthropic-ai/claude-code | 2.0.76 | Claude CLI 代码审查工具 |
954
- | corepack | 0.32.0 | Node.js 包管理器代理 |
955
-
956
- **环境变量配置**:
957
- | 变量名 | 默认值 | 说明 |
958
- |--------|--------|------|
959
- | `NODE_VERSION` | 18.20.8 | Node.js 版本号 |
960
- | `YARN_VERSION` | 1.22.22 | Yarn 版本号 |
961
- | `ANTHROPIC_BASE_URL` | https://dashscope.aliyuncs.com/apps/anthropic | AI API 地址 |
962
- | `API_TIMEOUT_MS` | 3000000 | API 超时时间 (50 分钟) |
963
- | `ANTHROPIC_AUTH_TOKEN` | (空) | AI API 认证 Token |
964
- | `ANTHROPIC_MODEL` | (空) | 主 AI 模型 |
965
- | `ANTHROPIC_SMALL_FAST_MODEL` | (空) | 快速 AI 模型 |
966
-
967
- **curl 支持库**:
968
- - OpenSSL 3.0.16 (HTTPS/TLS 支持)
969
- - zlib 1.2.13 (压缩)
970
- - brotli 1.0.9 (压缩)
971
- - zstd 1.5.4 (压缩)
972
- - libidn2 2.3.3 (国际化域名)
973
- - libssh2 1.10.0 (SSH 支持)
974
- - nghttp2 1.52.0 (HTTP/2 支持)
975
- - OpenLDAP 2.5.13 (LDAP 支持)
976
-
977
- **镜像用途**:
978
- 该镜像是专为 AI 代码审查流程定制的容器环境,预装了:
979
- 1. Node.js 18 运行时环境
980
- 2. Claude Code CLI 工具 (2.0.76 版本)
981
- 3. Git、curl 等必要的开发工具
982
- 4. 配置了灵积 AI API 的基础 URL
983
-
984
450
  ### 6.1 环境变量
985
451
 
986
452
  | 变量名 | 必填 | 默认值 | 说明 |
@@ -990,6 +456,7 @@ extractSingleProblemReport(fullReport, problemIndex) {
990
456
  | `CI_PROJECT_ID` | 是 | - | GitLab 项目 ID |
991
457
  | `CI_MERGE_REQUEST_IID` | 是 | - | 合并请求 IID |
992
458
  | `GITLAB_CR_CONCURRENCY` | 否 | 3 | 最大并发审查数 |
459
+ | `LOG_LEVEL` | 否 | 0 (DEBUG) | 日志级别 (0=DEBUG, 1=INFO, 2=WARN, 3=ERROR) |
993
460
  | `ANTHROPIC_MODEL` | 否 | qwen3.5-plus | AI 模型配置 |
994
461
 
995
462
  ### 6.2 安装方式
@@ -1005,108 +472,14 @@ npm install
1005
472
  npm run dev # 使用 nodemon 热加载
1006
473
  ```
1007
474
 
1008
- ### 6.3 GitLab CI/CD 集成
1009
-
1010
- **配置文件**: `.gitlab-ci.yml`
1011
-
1012
- **完整配置**:
1013
- ```yaml
1014
- stages:
1015
- - ".pre"
1016
- - review
1017
- - ".post"
1018
-
1019
- review:
1020
- stage: review
1021
- image: harbor.51job.com/codereview/ai-ide-cli:20251227
1022
- tags:
1023
- - ai
1024
- rules:
1025
- - if: $CI_PIPELINE_SOURCE == "web"
1026
- - if: $CI_PIPELINE_SOURCE == "merge_request_event"
1027
- variables:
1028
- ANTHROPIC_MODEL: qwen3.5-plus
1029
- ANTHROPIC_SMALL_FAST_MODEL: qwen3.5-plus
1030
- script:
1031
- - npm install -g job51-gitlab-cr-node-skill-prompt-optimize
1032
- - cp -r $(npm root -g)/job51-gitlab-cr-node-skill-prompt-optimize/.claude $CI_PROJECT_DIR/
1033
- - export GITLAB_CR_PROJECT_DIR=$CI_PROJECT_DIR
1034
- - gitlab-cr
1035
- ```
1036
-
1037
- **CI 触发条件**:
1038
- | 触发源 | 条件 | 说明 |
1039
- |--------|------|------|
1040
- | web | `$CI_PIPELINE_SOURCE == "web"` | 手动触发 |
1041
- | merge_request_event | `$CI_PIPELINE_SOURCE == "merge_request_event"` | MR 事件自动触发 |
1042
-
1043
- **智能审查逻辑**:
1044
- ```
1045
- ┌─────────────────────────────────────────────────────────────────┐
1046
- │ CI 审查流程控制 │
1047
- ├─────────────────────────────────────────────────────────────────┤
1048
- │ │
1049
- │ 1. 检查流水线来源 │
1050
- │ - web → 直接执行审查 │
1051
- │ - merge_request_event → 进入步骤 2 │
1052
- │ ↓ │
1053
- │ 2. 获取 MR 所有评论/笔记 │
1054
- │ GET /projects/:id/merge_requests/:iid/notes │
1055
- │ ↓ │
1056
- │ 3. 统计未解决的 AI 审查评论 │
1057
- │ 条件:type=DiffNote && author="AI 审查 - 乌萨奇" && resolved=false │
1058
- │ ↓ │
1059
- │ 4. 判断: │
1060
- │ - 有未解决评论 (>0) → 跳过审查,避免重复 │
1061
- │ - 无未解决评论 (=0) → 执行审查 │
1062
- │ ↓ │
1063
- │ 5. 审查结束后发表评论 │
1064
- │ 提醒用户 resolve 所有评论后手动重新运行流水线 │
1065
- └─────────────────────────────────────────────────────────────────┘
1066
- ```
1067
-
1068
- **未解决评论检测脚本**:
1069
- ```bash
1070
- # 获取 MR 的所有评论
1071
- DISCUSSIONS=$(curl --silent --header "PRIVATE-TOKEN: glpat-xxx" \
1072
- "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes")
1073
-
1074
- # 统计未解决的 AI 审查评论数量
1075
- UNRESOLVED_COUNT=$(echo "$DISCUSSIONS" | node -e "
1076
- const data = JSON.parse(input);
1077
- const unresolved = data.filter(item =>
1078
- item.type === 'DiffNote' &&
1079
- item.author &&
1080
- item.author.name === 'AI 审查 - 乌萨奇' &&
1081
- item.resolved === false
1082
- );
1083
- console.log(unresolved.length);
1084
- ")
1085
-
1086
- if [ "$UNRESOLVED_COUNT" -gt 0 ]; then
1087
- echo "存在未解决的 AI 审查评论,跳过审查"
1088
- exit 0
1089
- fi
1090
- ```
1091
-
1092
- **审查结束提醒**:
1093
- ```bash
1094
- # 审查完成后发表评论(仅 MR 事件)
1095
- curl --request POST \
1096
- "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \
1097
- --data-urlencode 'body=本轮 ai 代码审查已结束,请 resolve 所有评论后,
1098
- 并在 Merge request pipeline 右侧手动点击"Run again"以开启新一轮的评审'
1099
- ```
1100
-
1101
- ### 6.4 项目目录结构
475
+ ### 6.3 项目目录结构
1102
476
 
1103
477
  ```
1104
478
  cr-node/
1105
479
  ├── index.js # 主入口,GitLabCodeReviewer 类
1106
- ├── utils.js # 工具函数(GitLabAPIClient, debugLog)
480
+ ├── utils.js # 工具函数(GitLabAPIClient, MetricsCollector, 日志分级)
1107
481
  ├── example.js # 使用示例
1108
482
  ├── package.json # 项目配置
1109
- ├── mr-review-template.md # 审查模板(旧版)
1110
483
  ├── docs/
1111
484
  │ └── GITLAB_CR_NODE_TECHNICAL_DOCS.md # 技术文档
1112
485
  ├── .claude/
@@ -1118,21 +491,10 @@ cr-node/
1118
491
  └── node_modules/
1119
492
  ```
1120
493
 
1121
- ### 6.5 依赖要求
1122
-
1123
- | 依赖 | 版本 | 用途 |
1124
- |------|------|------|
1125
- | Node.js | 10+ | 运行时环境 |
1126
- | axios | ^1.13.3 | HTTP 客户端 |
1127
- | nodemon | ^3.0.1 | 开发热加载 |
1128
- | Claude CLI | 最新 | AI 审查引擎 |
1129
-
1130
494
  ---
1131
495
 
1132
496
  ## 7. GitLab CI/CD 配置
1133
497
 
1134
- **配置文件路径**: `D:\code\ai-agent\code-review\.gitlab-ci.yml`
1135
-
1136
498
  ### 7.1 流水线阶段
1137
499
 
1138
500
  ```yaml
@@ -1204,34 +566,7 @@ gitlab-cr
1204
566
  └─────────────────────────────────────────────────────────────────┘
1205
567
  ```
1206
568
 
1207
- **检测脚本**:
1208
- ```bash
1209
- # 获取 MR 的所有评论
1210
- DISCUSSIONS=$(curl --silent --header "PRIVATE-TOKEN: glpat-xxx" \
1211
- "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes")
1212
-
1213
- # Node.js 解析 JSON 统计未解决评论
1214
- UNRESOLVED_COUNT=$(echo "$DISCUSSIONS" | node -e "
1215
- const data = JSON.parse(input);
1216
- const unresolved = data.filter(item =>
1217
- item.type === 'DiffNote' &&
1218
- item.author &&
1219
- item.author.name === 'AI 审查 - 乌萨奇' &&
1220
- item.resolved === false
1221
- );
1222
- console.log(unresolved.length);
1223
- ")
1224
-
1225
- # 有未解决评论则跳过
1226
- if [ "$UNRESOLVED_COUNT" -gt 0 ]; then
1227
- echo "存在未解决的 AI 审查评论,跳过审查"
1228
- exit 0
1229
- fi
1230
- ```
1231
-
1232
- ### 7.6 审查结束提醒
1233
-
1234
- 审查完成后自动发表评论(仅 MR 事件):
569
+ **审查结束提醒**:
1235
570
  ```bash
1236
571
  curl --request POST \
1237
572
  "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \
@@ -1239,9 +574,6 @@ curl --request POST \
1239
574
  并在 Merge request pipeline 右侧手动点击"Run again"以开启新一轮的评审'
1240
575
  ```
1241
576
 
1242
- **提醒内容**:
1243
- > 本轮 ai 代码审查已结束,请 resolve 所有评论后,并在上方 Merge request pipeline 右侧手动点击"Run again"以开启新一轮的评审,防止多次 push 代码引发 ci 的邮件打扰
1244
-
1245
577
  ---
1246
578
 
1247
579
  ## 8. API 参考
@@ -1254,6 +586,7 @@ curl --request POST \
1254
586
  | `reviewMergeRequest` | projectId, mergeRequestIid, maxConcurrency | `Promise<Array>` | 审查整个 MR |
1255
587
  | `postSingleCommentToGitLab` | projectId, mergeRequestIid, result | `Promise<void>` | 发布单条评论 |
1256
588
  | `getMergeRequestVersions` | projectId, mergeRequestIid | `Promise<Object>` | 获取 MR 版本信息 |
589
+ | `getVersionInfo` | projectId, mergeRequestIid | `Promise<Object>` | 获取 MR 版本信息 (带缓存) |
1257
590
 
1258
591
  ### 8.2 GitLab API 端点
1259
592
 
@@ -1314,7 +647,11 @@ POST /projects/{projectId}/merge_requests/{iid}/discussions
1314
647
 
1315
648
  ### 🔴 严重问题
1316
649
 
1317
- 1️⃣ [问题描述]<br/>
650
+ **问题 1**: [问题描述]<br/>
651
+ **文件及行号**: [path:line]<br/>
652
+ **修改建议**: [示例代码]
653
+
654
+ **问题 2**: [问题描述]<br/>
1318
655
  **文件及行号**: [path:line]<br/>
1319
656
  **修改建议**: [示例代码]
1320
657
 
@@ -1386,7 +723,32 @@ do {
1386
723
  - 无问题的块跳过评论发布
1387
724
  - 减少无效 API 调用
1388
725
 
1389
- ### 11.4 性能指标收集(v2.4.8 新增)
726
+ ### 11.4 版本信息缓存 (v2.4.9)
727
+
728
+ ```javascript
729
+ // 构造函数中初始化缓存
730
+ this._versionCache = new Map();
731
+
732
+ // 获取版本信息(带缓存)
733
+ async getVersionInfo(projectId, mergeRequestIid) {
734
+ const cacheKey = `${projectId}-${mergeRequestIid}`;
735
+
736
+ if (this._versionCache.has(cacheKey)) {
737
+ return this._versionCache.get(cacheKey);
738
+ }
739
+
740
+ const versionInfo = await this.getMergeRequestVersions(projectId, mergeRequestIid);
741
+ this._versionCache.set(cacheKey, versionInfo);
742
+ return versionInfo;
743
+ }
744
+ ```
745
+
746
+ **优化效果**:
747
+ - **优化前**: 每个问题发布评论时都调用 `getMergeRequestVersions` API
748
+ - **优化后**: 同一个 MR 的版本信息只获取一次,后续直接使用缓存
749
+ - **适用场景**: 一个 diff 块有多个问题时,避免重复 API 调用
750
+
751
+ ### 11.5 性能指标收集 (v2.4.9)
1390
752
 
1391
753
  **指标收集器位置**: `utils.js:MetricsCollector`
1392
754
 
@@ -1433,14 +795,7 @@ large (200+ 行): 2 块,平均耗时 10.9s, 总耗时 21.8s
1433
795
  ===============================
1434
796
  ```
1435
797
 
1436
- **指标使用场景**:
1437
-
1438
- 1. **优化 diff 拆分策略**:如果 large 块平均耗时远高于 small 块,可以考虑减小 diff 块大小阈值
1439
- 2. **优化并发配置**:根据慢调用分析,调整 `GITLAB_CR_CONCURRENCY` 环境变量
1440
- 3. **识别性能瓶颈**:通过慢调用分析定位耗时最长的文件类型和 diff 大小
1441
- 4. **容量规划**:根据 diff 大小和耗时的关系,预估大规模代码审查的耗时
1442
-
1443
- ### 11.5 日志分级(v2.4.8 新增)
798
+ ### 11.6 日志分级 (v2.4.9)
1444
799
 
1445
800
  **日志级别**: `DEBUG < INFO < WARN < ERROR`
1446
801
 
@@ -1494,40 +849,61 @@ npm run dev
1494
849
 
1495
850
  | 版本 | 日期 | 变更说明 |
1496
851
  |------|------|----------|
1497
- | 2.4.10 | 2026-04-16 | **行号验证逻辑修复**:修复 AI 返回的行号超出 diff 块范围导致 GitLab API 400 错误的问题。新增功能:(1) `postSingleCommentToGitLab` 方法中添加行号验证逻辑,检查 AI 返回的行号是否在 diff 块有效范围内 `[oldStart, oldEnd]` 或 `[newStart, newEnd]`;(2) 支持删除文件(`new_count=0`)的行号处理,将 AI 返回的 `new_line` 自动映射为 `old_line`;(3) 当行号超出范围时,自动降级为一般讨论(general discussion),避免 GitLab API 报错;(4) 增强 diff 块信息日志输出,包含 `old_start/old_count/old_end/new_start/new_count/new_end/isDeleteFile` 完整信息。修复场景:AI 审查删除文件时返回 `new_line=79`,但文件只有 75 行,导致 GitLab API 返回 `line_code must be a valid line code` 错误 |
1498
- | 2.4.9 | 2026-04-16 | **代码优化与性能指标收集**:(1) 删除已废弃的 `reviewDiffWithClaude` 方法,精简 79 行代码;(2) 添加 GitLab 版本信息缓存机制,避免重复 API 调用;(3) 实现日志分级系统(DEBUG/INFO/WARN/ERROR),支持通过 `LOG_LEVEL` 环境变量控制输出级别;(4) 实现完整的性能指标收集器 `MetricsCollector`,收集 diff 块大小/文件类型/耗时分布,慢调用分析 (>10 秒),AI/API 调用成功率,评论发布统计等,输出按 diff 大小(small/medium/large)和文件类型分组的性能分析,支持识别性能瓶颈和优化审查策略 |
1499
- | 2.4.7 | 2026-04-15 | **行号计算逻辑重大修正**:修正 AI 行号计算方式,从使用 `New Start` 基准计算改为返回文件绝对行号;移除基于 diff 块范围的行号验证逻辑;更新 `code-review-rules.md` `SKILL.md` 中的行号计算规范说明 |
1500
- | 2.4.6 | 2026-04-15 | **多问题评论发布支持**:新增 `parseAllLineInfoFromReviewResult` 方法解析所有问题行号;新增 `extractSingleProblemReport` 方法切分单个问题报告;重构 `postSingleCommentToGitLab` 方法支持循环发布多条评论,每条评论定位到对应问题的行号 |
1501
- | 2.4.5 | 2026-04-15 | **报告模板格式优化**:更新 `SKILL.md` 输出模板,使用 `**问题 N**:` 格式替代带圈数字序号;移除问题间的 `---` 分隔符,简化报告切分逻辑 |
1502
- | 2.4.4 | 2026-04-15 | **标题验证逻辑修复**:修复 AI 审查结果标题验证逻辑,当有严重问题但标题不符合要求时重试,达到最大重试次数后返回最后一次结果 |
1503
- | 2.4.3 | 2026-04-15 | **调试日志增强**:在 `getDiffBlocks` 方法中添加 diff 块解析日志,记录 `new_start` 和 `new_count` 值;添加文件拆分块数日志 |
1504
- | 2.4.2 | 2026-04-15 | **元数据增强**:在临时 diff 文件中添加 `New Start` 和 `New Count` 元数据,供 AI 参考计算行号 |
1505
- | 2.4.1 | 2026-04-08 | 修复 AI 审查结果为空时的返回值 |
1506
- | 2.4.0 | 2026-03-25 | 更新 MR 审查模板 |
1507
- | 2.3.8 | 2026-03-25 | 优化 AI 审核结果处理逻辑 |
1508
-
1509
- ---
1510
-
1511
- ## 附录
852
+ | **2.5.7** | 2026-04-16 | **行号计算规则修复**:修复 AI 返回的行号超出 diff 块范围的问题。问题根因:AI 读取完整源文件后返回文件的绝对行号,而不是基于 diff 块的相对行号。修复方案:(1) 更新 `code-review-rules.md` 13 条,明确行号是**diff 块内的相对行号**,不是文件的绝对行号;(2) 添加行号验证规则:最大行号不能超过 diff 块的 `new_count` 值;(3) 简化 `SKILL.md` 中的行号计算说明,将详细规则移至 rules 文件;(4) rules 中添加强制验证步骤:输出前必须检查 diff 头的 `new_count` 值,确保所有问题行号 new_count |
853
+ | **2.5.6** | 2026-04-16 | **当前版本**:技术文档更新 |
854
+ | 2.4.10 | 2026-04-16 | **行号验证逻辑修复**:修复 AI 返回的行号超出 diff 块范围导致 GitLab API 400 错误的问题。新增功能:(1) 在 `postSingleCommentToGitLab` 方法中添加行号验证逻辑,检查 AI 返回的行号是否在 diff 块有效范围内 `[oldStart, oldEnd]` `[newStart, newEnd]`;(2) 支持删除文件(`new_count=0`)的行号处理,将 AI 返回的 `new_line` 自动映射为 `old_line`;(3) 当行号超出范围时,自动降级为一般讨论(general discussion),避免 GitLab API 报错;(4) 增强 diff 块信息日志输出,包含 `old_start/old_count/old_end/new_start/new_count/isDeleteFile` 完整信息 |
855
+ | 2.4.9 | 2026-04-16 | **代码优化与性能指标收集**:(1) 删除已废弃的 `reviewDiffWithClaude` 方法,精简 79 行代码;(2) 添加 GitLab 版本信息缓存机制,避免重复 API 调用;(3) 实现日志分级系统(DEBUG/INFO/WARN/ERROR),支持通过 `LOG_LEVEL` 环境变量控制输出级别;(4) 实现完整的性能指标收集器 `MetricsCollector`,收集 diff 块大小/文件类型/耗时分布,慢调用分析 (>10 秒),AI/API 调用成功率,评论发布统计等 |
856
+ | 2.4.7 | 2026-04-15 | **行号计算逻辑重大修正**:修正 AI 行号计算方式,从使用 `New Start` 基准计算改为返回文件绝对行号;移除基于 diff 块范围的行号验证逻辑 |
857
+ | 2.4.6 | 2026-04-15 | **多问题评论发布支持**:新增 `parseAllLineInfoFromReviewResult` 方法解析所有问题行号;新增 `extractSingleProblemReport` 方法切分单个问题报告;重构 `postSingleCommentToGitLab` 方法支持循环发布多条评论 |
858
+ | 2.4.5 | 2026-04-15 | **报告模板格式优化**:更新 `SKILL.md` 输出模板,使用 `**问题 N**:` 格式替代带圈数字序号 |
859
+ | 2.4.4 | 2026-04-15 | **标题验证逻辑修复**:修复 AI 审查结果标题验证逻辑,当有严重问题但标题不符合要求时重试 |
1512
860
 
1513
- ### A. 文件清单
861
+ ### 12.4 文件清单
1514
862
 
1515
863
  | 文件 | 行数 | 职责 |
1516
864
  |------|------|------|
1517
- | `index.js` | 717 | 主入口,审查逻辑 |
1518
- | `utils.js` | 75 | 工具函数,GitLab 客户端 |
1519
- | `example.js` | 30 | 使用示例 |
865
+ | `index.js` | 796 | 主入口,审查逻辑 |
866
+ | `utils.js` | 354 | 工具函数,GitLab 客户端,指标收集器 |
1520
867
  | `package.json` | 28 | 项目配置 |
1521
- | `.gitlab-ci.yml` | 70 | GitLab CI 配置 |
1522
868
  | `.claude/rules/code-review-rules.md` | 200+ | 审查规则 |
1523
- | `.claude/skills/simple-code-review/SKILL.md` | 53 | 技能定义 |
1524
- | `docs/GITLAB_CR_NODE_TECHNICAL_DOCS.md` | 1100+ | 技术文档 |
869
+ | `.claude/skills/simple-code-review/SKILL.md` | 91 | 技能定义 |
870
+ | `docs/GITLAB_CR_NODE_TECHNICAL_DOCS.md` | 500+ | 技术文档 |
871
+
872
+ ---
873
+
874
+ ## 附录
1525
875
 
1526
- ### B. 联系方式
876
+ ### A. 快速开始
877
+
878
+ ```bash
879
+ # 1. 安装依赖
880
+ npm install
881
+
882
+ # 2. 设置环境变量
883
+ export CI_API_V4_URL="https://gitlab.example.com"
884
+ export GITLAB_ACCESS_TOKEN="your-token"
885
+ export CI_PROJECT_ID="123"
886
+ export CI_MERGE_REQUEST_IID="456"
887
+
888
+ # 3. 运行审查
889
+ node index.js
890
+ ```
891
+
892
+ ### B. 常见问题
893
+
894
+ **Q: 如何调整并发数?**
895
+ ```bash
896
+ export GITLAB_CR_CONCURRENCY=5
897
+ ```
898
+
899
+ **Q: 如何查看详细日志?**
900
+ ```bash
901
+ export LOG_LEVEL=0 # DEBUG 级别
902
+ ```
1527
903
 
1528
- - 作者:tao.jing
1529
- - 项目仓库:(内部)
904
+ **Q: 如何跳过某些文件的审查?**
905
+ 修改 `.claude/rules/code-review-rules.md` 中的审查范围规则。
1530
906
 
1531
907
  ---
1532
908
 
1533
- *文档生成时间:2026-04-14*
909
+ *文档生成时间:2026-04-16*
package/index.js CHANGED
@@ -81,7 +81,7 @@ class GitLabCodeReviewer {
81
81
  debugLog(`========== Diff Block ${blockIndex} 开始 ==========`);
82
82
  debugLog(`文件路径:${diffObject.new_path || diffObject.old_path}`);
83
83
  debugLog(`line_info: old_start=${diffObject.line_info?.old_start}, old_count=${diffObject.line_info?.old_count}, new_start=${diffObject.line_info?.new_start}, new_count=${diffObject.line_info?.new_count}`);
84
- debugLog(`diff 内容预览(前 500 字符):${diffObject.diff?.substring(0, 500)}`);
84
+ debugLog(`diff 内容:${diffObject.diff}`);
85
85
  debugLog(`========== Diff Block ${blockIndex} 结束 ==========`);
86
86
 
87
87
  // 构造包含元数据的 diff 内容
@@ -210,7 +210,7 @@ ${diffObject.diff}`;
210
210
 
211
211
  // 打印报告内容前 500 字符(避免过长)
212
212
  const reportPreview = claudeResult?.length > 500 ? claudeResult.substring(0, 500) + '...' : claudeResult;
213
- debugLog(`AI 审核报告内容预览:${reportPreview}`);
213
+ debugLog(`AI 审核报告内容预览:${claudeResult}`);
214
214
 
215
215
  // 使用正则提取 LINE_INFO 内容(支持换行)
216
216
  const lineInfoMatch = claudeResult?.match(/<LINE_INFO>\s*\[([^\]]*)\]\s*<\/LINE_INFO>/);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "job51-gitlab-cr-node-jt-1",
3
- "version": "2.5.6",
3
+ "version": "2.5.8",
4
4
  "description": "GitLab merge request code review tool with AI-powered analysis and project context support",
5
5
  "main": "index.js",
6
6
  "bin": {