job51-gitlab-cr-node-jt-1 2.9.6 → 2.9.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.
@@ -21,40 +21,66 @@ description: 代码审查技能,审查变更代码并输出 REPORT 和 LINE_IN
21
21
  4. `-` 开头的删除行**不计数**,行号不增加
22
22
  5. 示例:`New Start: 1` 时,第 1 行代码行号=1,第 2 行行号=2,以此类推
23
23
  - **忽略元数据行**:文件末尾的 `# File Information` 等以 `# ` 开头的行都**不编号**,直接跳过
24
- - **⚠️ 临时文件格式示例**(理解计数起点):
25
- ```
26
- [代码行 1] <-- 空格开头,行号 = 29(New Start=29)
27
- + [代码行 2] <-- + 开头,行号 = 30(29+1)
28
- + [代码行 3] <-- + 开头,行号 = 31(30+1)
29
- [代码行 4] <-- 空格开头,行号 = 32(31+1)
30
- ...(diff 内容结束)
31
- # File Information
32
- # New Path: b/src/Test.java
33
- # New Start: 29 <-- 元数据在末尾,提供行号计算起点
34
- ```
35
24
  - **⚠️ 强制验证步骤**(输出前必须执行):
36
25
  1. 从元数据提取 `New Start` 和 `New Count`,计算行号范围:`[New Start, New Start + New Count - 1]`
37
26
  2. **逐个检查**每个问题的行号是否在该范围内
38
27
  3. **如果任何行号超出范围,必须重新计数**
39
28
  4. **禁止**直接使用从完整文件读取的行号
40
- - **示例**:
41
- - `New Start: 1, New Count: 56` 的行号范围是 [1, 56],**不能返回 57、60 等超出范围的值**
42
- - `New Start: 167, New Count: 6` 的行号范围是 [167, 172],**不能返回 <167 或 >172 的值**
43
29
 
44
30
  4. **读取上下文**:基于 New Path 读取变更后文件,涉及方法调用时追踪读取实现代码
45
- - **⚠️ 重要**:读取完整文件仅用于理解代码逻辑,**不得**直接使用完整文件中的行号
31
+ - **⚠️ 重要**:读取完整文件用于理解代码逻辑,但问题行号必须基于 diff 块计算
46
32
  - 如果从完整文件中定位到问题行号,**必须验证**该行号是否在 diff 块范围内
47
33
  - **验证公式**:`new_start <= 行号 <= new_start + new_count - 1`
48
- - **如果行号超出范围**:必须回到 diff 块内容,重新基于 `@@` 头之后逐行计数
49
34
 
50
35
  5. **执行审查**:按照 @.claude/rules/code-review-rules.md 中的规则进行审查
51
36
 
52
- 6. **输出结果**:
37
+ 6. **重点检测问题类型**(详见规则文件对应章节):
38
+
39
+ **A. 资源泄漏问题**:
40
+ - 检测数据库连接、文件流、网络资源是否正确关闭
41
+ - 搜索资源创建代码,检查是否有 try-with-resources 或 finally 关闭机制
42
+ - 循环内创建资源未关闭 → 必须报告
43
+
44
+ **B. 框架特性问题**:
45
+ - Spring @Async 方法返回具体类型 → 调用方拿到 null → 必须报告
46
+ - MyBatis-Plus 更新实体设置了与查询条件重复的非主键字段 → 必须报告
47
+ - 循环中使用乐观锁版本号未每次重新获取 → 必须报告
48
+
49
+ **C. 循环逻辑问题**:
50
+ - 循环内调用外部对象的方法修改其状态,后续迭代继续使用该对象的状态值 → 状态过期 → 必须报告
51
+ - 循环内累积计算与数据库实际状态不一致 → 必须报告
52
+
53
+ **D. 异常处理问题**:
54
+ - catch 块仅记录日志无后续处理 → 异常吞没 → 必须报告
55
+ - catch 块为空实现 → 必须报告
56
+
57
+ **E. 类型转换问题**:
58
+ - 包装类型用于条件判断或运算可能为空 → 自动拆箱空指针 → 必须报告
59
+ - 高精度转低精度可能溢出 → 必须报告
60
+
61
+ **F. 集合操作问题**:
62
+ - 遍历集合过程中直接修改集合 → 并发修改异常 → 必须报告
63
+ - 空集合直接获取特定位置元素 → 索引越界 → 必须报告
64
+
65
+ **G. 并发安全问题**:
66
+ - 共享变量多线程访问无同步机制 → 必须报告
67
+ - 分布式场景使用本地锁 → 必须报告
68
+
69
+ **H. 边界条件问题**:
70
+ - 循环首次/末次迭代特殊处理不正确 → 必须报告
71
+ - 空输入、零值、边界值未正确处理 → 必须报告
72
+
73
+ **I. 空指针问题**:
74
+ - 遵循规则文件中的深度验证流程
75
+ - 外部依赖返回值默认假设安全,禁止报告
76
+ - 项目内方法需读取源码确认是否可能返回空值
77
+
78
+ 7. **输出结果**:
53
79
  - 审查结果放入 `<REPORT>` 标签
54
80
  - 行号信息放入 `<LINE_INFO>` 标签(必须输出)
55
81
  - LINE_INFO 格式参考 @.claude/rules/code-review-rules.md 第 18 条
56
82
  - **问题和行号对应**:`<LINE_INFO>` 中的第 N 个元素对应第 N 个问题的行号
57
- - **⚠️ 关键格式要求**:`<LINE_INFO>` 标签必须放置在 `</REPORT>` 标签之后,**禁止**出现在 `</REPORT>` 之前或穿插在报告内容中
83
+ - **⚠️ 关键格式要求**:`<LINE_INFO>` 标签必须放置在 `</REPORT>` 标签之后
58
84
 
59
85
  ## 输出模板
60
86
 
@@ -63,15 +89,10 @@ description: 代码审查技能,审查变更代码并输出 REPORT 和 LINE_IN
63
89
  1. **报告开头必须包含 `## 🤖 AI 代码审查结果`**:
64
90
  - **正确**:`<REPORT>\n## 🤖 AI 代码审查结果\n\n### 🔴 严重问题`
65
91
  - **错误**:`<REPORT>\n### 🔴 严重问题`(缺少主标题)
66
- - **错误**:`<REPORT>\n## 代码审查结果`(缺少 emoji)
67
92
 
68
93
  2. **问题格式**:每个问题必须以 `**问题 N**:` 开头(N 为阿拉伯数字 1,2,3...)
69
- - **正确**:`**问题 1**:`、`**问题 2**:`
70
- - **错误**:`1. **xxx**`、`### 问题 1`、`**问题一**:`、`**问题 1**: xxx(行 43)`
71
94
 
72
- 3. **每个问题块只能有一个 `**文件及行号**:` 行**:
73
- - **正确**:每个问题块中 `**文件及行号**:` 只出现一次
74
- - **错误**:同一个问题块中出现两次 `**文件及行号**:` 行
95
+ 3. **每个问题块只能有一个 `**文件及行号**:` 行**
75
96
 
76
97
  4. **行号标注**:**禁止**在问题描述中写"(行 X)",行号只能通过 `<LINE_INFO>` 标签提供
77
98
 
@@ -81,20 +102,6 @@ description: 代码审查技能,审查变更代码并输出 REPORT 和 LINE_IN
81
102
 
82
103
  7. **必须输出 `<LINE_INFO>` 标签**:即使无问题也要输出空数组 `<LINE_INFO>[]</LINE_INFO>`
83
104
 
84
- 8. **修改建议中的代码格式**:
85
- - **必须使用 markdown 代码块**(```language ... ```)展示建议代码
86
- - **禁止**使用行内代码(\`xxx\`)展示多行代码示例
87
- - 代码块的语言根据文件类型自动选择(如 \`java\`、\`js\`、\`ts\`、\`py\`、\`go\` 等)
88
- - 示例:
89
- \`\`\`
90
- **修改建议**:添加空值检查
91
- \`\`\`java
92
- if ([变量] != null) {
93
- [方法调用];
94
- }
95
- \`\`\`
96
- \`\`\`
97
-
98
105
  <REPORT>
99
106
  ## 🤖 AI 代码审查结果
100
107
 
@@ -104,19 +111,15 @@ description: 代码审查技能,审查变更代码并输出 REPORT 和 LINE_IN
104
111
  **文件及行号**:[文件路径:行号]<br/>
105
112
  **修改建议**:[正确示例代码或说明]
106
113
 
107
- **问题 2**:[问题描述]<br/>
108
- **文件及行号**:[文件路径:行号]<br/>
109
- **修改建议**:[正确示例代码或说明]
110
-
111
114
  </REPORT>
112
115
 
113
116
  <LINE_INFO>
114
- [{"new_path":"文件路径","new_line":行号,"old_path":"文件路径","old_line":行号}]
117
+ [{"new_path":"文件路径","new_line":行号}]
115
118
  </LINE_INFO>
116
119
 
117
120
  **说明**:
118
121
  - 无问题时省略 `### 🔴 严重问题` 标题,但 `<LINE_INFO>` 必须输出(空数组:`[]`)
119
122
  - 每个问题以 `**问题 N**:` 开头,方便切分
120
- - **`<LINE_INFO>` 数组中的元素顺序必须与问题顺序一致**:第 1 个元素对应问题 1 的行号,第 2 个元素对应问题 2 的行号,以此类推
121
- - 行号:返回变更后文件中的绝对行号(从 1 开始计数),例如文件第 43 行则 `new_line = 43`
123
+ - **`<LINE_INFO>` 数组中的元素顺序必须与问题顺序一致**
124
+ - 行号:返回变更后文件中的绝对行号(从 1 开始计数)
122
125
  - **无严重问题时,LINE_INFO 必须输出空数组 `[]`**
package/index.js CHANGED
@@ -81,9 +81,100 @@ 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 内容:${diffObject.diff}`);
84
+ debugLog(`is_large_file: ${diffObject.is_large_file || false}`);
85
+ debugLog(`diff 内容长度:${diffObject.diff?.length || 0}`);
85
86
  debugLog(`========== Diff Block ${blockIndex} 结束 ==========`);
86
87
 
88
+ // 处理大文件情况:diff 内容为空,需要读取完整文件
89
+ if (diffObject.is_large_file || !diffObject.diff || diffObject.diff.trim() === '') {
90
+ debugLog(`检测到大文件或空 diff,将读取完整文件内容进行审查`);
91
+
92
+ // 读取完整文件内容
93
+ const fullPath = path.join(process.cwd(), diffObject.new_path || diffObject.old_path);
94
+ let fileContent = '';
95
+
96
+ try {
97
+ if (fs.existsSync(fullPath)) {
98
+ fileContent = fs.readFileSync(fullPath, 'utf-8');
99
+ const lineCount = fileContent.split('\n').length;
100
+ debugLog(`成功读取完整文件,行数:${lineCount}`);
101
+ } else {
102
+ debugLog(`文件不存在:${fullPath},跳过审查`);
103
+ return {
104
+ diff_info: diffObject,
105
+ block_index: blockIndex,
106
+ review_result: { reportContent: '<REPORT>\n## 🤖 AI 代码审查结果\n\n文件无法读取,跳过审查。\n</REPORT>', lineInfo: '[]' },
107
+ temp_file_path: null,
108
+ hallucination_detected: false,
109
+ };
110
+ }
111
+ } catch (readError) {
112
+ debugLog(`读取文件失败:${readError.message}`);
113
+ return {
114
+ diff_info: diffObject,
115
+ block_index: blockIndex,
116
+ review_result: { reportContent: '<REPORT>\n## 🤖 AI 代码审查结果\n\n文件读取失败,跳过审查。\n</REPORT>', lineInfo: '[]' },
117
+ temp_file_path: null,
118
+ hallucination_detected: false,
119
+ };
120
+ }
121
+
122
+ // 对于新增文件,全部代码都是新增
123
+ // 对于修改文件,需要标记为"全文件审查"
124
+ const prefix = diffObject.new_file ? '+' : ' ';
125
+ const prefixedContent = fileContent.split('\n').map(line => prefix + line).join('\n');
126
+
127
+ // 构造临时文件内容
128
+ const diffContentWithMetadata = `${prefixedContent}
129
+
130
+ # File Information
131
+ # New Path: ${diffObject.new_path || 'N/A'}
132
+ # Old Path: ${diffObject.old_path || 'N/A'}
133
+ # New Start: 1
134
+ # New Count: ${fileContent.split('\n').length}
135
+ # Is Large File: true
136
+ # 注意:这是完整文件内容审查,因为 GitLab diff 被截断。所有代码行都应审查。
137
+ # 行号计算:从第 1 行代码开始计数,第 1 行行号=1,每往下一行行号 +1`;
138
+
139
+ // 将内容写入临时文件
140
+ fs.writeFileSync(tmpFileName, diffContentWithMetadata);
141
+ debugLog(`大文件临时文件已创建,大小:${diffContentWithMetadata.length}`);
142
+
143
+ // 审核完整文件
144
+ const blockStartTime = Date.now();
145
+ const blockIdentifier = `${diffObject.new_path || diffObject.old_path}#large-file`;
146
+ const review_result = await this.reviewDiffWithClaudeUsingFile(tmpFileName, blockIdentifier, true);
147
+ const blockObj = { ...diffObject, review_result, temp_file_path: tmpFileName, is_large_file: true };
148
+
149
+ // 记录审查指标
150
+ const reviewTime = Date.now() - blockStartTime;
151
+ const hasSeriousProblems = blockObj.review_result && blockObj.review_result.reportContent && blockObj.review_result.reportContent.includes('严重问题');
152
+ const filePath = diffObject.new_path || diffObject.old_path || '';
153
+ this.metrics.recordBlockReviewed(reviewTime, hasSeriousProblems ? 1 : 0, hasSeriousProblems, fileContent.length, filePath);
154
+
155
+ let hallucinationDetected = false;
156
+
157
+ // 检查审查结果中是否包含严重问题
158
+ if (blockObj.review_result && blockObj.review_result.reportContent && blockObj.review_result.reportContent.includes('严重问题')) {
159
+ // 对于大文件,发布为一般讨论(无法精确定位行号)
160
+ const file_path_with_line = `${diffObject.new_path || diffObject.old_path}#L1`;
161
+ await this.createGeneralDiscussion(projectId, mergeRequestIid, file_path_with_line, blockObj.review_result.reportContent);
162
+ debugLog(`大文件审查结果已发布为一般讨论: ${file_path_with_line}`);
163
+ this.metrics.recordCommentPublished();
164
+ } else {
165
+ debugLog(`大文件审查未发现严重问题: ${diffObject.new_path || diffObject.old_path}`);
166
+ }
167
+
168
+ return {
169
+ diff_info: blockObj,
170
+ block_index: blockIndex,
171
+ review_result: blockObj.review_result,
172
+ temp_file_path: tmpFileName,
173
+ hallucination_detected: hallucinationDetected,
174
+ };
175
+ }
176
+
177
+ // 正常 diff 处理逻辑
87
178
  // 解析 diff 内容,移除 @@ 行,将其信息放入元数据
88
179
  // 这样 AI 不会把 @@ 行误计数
89
180
  const diffLines = diffObject.diff.split('\n');
@@ -449,26 +540,48 @@ ${allReportsText}
449
540
  * 使用Claude对单个diff文件进行代码审核
450
541
  * @param {string} filePath 临时文件路径
451
542
  * @param {string} blockIdentifier 块标识(格式:文件路径#块索引),用于日志追踪
543
+ * @param {boolean} isLargeFile 是否为大文件审查模式
452
544
  * @returns {Promise<string>} 审核结果
453
545
  */
454
- async reviewDiffWithClaudeUsingFile(filePath, blockIdentifier = 'unknown') {
455
- debugLog(`[${blockIdentifier}] 开始审核文件: ${filePath}`);
546
+ async reviewDiffWithClaudeUsingFile(filePath, blockIdentifier = 'unknown', isLargeFile = false) {
547
+ debugLog(`[${blockIdentifier}] 开始审核文件: ${filePath}, isLargeFile: ${isLargeFile}`);
456
548
  const startTime = Date.now();
457
549
 
550
+ // 大文件模式下的特殊提示
551
+ const largeFilePrompt = isLargeFile ? `
552
+ **⚠️ 大文件审查模式**:
553
+ 这是一个完整文件内容的审查(因为 GitLab diff 被截断)。
554
+ 1. 所有代码行都是新增或修改的内容
555
+ 2. 必须审查整个文件的业务逻辑,包括:
556
+ - 所有方法中的循环逻辑、状态管理
557
+ - 所有数据库操作和资源管理
558
+ - 所有框架特性使用(如 @Async、MyBatis-Plus)
559
+ 3. 发现问题时,准确报告行号(基于文件内容的实际行号)
560
+ ` : '';
561
+
458
562
  const prompt = `请调用 simple-code-review 技能审核代码变更。
459
563
  文件路径:${filePath}
460
-
564
+ ${largeFilePrompt}
461
565
  **重要审查规则**:
462
- 1. **只审查当前 diff 块内的新增代码**(+ 开头的行)
463
- 2. **只报告当前 diff 块内能直接发现的问题**,不要追踪方法调用链去其他地方报告问题
464
- 3. **同一问题只报告一次**:如 import 行、声明行、调用行都有问题,只在真正会出错的调用行报告一次
465
- 4. **禁止报告的位置**:import 语句、类定义、方法签名、依赖注入声明行
566
+ 1. **严格按照 .claude/rules/code-review-rules.md 中的规则执行审查**,该文件包含详细的审查规则
567
+ 2. **重点检测以下问题类型**(详见规则文件):
568
+ - 资源泄漏问题:JDBC连接、文件流、网络资源未关闭
569
+ - 框架特性问题:Spring @Async返回值、MyBatis-Plus更新实体、乐观锁版本号
570
+ - 循环逻辑问题:循环内状态失效、累积状态不一致
571
+ - 异常处理问题:异常吞没、catch块空实现
572
+ - 类型转换问题:精度丢失、装箱拆箱空指针
573
+ - 集合操作问题:遍历中修改、空集合访问
574
+ - 并发安全问题:线程安全、分布式锁
575
+ - 边界条件问题:循环边界、输入边界
576
+ - 空指针问题:需深度分析,遵循规则文件中的验证流程
577
+ 3. **上下文读取要求**:必须使用 Read 工具读取完整方法代码,分析循环逻辑、状态管理、框架使用
578
+ 4. **问题报告位置**:只报告在 diff 块内实际会出错的代码行,禁止在 import/类定义/方法签名等位置报告
466
579
 
467
580
  **输出要求**:
468
581
  1. 严格按照 .claude/skills/simple-code-review/SKILL.md 中定义的模板格式输出
469
582
  2. **每个模块不是必须的**:没有对应问题时,完全省略该模块(不输出标题)
470
583
  3. 必须以 <REPORT> 开始,以 </REPORT> 结束
471
- 4. **必须输出 '<LINE_INFO>' 标签**,包含所有问题的行号信息(无问题时输出空数组 [])
584
+ 4. **必须输出 '<LINE_INFO>' 标签**,包含所有问题的行号信息(无问题时输出空数组 []
472
585
  5. 不要输出任何额外的解释、问候或总结文本`;
473
586
  //打印
474
587
  debugLog(`[${blockIdentifier}] Claude命令: ${prompt}`);
@@ -604,6 +717,61 @@ ${allReportsText}
604
717
  */
605
718
  getDiffBlocks(diffObj) {
606
719
  const regex = /(?=@@\s-\d+(?:,\d+)?\s\+\d+(?:,\d+)?\s@@)/g;
720
+
721
+ // 检查 diff 内容是否有效
722
+ if (!diffObj.diff || diffObj.diff.trim() === '') {
723
+ debugLog(`文件 ${diffObj.new_path || diffObj.old_path} diff 内容为空,可能文件过大被 GitLab 截断`);
724
+ // 对于新增文件,返回一个标记对象,后续会用完整文件内容审查
725
+ if (diffObj.new_file) {
726
+ debugLog(`文件 ${diffObj.new_path} 是新增文件,将使用完整文件内容审查`);
727
+ return [{
728
+ diff: '', // 空内容,后续会读取完整文件
729
+ new_path: diffObj.new_path,
730
+ old_path: diffObj.old_path,
731
+ a_mode: diffObj.a_mode,
732
+ b_mode: diffObj.b_mode,
733
+ new_file: diffObj.new_file,
734
+ renamed_file: diffObj.renamed_file,
735
+ deleted_file: diffObj.deleted_file,
736
+ generated_file: diffObj.generated_file,
737
+ block_index: 0,
738
+ line_info: {
739
+ old_start: 0,
740
+ old_count: 0,
741
+ new_start: 1,
742
+ new_count: 0, // 未知,后续会从完整文件计算
743
+ firstLineFirstChar: '+'
744
+ },
745
+ is_large_file: true // 标记为大文件,需要特殊处理
746
+ }];
747
+ }
748
+ // 对于修改文件但 diff 为空,同样返回标记对象
749
+ if (diffObj.new_path && !diffObj.deleted_file) {
750
+ debugLog(`文件 ${diffObj.new_path} 是修改文件但 diff 截断,将使用完整文件内容审查`);
751
+ return [{
752
+ diff: '',
753
+ new_path: diffObj.new_path,
754
+ old_path: diffObj.old_path,
755
+ a_mode: diffObj.a_mode,
756
+ b_mode: diffObj.b_mode,
757
+ new_file: false,
758
+ renamed_file: diffObj.renamed_file,
759
+ deleted_file: diffObj.deleted_file,
760
+ generated_file: diffObj.generated_file,
761
+ block_index: 0,
762
+ line_info: {
763
+ old_start: 0,
764
+ old_count: 0,
765
+ new_start: 1,
766
+ new_count: 0,
767
+ firstLineFirstChar: ' '
768
+ },
769
+ is_large_file: true
770
+ }];
771
+ }
772
+ return [];
773
+ }
774
+
607
775
  const diffBlocks = diffObj.diff.split(regex);
608
776
  // 过滤掉空块并提取行号信息
609
777
  const result = diffBlocks
@@ -614,7 +782,8 @@ ${allReportsText}
614
782
  const headerMatch = block.match(headerRegex);
615
783
 
616
784
  // 取block第一行是否是加号或者减号开头还是没有开头,block需要用.split(/\r?\n/)
617
- const firstLine = block.split(/\r?\n/)[1];
785
+ const lines = block.split(/\r?\n/);
786
+ const firstLine = lines[1] || '';
618
787
  // 取出第一行的第一个字符
619
788
  const firstLineFirstChar = firstLine.charAt(0);
620
789
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "job51-gitlab-cr-node-jt-1",
3
- "version": "2.9.6",
3
+ "version": "2.9.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": {