gitlab-ai-review 1.1.0 → 1.2.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 +41 -4
- package/lib/ai-client.js +40 -127
- package/lib/prompt-tools.js +142 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
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
|
+
import * as PromptTools from './lib/prompt-tools.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* GitLab AI Review SDK 主类
|
|
@@ -13,7 +14,7 @@ import { AIClient } from './lib/ai-client.js';
|
|
|
13
14
|
export class GitLabAIReview {
|
|
14
15
|
constructor(options = {}) {
|
|
15
16
|
this.name = 'GitLab AI Review SDK';
|
|
16
|
-
this.version = '1.0
|
|
17
|
+
this.version = '1.2.0';
|
|
17
18
|
|
|
18
19
|
// 如果传入了配置,使用手动配置;否则使用自动检测
|
|
19
20
|
if (options.token || options.gitlab) {
|
|
@@ -137,27 +138,62 @@ export class GitLabAIReview {
|
|
|
137
138
|
|
|
138
139
|
/**
|
|
139
140
|
* AI 审查单个文件的代码变更
|
|
141
|
+
* @param {Object} change - 代码变更对象
|
|
142
|
+
* @returns {Promise<Object>} 审查结果
|
|
140
143
|
*/
|
|
141
144
|
async reviewCodeChange(change) {
|
|
142
145
|
const aiClient = this.getAIClient();
|
|
143
146
|
const guardConfig = this.config.ai?.guardConfig?.content || '';
|
|
144
147
|
|
|
145
|
-
|
|
148
|
+
// 使用 PromptTools 构建消息
|
|
149
|
+
const messages = PromptTools.buildReviewMessages({
|
|
146
150
|
diff: change.diff,
|
|
147
151
|
fileName: change.new_path || change.old_path,
|
|
148
152
|
guardConfig,
|
|
149
153
|
});
|
|
154
|
+
|
|
155
|
+
// 调用 AI
|
|
156
|
+
return aiClient.sendMessage(messages);
|
|
150
157
|
}
|
|
151
158
|
|
|
152
159
|
/**
|
|
153
160
|
* AI 审查 MR 的所有代码变更
|
|
161
|
+
* @returns {Promise<Array>} 审查结果数组
|
|
154
162
|
*/
|
|
155
163
|
async reviewMergeRequest() {
|
|
156
164
|
const changes = await this.getMergeRequestChanges();
|
|
157
165
|
const aiClient = this.getAIClient();
|
|
158
166
|
const guardConfig = this.config.ai?.guardConfig?.content || '';
|
|
159
167
|
|
|
160
|
-
|
|
168
|
+
const reviews = [];
|
|
169
|
+
|
|
170
|
+
for (const change of changes) {
|
|
171
|
+
try {
|
|
172
|
+
// 使用 PromptTools 构建消息
|
|
173
|
+
const messages = PromptTools.buildReviewMessages({
|
|
174
|
+
diff: change.diff,
|
|
175
|
+
fileName: change.new_path || change.old_path,
|
|
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
|
+
}
|
|
195
|
+
|
|
196
|
+
return reviews;
|
|
161
197
|
}
|
|
162
198
|
|
|
163
199
|
/**
|
|
@@ -198,10 +234,11 @@ export class GitLabAIReview {
|
|
|
198
234
|
}
|
|
199
235
|
}
|
|
200
236
|
|
|
201
|
-
//
|
|
237
|
+
// 导出工具函数和类
|
|
202
238
|
export { getConfig, validateConfig } from './lib/config.js';
|
|
203
239
|
export { GitLabClient } from './lib/gitlab-client.js';
|
|
204
240
|
export { AIClient } from './lib/ai-client.js';
|
|
241
|
+
export { PromptTools };
|
|
205
242
|
|
|
206
243
|
// 默认导出
|
|
207
244
|
export default GitLabAIReview;
|
package/lib/ai-client.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AI 客户端 - 基于 ARK API (豆包大模型)
|
|
3
|
+
* 只负责调用 AI API,不处理业务逻辑
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import OpenAI from 'openai';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
* AI 客户端类
|
|
9
|
+
* AI 客户端类 - 纯粹的 API 调用封装
|
|
9
10
|
*/
|
|
10
11
|
export class AIClient {
|
|
11
12
|
constructor(config = {}) {
|
|
@@ -27,54 +28,51 @@ export class AIClient {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
-
* @param {
|
|
32
|
-
* @param {
|
|
33
|
-
* @
|
|
34
|
-
* @param {string} params.guardConfig - AI Review Guard 配置内容
|
|
35
|
-
* @returns {Promise<string>} 审查结果
|
|
31
|
+
* 发送消息到 AI(非流式)
|
|
32
|
+
* @param {Array|string} prompt - 消息数组或单个提示词
|
|
33
|
+
* @param {Object} options - 可选参数
|
|
34
|
+
* @returns {Promise<Object>} AI 响应
|
|
36
35
|
*/
|
|
37
|
-
async
|
|
38
|
-
|
|
39
|
-
const
|
|
36
|
+
async sendMessage(prompt, options = {}) {
|
|
37
|
+
// 如果传入的是字符串,转换为消息数组
|
|
38
|
+
const messages = typeof prompt === 'string'
|
|
39
|
+
? [{ role: 'user', content: prompt }]
|
|
40
|
+
: prompt;
|
|
40
41
|
|
|
41
42
|
const completion = await this.openai.chat.completions.create({
|
|
42
|
-
messages
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
model: this.model,
|
|
47
|
-
reasoning_effort: this.config.reasoningEffort || 'medium',
|
|
43
|
+
messages,
|
|
44
|
+
model: options.model || this.model,
|
|
45
|
+
reasoning_effort: options.reasoningEffort || this.config.reasoningEffort || 'medium',
|
|
46
|
+
...options,
|
|
48
47
|
});
|
|
49
48
|
|
|
50
49
|
return {
|
|
51
50
|
reasoning: completion.choices[0]?.message?.reasoning_content || '',
|
|
52
51
|
content: completion.choices[0]?.message?.content || '',
|
|
53
52
|
usage: completion.usage,
|
|
53
|
+
raw: completion,
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
59
|
-
* @param {
|
|
60
|
-
* @param {string} params.diff - 代码差异
|
|
61
|
-
* @param {string} params.fileName - 文件名
|
|
62
|
-
* @param {string} params.guardConfig - AI Review Guard 配置内容
|
|
58
|
+
* 发送消息到 AI(流式)
|
|
59
|
+
* @param {Array|string} prompt - 消息数组或单个提示词
|
|
63
60
|
* @param {Function} onChunk - 流式回调函数
|
|
64
|
-
* @
|
|
61
|
+
* @param {Object} options - 可选参数
|
|
62
|
+
* @returns {Promise<Object>} 完整 AI 响应
|
|
65
63
|
*/
|
|
66
|
-
async
|
|
67
|
-
|
|
68
|
-
const
|
|
64
|
+
async sendMessageStream(prompt, onChunk, options = {}) {
|
|
65
|
+
// 如果传入的是字符串,转换为消息数组
|
|
66
|
+
const messages = typeof prompt === 'string'
|
|
67
|
+
? [{ role: 'user', content: prompt }]
|
|
68
|
+
: prompt;
|
|
69
69
|
|
|
70
70
|
const stream = await this.openai.chat.completions.create({
|
|
71
|
-
messages
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
],
|
|
75
|
-
model: this.model,
|
|
76
|
-
reasoning_effort: this.config.reasoningEffort || 'medium',
|
|
71
|
+
messages,
|
|
72
|
+
model: options.model || this.model,
|
|
73
|
+
reasoning_effort: options.reasoningEffort || this.config.reasoningEffort || 'medium',
|
|
77
74
|
stream: true,
|
|
75
|
+
...options,
|
|
78
76
|
});
|
|
79
77
|
|
|
80
78
|
let fullReasoning = '';
|
|
@@ -99,107 +97,22 @@ export class AIClient {
|
|
|
99
97
|
}
|
|
100
98
|
|
|
101
99
|
/**
|
|
102
|
-
*
|
|
103
|
-
* @
|
|
104
|
-
* @param {Object} options - 可选参数
|
|
105
|
-
* @returns {Promise<Object>} AI 响应
|
|
100
|
+
* 获取原始 OpenAI 客户端(高级用法)
|
|
101
|
+
* @returns {OpenAI} OpenAI 客户端实例
|
|
106
102
|
*/
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
messages,
|
|
110
|
-
model: options.model || this.model,
|
|
111
|
-
reasoning_effort: options.reasoningEffort || 'medium',
|
|
112
|
-
stream: options.stream || false,
|
|
113
|
-
...options,
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
if (options.stream) {
|
|
117
|
-
return completion;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
reasoning: completion.choices[0]?.message?.reasoning_content || '',
|
|
122
|
-
content: completion.choices[0]?.message?.content || '',
|
|
123
|
-
usage: completion.usage,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* 构建系统提示词
|
|
129
|
-
* @private
|
|
130
|
-
*/
|
|
131
|
-
_buildSystemPrompt(guardConfig) {
|
|
132
|
-
let prompt = `你是一个专业的代码审查助手,负责审查 GitLab Merge Request 的代码变更。
|
|
133
|
-
|
|
134
|
-
你的职责是:
|
|
135
|
-
1. 识别代码中的潜在问题(安全漏洞、性能问题、逻辑错误等)
|
|
136
|
-
2. 提供具体的改进建议
|
|
137
|
-
3. 指出不符合最佳实践的代码
|
|
138
|
-
4. 评估代码的可维护性和可读性
|
|
139
|
-
|
|
140
|
-
请以专业、建设性的语气提供审查意见。`;
|
|
141
|
-
|
|
142
|
-
if (guardConfig) {
|
|
143
|
-
prompt += `\n\n项目特定的审查规则:\n${guardConfig}`;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return prompt;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* 构建代码审查提示词
|
|
151
|
-
* @private
|
|
152
|
-
*/
|
|
153
|
-
_buildReviewPrompt(diff, fileName) {
|
|
154
|
-
return `请审查以下代码变更:
|
|
155
|
-
|
|
156
|
-
**文件名**: ${fileName}
|
|
157
|
-
|
|
158
|
-
**代码差异**:
|
|
159
|
-
\`\`\`diff
|
|
160
|
-
${diff}
|
|
161
|
-
\`\`\`
|
|
162
|
-
|
|
163
|
-
请提供:
|
|
164
|
-
1. 主要问题(如果有)
|
|
165
|
-
2. 改进建议
|
|
166
|
-
3. 优点(如果有)
|
|
167
|
-
|
|
168
|
-
如果代码没有问题,请简要说明代码质量良好。`;
|
|
103
|
+
getClient() {
|
|
104
|
+
return this.openai;
|
|
169
105
|
}
|
|
170
106
|
|
|
171
107
|
/**
|
|
172
|
-
*
|
|
173
|
-
* @
|
|
174
|
-
* @param {string} guardConfig - AI Review Guard 配置
|
|
175
|
-
* @returns {Promise<Array>} 审查结果数组
|
|
108
|
+
* 获取当前配置
|
|
109
|
+
* @returns {Object} 配置对象
|
|
176
110
|
*/
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const result = await this.reviewCode({
|
|
183
|
-
diff: change.diff,
|
|
184
|
-
fileName: change.new_path || change.old_path,
|
|
185
|
-
guardConfig,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
reviews.push({
|
|
189
|
-
fileName: change.new_path || change.old_path,
|
|
190
|
-
status: 'success',
|
|
191
|
-
...result,
|
|
192
|
-
});
|
|
193
|
-
} catch (error) {
|
|
194
|
-
reviews.push({
|
|
195
|
-
fileName: change.new_path || change.old_path,
|
|
196
|
-
status: 'error',
|
|
197
|
-
error: error.message,
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return reviews;
|
|
111
|
+
getConfig() {
|
|
112
|
+
return {
|
|
113
|
+
model: this.model,
|
|
114
|
+
...this.config,
|
|
115
|
+
};
|
|
203
116
|
}
|
|
204
117
|
}
|
|
205
118
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt 工具 - 用于拼接代码审查的提示词
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 构建系统提示词
|
|
7
|
+
* @param {string} guardConfig - AI Review Guard 配置内容
|
|
8
|
+
* @returns {string} 系统提示词
|
|
9
|
+
*/
|
|
10
|
+
export function buildSystemPrompt(guardConfig = '') {
|
|
11
|
+
let prompt = `你是一个专业的代码审查助手,负责审查 GitLab Merge Request 的代码变更。
|
|
12
|
+
|
|
13
|
+
你的职责是:
|
|
14
|
+
1. 识别代码中的潜在问题(安全漏洞、性能问题、逻辑错误等)
|
|
15
|
+
2. 提供具体的改进建议
|
|
16
|
+
3. 指出不符合最佳实践的代码
|
|
17
|
+
4. 评估代码的可维护性和可读性
|
|
18
|
+
|
|
19
|
+
请以专业、建设性的语气提供审查意见。`;
|
|
20
|
+
|
|
21
|
+
if (guardConfig) {
|
|
22
|
+
prompt += `\n\n项目特定的审查规则:\n${guardConfig}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return prompt;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 构建代码审查的用户提示词
|
|
30
|
+
* @param {string} diff - 代码差异
|
|
31
|
+
* @param {string} fileName - 文件名
|
|
32
|
+
* @returns {string} 用户提示词
|
|
33
|
+
*/
|
|
34
|
+
export function buildReviewPrompt(diff, fileName) {
|
|
35
|
+
return `请审查以下代码变更:
|
|
36
|
+
|
|
37
|
+
**文件名**: ${fileName}
|
|
38
|
+
|
|
39
|
+
**代码差异**:
|
|
40
|
+
\`\`\`diff
|
|
41
|
+
${diff}
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
请提供:
|
|
45
|
+
1. 主要问题(如果有)
|
|
46
|
+
2. 改进建议
|
|
47
|
+
3. 优点(如果有)
|
|
48
|
+
|
|
49
|
+
如果代码没有问题,请简要说明代码质量良好。`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 构建完整的审查消息数组
|
|
54
|
+
* @param {Object} params - 参数
|
|
55
|
+
* @param {string} params.diff - 代码差异
|
|
56
|
+
* @param {string} params.fileName - 文件名
|
|
57
|
+
* @param {string} params.guardConfig - AI Review Guard 配置
|
|
58
|
+
* @returns {Array} 消息数组
|
|
59
|
+
*/
|
|
60
|
+
export function buildReviewMessages({ diff, fileName, guardConfig = '' }) {
|
|
61
|
+
return [
|
|
62
|
+
{ role: 'system', content: buildSystemPrompt(guardConfig) },
|
|
63
|
+
{ role: 'user', content: buildReviewPrompt(diff, fileName) },
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
|
|
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
|
+
|
|
134
|
+
export default {
|
|
135
|
+
buildSystemPrompt,
|
|
136
|
+
buildReviewPrompt,
|
|
137
|
+
buildReviewMessages,
|
|
138
|
+
buildBatchReviewPrompt,
|
|
139
|
+
formatDiff,
|
|
140
|
+
extractChangeInfo,
|
|
141
|
+
};
|
|
142
|
+
|