job51-gitlab-cr-node-jt-1 2.4.6 → 2.4.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.
- package/.claude/rules/code-review-rules.md +9 -12
- package/.claude/skills/simple-code-review/SKILL.md +6 -11
- package/docs/GITLAB_CR_NODE_TECHNICAL_DOCS.md +161 -91
- package/index.js +20 -23
- package/package.json +1 -1
- package/log.txt +0 -731
|
@@ -181,18 +181,15 @@
|
|
|
181
181
|
12. 最终输出必须以 `<REPORT>` 开始,以 `</REPORT>` 结束;
|
|
182
182
|
|
|
183
183
|
13. **行号计算规范**:
|
|
184
|
-
> -
|
|
185
|
-
|
|
186
|
-
> -
|
|
187
|
-
|
|
188
|
-
> -
|
|
189
|
-
|
|
190
|
-
> -
|
|
191
|
-
> -
|
|
192
|
-
> -
|
|
193
|
-
> - 第 1 行 `+`:行号 = 1
|
|
194
|
-
> - 第 2 行 `+`:行号 = 2
|
|
195
|
-
> - **关键**:问题行号必须准确指向变更后代码的实际行号(绝对行号)
|
|
184
|
+
> - **行号定义**:行号是变更后文件中的**绝对行号**(从 1 开始计数)
|
|
185
|
+
> - **如何确定行号**:
|
|
186
|
+
> - 读取变更后文件(基于 `New Path`)
|
|
187
|
+
> - 找到新增代码在文件中的实际行号
|
|
188
|
+
> - 例如:如果新增代码在变更后文件的第 43 行,则 `new_line = 43`
|
|
189
|
+
> - **示例**:
|
|
190
|
+
> - 新增文件 `@@ -0,0 +1,49 @@`:第 1 行 `+` 的行号 = 1,第 2 行 `+` 的行号 = 2
|
|
191
|
+
> - 已有文件 `@@ -42,3 +43,5 @@`:新增代码在文件第 43 行,则 `new_line = 43`
|
|
192
|
+
> - **关键**:问题行号必须准确指向变更后代码在文件中的实际行号(绝对行号)
|
|
196
193
|
|
|
197
194
|
14. `<REPORT>` 标签示例中的问题块数量仅用于展示格式,实际数量由 review 结果决定;
|
|
198
195
|
|
|
@@ -9,19 +9,14 @@ description: 代码审查技能,审查变更代码并输出 REPORT 和 LINE_IN
|
|
|
9
9
|
|
|
10
10
|
2. **解析文件信息**:从 `=== File Information ===` 部分提取
|
|
11
11
|
- `Block Index`: 当前块索引号
|
|
12
|
-
- `New Start`: 当前 diff
|
|
12
|
+
- `New Start`: 当前 diff 块在变更后文件中的起始行号(仅供参考)
|
|
13
13
|
- `New Count`: 当前 diff 块的行数
|
|
14
14
|
- `New Path` / `Old Path`: 文件路径(去掉 `a/` 或 `b/` 前缀)
|
|
15
15
|
|
|
16
|
-
3.
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- 第 1 行 `+` 或空格:行号 = `New Start`
|
|
21
|
-
- 第 2 行 `+` 或空格:行号 = `New Start + 1`
|
|
22
|
-
- 以此类推,`-` 开头的删除行不计入行号
|
|
23
|
-
- **重要**:行号是变更后文件中的绝对行号,必须使用 `New Start` 作为基准计算
|
|
24
|
-
- **只报告当前 diff 块内的问题**:问题的行号必须在 `[New Start, New Start + New Count - 1]` 范围内
|
|
16
|
+
3. **解析行号**:返回文件中变更代码的**绝对行号**(即变更后文件中的行号)
|
|
17
|
+
- 行号定义:行号是变更后文件中的绝对行号(从 1 开始计数)
|
|
18
|
+
- 例如:如果新增代码在变更后文件的第 43 行,则 `new_line = 43`
|
|
19
|
+
- **如何确定行号**:读取变更后文件(基于 `New Path`),找到新增代码在文件中的实际行号
|
|
25
20
|
|
|
26
21
|
4. **读取上下文**:基于 New Path 读取变更后文件,涉及方法调用时追踪读取实现代码
|
|
27
22
|
|
|
@@ -58,5 +53,5 @@ description: 代码审查技能,审查变更代码并输出 REPORT 和 LINE_IN
|
|
|
58
53
|
- 无问题时省略 `### 🔴 严重问题` 标题,但 `<LINE_INFO>` 必须输出(空数组:`[]`)
|
|
59
54
|
- 每个问题以 `**问题 N**:` 开头,方便切分
|
|
60
55
|
- **`<LINE_INFO>` 数组中的元素顺序必须与问题顺序一致**:第 1 个元素对应问题 1 的行号,第 2 个元素对应问题 2 的行号,以此类推
|
|
61
|
-
-
|
|
56
|
+
- 行号:返回变更后文件中的绝对行号(从 1 开始计数),例如文件第 43 行则 `new_line = 43`
|
|
62
57
|
- **无严重问题时,LINE_INFO 必须输出空数组 `[]`**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# GitLab Code Review AI Tool 技术文档
|
|
2
2
|
|
|
3
3
|
**项目名称**: job51-gitlab-cr-node
|
|
4
|
-
**当前版本**: 2.4.
|
|
4
|
+
**当前版本**: 2.4.7
|
|
5
5
|
**作者**: tao.jing
|
|
6
6
|
**最后更新**: 2026-04-15
|
|
7
7
|
**项目地址**: https://gitdev.51job.com/51jobweb/ai-agent
|
|
@@ -338,7 +338,7 @@ class GitLabAPIClient {
|
|
|
338
338
|
|
|
339
339
|
## 4. 关键算法与流程
|
|
340
340
|
|
|
341
|
-
### 4.1
|
|
341
|
+
### 4.1 严重问题判断逻辑(核心)(2.4.4 更新)
|
|
342
342
|
|
|
343
343
|
**位置**: `index.js:55-133`
|
|
344
344
|
|
|
@@ -368,9 +368,9 @@ class GitLabAPIClient {
|
|
|
368
368
|
│ │ const hasSeriousProblem = │ │
|
|
369
369
|
│ │ claudeResult.includes('严重问题') │ │
|
|
370
370
|
│ │ │ │
|
|
371
|
-
│ │ 如果 false →
|
|
372
|
-
│ │
|
|
373
|
-
│ │
|
|
371
|
+
│ │ 如果 false → 无严重问题,返回标准空格式 │ │
|
|
372
|
+
│ │ reportContent: '<REPORT>\n## 🤖 AI 代码审查结果\n\n</REPORT>'│ │
|
|
373
|
+
│ │ lineInfo: '[]' │ │
|
|
374
374
|
│ └───────────────────────────────────────────────────────┘ │
|
|
375
375
|
│ ↓ (有严重问题) │
|
|
376
376
|
│ 步骤 4: 检查标题是否符合要求 │
|
|
@@ -390,7 +390,7 @@ class GitLabAPIClient {
|
|
|
390
390
|
| 判断步骤 | 检查内容 | 判断条件 | 通过时动作 | 失败时动作 |
|
|
391
391
|
|----------|----------|----------|------------|------------|
|
|
392
392
|
| **步骤 1** | LINE_INFO 标签存在性 | `hasLineInfoTag && hasNonEmptyLineInfo` | 进入步骤 2 | 返回空格式 |
|
|
393
|
-
| **步骤 2** | 严重问题关键词 | `claudeResult.includes('严重问题')` | 进入步骤 3 |
|
|
393
|
+
| **步骤 2** | 严重问题关键词 | `claudeResult.includes('严重问题')` | 进入步骤 3 | 返回空格式 |
|
|
394
394
|
| **步骤 3** | 标题格式正确性 | `claudeResult.includes('🤖 AI 代码审查结果')` | 接受结果 | 重试 (最多 5 次) |
|
|
395
395
|
|
|
396
396
|
**重试策略详情**:
|
|
@@ -413,6 +413,10 @@ if (attempts >= maxAttempts) {
|
|
|
413
413
|
[DEBUG] ... 【决策】报告包含严重问题且标题正确,接受结果 (尝试 1)
|
|
414
414
|
```
|
|
415
415
|
|
|
416
|
+
**v2.4.4 变更**:
|
|
417
|
+
- 无严重问题时统一返回标准空格式(之前返回原始结果)
|
|
418
|
+
- 达到最大重试次数时返回最后一次 AI 结果(之前返回空格式)
|
|
419
|
+
|
|
416
420
|
### 4.2 评论发布决策逻辑
|
|
417
421
|
|
|
418
422
|
**位置**: `index.js:173-183`
|
|
@@ -491,11 +495,11 @@ getDiffBlocks(diffObj) {
|
|
|
491
495
|
| 空格 | `current_line++` | 上下文代码,计入行号 |
|
|
492
496
|
| `-` | 不变 | 删除代码,不计入行号 |
|
|
493
497
|
|
|
494
|
-
### 4.4
|
|
498
|
+
### 4.4 评论发布核心逻辑(详细)(2.4.6/2.4.7 更新)
|
|
495
499
|
|
|
496
|
-
**位置**: `index.js:
|
|
500
|
+
**位置**: `index.js:577-665`
|
|
497
501
|
|
|
498
|
-
|
|
502
|
+
**完整发布流程**(v2.4.6 更新):
|
|
499
503
|
```
|
|
500
504
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
501
505
|
│ 评论发布完整流程 │
|
|
@@ -509,54 +513,75 @@ getDiffBlocks(diffObj) {
|
|
|
509
513
|
│ │ - start_sha: 起始提交 SHA │ │
|
|
510
514
|
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
511
515
|
│ ↓ │
|
|
512
|
-
│ 步骤 2:
|
|
516
|
+
│ 步骤 2: 解析 LINE_INFO 中的所有问题行号 │
|
|
513
517
|
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
514
|
-
│ │
|
|
515
|
-
│ │ 优先级 2: diff 块起始行号 (后备方案) │ │
|
|
518
|
+
│ │ allLineInfo = parseAllLineInfoFromReviewResult(lineInfoTag) │ │
|
|
516
519
|
│ │ │ │
|
|
517
|
-
│ │
|
|
520
|
+
│ │ v2.4.6 新增:支持多个问题行号 │ │
|
|
518
521
|
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
519
522
|
│ ↓ │
|
|
520
|
-
│ 步骤 3:
|
|
523
|
+
│ 步骤 3: 循环处理每个问题 │
|
|
521
524
|
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
522
|
-
│ │
|
|
523
|
-
│ │
|
|
525
|
+
│ │ for (let i = 0; i < allLineInfo.length; i++) │ │
|
|
526
|
+
│ │ a. 提取单个问题报告内容 │ │
|
|
527
|
+
│ │ singleProblemContent = extractSingleProblemReport(i + 1) │ │
|
|
528
|
+
│ │ b. 构建目标行号 │ │
|
|
529
|
+
│ │ - 删除代码:使用 old_line + old_path │ │
|
|
530
|
+
│ │ - 新增/上下文:使用 new_line + new_path │ │
|
|
531
|
+
│ │ c. 发布评论到对应行 │ │
|
|
532
|
+
│ │ d. GitLab API 失败降级为一般讨论 │ │
|
|
524
533
|
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
525
534
|
│ ↓ │
|
|
526
|
-
│ 步骤 4:
|
|
527
|
-
│ ┌───────────────────────────────────────────────────────────────────┐ │
|
|
528
|
-
│ │ Diff 评论失败 → 自动降级为一般讨论 │ │
|
|
529
|
-
│ └───────────────────────────────────────────────────────────────────┘ │
|
|
535
|
+
│ 步骤 4: 所有评论发布完成 │
|
|
530
536
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
531
537
|
```
|
|
532
538
|
|
|
533
|
-
**targetLine
|
|
539
|
+
**targetLine 确定逻辑**(v2.4.7 更新):
|
|
534
540
|
```javascript
|
|
535
|
-
//
|
|
536
|
-
let
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
+
}
|
|
549
559
|
|
|
550
|
-
if (
|
|
551
|
-
//
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
// 行号越界,使用 diff 块起始行号
|
|
555
|
-
debugLog(`解析的行号 ${parsedLineInfo.new_line} 不在 diff 块范围 [${newStart}, ${newEnd}] 内`);
|
|
560
|
+
if (!targetLine) {
|
|
561
|
+
// 无法解析行号,使用一般讨论
|
|
562
|
+
await this.createGeneralDiscussion(...);
|
|
563
|
+
continue;
|
|
556
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
|
+
};
|
|
557
577
|
}
|
|
558
578
|
```
|
|
559
579
|
|
|
580
|
+
**v2.4.7 重要变更**:
|
|
581
|
+
- **移除行号范围验证**:之前会验证 `parsedLineInfo.new_line ∈ [new_start, new_end]`
|
|
582
|
+
- **直接使用绝对行号**:AI 返回的行号是文件中的绝对行号,直接使用
|
|
583
|
+
- **原因**:AI 读取完整源文件后定位问题,返回的是文件实际行号,不是基于 `New Start` 计算的
|
|
584
|
+
|
|
560
585
|
**GitLab API Payload 结构**:
|
|
561
586
|
```javascript
|
|
562
587
|
// Diff 评论 Payload
|
|
@@ -626,38 +651,52 @@ async processWithThreadPool(tasks, processor, maxConcurrency = 3) {
|
|
|
626
651
|
export GITLAB_CR_CONCURRENCY=5 # 默认 3
|
|
627
652
|
```
|
|
628
653
|
|
|
629
|
-
### 4.6
|
|
654
|
+
### 4.6 行号解析算法(2.4.7 更新)
|
|
655
|
+
|
|
656
|
+
**行号定义**:
|
|
657
|
+
- 行号是变更后文件中的**绝对行号**(从 1 开始计数)
|
|
658
|
+
- 例如:如果新增代码在变更后文件的第 43 行,则 `new_line = 43`
|
|
630
659
|
|
|
631
|
-
|
|
660
|
+
**重要变更**(v2.4.7):
|
|
661
|
+
- **v2.4.7 之前**:使用 diff 块的 `New Start` 作为基准计算行号,并验证行号是否在 diff 块范围内
|
|
662
|
+
- **v2.4.7 及之后**:AI 直接返回文件中的绝对行号,不再基于 `New Start` 计算,移除范围验证
|
|
632
663
|
|
|
633
|
-
|
|
664
|
+
**原因**:
|
|
665
|
+
- AI 会读取完整的变更后文件来分析上下文
|
|
666
|
+
- AI 在完整源文件中定位问题,返回的是文件中的绝对行号
|
|
667
|
+
- 使用 `New Start` 验证行号范围会导致误判(如 diff 块 `New Start=1` 但 AI 返回行号 43)
|
|
668
|
+
|
|
669
|
+
**原解析函数**:
|
|
634
670
|
```javascript
|
|
635
671
|
parseLineInfoFromReviewResult(lineInfoTag) {
|
|
636
|
-
//
|
|
672
|
+
// 从标签中提取 JSON 内容
|
|
637
673
|
const jsonContent = lineInfoTag
|
|
638
674
|
.replace(/<LINE_INFO>\s*/g, '')
|
|
639
675
|
.replace(/\s*<\/LINE_INFO>/g, '')
|
|
640
676
|
.trim();
|
|
641
677
|
|
|
642
|
-
// 2. 空数组处理 → 使用后备方案
|
|
643
678
|
if (jsonContent === '[]') {
|
|
644
|
-
|
|
645
|
-
return null;
|
|
679
|
+
return null; // 空数组使用后备方案
|
|
646
680
|
}
|
|
647
681
|
|
|
648
|
-
// 3. 解析 JSON 数组
|
|
649
682
|
const lineInfoArray = JSON.parse(jsonContent);
|
|
683
|
+
// 返回第一个问题的行号信息
|
|
684
|
+
return lineInfoArray[0];
|
|
685
|
+
}
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
**新增解析函数**(v2.4.6):
|
|
689
|
+
```javascript
|
|
690
|
+
parseAllLineInfoFromReviewResult(lineInfoTag) {
|
|
691
|
+
// 解析 LINE_INFO 中的所有问题行号
|
|
692
|
+
const jsonContent = lineInfoTag
|
|
693
|
+
.replace(/<LINE_INFO>\s*/g, '')
|
|
694
|
+
.replace(/\s*<\/LINE_INFO>/g, '')
|
|
695
|
+
.trim();
|
|
650
696
|
|
|
651
|
-
|
|
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
|
-
}
|
|
697
|
+
if (jsonContent === '[]') return [];
|
|
659
698
|
|
|
660
|
-
return
|
|
699
|
+
return JSON.parse(jsonContent);
|
|
661
700
|
}
|
|
662
701
|
```
|
|
663
702
|
|
|
@@ -669,27 +708,7 @@ parseLineInfoFromReviewResult(lineInfoTag) {
|
|
|
669
708
|
| 删除代码 (`-`) | 使用 `old_line` + `old_path` |
|
|
670
709
|
| 上下文代码 | 使用 `new_line` + `new_path` |
|
|
671
710
|
|
|
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 评论发布策略
|
|
711
|
+
### 4.7 评论发布策略(2.4.6 更新)
|
|
693
712
|
|
|
694
713
|
**增量发布模式** (`index.js:174-183`):
|
|
695
714
|
```javascript
|
|
@@ -701,14 +720,64 @@ if (review_result.includes('严重问题')) {
|
|
|
701
720
|
}
|
|
702
721
|
```
|
|
703
722
|
|
|
704
|
-
|
|
723
|
+
**多问题评论发布**(v2.4.6 新增):
|
|
705
724
|
```javascript
|
|
706
|
-
//
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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
|
+
let targetLine;
|
|
733
|
+
if (problemInfo.old_line && !problemInfo.new_line) {
|
|
734
|
+
targetLine = { old_path: problemInfo.old_path, old_line: problemInfo.old_line };
|
|
735
|
+
} else if (problemInfo.new_line) {
|
|
736
|
+
// AI 返回的是文件中的绝对行号,直接使用
|
|
737
|
+
targetLine = { new_path: problemInfo.new_path, new_line: problemInfo.new_line };
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// 发布评论到对应行
|
|
741
|
+
await this.createDiffDiscussion(projectId, mergeRequestIid, {
|
|
742
|
+
body: singleProblemContent,
|
|
743
|
+
position: { ...targetLine, ...shaInfo }
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
**行级评论定位**(v2.4.7 更新):
|
|
749
|
+
```javascript
|
|
750
|
+
// v2.4.7:直接使用 AI 返回的绝对行号,不再验证范围
|
|
751
|
+
if (problemInfo.new_line) {
|
|
752
|
+
// AI 返回的是文件中的绝对行号,直接使用
|
|
753
|
+
targetLine = {
|
|
754
|
+
new_path: problemInfo.new_path,
|
|
755
|
+
new_line: problemInfo.new_line
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
**报告切分方法**(v2.4.6 新增):
|
|
761
|
+
```javascript
|
|
762
|
+
extractSingleProblemReport(fullReport, problemIndex) {
|
|
763
|
+
// 使用 **问题 N**:模式来切分问题
|
|
764
|
+
const problemBlocks = [];
|
|
765
|
+
let currentBlock = [];
|
|
766
|
+
|
|
767
|
+
for (const line of lines) {
|
|
768
|
+
const problemMatch = line.match(/\*\*问题\s*(\d+)\*\*/);
|
|
769
|
+
if (problemMatch) {
|
|
770
|
+
if (currentBlock.length > 0) {
|
|
771
|
+
problemBlocks.push(currentBlock.join('\n'));
|
|
772
|
+
currentBlock = [];
|
|
773
|
+
}
|
|
774
|
+
currentBlock.push(line);
|
|
775
|
+
} else {
|
|
776
|
+
currentBlock.push(line);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return problemBlocks[problemIndex - 1];
|
|
712
781
|
}
|
|
713
782
|
```
|
|
714
783
|
|
|
@@ -1323,11 +1392,12 @@ npm run dev
|
|
|
1323
1392
|
|
|
1324
1393
|
| 版本 | 日期 | 变更说明 |
|
|
1325
1394
|
|------|------|----------|
|
|
1326
|
-
| 2.4.
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
|
1330
|
-
|
|
1395
|
+
| 2.4.7 | 2026-04-15 | **行号计算逻辑重大修正**:修正 AI 行号计算方式,从使用 `New Start` 基准计算改为返回文件绝对行号;移除基于 diff 块范围的行号验证逻辑;更新 `code-review-rules.md` 和 `SKILL.md` 中的行号计算规范说明 |
|
|
1396
|
+
| 2.4.6 | 2026-04-15 | **多问题评论发布支持**:新增 `parseAllLineInfoFromReviewResult` 方法解析所有问题行号;新增 `extractSingleProblemReport` 方法切分单个问题报告;重构 `postSingleCommentToGitLab` 方法支持循环发布多条评论,每条评论定位到对应问题的行号 |
|
|
1397
|
+
| 2.4.5 | 2026-04-15 | **报告模板格式优化**:更新 `SKILL.md` 输出模板,使用 `**问题 N**:` 格式替代带圈数字序号;移除问题间的 `---` 分隔符,简化报告切分逻辑 |
|
|
1398
|
+
| 2.4.4 | 2026-04-15 | **标题验证逻辑修复**:修复 AI 审查结果标题验证逻辑,当有严重问题但标题不符合要求时重试,达到最大重试次数后返回最后一次结果 |
|
|
1399
|
+
| 2.4.3 | 2026-04-15 | **调试日志增强**:在 `getDiffBlocks` 方法中添加 diff 块解析日志,记录 `new_start` 和 `new_count` 值;添加文件拆分块数日志 |
|
|
1400
|
+
| 2.4.2 | 2026-04-15 | **元数据增强**:在临时 diff 文件中添加 `New Start` 和 `New Count` 元数据,供 AI 参考计算行号 |
|
|
1331
1401
|
| 2.4.1 | 2026-04-08 | 修复 AI 审查结果为空时的返回值 |
|
|
1332
1402
|
| 2.4.0 | 2026-03-25 | 更新 MR 审查模板 |
|
|
1333
1403
|
| 2.3.8 | 2026-03-25 | 优化 AI 审核结果处理逻辑 |
|
package/index.js
CHANGED
|
@@ -383,7 +383,7 @@ ${diffObject.diff}`;
|
|
|
383
383
|
const regex = /(?=@@\s-\d+(?:,\d+)?\s\+\d+(?:,\d+)?\s@@)/g;
|
|
384
384
|
const diffBlocks = diffObj.diff.split(regex);
|
|
385
385
|
// 过滤掉空块并提取行号信息
|
|
386
|
-
|
|
386
|
+
const result = diffBlocks
|
|
387
387
|
.filter(block => block.trim() !== '')
|
|
388
388
|
.map(block => {
|
|
389
389
|
// 解析diff头信息 @@ -old_start,old_count +new_start,new_count @@
|
|
@@ -407,6 +407,8 @@ ${diffObject.diff}`;
|
|
|
407
407
|
new_count = headerMatch[4] ? parseInt(headerMatch[4], 10) : 1;
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
debugLog(`解析 diff 块:file=${diffObj.new_path || diffObj.old_path}, headerMatch=${headerMatch ? headerMatch[0] : 'N/A'}, new_start=${new_start}, new_count=${new_count}`);
|
|
411
|
+
|
|
410
412
|
return {
|
|
411
413
|
diff: block,
|
|
412
414
|
new_path: diffObj.new_path,
|
|
@@ -427,6 +429,9 @@ ${diffObject.diff}`;
|
|
|
427
429
|
}
|
|
428
430
|
};
|
|
429
431
|
});
|
|
432
|
+
|
|
433
|
+
debugLog(`文件 ${diffObj.new_path || diffObj.old_path} 拆分为 ${result.length} 个 diff 块`);
|
|
434
|
+
return result;
|
|
430
435
|
}
|
|
431
436
|
|
|
432
437
|
/**
|
|
@@ -591,11 +596,10 @@ ${diffObject.diff}`;
|
|
|
591
596
|
const startSha = versionInfo.start_commit_sha;
|
|
592
597
|
debugLog(`获取到版本信息 - base: ${baseSha}, head: ${headSha}, start: ${startSha}`);
|
|
593
598
|
|
|
594
|
-
// 解析 diff
|
|
595
|
-
const newStart = diff_info.new_start || 1;
|
|
596
|
-
const newCount = diff_info.new_count || 1;
|
|
597
|
-
|
|
598
|
-
debugLog(`Diff 块行号范围:[${newStart}, ${maxValidLine}],共 ${newCount} 行`);
|
|
599
|
+
// 解析 diff 头信息(用于日志记录)
|
|
600
|
+
const newStart = diff_info.line_info?.new_start || 1;
|
|
601
|
+
const newCount = diff_info.line_info?.new_count || 1;
|
|
602
|
+
debugLog(`Diff 块信息:new_start=${newStart}, new_count=${newCount}`);
|
|
599
603
|
|
|
600
604
|
const allLineInfo = this.parseAllLineInfoFromReviewResult(lineInfoTag);
|
|
601
605
|
debugLog(`解析到 ${allLineInfo.length} 个问题的行号信息`);
|
|
@@ -609,10 +613,8 @@ ${diffObject.diff}`;
|
|
|
609
613
|
debugLog(`处理第 ${i + 1}/${allLineInfo.length} 个问题:文件=${problemInfo.new_path}, 行号=${problemInfo.new_line}`);
|
|
610
614
|
const singleProblemContent = this.extractSingleProblemReport(fullReportContent, i + 1);
|
|
611
615
|
|
|
612
|
-
//
|
|
616
|
+
// 构建目标行号
|
|
613
617
|
let targetLine;
|
|
614
|
-
let originalLine = problemInfo.new_line;
|
|
615
|
-
let needsGeneralDiscussion = false;
|
|
616
618
|
|
|
617
619
|
if (problemInfo.old_line && !problemInfo.new_line) {
|
|
618
620
|
// 删除代码,使用 old_line
|
|
@@ -621,23 +623,18 @@ ${diffObject.diff}`;
|
|
|
621
623
|
old_line: problemInfo.old_line
|
|
622
624
|
};
|
|
623
625
|
} else if (problemInfo.new_line) {
|
|
624
|
-
//
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
targetLine = {
|
|
630
|
-
new_path: problemInfo.new_path,
|
|
631
|
-
new_line: problemInfo.new_line
|
|
632
|
-
};
|
|
633
|
-
}
|
|
626
|
+
// AI 返回的是文件中的绝对行号,直接使用
|
|
627
|
+
targetLine = {
|
|
628
|
+
new_path: problemInfo.new_path,
|
|
629
|
+
new_line: problemInfo.new_line
|
|
630
|
+
};
|
|
634
631
|
}
|
|
635
632
|
|
|
636
|
-
if (
|
|
637
|
-
//
|
|
638
|
-
debugLog(`第 ${i + 1}
|
|
633
|
+
if (!targetLine) {
|
|
634
|
+
// 无法解析行号,使用一般讨论
|
|
635
|
+
debugLog(`第 ${i + 1} 个问题无法解析行号,发布为一般讨论`);
|
|
639
636
|
await this.createGeneralDiscussion(projectId, mergeRequestIid, file_path_with_line, singleProblemContent);
|
|
640
|
-
debugLog(`第 ${i + 1} 个问题的评论已发布 (
|
|
637
|
+
debugLog(`第 ${i + 1} 个问题的评论已发布 (作为一般讨论)`);
|
|
641
638
|
continue;
|
|
642
639
|
}
|
|
643
640
|
|