gitlab-ai-review 1.2.0 → 2.0.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 +68 -71
- package/lib/diff-parser.js +197 -0
- package/lib/gitlab-client.js +64 -2
- package/lib/prompt-tools.js +60 -101
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { getConfig, validateConfig } from './lib/config.js';
|
|
|
7
7
|
import { GitLabClient } from './lib/gitlab-client.js';
|
|
8
8
|
import { AIClient } from './lib/ai-client.js';
|
|
9
9
|
import * as PromptTools from './lib/prompt-tools.js';
|
|
10
|
+
import * as DiffParser from './lib/diff-parser.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* GitLab AI Review SDK 主类
|
|
@@ -14,7 +15,7 @@ import * as PromptTools from './lib/prompt-tools.js';
|
|
|
14
15
|
export class GitLabAIReview {
|
|
15
16
|
constructor(options = {}) {
|
|
16
17
|
this.name = 'GitLab AI Review SDK';
|
|
17
|
-
this.version = '
|
|
18
|
+
this.version = '2.0.0';
|
|
18
19
|
|
|
19
20
|
// 如果传入了配置,使用手动配置;否则使用自动检测
|
|
20
21
|
if (options.token || options.gitlab) {
|
|
@@ -137,93 +138,89 @@ export class GitLabAIReview {
|
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
/**
|
|
140
|
-
* AI
|
|
141
|
+
* AI 审查特定行的变更并在该行添加评论(附带项目 prompt)
|
|
141
142
|
* @param {Object} change - 代码变更对象
|
|
142
|
-
* @
|
|
143
|
+
* @param {Object} changeInfo - 具体的行变更信息
|
|
144
|
+
* @returns {Promise<Object>} 评论结果
|
|
143
145
|
*/
|
|
144
|
-
async
|
|
146
|
+
async reviewAndCommentOnLine(change, changeInfo) {
|
|
145
147
|
const aiClient = this.getAIClient();
|
|
146
|
-
|
|
148
|
+
|
|
149
|
+
// 获取项目配置的 prompt(来自 reviewguard.md)
|
|
150
|
+
const projectPrompt = this.config.ai?.guardConfig?.content || '';
|
|
147
151
|
|
|
148
|
-
//
|
|
149
|
-
const messages = PromptTools.
|
|
150
|
-
|
|
152
|
+
// 构建针对特定行的审查消息(附带项目 prompt)
|
|
153
|
+
const messages = PromptTools.buildLineReviewMessages({
|
|
154
|
+
...changeInfo,
|
|
151
155
|
fileName: change.new_path || change.old_path,
|
|
152
|
-
|
|
153
|
-
});
|
|
156
|
+
}, projectPrompt);
|
|
154
157
|
|
|
155
|
-
// 调用 AI
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* AI 审查 MR 的所有代码变更
|
|
161
|
-
* @returns {Promise<Array>} 审查结果数组
|
|
162
|
-
*/
|
|
163
|
-
async reviewMergeRequest() {
|
|
164
|
-
const changes = await this.getMergeRequestChanges();
|
|
165
|
-
const aiClient = this.getAIClient();
|
|
166
|
-
const guardConfig = this.config.ai?.guardConfig?.content || '';
|
|
158
|
+
// 调用 AI 审查
|
|
159
|
+
const review = await aiClient.sendMessage(messages);
|
|
167
160
|
|
|
168
|
-
|
|
161
|
+
// 在该行添加评论
|
|
162
|
+
const lineInfo = {
|
|
163
|
+
filePath: change.new_path || change.old_path,
|
|
164
|
+
oldPath: change.old_path,
|
|
165
|
+
newLine: changeInfo.lineNumber,
|
|
166
|
+
};
|
|
169
167
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
guardConfig,
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// 调用 AI
|
|
180
|
-
const result = await aiClient.sendMessage(messages);
|
|
181
|
-
|
|
182
|
-
reviews.push({
|
|
183
|
-
fileName: change.new_path || change.old_path,
|
|
184
|
-
status: 'success',
|
|
185
|
-
...result,
|
|
186
|
-
});
|
|
187
|
-
} catch (error) {
|
|
188
|
-
reviews.push({
|
|
189
|
-
fileName: change.new_path || change.old_path,
|
|
190
|
-
status: 'error',
|
|
191
|
-
error: error.message,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
}
|
|
168
|
+
const commentResult = await this.gitlabClient.createLineComment(
|
|
169
|
+
this.config.project.projectId,
|
|
170
|
+
this.config.project.mergeRequestIid,
|
|
171
|
+
`🤖 **AI 代码审查**\n\n${review.content}`,
|
|
172
|
+
lineInfo
|
|
173
|
+
);
|
|
195
174
|
|
|
196
|
-
return
|
|
175
|
+
return {
|
|
176
|
+
review,
|
|
177
|
+
comment: commentResult,
|
|
178
|
+
};
|
|
197
179
|
}
|
|
198
180
|
|
|
199
181
|
/**
|
|
200
|
-
* AI 审查 MR
|
|
182
|
+
* AI 审查 MR 的所有有意义的变更并自动添加行级评论
|
|
183
|
+
* @param {Object} options - 选项
|
|
184
|
+
* @param {number} options.maxComments - 最大评论数量(避免过多评论)
|
|
185
|
+
* @returns {Promise<Array>} 评论结果数组
|
|
201
186
|
*/
|
|
202
|
-
async
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
comment += `审查失败: ${review.error}\n\n`;
|
|
212
|
-
continue;
|
|
187
|
+
async reviewAndCommentOnLines(options = {}) {
|
|
188
|
+
const { maxComments = 10 } = options;
|
|
189
|
+
const changes = await this.getMergeRequestChanges();
|
|
190
|
+
const results = [];
|
|
191
|
+
|
|
192
|
+
for (const change of changes) {
|
|
193
|
+
if (results.length >= maxComments) {
|
|
194
|
+
console.log(`已达到最大评论数量限制 (${maxComments}),停止审查`);
|
|
195
|
+
break;
|
|
213
196
|
}
|
|
214
197
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
+
});
|
|
219
|
+
}
|
|
219
220
|
}
|
|
220
|
-
|
|
221
|
-
comment += `**审查意见**:\n${review.content}\n\n`;
|
|
222
|
-
comment += `---\n\n`;
|
|
223
221
|
}
|
|
224
222
|
|
|
225
|
-
|
|
226
|
-
return this.addComment(comment);
|
|
223
|
+
return results;
|
|
227
224
|
}
|
|
228
225
|
|
|
229
226
|
/**
|
|
@@ -238,7 +235,7 @@ export class GitLabAIReview {
|
|
|
238
235
|
export { getConfig, validateConfig } from './lib/config.js';
|
|
239
236
|
export { GitLabClient } from './lib/gitlab-client.js';
|
|
240
237
|
export { AIClient } from './lib/ai-client.js';
|
|
241
|
-
export { PromptTools };
|
|
238
|
+
export { PromptTools, DiffParser };
|
|
242
239
|
|
|
243
240
|
// 默认导出
|
|
244
241
|
export default GitLabAIReview;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff 解析工具 - 解析 Git Diff 格式
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 解析 diff 的 hunk 头部信息
|
|
7
|
+
* 例如: @@ -14,7 +14,7 @@
|
|
8
|
+
* @param {string} hunkHeader - hunk 头部字符串
|
|
9
|
+
* @returns {Object} 解析后的位置信息
|
|
10
|
+
*/
|
|
11
|
+
export function parseHunkHeader(hunkHeader) {
|
|
12
|
+
const match = hunkHeader.match(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
|
|
13
|
+
|
|
14
|
+
if (!match) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
oldStart: parseInt(match[1], 10),
|
|
20
|
+
oldLines: match[2] ? parseInt(match[2], 10) : 1,
|
|
21
|
+
newStart: parseInt(match[3], 10),
|
|
22
|
+
newLines: match[4] ? parseInt(match[4], 10) : 1,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 解析完整的 diff 内容,提取所有变更位置
|
|
28
|
+
* @param {string} diff - diff 内容
|
|
29
|
+
* @returns {Array} 变更块数组
|
|
30
|
+
*/
|
|
31
|
+
export function parseDiff(diff) {
|
|
32
|
+
if (!diff) return [];
|
|
33
|
+
|
|
34
|
+
const lines = diff.split('\n');
|
|
35
|
+
const hunks = [];
|
|
36
|
+
let currentHunk = null;
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < lines.length; i++) {
|
|
39
|
+
const line = lines[i];
|
|
40
|
+
|
|
41
|
+
// 检测 hunk 头部
|
|
42
|
+
if (line.startsWith('@@')) {
|
|
43
|
+
if (currentHunk) {
|
|
44
|
+
hunks.push(currentHunk);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const hunkInfo = parseHunkHeader(line);
|
|
48
|
+
currentHunk = {
|
|
49
|
+
...hunkInfo,
|
|
50
|
+
header: line,
|
|
51
|
+
changes: [],
|
|
52
|
+
contextBefore: [],
|
|
53
|
+
contextAfter: [],
|
|
54
|
+
};
|
|
55
|
+
} else if (currentHunk) {
|
|
56
|
+
// 收集变更行
|
|
57
|
+
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
58
|
+
currentHunk.changes.push({
|
|
59
|
+
type: 'addition',
|
|
60
|
+
content: line.substring(1),
|
|
61
|
+
line: line,
|
|
62
|
+
});
|
|
63
|
+
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
64
|
+
currentHunk.changes.push({
|
|
65
|
+
type: 'deletion',
|
|
66
|
+
content: line.substring(1),
|
|
67
|
+
line: line,
|
|
68
|
+
});
|
|
69
|
+
} else if (line.startsWith(' ')) {
|
|
70
|
+
currentHunk.changes.push({
|
|
71
|
+
type: 'context',
|
|
72
|
+
content: line.substring(1),
|
|
73
|
+
line: line,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (currentHunk) {
|
|
80
|
+
hunks.push(currentHunk);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return hunks;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 计算变更在新文件中的具体行号
|
|
88
|
+
* @param {Object} hunk - hunk 信息
|
|
89
|
+
* @param {number} changeIndex - 变更在 changes 数组中的索引
|
|
90
|
+
* @returns {number} 新文件中的行号
|
|
91
|
+
*/
|
|
92
|
+
export function calculateNewLineNumber(hunk, changeIndex) {
|
|
93
|
+
let lineNumber = hunk.newStart;
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i <= changeIndex; i++) {
|
|
96
|
+
const change = hunk.changes[i];
|
|
97
|
+
if (change.type === 'addition' || change.type === 'context') {
|
|
98
|
+
if (i === changeIndex) {
|
|
99
|
+
return lineNumber;
|
|
100
|
+
}
|
|
101
|
+
lineNumber++;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return lineNumber;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 提取有意义的变更(忽略纯空白变更)
|
|
110
|
+
* @param {Array} hunks - hunks 数组
|
|
111
|
+
* @returns {Array} 有意义的变更数组
|
|
112
|
+
*/
|
|
113
|
+
export function extractMeaningfulChanges(hunks) {
|
|
114
|
+
const meaningfulChanges = [];
|
|
115
|
+
|
|
116
|
+
hunks.forEach(hunk => {
|
|
117
|
+
let currentNewLine = hunk.newStart;
|
|
118
|
+
|
|
119
|
+
hunk.changes.forEach((change, index) => {
|
|
120
|
+
if (change.type === 'addition') {
|
|
121
|
+
const content = change.content.trim();
|
|
122
|
+
|
|
123
|
+
// 过滤空行和仅包含符号的行
|
|
124
|
+
if (content.length > 0 && !/^[{}()\[\];,]*$/.test(content)) {
|
|
125
|
+
meaningfulChanges.push({
|
|
126
|
+
type: 'addition',
|
|
127
|
+
content: change.content,
|
|
128
|
+
lineNumber: currentNewLine,
|
|
129
|
+
hunk: hunk.header,
|
|
130
|
+
context: {
|
|
131
|
+
before: hunk.changes.slice(Math.max(0, index - 2), index).map(c => c.content),
|
|
132
|
+
after: hunk.changes.slice(index + 1, index + 3).map(c => c.content),
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
currentNewLine++;
|
|
137
|
+
} else if (change.type === 'deletion') {
|
|
138
|
+
const content = change.content.trim();
|
|
139
|
+
|
|
140
|
+
if (content.length > 0 && !/^[{}()\[\];,]*$/.test(content)) {
|
|
141
|
+
meaningfulChanges.push({
|
|
142
|
+
type: 'deletion',
|
|
143
|
+
content: change.content,
|
|
144
|
+
oldLineNumber: currentNewLine,
|
|
145
|
+
hunk: hunk.header,
|
|
146
|
+
context: {
|
|
147
|
+
before: hunk.changes.slice(Math.max(0, index - 2), index).map(c => c.content),
|
|
148
|
+
after: hunk.changes.slice(index + 1, index + 3).map(c => c.content),
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
} else if (change.type === 'context') {
|
|
153
|
+
currentNewLine++;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return meaningfulChanges;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 生成变更摘要
|
|
163
|
+
* @param {string} diff - diff 内容
|
|
164
|
+
* @returns {Object} 变更摘要
|
|
165
|
+
*/
|
|
166
|
+
export function generateDiffSummary(diff) {
|
|
167
|
+
if (!diff) {
|
|
168
|
+
return { additions: 0, deletions: 0, changes: 0 };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const lines = diff.split('\n');
|
|
172
|
+
let additions = 0;
|
|
173
|
+
let deletions = 0;
|
|
174
|
+
|
|
175
|
+
lines.forEach(line => {
|
|
176
|
+
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
177
|
+
additions++;
|
|
178
|
+
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
179
|
+
deletions++;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
additions,
|
|
185
|
+
deletions,
|
|
186
|
+
changes: additions + deletions,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export default {
|
|
191
|
+
parseHunkHeader,
|
|
192
|
+
parseDiff,
|
|
193
|
+
calculateNewLineNumber,
|
|
194
|
+
extractMeaningfulChanges,
|
|
195
|
+
generateDiffSummary,
|
|
196
|
+
};
|
|
197
|
+
|
package/lib/gitlab-client.js
CHANGED
|
@@ -69,9 +69,37 @@ export class GitLabClient {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* 在 MR
|
|
72
|
+
* 在 MR 的特定行上创建评论
|
|
73
|
+
* @param {string} projectId - 项目 ID
|
|
74
|
+
* @param {number} mergeRequestIid - MR IID
|
|
75
|
+
* @param {string} body - 评论内容
|
|
76
|
+
* @param {Object} lineInfo - 行信息
|
|
77
|
+
* @param {string} lineInfo.filePath - 文件路径
|
|
78
|
+
* @param {number} lineInfo.newLine - 新文件行号
|
|
79
|
+
* @param {number} lineInfo.oldLine - 旧文件行号(可选)
|
|
80
|
+
* @returns {Promise<Object>} 创建的讨论
|
|
73
81
|
*/
|
|
74
|
-
async
|
|
82
|
+
async createLineComment(projectId, mergeRequestIid, body, lineInfo) {
|
|
83
|
+
// 先获取 MR 信息以获取 SHA
|
|
84
|
+
const mr = await this.getMergeRequest(projectId, mergeRequestIid);
|
|
85
|
+
|
|
86
|
+
const position = {
|
|
87
|
+
base_sha: mr.diff_refs.base_sha,
|
|
88
|
+
head_sha: mr.diff_refs.head_sha,
|
|
89
|
+
start_sha: mr.diff_refs.start_sha,
|
|
90
|
+
position_type: 'text',
|
|
91
|
+
new_path: lineInfo.filePath,
|
|
92
|
+
old_path: lineInfo.oldPath || lineInfo.filePath,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// 设置行号
|
|
96
|
+
if (lineInfo.newLine !== undefined) {
|
|
97
|
+
position.new_line = lineInfo.newLine;
|
|
98
|
+
}
|
|
99
|
+
if (lineInfo.oldLine !== undefined) {
|
|
100
|
+
position.old_line = lineInfo.oldLine;
|
|
101
|
+
}
|
|
102
|
+
|
|
75
103
|
return this.request(
|
|
76
104
|
`/projects/${encodeURIComponent(projectId)}/merge_requests/${mergeRequestIid}/discussions`,
|
|
77
105
|
{
|
|
@@ -83,5 +111,39 @@ export class GitLabClient {
|
|
|
83
111
|
}
|
|
84
112
|
);
|
|
85
113
|
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 批量创建行级评论
|
|
117
|
+
* @param {string} projectId - 项目 ID
|
|
118
|
+
* @param {number} mergeRequestIid - MR IID
|
|
119
|
+
* @param {Array} comments - 评论数组
|
|
120
|
+
* @returns {Promise<Array>} 创建结果数组
|
|
121
|
+
*/
|
|
122
|
+
async createMultipleLineComments(projectId, mergeRequestIid, comments) {
|
|
123
|
+
const results = [];
|
|
124
|
+
|
|
125
|
+
for (const comment of comments) {
|
|
126
|
+
try {
|
|
127
|
+
const result = await this.createLineComment(
|
|
128
|
+
projectId,
|
|
129
|
+
mergeRequestIid,
|
|
130
|
+
comment.body,
|
|
131
|
+
comment.lineInfo
|
|
132
|
+
);
|
|
133
|
+
results.push({
|
|
134
|
+
status: 'success',
|
|
135
|
+
result,
|
|
136
|
+
});
|
|
137
|
+
} catch (error) {
|
|
138
|
+
results.push({
|
|
139
|
+
status: 'error',
|
|
140
|
+
error: error.message,
|
|
141
|
+
comment,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return results;
|
|
147
|
+
}
|
|
86
148
|
}
|
|
87
149
|
|
package/lib/prompt-tools.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Prompt 工具 -
|
|
2
|
+
* Prompt 工具 - 用于拼接行级代码审查的提示词
|
|
3
|
+
* 简化版:只支持行级审查
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
* @param {string}
|
|
7
|
+
* 构建系统提示词(附带项目 prompt)
|
|
8
|
+
* @param {string} projectPrompt - 项目配置的 prompt(来自 reviewguard.md)
|
|
8
9
|
* @returns {string} 系统提示词
|
|
9
10
|
*/
|
|
10
|
-
export function buildSystemPrompt(
|
|
11
|
+
export function buildSystemPrompt(projectPrompt = '') {
|
|
11
12
|
let prompt = `你是一个专业的代码审查助手,负责审查 GitLab Merge Request 的代码变更。
|
|
12
13
|
|
|
13
14
|
你的职责是:
|
|
@@ -18,125 +19,83 @@ export function buildSystemPrompt(guardConfig = '') {
|
|
|
18
19
|
|
|
19
20
|
请以专业、建设性的语气提供审查意见。`;
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
// 附带项目特定的审查规则
|
|
23
|
+
if (projectPrompt) {
|
|
24
|
+
prompt += `\n\n## 项目特定的审查规则\n\n${projectPrompt}`;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
return prompt;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
|
-
*
|
|
30
|
-
* @param {
|
|
31
|
-
* @param {string}
|
|
32
|
-
* @
|
|
31
|
+
* 构建针对特定行变更的审查提示词(附带项目 prompt)
|
|
32
|
+
* @param {Object} changeInfo - 变更信息
|
|
33
|
+
* @param {string} changeInfo.content - 变更内容
|
|
34
|
+
* @param {string} changeInfo.type - 变更类型(addition/deletion)
|
|
35
|
+
* @param {number} changeInfo.lineNumber - 行号
|
|
36
|
+
* @param {string} changeInfo.fileName - 文件名
|
|
37
|
+
* @param {Object} changeInfo.context - 上下文
|
|
38
|
+
* @returns {string} 审查提示词
|
|
33
39
|
*/
|
|
34
|
-
export function
|
|
35
|
-
|
|
40
|
+
export function buildLineReviewPrompt(changeInfo) {
|
|
41
|
+
const { content, type, lineNumber, fileName, context } = changeInfo;
|
|
42
|
+
|
|
43
|
+
let prompt = `请审查以下代码变更:
|
|
36
44
|
|
|
37
|
-
|
|
45
|
+
**文件**: ${fileName}
|
|
46
|
+
**行号**: ${lineNumber}
|
|
47
|
+
**变更类型**: ${type === 'addition' ? '新增' : '删除'}
|
|
38
48
|
|
|
39
|
-
|
|
40
|
-
\`\`\`
|
|
41
|
-
${
|
|
49
|
+
**变更内容**:
|
|
50
|
+
\`\`\`
|
|
51
|
+
${type === 'addition' ? '+' : '-'} ${content}
|
|
42
52
|
\`\`\`
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
if (context && (context.before.length > 0 || context.after.length > 0)) {
|
|
56
|
+
prompt += '\n**上下文**:\n```\n';
|
|
57
|
+
if (context.before.length > 0) {
|
|
58
|
+
context.before.forEach(line => {
|
|
59
|
+
prompt += ` ${line}\n`;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
prompt += `${type === 'addition' ? '+' : '-'} ${content}\n`;
|
|
63
|
+
if (context.after.length > 0) {
|
|
64
|
+
context.after.forEach(line => {
|
|
65
|
+
prompt += ` ${line}\n`;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
prompt += '```\n';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
prompt += `
|
|
72
|
+
请提供简洁的审查意见:
|
|
73
|
+
1. 如果有问题,指出具体问题
|
|
74
|
+
2. 如果有改进建议,提供具体的建议
|
|
75
|
+
3. 如果代码没有问题,简单说明"代码质量良好"
|
|
43
76
|
|
|
44
|
-
|
|
45
|
-
1. 主要问题(如果有)
|
|
46
|
-
2. 改进建议
|
|
47
|
-
3. 优点(如果有)
|
|
77
|
+
请直接给出审查意见,不要重复代码内容。`;
|
|
48
78
|
|
|
49
|
-
|
|
79
|
+
return prompt;
|
|
50
80
|
}
|
|
51
81
|
|
|
52
82
|
/**
|
|
53
|
-
*
|
|
54
|
-
* @param {Object}
|
|
55
|
-
* @param {string}
|
|
56
|
-
* @param {string} params.fileName - 文件名
|
|
57
|
-
* @param {string} params.guardConfig - AI Review Guard 配置
|
|
83
|
+
* 构建针对特定行变更的完整消息数组(附带项目 prompt)
|
|
84
|
+
* @param {Object} changeInfo - 变更信息
|
|
85
|
+
* @param {string} projectPrompt - 项目配置的 prompt(来自 reviewguard.md)
|
|
58
86
|
* @returns {Array} 消息数组
|
|
59
87
|
*/
|
|
60
|
-
export function
|
|
88
|
+
export function buildLineReviewMessages(changeInfo, projectPrompt = '') {
|
|
61
89
|
return [
|
|
62
|
-
{ role: 'system', content: buildSystemPrompt(
|
|
63
|
-
{ role: 'user', content:
|
|
90
|
+
{ role: 'system', content: buildSystemPrompt(projectPrompt) },
|
|
91
|
+
{ role: 'user', content: buildLineReviewPrompt(changeInfo) },
|
|
64
92
|
];
|
|
65
93
|
}
|
|
66
94
|
|
|
67
|
-
|
|
68
|
-
* 构建批量审查的提示词
|
|
69
|
-
* @param {Array} changes - 代码变更数组
|
|
70
|
-
* @param {string} guardConfig - AI Review Guard 配置
|
|
71
|
-
* @returns {string} 批量审查提示词
|
|
72
|
-
*/
|
|
73
|
-
export function buildBatchReviewPrompt(changes, guardConfig = '') {
|
|
74
|
-
let prompt = buildSystemPrompt(guardConfig);
|
|
75
|
-
|
|
76
|
-
prompt += '\n\n请审查以下 ' + changes.length + ' 个文件的代码变更:\n\n';
|
|
77
|
-
|
|
78
|
-
changes.forEach((change, index) => {
|
|
79
|
-
const fileName = change.new_path || change.old_path;
|
|
80
|
-
const status = change.new_file ? '新增' : change.deleted_file ? '删除' : '修改';
|
|
81
|
-
|
|
82
|
-
prompt += `## ${index + 1}. ${fileName} (${status})\n\n`;
|
|
83
|
-
prompt += '```diff\n';
|
|
84
|
-
prompt += change.diff || '(无差异内容)';
|
|
85
|
-
prompt += '\n```\n\n';
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
prompt += '\n请为每个文件提供审查意见,并在最后给出整体评价。';
|
|
89
|
-
|
|
90
|
-
return prompt;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* 格式化 diff 内容(可选的预处理)
|
|
95
|
-
* @param {string} diff - 原始 diff
|
|
96
|
-
* @returns {string} 格式化后的 diff
|
|
97
|
-
*/
|
|
98
|
-
export function formatDiff(diff) {
|
|
99
|
-
if (!diff) return '(无变更内容)';
|
|
100
|
-
|
|
101
|
-
// 移除过长的 diff(可选)
|
|
102
|
-
const maxLines = 500;
|
|
103
|
-
const lines = diff.split('\n');
|
|
104
|
-
|
|
105
|
-
if (lines.length > maxLines) {
|
|
106
|
-
return lines.slice(0, maxLines).join('\n') +
|
|
107
|
-
`\n... (省略 ${lines.length - maxLines} 行)`;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return diff;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* 从 change 对象中提取关键信息
|
|
115
|
-
* @param {Object} change - GitLab change 对象
|
|
116
|
-
* @returns {Object} 提取的信息
|
|
117
|
-
*/
|
|
118
|
-
export function extractChangeInfo(change) {
|
|
119
|
-
return {
|
|
120
|
-
fileName: change.new_path || change.old_path,
|
|
121
|
-
oldPath: change.old_path,
|
|
122
|
-
newPath: change.new_path,
|
|
123
|
-
status: change.new_file ? 'added' :
|
|
124
|
-
change.deleted_file ? 'deleted' :
|
|
125
|
-
change.renamed_file ? 'renamed' :
|
|
126
|
-
'modified',
|
|
127
|
-
diff: change.diff || '',
|
|
128
|
-
isNewFile: change.new_file,
|
|
129
|
-
isDeleted: change.deleted_file,
|
|
130
|
-
isRenamed: change.renamed_file,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
95
|
+
// 只导出行级审查需要的函数
|
|
134
96
|
export default {
|
|
135
97
|
buildSystemPrompt,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
buildBatchReviewPrompt,
|
|
139
|
-
formatDiff,
|
|
140
|
-
extractChangeInfo,
|
|
98
|
+
buildLineReviewPrompt,
|
|
99
|
+
buildLineReviewMessages,
|
|
141
100
|
};
|
|
142
101
|
|
package/package.json
CHANGED