gitlab-ai-review 2.0.0 → 2.1.0

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 CHANGED
@@ -15,7 +15,7 @@ import * as DiffParser from './lib/diff-parser.js';
15
15
  export class GitLabAIReview {
16
16
  constructor(options = {}) {
17
17
  this.name = 'GitLab AI Review SDK';
18
- this.version = '2.0.0';
18
+ this.version = '2.1.0';
19
19
 
20
20
  // 如果传入了配置,使用手动配置;否则使用自动检测
21
21
  if (options.token || options.gitlab) {
@@ -179,50 +179,131 @@ export class GitLabAIReview {
179
179
  }
180
180
 
181
181
  /**
182
- * AI 审查 MR 的所有有意义的变更并自动添加行级评论
182
+ * AI 审查 MR 的所有有意义的变更并自动添加行级评论(按文件批量处理)
183
183
  * @param {Object} options - 选项
184
- * @param {number} options.maxComments - 最大评论数量(避免过多评论)
184
+ * @param {number} options.maxFiles - 最大审查文件数量(默认 5)
185
185
  * @returns {Promise<Array>} 评论结果数组
186
186
  */
187
187
  async reviewAndCommentOnLines(options = {}) {
188
- const { maxComments = 10 } = options;
188
+ const { maxFiles = 5 } = options;
189
189
  const changes = await this.getMergeRequestChanges();
190
190
  const results = [];
191
191
 
192
- for (const change of changes) {
193
- if (results.length >= maxComments) {
194
- console.log(`已达到最大评论数量限制 (${maxComments}),停止审查`);
195
- break;
196
- }
192
+ console.log(`共 ${changes.length} 个文件需要审查(最多审查 ${maxFiles} 个)`);
193
+
194
+ for (const change of changes.slice(0, maxFiles)) {
195
+ const fileName = change.new_path || change.old_path;
196
+
197
+ try {
198
+ console.log(`\n审查文件: ${fileName}`);
199
+
200
+ // 解析 diff,提取有意义的变更
201
+ const hunks = DiffParser.parseDiff(change.diff);
202
+ const meaningfulChanges = DiffParser.extractMeaningfulChanges(hunks);
203
+
204
+ if (meaningfulChanges.length === 0) {
205
+ console.log(` 跳过:没有有意义的变更`);
206
+ continue;
207
+ }
208
+
209
+ console.log(` 发现 ${meaningfulChanges.length} 个有意义的变更`);
210
+
211
+ // 调用 AI 一次性审查整个文件的所有变更(按文件批量)
212
+ const fileReview = await this.reviewFileChanges(change, meaningfulChanges);
213
+
214
+ // 根据 AI 返回的结果,只对有问题的行添加评论
215
+ for (const review of fileReview.reviews) {
216
+ if (review.hasIssue) {
217
+ try {
218
+ const commentResult = await this.gitlabClient.createLineComment(
219
+ this.config.project.projectId,
220
+ this.config.project.mergeRequestIid,
221
+ `🤖 **AI 代码审查**\n\n${review.comment}`,
222
+ {
223
+ filePath: fileName,
224
+ oldPath: change.old_path,
225
+ newLine: review.lineNumber,
226
+ }
227
+ );
197
228
 
198
- // 解析 diff,提取有意义的变更
199
- const hunks = DiffParser.parseDiff(change.diff);
200
- const meaningfulChanges = DiffParser.extractMeaningfulChanges(hunks);
201
-
202
- // 对每个有意义的变更进行审查
203
- for (const meaningfulChange of meaningfulChanges.slice(0, maxComments - results.length)) {
204
- try {
205
- const result = await this.reviewAndCommentOnLine(change, meaningfulChange);
206
- results.push({
207
- status: 'success',
208
- fileName: change.new_path || change.old_path,
209
- lineNumber: meaningfulChange.lineNumber,
210
- ...result,
211
- });
212
- } catch (error) {
213
- results.push({
214
- status: 'error',
215
- fileName: change.new_path || change.old_path,
216
- lineNumber: meaningfulChange.lineNumber,
217
- error: error.message,
218
- });
229
+ results.push({
230
+ status: 'success',
231
+ fileName,
232
+ lineNumber: review.lineNumber,
233
+ comment: review.comment,
234
+ commentResult,
235
+ });
236
+
237
+ console.log(` ✓ 第 ${review.lineNumber} 行:已添加评论`);
238
+ } catch (error) {
239
+ results.push({
240
+ status: 'error',
241
+ fileName,
242
+ lineNumber: review.lineNumber,
243
+ error: error.message,
244
+ });
245
+ console.log(` ✗ 第 ${review.lineNumber} 行:评论失败 - ${error.message}`);
246
+ }
247
+ } else {
248
+ console.log(` ✓ 第 ${review.lineNumber} 行:代码质量良好`);
249
+ }
250
+ }
251
+
252
+ // 如果没有问题
253
+ if (fileReview.reviews.length === 0 || fileReview.reviews.every(r => !r.hasIssue)) {
254
+ console.log(` ✓ 所有代码质量良好,无需评论`);
219
255
  }
256
+
257
+ } catch (error) {
258
+ results.push({
259
+ status: 'error',
260
+ fileName,
261
+ error: error.message,
262
+ });
263
+ console.log(` ✗ 文件审查失败: ${error.message}`);
220
264
  }
221
265
  }
222
266
 
223
267
  return results;
224
268
  }
225
269
 
270
+ /**
271
+ * 审查单个文件的所有变更(一次 API 调用)
272
+ * @param {Object} change - 代码变更对象
273
+ * @param {Array} meaningfulChanges - 有意义的变更数组
274
+ * @returns {Promise<Object>} 审查结果 { reviews: [{lineNumber, hasIssue, comment}] }
275
+ */
276
+ async reviewFileChanges(change, meaningfulChanges) {
277
+ const aiClient = this.getAIClient();
278
+ const projectPrompt = this.config.ai?.guardConfig?.content || '';
279
+ const fileName = change.new_path || change.old_path;
280
+
281
+ // 构建整个文件的批量审查消息
282
+ const messages = PromptTools.buildFileReviewMessages(fileName, meaningfulChanges, projectPrompt);
283
+
284
+ // 调用 AI(一次调用审查整个文件)
285
+ const response = await aiClient.sendMessage(messages);
286
+
287
+ // 解析 AI 返回的 JSON
288
+ try {
289
+ // 提取 JSON(可能被包裹在 ```json ``` 中)
290
+ let jsonStr = response.content.trim();
291
+ const jsonMatch = jsonStr.match(/```json\s*([\s\S]*?)\s*```/);
292
+ if (jsonMatch) {
293
+ jsonStr = jsonMatch[1];
294
+ } else if (jsonStr.startsWith('```') && jsonStr.endsWith('```')) {
295
+ jsonStr = jsonStr.slice(3, -3).trim();
296
+ }
297
+
298
+ const result = JSON.parse(jsonStr);
299
+ return result;
300
+ } catch (error) {
301
+ console.error('解析 AI 返回的 JSON 失败:', error.message);
302
+ console.error('AI 原始返回:', response.content);
303
+ return { reviews: [] };
304
+ }
305
+ }
306
+
226
307
  /**
227
308
  * 测试方法
228
309
  */
@@ -92,10 +92,94 @@ export function buildLineReviewMessages(changeInfo, projectPrompt = '') {
92
92
  ];
93
93
  }
94
94
 
95
- // 只导出行级审查需要的函数
95
+ /**
96
+ * 构建整个文件所有变更行的批量审查提示词
97
+ * @param {string} fileName - 文件名
98
+ * @param {Array} meaningfulChanges - 有意义的变更数组
99
+ * @param {string} projectPrompt - 项目配置的 prompt
100
+ * @returns {string} 批量审查提示词
101
+ */
102
+ export function buildFileReviewPrompt(fileName, meaningfulChanges) {
103
+ let prompt = `请审查以下文件的代码变更,只对**有问题的行**提出审查意见,代码没有问题的行不需要评论。
104
+
105
+ **文件名**: ${fileName}
106
+ **变更数量**: ${meaningfulChanges.length} 行
107
+
108
+ `;
109
+
110
+ meaningfulChanges.forEach((change, index) => {
111
+ prompt += `\n### 变更 ${index + 1} - 第 ${change.lineNumber} 行\n`;
112
+ prompt += `**类型**: ${change.type === 'addition' ? '新增' : '删除'}\n`;
113
+ prompt += `**内容**:\n\`\`\`\n`;
114
+
115
+ // 上下文
116
+ if (change.context?.before?.length > 0) {
117
+ change.context.before.forEach(line => {
118
+ prompt += ` ${line}\n`;
119
+ });
120
+ }
121
+
122
+ // 变更行
123
+ prompt += `${change.type === 'addition' ? '+' : '-'} ${change.content}\n`;
124
+
125
+ // 下文
126
+ if (change.context?.after?.length > 0) {
127
+ change.context.after.forEach(line => {
128
+ prompt += ` ${line}\n`;
129
+ });
130
+ }
131
+
132
+ prompt += `\`\`\`\n`;
133
+ });
134
+
135
+ prompt += `\n## 审查要求
136
+
137
+ 1. 仔细审查每一行变更,判断是否存在问题
138
+ 2. **只对有问题的行提出审查意见**,没有问题的行不需要评论
139
+ 3. 返回 JSON 格式的结果,格式如下:
140
+
141
+ \`\`\`json
142
+ {
143
+ "reviews": [
144
+ {
145
+ "lineNumber": 15,
146
+ "hasIssue": true,
147
+ "comment": "具体的问题描述和改进建议"
148
+ },
149
+ {
150
+ "lineNumber": 20,
151
+ "hasIssue": false
152
+ }
153
+ ]
154
+ }
155
+ \`\`\`
156
+
157
+ 4. 如果所有代码都没有问题,返回空的 reviews 数组:\`{"reviews": []}\`
158
+ 5. 只返回 JSON,不要包含其他文字说明`;
159
+
160
+ return prompt;
161
+ }
162
+
163
+ /**
164
+ * 构建整个文件批量审查的完整消息数组
165
+ * @param {string} fileName - 文件名
166
+ * @param {Array} meaningfulChanges - 有意义的变更数组
167
+ * @param {string} projectPrompt - 项目配置的 prompt
168
+ * @returns {Array} 消息数组
169
+ */
170
+ export function buildFileReviewMessages(fileName, meaningfulChanges, projectPrompt = '') {
171
+ return [
172
+ { role: 'system', content: buildSystemPrompt(projectPrompt) },
173
+ { role: 'user', content: buildFileReviewPrompt(fileName, meaningfulChanges) },
174
+ ];
175
+ }
176
+
177
+ // 导出函数
96
178
  export default {
97
179
  buildSystemPrompt,
98
180
  buildLineReviewPrompt,
99
181
  buildLineReviewMessages,
182
+ buildFileReviewPrompt,
183
+ buildFileReviewMessages,
100
184
  };
101
185
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gitlab-ai-review",
3
- "version": "2.0.0",
4
- "description": "GitLab AI Review SDK - 行级代码审查,附带项目 Prompt",
3
+ "version": "2.1.0",
4
+ "description": "GitLab AI Review SDK - 按文件批量审查,只评论有问题的代码",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "scripts": {