job51-gitlab-cr-node-jt-1 2.9.7 → 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.
- package/index.js +165 -5
- package/package.json +1 -1
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(`
|
|
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,15 +540,28 @@ ${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
566
|
1. **严格按照 .claude/rules/code-review-rules.md 中的规则执行审查**,该文件包含详细的审查规则
|
|
463
567
|
2. **重点检测以下问题类型**(详见规则文件):
|
|
@@ -613,6 +717,61 @@ ${allReportsText}
|
|
|
613
717
|
*/
|
|
614
718
|
getDiffBlocks(diffObj) {
|
|
615
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
|
+
|
|
616
775
|
const diffBlocks = diffObj.diff.split(regex);
|
|
617
776
|
// 过滤掉空块并提取行号信息
|
|
618
777
|
const result = diffBlocks
|
|
@@ -623,7 +782,8 @@ ${allReportsText}
|
|
|
623
782
|
const headerMatch = block.match(headerRegex);
|
|
624
783
|
|
|
625
784
|
// 取block第一行是否是加号或者减号开头还是没有开头,block需要用.split(/\r?\n/)
|
|
626
|
-
const
|
|
785
|
+
const lines = block.split(/\r?\n/);
|
|
786
|
+
const firstLine = lines[1] || '';
|
|
627
787
|
// 取出第一行的第一个字符
|
|
628
788
|
const firstLineFirstChar = firstLine.charAt(0);
|
|
629
789
|
|