gitlab-ai-review 4.2.4 → 6.3.9
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/README.md +36 -2
- package/cli.js +118 -32
- package/index.js +741 -335
- package/lib/ai-client.js +145 -61
- package/lib/config.js +798 -11
- package/lib/diff-parser.js +143 -44
- package/lib/document-loader.js +329 -0
- package/lib/export-analyzer.js +384 -0
- package/lib/gitlab-client.js +588 -7
- package/lib/prompt-tools.js +241 -453
- package/package.json +52 -50
- package/lib/impact-analyzer.js +0 -700
- package/lib/incremental-callchain-analyzer.js +0 -811
package/lib/prompt-tools.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Prompt 工具 -
|
|
3
|
-
*
|
|
2
|
+
* Prompt 工具 - 用于拼接代码审查的提示词
|
|
3
|
+
* 支持按块审查(删除+新增组合为一个变更块)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* 构建系统提示词(附带项目 prompt
|
|
7
|
+
* 构建系统提示词(附带项目 prompt 和正负样本)
|
|
8
8
|
* @param {string} projectPrompt - 项目配置的 prompt(来自 reviewguard.md)
|
|
9
|
+
* @param {Object} feedbackSamples - 历史反馈样本(可选)
|
|
10
|
+
* @param {Array} feedbackSamples.positive - 正样本(获得好评的审查意见)
|
|
11
|
+
* @param {Array} feedbackSamples.negative - 负样本(获得差评的审查意见)
|
|
12
|
+
* @param {string} documentsContext - 项目文档上下文(可选)
|
|
9
13
|
* @returns {string} 系统提示词
|
|
10
14
|
*/
|
|
11
|
-
export function buildSystemPrompt(projectPrompt = '') {
|
|
15
|
+
export function buildSystemPrompt(projectPrompt = '', feedbackSamples = null, documentsContext = '') {
|
|
12
16
|
let prompt = `你是一个专业的代码审查助手,负责审查 GitLab Merge Request 的代码变更。
|
|
13
17
|
|
|
14
18
|
你的职责是:
|
|
@@ -19,437 +23,260 @@ export function buildSystemPrompt(projectPrompt = '') {
|
|
|
19
23
|
|
|
20
24
|
请以专业、建设性的语气提供审查意见。`;
|
|
21
25
|
|
|
26
|
+
// 附带项目文档上下文(PRD 和 TD 的总结)
|
|
27
|
+
if (documentsContext && documentsContext.trim().length > 0) {
|
|
28
|
+
prompt += `\n\n${documentsContext}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
// 附带项目特定的审查规则
|
|
23
32
|
if (projectPrompt) {
|
|
24
33
|
prompt += `\n\n## 项目特定的审查规则\n\n${projectPrompt}`;
|
|
25
34
|
}
|
|
26
35
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* 构建整个文件所有变更行的批量审查提示词
|
|
32
|
-
* @param {string} fileName - 文件名
|
|
33
|
-
* @param {Array} meaningfulChanges - 有意义的变更数组
|
|
34
|
-
* @param {string} projectPrompt - 项目配置的 prompt
|
|
35
|
-
* @returns {string} 批量审查提示词
|
|
36
|
-
*/
|
|
37
|
-
export function buildFileReviewPrompt(fileName, meaningfulChanges) {
|
|
38
|
-
let prompt = `请审查以下文件的代码变更,只对**有问题的行**提出审查意见,代码没有问题的行不需要评论。
|
|
36
|
+
// 附带历史反馈样本(正负样本学习)
|
|
37
|
+
if (feedbackSamples && (feedbackSamples.positive?.length > 0 || feedbackSamples.negative?.length > 0)) {
|
|
38
|
+
prompt += `\n\n## 📊 历史反馈学习(基于用户真实评价)\n`;
|
|
39
|
+
prompt += `\n以下是用户对历史审查意见的反馈,请学习这些示例来提升审查质量。\n`;
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
`;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
prompt += `**内容**:\n\`\`\`\n`;
|
|
49
|
-
|
|
50
|
-
// 收集所有其他变更的内容(用于过滤)
|
|
51
|
-
const otherChangesContent = meaningfulChanges
|
|
52
|
-
.filter((_, i) => i !== index)
|
|
53
|
-
.map(c => c.content.trim());
|
|
54
|
-
|
|
55
|
-
// 上下文(只显示不变的行,过滤掉其他变更的内容)
|
|
56
|
-
if (change.context?.before?.length > 0) {
|
|
57
|
-
change.context.before.forEach(line => {
|
|
58
|
-
const trimmedLine = line.trim();
|
|
59
|
-
// 跳过其他变更的内容
|
|
60
|
-
if (!otherChangesContent.includes(trimmedLine)) {
|
|
61
|
-
prompt += ` ${line}\n`;
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 变更行
|
|
67
|
-
prompt += `${change.type === 'addition' ? '+' : '-'} ${change.content}\n`;
|
|
68
|
-
|
|
69
|
-
// 下文(只显示不变的行,过滤掉其他变更的内容)
|
|
70
|
-
if (change.context?.after?.length > 0) {
|
|
71
|
-
change.context.after.forEach(line => {
|
|
72
|
-
const trimmedLine = line.trim();
|
|
73
|
-
// 跳过其他变更的内容
|
|
74
|
-
if (!otherChangesContent.includes(trimmedLine)) {
|
|
75
|
-
prompt += ` ${line}\n`;
|
|
76
|
-
}
|
|
41
|
+
// 正样本 - 获得好评的审查
|
|
42
|
+
if (feedbackSamples.positive?.length > 0) {
|
|
43
|
+
prompt += `\n### ✅ 优秀审查意见示例(获得用户好评 👍)\n`;
|
|
44
|
+
prompt += `\n学习这些示例的:语气、格式、问题描述方式、建议的具体性\n\n`;
|
|
45
|
+
|
|
46
|
+
feedbackSamples.positive.forEach((sample, index) => {
|
|
47
|
+
prompt += `**示例 ${index + 1}** (👍${sample.up} 👎${sample.down}):\n`;
|
|
48
|
+
prompt += `\`\`\`\n${sample.content}\n\`\`\`\n\n`;
|
|
77
49
|
});
|
|
78
50
|
}
|
|
79
|
-
|
|
80
|
-
prompt += `\`\`\`\n`;
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
prompt += `\n## ⚠️ 重要说明:如何区分删除和新增
|
|
84
|
-
|
|
85
|
-
**请务必注意以下标记:**
|
|
86
|
-
- 标记为 **"类型: 删除"** 且代码以 **"-"** 开头的,是**被删除的代码**(不再存在)
|
|
87
|
-
- 标记为 **"类型: 新增"** 且代码以 **"+"** 开头的,是**新增的代码**(新添加的)
|
|
88
|
-
|
|
89
|
-
**特别注意:**
|
|
90
|
-
- 如果看到 **"类型: 删除"** 和 **"-"**,这意味着该代码已被移除,不存在了
|
|
91
|
-
- 不要把"删除"理解成"存在"或"新增"
|
|
92
|
-
- 删除的代码需要重点关注是否仍被其他地方使用
|
|
93
51
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
{
|
|
104
|
-
"reviews": [
|
|
105
|
-
{
|
|
106
|
-
"lineNumber": 15,
|
|
107
|
-
"hasIssue": true,
|
|
108
|
-
"comment": "必须按照上面'项目特定的审查规则'中定义的格式填写"
|
|
52
|
+
// 负样本 - 获得差评的审查
|
|
53
|
+
if (feedbackSamples.negative?.length > 0) {
|
|
54
|
+
prompt += `\n### ❌ 需要避免的审查意见示例(获得用户差评 👎)\n`;
|
|
55
|
+
prompt += `\n避免这些示例中的问题:可能过于严苛、建议不实用、或误报问题\n\n`;
|
|
56
|
+
|
|
57
|
+
feedbackSamples.negative.forEach((sample, index) => {
|
|
58
|
+
prompt += `**反例 ${index + 1}** (👍${sample.up} 👎${sample.down}):\n`;
|
|
59
|
+
prompt += `\`\`\`\n${sample.content}\n\`\`\`\n\n`;
|
|
60
|
+
});
|
|
109
61
|
}
|
|
110
|
-
]
|
|
111
|
-
}
|
|
112
|
-
\`\`\`
|
|
113
62
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
- 不要在最外层使用【】、emoji 或其他特殊格式
|
|
117
|
-
- lineNumber 必须是数字
|
|
118
|
-
- hasIssue 必须是布尔值(true/false)
|
|
119
|
-
- comment 必须是字符串
|
|
120
|
-
|
|
121
|
-
**comment 字段的内容:**
|
|
122
|
-
- **必须严格遵循"项目特定的审查规则"(reviewguard.md)中定义的格式**
|
|
123
|
-
- 如果规则中定义了包含 emoji、评分等格式,**必须完整使用该格式**
|
|
124
|
-
- comment 是一个字符串,可以包含换行符 \\n 来格式化内容
|
|
125
|
-
- 不要自创格式,必须参考上面"项目特定的审查规则"中的示例
|
|
126
|
-
|
|
127
|
-
6. 如果所有代码都没有问题,返回空的 reviews 数组:\`{"reviews": []}\`
|
|
128
|
-
7. 只返回 JSON,不要包含其他文字说明`;
|
|
63
|
+
prompt += `---\n\n**重要提示**: 请参考正样本的风格,避免负样本中的问题,生成高质量的审查意见。\n`;
|
|
64
|
+
}
|
|
129
65
|
|
|
130
66
|
return prompt;
|
|
131
67
|
}
|
|
132
68
|
|
|
133
69
|
/**
|
|
134
|
-
*
|
|
135
|
-
* @param {
|
|
136
|
-
* @param {
|
|
137
|
-
* @
|
|
138
|
-
* @returns {Array} 消息数组
|
|
70
|
+
* 格式化单个变更块的内容
|
|
71
|
+
* @param {Object} block - 变更块
|
|
72
|
+
* @param {number} index - 变更块索引
|
|
73
|
+
* @returns {string} 格式化后的内容
|
|
139
74
|
*/
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
75
|
+
function formatChangeBlock(block, index) {
|
|
76
|
+
// 计算评论应该使用的行号
|
|
77
|
+
// 使用 endLine(结束行号),这样 GitLab 显示评论时会包含整个代码块的上下文
|
|
78
|
+
let targetLine = null;
|
|
79
|
+
let isOldLine = false;
|
|
80
|
+
|
|
81
|
+
if (block.additions) {
|
|
82
|
+
// 新增或修改:使用新增部分的最后一行
|
|
83
|
+
targetLine = block.additions.endLine;
|
|
84
|
+
} else if (block.deletions) {
|
|
85
|
+
// 纯删除:使用删除部分的最后一行
|
|
86
|
+
targetLine = block.deletions.endLine;
|
|
87
|
+
isOldLine = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let content = `\n### 变更 ${index + 1}`;
|
|
91
|
+
|
|
92
|
+
// 添加变更类型标识和目标行号
|
|
93
|
+
if (block.type === 'modification') {
|
|
94
|
+
content += ` [修改] → 评论行号: ${targetLine}\n`;
|
|
95
|
+
} else if (block.type === 'deletion') {
|
|
96
|
+
content += ` [删除] → 评论行号: ${targetLine} (旧文件, isOldLine: true)\n`;
|
|
97
|
+
} else {
|
|
98
|
+
content += ` [新增] → 评论行号: ${targetLine}\n`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 显示删除部分
|
|
102
|
+
if (block.deletions) {
|
|
103
|
+
const { startLine, endLine, lines } = block.deletions;
|
|
104
|
+
const lineRange = startLine === endLine ? `旧文件第 ${startLine} 行` : `旧文件第 ${startLine}-${endLine} 行`;
|
|
105
|
+
content += `\n**删除** ${lineRange}:\n\`\`\`diff\n`;
|
|
106
|
+
lines.forEach((line, i) => {
|
|
107
|
+
content += `${startLine + i}: - ${line}\n`;
|
|
108
|
+
});
|
|
109
|
+
content += `\`\`\`\n`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 显示新增部分
|
|
113
|
+
if (block.additions) {
|
|
114
|
+
const { startLine, endLine, lines } = block.additions;
|
|
115
|
+
const lineRange = startLine === endLine ? `新文件第 ${startLine} 行` : `新文件第 ${startLine}-${endLine} 行`;
|
|
116
|
+
content += `\n**新增** ${lineRange}:\n\`\`\`diff\n`;
|
|
117
|
+
lines.forEach((line, i) => {
|
|
118
|
+
content += `${startLine + i}: + ${line}\n`;
|
|
119
|
+
});
|
|
120
|
+
content += `\`\`\`\n`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return content;
|
|
145
124
|
}
|
|
146
125
|
|
|
147
126
|
/**
|
|
148
|
-
*
|
|
127
|
+
* 构建整个文件所有变更的批量审查提示词
|
|
149
128
|
* @param {string} fileName - 文件名
|
|
150
|
-
* @param {Array} meaningfulChanges -
|
|
151
|
-
* @param {
|
|
152
|
-
* @
|
|
129
|
+
* @param {Array} meaningfulChanges - 有意义的变更数组(按块格式)
|
|
130
|
+
* @param {string} fullFileContent - 完整文件内容(可选)
|
|
131
|
+
* @param {Object} impactInfo - 调用链影响信息(可选)
|
|
132
|
+
* @returns {string} 批量审查提示词
|
|
153
133
|
*/
|
|
154
|
-
export function
|
|
155
|
-
let prompt =
|
|
134
|
+
export function buildFileReviewPrompt(fileName, meaningfulChanges, fullFileContent = null, impactInfo = null) {
|
|
135
|
+
let prompt = `请审查以下文件的代码变更,只对**有问题的变更块**提出审查意见,没有问题的不需要评论。
|
|
156
136
|
|
|
157
137
|
**文件名**: ${fileName}
|
|
158
|
-
|
|
138
|
+
**变更块数量**: ${meaningfulChanges.length} 个
|
|
159
139
|
|
|
160
140
|
`;
|
|
161
141
|
|
|
162
|
-
//
|
|
163
|
-
if (
|
|
164
|
-
|
|
142
|
+
// 🎯 添加完整文件内容(如果有)
|
|
143
|
+
if (fullFileContent) {
|
|
144
|
+
const lines = fullFileContent.split('\n');
|
|
145
|
+
const totalLines = lines.length;
|
|
146
|
+
const maxLines = 500; // 限制最多显示 500 行,避免 prompt 过长
|
|
165
147
|
|
|
166
|
-
|
|
167
|
-
if (impactAnalysis.changedSymbols) {
|
|
168
|
-
const { added, deleted, modified, commented } = impactAnalysis.changedSymbols;
|
|
169
|
-
|
|
170
|
-
if (added.length > 0 || deleted.length > 0 || modified.length > 0 || (commented && commented.length > 0)) {
|
|
171
|
-
prompt += `**符号变更统计**:\n`;
|
|
172
|
-
if (added.length > 0) {
|
|
173
|
-
prompt += `- 新增: ${added.length} 个\n`;
|
|
174
|
-
}
|
|
175
|
-
if (deleted.length > 0) {
|
|
176
|
-
prompt += `- 删除/注释掉: ${deleted.length} 个 ⚠️\n`;
|
|
177
|
-
}
|
|
178
|
-
if (commented && commented.length > 0) {
|
|
179
|
-
prompt += ` (其中 ${commented.length} 个是被注释掉的代码)\n`;
|
|
180
|
-
}
|
|
181
|
-
if (modified.length > 0) {
|
|
182
|
-
prompt += `- 修改: ${modified.length} 个 ⚠️\n`;
|
|
183
|
-
}
|
|
184
|
-
prompt += `\n`;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// 被删除的符号详情
|
|
188
|
-
if (deleted.length > 0) {
|
|
189
|
-
const deletedDefs = deleted.filter(s => s.type === 'definition' || s.type === 'commented-out');
|
|
190
|
-
if (deletedDefs.length > 0) {
|
|
191
|
-
prompt += `**⚠️ 被删除/注释掉的函数/类/组件** (${deletedDefs.length} 个):\n`;
|
|
192
|
-
deletedDefs.slice(0, 10).forEach(symbol => {
|
|
193
|
-
const status = symbol.type === 'commented-out' ? ' (注释掉)' : '';
|
|
194
|
-
prompt += `- \`${symbol.name}\`${status}\n`;
|
|
195
|
-
});
|
|
196
|
-
prompt += `\n`;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// 新增或修改的符号
|
|
201
|
-
const changedDefs = [...added, ...modified].filter(s => s.type === 'definition');
|
|
202
|
-
if (changedDefs.length > 0) {
|
|
203
|
-
prompt += `**新增/修改的函数/类/组件** (${changedDefs.length} 个):\n`;
|
|
204
|
-
changedDefs.slice(0, 10).forEach(symbol => {
|
|
205
|
-
prompt += `- \`${symbol.name}\`\n`;
|
|
206
|
-
});
|
|
207
|
-
prompt += `\n`;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
148
|
+
prompt += `## 📄 完整文件内容(共 ${totalLines} 行)\n\n`;
|
|
210
149
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
impactAnalysis.internalUsage.slice(0, 5).forEach((usage, index) => {
|
|
217
|
-
const localWarning = usage.maybeLocal ? ' ⚠️ **可能有局部定义/导入**' : '';
|
|
218
|
-
prompt += `${index + 1}. 第 ${usage.lineNumber} 行使用了被删除的 \`${usage.symbol}\`${localWarning}:\n`;
|
|
219
|
-
prompt += `\`\`\`javascript\n${usage.line}\n\`\`\`\n`;
|
|
150
|
+
if (totalLines <= maxLines) {
|
|
151
|
+
prompt += '```\n';
|
|
152
|
+
lines.forEach((line, index) => {
|
|
153
|
+
prompt += `${index + 1}| ${line}\n`;
|
|
220
154
|
});
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
prompt += `标记了 ⚠️ **可能有局部定义/导入** 的行,表示该行可能:\n`;
|
|
228
|
-
prompt += `1. 定义了局部变量(const/let/var symbol = ...)\n`;
|
|
229
|
-
prompt += `2. 是函数参数(function(symbol) 或 (symbol) => ...)\n`;
|
|
230
|
-
prompt += `3. 从其他模块导入(import { symbol } from ...)\n`;
|
|
231
|
-
prompt += `4. 对象属性或解构({ symbol } 或 symbol:)\n`;
|
|
232
|
-
prompt += `\n**对于这些标记的行,请仔细检查代码上下文,确认是否真的使用了被删除的符号。**\n`;
|
|
233
|
-
prompt += `**如果有局部定义或导入,则不是问题,不要报告!**\n\n`;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// 函数签名
|
|
237
|
-
if (impactAnalysis.signatures && impactAnalysis.signatures.length > 0) {
|
|
238
|
-
prompt += `**函数/类签名**:\n`;
|
|
239
|
-
impactAnalysis.signatures.forEach(sig => {
|
|
240
|
-
prompt += `\`\`\`javascript\n${sig.signature}\n\`\`\`\n`;
|
|
155
|
+
prompt += '```\n\n';
|
|
156
|
+
} else {
|
|
157
|
+
prompt += `⚠️ 文件过长(${totalLines} 行),仅显示前 ${maxLines} 行\n\n`;
|
|
158
|
+
prompt += '```\n';
|
|
159
|
+
lines.slice(0, maxLines).forEach((line, index) => {
|
|
160
|
+
prompt += `${index + 1}| ${line}\n`;
|
|
241
161
|
});
|
|
242
|
-
prompt +=
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// 🎯 优先显示 TypeScript 调用链信息
|
|
246
|
-
if (impactAnalysis.callChain && impactAnalysis.callChain.chains) {
|
|
247
|
-
prompt += `**🔗 TypeScript 调用链分析**:\n`;
|
|
248
|
-
prompt += `使用了 TypeScript Compiler API 进行精确的类型分析和调用链追踪\n\n`;
|
|
249
|
-
|
|
250
|
-
impactAnalysis.callChain.chains.forEach((chain, chainIndex) => {
|
|
251
|
-
prompt += `### 调用链 ${chainIndex + 1}: \`${chain.symbol}\`\n`;
|
|
252
|
-
|
|
253
|
-
// 显示问题(如果有)
|
|
254
|
-
if (chain.issues && chain.issues.length > 0) {
|
|
255
|
-
prompt += `**🚨 检测到的问题**:\n`;
|
|
256
|
-
chain.issues.forEach(issue => {
|
|
257
|
-
prompt += `- [${issue.severity}] ${issue.message}\n`;
|
|
258
|
-
});
|
|
259
|
-
prompt += `\n`;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// 显示使用处
|
|
263
|
-
if (chain.usages && chain.usages.length > 0) {
|
|
264
|
-
prompt += `**使用位置** (${chain.usages.length} 处):\n\n`;
|
|
265
|
-
|
|
266
|
-
chain.usages.slice(0, 5).forEach((usage, usageIndex) => {
|
|
267
|
-
prompt += `${usageIndex + 1}. **${usage.file}:${usage.line}**\n`;
|
|
268
|
-
|
|
269
|
-
// 显示数据流(如果有)
|
|
270
|
-
if (usage.dataFlow && usage.dataFlow.length > 0) {
|
|
271
|
-
prompt += ` 数据流: ${usage.dataFlow.join(' → ')}\n`;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// 显示问题(如果有)
|
|
275
|
-
if (usage.issue) {
|
|
276
|
-
prompt += ` ⚠️ **问题**: ${usage.issue.message}\n`;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// 显示代码上下文
|
|
280
|
-
if (usage.context) {
|
|
281
|
-
prompt += `\n\`\`\`typescript\n${usage.context}\n\`\`\`\n`;
|
|
282
|
-
}
|
|
283
|
-
prompt += `\n`;
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
if (chain.usages.length > 5) {
|
|
287
|
-
prompt += `... 还有 ${chain.usages.length - 5} 处使用\n\n`;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
// 显示摘要
|
|
293
|
-
if (impactAnalysis.callChain.summary) {
|
|
294
|
-
const summary = impactAnalysis.callChain.summary;
|
|
295
|
-
prompt += `**📊 调用链分析摘要**:\n`;
|
|
296
|
-
prompt += `- 分析的符号: ${summary.totalSymbols || 0} 个\n`;
|
|
297
|
-
prompt += `- 找到的引用: ${summary.totalReferences || 0} 处\n`;
|
|
298
|
-
prompt += `- 检测到的问题: ${summary.totalIssues || 0} 个\n`;
|
|
299
|
-
if (summary.issuesByType && Object.keys(summary.issuesByType).length > 0) {
|
|
300
|
-
prompt += `- 问题类型分布:\n`;
|
|
301
|
-
Object.entries(summary.issuesByType).forEach(([type, count]) => {
|
|
302
|
-
prompt += ` - ${type}: ${count}\n`;
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
prompt += `\n`;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
prompt += `**✅ 这是精确的类型分析结果,可以直接信任!**\n\n`;
|
|
309
|
-
prompt += `---\n\n`;
|
|
310
|
-
}
|
|
311
|
-
// 如果没有调用链,显示传统的 GitLab Search 结果
|
|
312
|
-
else if (impactAnalysis.affectedFiles && impactAnalysis.affectedFiles.length > 0) {
|
|
313
|
-
prompt += `**其他受影响的文件** (${impactAnalysis.totalAffectedFiles || impactAnalysis.affectedFiles.length} 个`;
|
|
314
|
-
if (impactAnalysis.totalAffectedFiles > impactAnalysis.affectedFiles.length) {
|
|
315
|
-
prompt += `,显示前 ${impactAnalysis.affectedFiles.length} 个`;
|
|
316
|
-
}
|
|
317
|
-
prompt += `):\n`;
|
|
318
|
-
prompt += `⚠️ 使用 GitLab Search API 搜索(文本匹配,可能不够精确)\n\n`;
|
|
319
|
-
|
|
320
|
-
impactAnalysis.affectedFiles.forEach((file, index) => {
|
|
321
|
-
prompt += `### ${index + 1}. ${file.path}\n`;
|
|
322
|
-
prompt += `使用的符号: ${file.symbols.join(', ')}\n`;
|
|
323
|
-
|
|
324
|
-
// 添加代码片段
|
|
325
|
-
if (file.snippets && file.snippets.length > 0) {
|
|
326
|
-
prompt += `\n**使用示例**:\n`;
|
|
327
|
-
file.snippets.forEach(snippet => {
|
|
328
|
-
prompt += `\`\`\`javascript (第 ${snippet.lineNumber} 行附近)\n`;
|
|
329
|
-
prompt += snippet.snippet;
|
|
330
|
-
prompt += `\n\`\`\`\n`;
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
prompt += `\n`;
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
prompt += `---\n\n`;
|
|
337
|
-
|
|
338
|
-
prompt += `**🔍 跨文件影响分析提醒:**\n`;
|
|
339
|
-
prompt += `上述"受影响的文件"是通过文本搜索找到的,可能存在以下情况:\n`;
|
|
340
|
-
prompt += `1. **局部定义**:受影响的文件中可能有同名的局部变量、函数参数、类方法等\n`;
|
|
341
|
-
prompt += `2. **重新导入**:受影响的文件可能从其他模块导入了同名符号\n`;
|
|
342
|
-
prompt += `3. **命名空间**:可能在不同的命名空间或作用域中\n`;
|
|
343
|
-
prompt += `\n**请仔细检查代码片段,只有确认是引用被删除的符号时,才报告为影响!**\n\n`;
|
|
162
|
+
prompt += '... (省略剩余 ' + (totalLines - maxLines) + ' 行)\n';
|
|
163
|
+
prompt += '```\n\n';
|
|
344
164
|
}
|
|
345
165
|
}
|
|
346
166
|
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
prompt += `### 变更 ${index + 1} - 第 ${change.lineNumber} 行\n`;
|
|
351
|
-
prompt += `**类型**: ${change.type === 'addition' ? '新增' : '删除'}\n`;
|
|
352
|
-
prompt += `**内容**:\n\`\`\`\n`;
|
|
353
|
-
|
|
354
|
-
// 收集所有其他变更的内容(用于过滤)
|
|
355
|
-
const otherChangesContent = meaningfulChanges
|
|
356
|
-
.filter((_, i) => i !== index)
|
|
357
|
-
.map(c => c.content.trim());
|
|
167
|
+
// 🎯 添加调用链影响信息(如果有)
|
|
168
|
+
if (impactInfo && impactInfo.references && impactInfo.references.length > 0) {
|
|
169
|
+
prompt += `## 🔗 调用链影响分析\n\n`;
|
|
358
170
|
|
|
359
|
-
//
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
171
|
+
// 显示导出变更
|
|
172
|
+
const { changedExports } = impactInfo;
|
|
173
|
+
if (changedExports) {
|
|
174
|
+
if (changedExports.removed?.length > 0) {
|
|
175
|
+
prompt += `**⚠️ 删除的导出:** \`${changedExports.removed.join('`, `')}\`\n`;
|
|
176
|
+
}
|
|
177
|
+
if (changedExports.modified?.length > 0) {
|
|
178
|
+
const modifiedList = changedExports.modified.map(m =>
|
|
179
|
+
`${m.name}(${m.changeType === 'signature' ? '签名变更' : '内容变更'})`
|
|
180
|
+
).join(', ');
|
|
181
|
+
prompt += `**✏️ 修改的导出:** ${modifiedList}\n`;
|
|
182
|
+
}
|
|
368
183
|
}
|
|
369
184
|
|
|
370
|
-
|
|
371
|
-
prompt += `${change.type === 'addition' ? '+' : '-'} ${change.content}\n`;
|
|
185
|
+
prompt += `\n以下文件使用了这些导出,请检查本次修改是否会导致它们出现问题:\n\n`;
|
|
372
186
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
prompt += ` ${line}\n`;
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
}
|
|
187
|
+
impactInfo.references.forEach((ref, index) => {
|
|
188
|
+
prompt += `### ${index + 1}. ${ref.file}\n`;
|
|
189
|
+
prompt += `**使用的符号:** \`${ref.symbols.join('`, `')}\`\n\n`;
|
|
190
|
+
prompt += `**使用代码片段:**\n`;
|
|
191
|
+
prompt += `\`\`\`\n${ref.usageContext}\n\`\`\`\n\n`;
|
|
192
|
+
});
|
|
383
193
|
|
|
384
|
-
prompt +=
|
|
194
|
+
prompt += `---\n\n`;
|
|
195
|
+
prompt += `⚠️ **请特别关注以下风险:**\n`;
|
|
196
|
+
prompt += `- 函数签名变化(参数增减、类型变更)可能导致调用方参数不匹配\n`;
|
|
197
|
+
prompt += `- 删除的导出会导致依赖文件的 import 失败\n`;
|
|
198
|
+
prompt += `- 返回值类型或结构变化可能导致调用方逻辑错误\n`;
|
|
199
|
+
prompt += `- 行为变更可能导致依赖方出现非预期结果\n\n`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
prompt += `## 📝 代码变更详情\n`;
|
|
203
|
+
|
|
204
|
+
// 格式化每个变更块
|
|
205
|
+
meaningfulChanges.forEach((block, index) => {
|
|
206
|
+
prompt += formatChangeBlock(block, index);
|
|
385
207
|
});
|
|
386
208
|
|
|
387
|
-
prompt +=
|
|
209
|
+
prompt += `
|
|
210
|
+
## ⚠️ 重要说明
|
|
211
|
+
|
|
212
|
+
**变更类型说明:**
|
|
213
|
+
- **[修改]**:同时包含删除和新增,表示代码被修改
|
|
214
|
+
- **[删除]**:只有删除,表示代码被移除
|
|
215
|
+
- **[新增]**:只有新增,表示新添加的代码
|
|
216
|
+
|
|
217
|
+
**审查重点:**
|
|
218
|
+
- 对于 **[修改]** 类型,关注修改是否正确、是否有遗漏
|
|
219
|
+
- 对于 **[删除]** 类型,关注被删除的代码是否还有其他地方在使用
|
|
220
|
+
- 对于 **[新增]** 类型,关注新代码的质量和规范性
|
|
388
221
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
4. 优先检查"类型: 删除"的代码,确认是否有其他地方仍在使用
|
|
397
|
-
5. **只对有问题的行提出审查意见**,没有问题的行不需要评论
|
|
398
|
-
6. **必须使用中文**返回审查意见
|
|
399
|
-
7. **必须返回标准 JSON 格式**,不要用其他格式,结构如下:
|
|
222
|
+
## 审查要求
|
|
223
|
+
|
|
224
|
+
1. 仔细审查每个变更块,判断是否存在问题
|
|
225
|
+
2. **优先检查删除的代码**,确认是否有其他地方仍在使用
|
|
226
|
+
3. **只对有问题的变更块提出审查意见**,没有问题的不需要评论
|
|
227
|
+
4. **必须使用中文**返回审查意见
|
|
228
|
+
5. **必须返回标准 JSON 格式**,结构如下:
|
|
400
229
|
|
|
401
230
|
\`\`\`json
|
|
402
231
|
{
|
|
403
232
|
"reviews": [
|
|
404
233
|
{
|
|
405
234
|
"lineNumber": 15,
|
|
235
|
+
"isOldLine": false,
|
|
406
236
|
"hasIssue": true,
|
|
407
|
-
"comment": "
|
|
237
|
+
"comment": "审查意见内容"
|
|
408
238
|
}
|
|
409
239
|
]
|
|
410
240
|
}
|
|
411
241
|
\`\`\`
|
|
412
242
|
|
|
413
|
-
**🚨 JSON
|
|
414
|
-
-
|
|
415
|
-
-
|
|
416
|
-
-
|
|
417
|
-
-
|
|
418
|
-
-
|
|
419
|
-
- 如果发现其他文件受影响,在当前变更行的 comment 字符串中说明
|
|
243
|
+
**🚨 JSON 格式规则:**
|
|
244
|
+
- 整个响应必须是合法的 JSON 对象
|
|
245
|
+
- **lineNumber**: 必须使用每个变更块标注的"评论行号"(这是代码块的最后一行,便于显示完整上下文)
|
|
246
|
+
- **isOldLine**: 如果标注了"(旧文件, isOldLine: true)",设为 true;否则设为 false
|
|
247
|
+
- hasIssue 必须是布尔值(true/false)
|
|
248
|
+
- comment 必须是字符串,可以包含换行符 \\n
|
|
420
249
|
|
|
421
250
|
**comment 字段的内容:**
|
|
422
251
|
- **必须严格遵循"项目特定的审查规则"(reviewguard.md)中定义的格式**
|
|
423
|
-
-
|
|
424
|
-
- comment 是一个字符串,可以包含换行符 \\n 来格式化内容
|
|
252
|
+
- 如果规则中定义了包含 emoji、评分等格式,**必须完整使用该格式**
|
|
425
253
|
- 不要自创格式,必须参考上面"项目特定的审查规则"中的示例
|
|
426
254
|
|
|
427
|
-
|
|
428
|
-
7.
|
|
429
|
-
8. **只返回 JSON**,不要在 JSON 外面添加任何文字说明`;
|
|
255
|
+
6. 如果所有代码都没有问题,返回空的 reviews 数组:\`{"reviews": []}\`
|
|
256
|
+
7. 只返回 JSON,不要包含其他文字说明`;
|
|
430
257
|
|
|
431
258
|
return prompt;
|
|
432
259
|
}
|
|
433
260
|
|
|
434
261
|
/**
|
|
435
|
-
*
|
|
262
|
+
* 构建整个文件批量审查的完整消息数组
|
|
436
263
|
* @param {string} fileName - 文件名
|
|
437
264
|
* @param {Array} meaningfulChanges - 有意义的变更数组
|
|
438
|
-
* @param {Object} impactAnalysis - 影响分析结果
|
|
439
265
|
* @param {string} projectPrompt - 项目配置的 prompt
|
|
266
|
+
* @param {string} fullFileContent - 完整文件内容(可选)
|
|
267
|
+
* @param {Object} impactInfo - 跨文件影响信息(可选)
|
|
268
|
+
* @param {Object} feedbackSamples - 历史反馈样本(可选)
|
|
440
269
|
* @returns {Array} 消息数组
|
|
441
270
|
*/
|
|
442
|
-
export function
|
|
271
|
+
export function buildFileReviewMessages(fileName, meaningfulChanges, projectPrompt = '', fullFileContent = null, impactInfo = null, feedbackSamples = null, documentsContext = '') {
|
|
443
272
|
return [
|
|
444
|
-
{ role: 'system', content: buildSystemPrompt(projectPrompt) },
|
|
445
|
-
{ role: 'user', content:
|
|
273
|
+
{ role: 'system', content: buildSystemPrompt(projectPrompt, feedbackSamples, documentsContext) },
|
|
274
|
+
{ role: 'user', content: buildFileReviewPrompt(fileName, meaningfulChanges, fullFileContent, impactInfo) },
|
|
446
275
|
];
|
|
447
276
|
}
|
|
448
277
|
|
|
449
278
|
/**
|
|
450
279
|
* 构建生成审核提示词的消息(当找不到 reviewguard.md 时使用)
|
|
451
|
-
* @param {Object} projectAnalysis - 项目分析结果
|
|
452
|
-
* @returns {Array} 消息数组
|
|
453
280
|
*/
|
|
454
281
|
export function buildGenerateGuardPromptMessages(projectAnalysis) {
|
|
455
282
|
const { techStack, keyFiles, snippets } = projectAnalysis;
|
|
@@ -481,110 +308,72 @@ ${snippet.content}
|
|
|
481
308
|
|
|
482
309
|
---
|
|
483
310
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
请根据以上项目信息,生成一份完整的 **reviewguard.md** 文档内容。文档应包含:
|
|
487
|
-
|
|
488
|
-
### 1. 项目概述
|
|
489
|
-
- 简要描述项目的技术栈和架构
|
|
490
|
-
- 列出主要使用的框架和工具
|
|
491
|
-
|
|
492
|
-
### 2. 开发原则
|
|
493
|
-
- 根据项目技术栈,列出该项目应遵循的开发原则
|
|
494
|
-
- 例如:React 项目应包含组件设计原则、Hooks 使用规范等
|
|
495
|
-
- 例如:使用 TypeScript 的项目应强调类型安全
|
|
496
|
-
|
|
497
|
-
### 3. AI 代码审查规则
|
|
498
|
-
必须包含以下审查类别(按严重程度分类):
|
|
311
|
+
请生成一份完整的 reviewguard.md 文档,包含审查规则和回复格式规范。`;
|
|
499
312
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
- 不必要的重渲染(React)
|
|
512
|
-
- 内存泄漏
|
|
513
|
-
- 大列表未优化
|
|
514
|
-
|
|
515
|
-
#### 类型安全(严重程度 4-7 分,TypeScript 项目必须包含)
|
|
516
|
-
- 滥用 any 类型
|
|
517
|
-
- 缺少类型定义
|
|
518
|
-
- 不安全的类型断言
|
|
519
|
-
|
|
520
|
-
#### 代码规范(严重程度 1-4 分)
|
|
521
|
-
- 命名不规范
|
|
522
|
-
- 注释缺失
|
|
523
|
-
- 函数过长
|
|
524
|
-
|
|
525
|
-
### 4. 回复格式规范(非常重要!)
|
|
526
|
-
|
|
527
|
-
必须包含统一的回复格式,要求 AI 按照以下结构返回审查意见:
|
|
528
|
-
|
|
529
|
-
\`\`\`
|
|
530
|
-
【严重程度评分:X/10】
|
|
313
|
+
return [
|
|
314
|
+
{
|
|
315
|
+
role: 'system',
|
|
316
|
+
content: `你是一个专业的代码审查规范设计专家。生成的文档必须使用中文。`
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
role: 'user',
|
|
320
|
+
content: userMessage
|
|
321
|
+
}
|
|
322
|
+
];
|
|
323
|
+
}
|
|
531
324
|
|
|
532
|
-
|
|
533
|
-
|
|
325
|
+
/**
|
|
326
|
+
* 构建 MR 总结的提示词
|
|
327
|
+
*/
|
|
328
|
+
export function buildMRSummaryPrompt(stats, commentsByFile) {
|
|
329
|
+
let prompt = `请为本次 Merge Request 生成一个专业的代码审查总结。
|
|
534
330
|
|
|
535
|
-
|
|
536
|
-
• 当前文件:[具体影响]
|
|
537
|
-
• 受影响的其他文件:[文件名和行号]
|
|
538
|
-
• 可能后果:[具体说明]
|
|
331
|
+
## 📊 审查统计
|
|
539
332
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
\`\`\`
|
|
333
|
+
- **审查文件数**: ${stats.totalFiles} 个
|
|
334
|
+
- **成功评论数**: ${stats.successComments} 条
|
|
335
|
+
- **失败评论数**: ${stats.failedComments} 条
|
|
544
336
|
|
|
545
|
-
### 5. 严重程度评分标准
|
|
546
337
|
|
|
547
|
-
|
|
548
|
-
- 1-3 分:低风险(代码风格、命名规范)
|
|
549
|
-
- 4-6 分:中等风险(违反最佳实践、性能问题)
|
|
550
|
-
- 7-8 分:高风险(不兼容的变更、安全漏洞)
|
|
551
|
-
- 9-10 分:严重风险(破坏性变更、系统崩溃)
|
|
338
|
+
## 📁 审查的文件列表
|
|
552
339
|
|
|
553
|
-
|
|
340
|
+
${stats.filesList.map((file, index) => `${index + 1}. ${file}`).join('\n')}
|
|
554
341
|
|
|
555
|
-
|
|
342
|
+
## 🔍 发现的问题汇总
|
|
556
343
|
|
|
557
|
-
|
|
344
|
+
`;
|
|
558
345
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
346
|
+
const filesWithIssues = Object.keys(commentsByFile);
|
|
347
|
+
|
|
348
|
+
if (filesWithIssues.length === 0) {
|
|
349
|
+
prompt += `✅ **代码质量良好,未发现需要改进的问题。**\n\n`;
|
|
350
|
+
} else {
|
|
351
|
+
prompt += `共在 ${filesWithIssues.length} 个文件中发现 ${stats.successComments} 个问题:\n\n`;
|
|
352
|
+
|
|
353
|
+
filesWithIssues.forEach((fileName, index) => {
|
|
354
|
+
const comments = commentsByFile[fileName];
|
|
355
|
+
prompt += `### ${index + 1}. ${fileName}\n\n`;
|
|
356
|
+
prompt += `发现 ${comments.length} 个问题\n\n`;
|
|
357
|
+
});
|
|
358
|
+
}
|
|
566
359
|
|
|
567
|
-
|
|
360
|
+
prompt += `---
|
|
568
361
|
|
|
569
|
-
|
|
570
|
-
{
|
|
571
|
-
role: 'system',
|
|
572
|
-
content: `你是一个专业的代码审查规范设计专家。你的任务是根据项目的技术栈和代码结构,生成一份详细的 AI 代码审查规则文档(reviewguard.md)。
|
|
362
|
+
请生成一个简洁、专业的 MR 总结评论。使用中文,内容控制在 500 字以内。`;
|
|
573
363
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
2. 包含清晰的严重程度评分标准(0-10分)
|
|
577
|
-
3. 定义统一的回复格式(必须包含 emoji、评分、问题描述、影响分析、改进建议)
|
|
578
|
-
4. 提供实际的审查示例
|
|
579
|
-
5. 全部使用中文
|
|
364
|
+
return prompt;
|
|
365
|
+
}
|
|
580
366
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
367
|
+
/**
|
|
368
|
+
* 格式化反馈样本信息(用于日志输出)
|
|
369
|
+
* @param {Object} samples - 反馈样本
|
|
370
|
+
* @returns {string} 格式化的信息
|
|
371
|
+
*/
|
|
372
|
+
export function formatFeedbackSamplesInfo(samples) {
|
|
373
|
+
if (!samples || (!samples.positive?.length && !samples.negative?.length)) {
|
|
374
|
+
return '无历史反馈样本';
|
|
375
|
+
}
|
|
376
|
+
return `正样本: ${samples.positive?.length || 0}, 负样本: ${samples.negative?.length || 0}`;
|
|
588
377
|
}
|
|
589
378
|
|
|
590
379
|
// 导出函数
|
|
@@ -592,8 +381,7 @@ export default {
|
|
|
592
381
|
buildSystemPrompt,
|
|
593
382
|
buildFileReviewPrompt,
|
|
594
383
|
buildFileReviewMessages,
|
|
595
|
-
buildFileReviewWithImpactPrompt,
|
|
596
|
-
buildFileReviewWithImpactMessages,
|
|
597
384
|
buildGenerateGuardPromptMessages,
|
|
385
|
+
buildMRSummaryPrompt,
|
|
386
|
+
formatFeedbackSamplesInfo,
|
|
598
387
|
};
|
|
599
|
-
|