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.
@@ -181,18 +181,15 @@
181
181
  12. 最终输出必须以 `<REPORT>` 开始,以 `</REPORT>` 结束;
182
182
 
183
183
  13. **行号计算规范**:
184
- > - **Diff 头信息格式**:`@@ -84,9 +84,11 @@`
185
- > - `-84,9`:旧文件从第 84 行开始,共 9 行
186
- > - `+84,11`:新文件从第 84 行开始,共 11 行
187
- > - **行号计算**(绝对行号,非相对偏移):
188
- > - 初始值 = `new_start`(从 diff 头解析)
189
- > - 空格开头(上下文):当前行号 = 计数器值,计数器 +1
190
- > - `-` 开头(删除):跳过,计数器不变
191
- > - `+` 开头(新增):当前行号 = 计数器值,计数器 +1
192
- > - **示例**:`@@ -0,0 +1,49 @@`(新增文件)
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. **解析行号**:使用 `New Start` 作为基准,遍历 diff 内容计算每行的绝对行号
17
- - 行号计算规则:
18
- - **基准行号** = `New Start`(从 File Information 中获取)
19
- - 遍历 diff 内容,只计数 `+` 开头(新增)和空格开头(上下文)的行
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
- - 行号计算:使用 `New Start` 作为基准,第 1 `+` 或空格的行号 = `New Start`,第 2 行 = `New Start + 1`,依此类推
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.1
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
- │ │ 返回:{ reportContent: claudeResult, │ │
373
- │ │ lineInfo: lineInfoContent } │ │
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:497-589`
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: 确定目标行号 (targetLine)
516
+ │ 步骤 2: 解析 LINE_INFO 中的所有问题行号
513
517
  │ ┌───────────────────────────────────────────────────────────────────┐ │
514
- │ │ 优先级 1: LINE_INFO 标签解析的行号 (最精确) │ │
515
- │ │ 优先级 2: diff 块起始行号 (后备方案) │ │
518
+ │ │ allLineInfo = parseAllLineInfoFromReviewResult(lineInfoTag) │ │
516
519
  │ │ │ │
517
- │ │ 验证:parsedLineInfo.new_line 必须在 [new_start, new_end] 范围内 │ │
520
+ │ │ v2.4.6 新增:支持多个问题行号 │ │
518
521
  │ └───────────────────────────────────────────────────────────────────┘ │
519
522
  │ ↓ │
520
- │ 步骤 3: 选择评论类型
523
+ │ 步骤 3: 循环处理每个问题
521
524
  │ ┌───────────────────────────────────────────────────────────────────┐ │
522
- │ │ targetLine Diff 评论 (行级评论,定位到具体代码行) │ │
523
- │ │ 无 targetLine → 一般讨论 (文件级评论) │ │
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: 调用 GitLab API 发布评论
527
- │ ┌───────────────────────────────────────────────────────────────────┐ │
528
- │ │ Diff 评论失败 → 自动降级为一般讨论 │ │
529
- │ └───────────────────────────────────────────────────────────────────┘ │
535
+ │ 步骤 4: 所有评论发布完成
530
536
  └─────────────────────────────────────────────────────────────────────────┘
531
537
  ```
532
538
 
533
- **targetLine 确定逻辑**:
539
+ **targetLine 确定逻辑**(v2.4.7 更新):
534
540
  ```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;
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 (parsedLineInfo.new_line >= newStart && parsedLineInfo.new_line <= newEnd) {
551
- // 行号有效,使用解析结果
552
- targetLine = parsedLineInfo;
553
- } else {
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
- **位置**: `index.js:435-470`
660
+ **重要变更**(v2.4.7):
661
+ - **v2.4.7 之前**:使用 diff 块的 `New Start` 作为基准计算行号,并验证行号是否在 diff 块范围内
662
+ - **v2.4.7 及之后**:AI 直接返回文件中的绝对行号,不再基于 `New Start` 计算,移除范围验证
632
663
 
633
- **LINE_INFO 解析函数**:
664
+ **原因**:
665
+ - AI 会读取完整的变更后文件来分析上下文
666
+ - AI 在完整源文件中定位问题,返回的是文件中的绝对行号
667
+ - 使用 `New Start` 验证行号范围会导致误判(如 diff 块 `New Start=1` 但 AI 返回行号 43)
668
+
669
+ **原解析函数**:
634
670
  ```javascript
635
671
  parseLineInfoFromReviewResult(lineInfoTag) {
636
- // 1. 从标签中提取 JSON 内容
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
- debugLog('LINE_INFO 为空数组,使用 diff 块起始行号作为后备方案');
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
- // 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
- }
697
+ if (jsonContent === '[]') return [];
659
698
 
660
- return null; // 解析失败,使用后备方案
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
- **行级评论定位** (`index.js:529-552`):
723
+ **多问题评论发布**(v2.4.6 新增):
705
724
  ```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; // 后备方案
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.1 | 2026-04-08 | 修复 AI 审查结果为空时的返回值 |
1327
- ### 12.3 版本历史
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
- return diffBlocks
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
- const maxValidLine = newStart + newCount - 1;
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
- if (problemInfo.new_line < newStart || problemInfo.new_line > maxValidLine) {
626
- debugLog(`⚠️ 行号 ${problemInfo.new_line} 超出有效范围 [${newStart}, ${maxValidLine}],改用一般讨论`);
627
- needsGeneralDiscussion = true;
628
- } else {
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 (needsGeneralDiscussion) {
637
- // 行号无效,改用一般讨论
638
- debugLog(`第 ${i + 1} 个问题的行号 ${originalLine} 无效,发布为一般讨论`);
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "job51-gitlab-cr-node-jt-1",
3
- "version": "2.4.6",
3
+ "version": "2.4.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": {