@raolin2025/claude-code-node 2.1.0 → 2.2.1
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 +117 -0
- package/package.json +1 -1
- package/src/__tests__/git-tool.integration.test.js +89 -0
- package/src/__tests__/git-tool.test.js +144 -0
- package/src/core/query-engine.js +21 -15
- package/src/core/streaming.js +11 -1
- package/src/git/github-api.js +360 -0
- package/src/git/index.js +37 -0
- package/src/git/llm-assistant.js +83 -0
- package/src/git/pr-merge-policy.js +367 -0
- package/src/git/pr-reviewer.js +533 -0
- package/src/git/utils/diff-parser.js +330 -0
- package/src/tools/git-tool.js +308 -0
- package/src/tools/index.js +2 -0
- package/src/types/index.js +3 -1
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub REST API 封装(零外部依赖)
|
|
3
|
+
* 使用原生 fetch (Node.js ≥ 18)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class GitHubAPI {
|
|
7
|
+
/**
|
|
8
|
+
* @param {Object} config
|
|
9
|
+
* @param {string} config.token - GitHub Personal Access Token
|
|
10
|
+
* @param {string} config.owner - Repository owner
|
|
11
|
+
* @param {string} config.repo - Repository name
|
|
12
|
+
* @param {string} [config.baseUrl] - API base URL (for enterprise)
|
|
13
|
+
*/
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.token = config.token || process.env.GITHUB_TOKEN
|
|
16
|
+
this.owner = config.owner
|
|
17
|
+
this.repo = config.repo
|
|
18
|
+
this.baseUrl = config.baseUrl || 'https://api.github.com'
|
|
19
|
+
this.apiVersion = '2022-11-28' // GitHub API version
|
|
20
|
+
|
|
21
|
+
if (!this.token) {
|
|
22
|
+
throw new Error('GitHub token required (set GITHUB_TOKEN or pass token param)')
|
|
23
|
+
}
|
|
24
|
+
if (!this.owner || !this.repo) {
|
|
25
|
+
throw new Error('GitHub repository owner and repo required')
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 发送请求到 GitHub API
|
|
31
|
+
*/
|
|
32
|
+
async request(endpoint, options = {}) {
|
|
33
|
+
const url = `${this.baseUrl}${endpoint}`
|
|
34
|
+
const headers = {
|
|
35
|
+
'Authorization': `Bearer ${this.token}`,
|
|
36
|
+
'Accept': 'application/vnd.github+json',
|
|
37
|
+
'X-GitHub-Api-Version': this.apiVersion,
|
|
38
|
+
...options.headers
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 速率限制检查
|
|
42
|
+
const shouldRetry = this._shouldRetryAfter(options)
|
|
43
|
+
if (shouldRetry.retry) {
|
|
44
|
+
await this._delay(shouldRetry.after)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const response = await fetch(url, {
|
|
48
|
+
...options,
|
|
49
|
+
headers
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// 更新速率限制信息
|
|
53
|
+
this._updateRateLimitInfo(response)
|
|
54
|
+
|
|
55
|
+
// 处理错误
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const error = await this._parseError(response)
|
|
58
|
+
if (response.status === 403 && this._isRateLimited(response)) {
|
|
59
|
+
throw new GitHubRateLimitError(error.message, response)
|
|
60
|
+
}
|
|
61
|
+
throw new GitHubError(error.message, response.status, error.documentation_url)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 204 No Content
|
|
65
|
+
if (response.status === 204) {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return await response.json()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* GET 请求
|
|
74
|
+
*/
|
|
75
|
+
async get(endpoint, params = {}) {
|
|
76
|
+
const fullUrl = `${this.baseUrl}${endpoint}`
|
|
77
|
+
const queryParts = []
|
|
78
|
+
Object.keys(params).forEach(key => {
|
|
79
|
+
if (params[key] !== undefined && params[key] !== null) {
|
|
80
|
+
queryParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(params[key]))}`)
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
const queryString = queryParts.length > 0 ? `?${queryParts.join('&')}` : ''
|
|
84
|
+
|
|
85
|
+
return this.request(`${endpoint}${queryString}`, { method: 'GET' })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* POST 请求
|
|
90
|
+
*/
|
|
91
|
+
async post(endpoint, body) {
|
|
92
|
+
return this.request(endpoint, {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
body: JSON.stringify(body),
|
|
95
|
+
headers: { 'Content-Type': 'application/json' }
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* PUT 请求
|
|
101
|
+
*/
|
|
102
|
+
async put(endpoint, body) {
|
|
103
|
+
return this.request(endpoint, {
|
|
104
|
+
method: 'PUT',
|
|
105
|
+
body: JSON.stringify(body),
|
|
106
|
+
headers: { 'Content-Type': 'application/json' }
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* PATCH 请求
|
|
112
|
+
*/
|
|
113
|
+
async patch(endpoint, body) {
|
|
114
|
+
return this.request(endpoint, {
|
|
115
|
+
method: 'PATCH',
|
|
116
|
+
body: JSON.stringify(body),
|
|
117
|
+
headers: { 'Content-Type': 'application/json' }
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* DELETE 请求
|
|
123
|
+
*/
|
|
124
|
+
async delete(endpoint) {
|
|
125
|
+
return this.request(endpoint, { method: 'DELETE' })
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ============ Pull Requests API ============
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 列出 PRs
|
|
132
|
+
* GET /repos/{owner}/{repo}/pulls
|
|
133
|
+
*/
|
|
134
|
+
async listPRs(params = {}) {
|
|
135
|
+
// 默认只获取 open 状态的 PR
|
|
136
|
+
const defaultParams = { state: 'open', per_page: 100 }
|
|
137
|
+
const query = { ...defaultParams, ...params }
|
|
138
|
+
return this.get(`/repos/${this.owner}/${this.repo}/pulls`, query)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 获取单个 PR
|
|
143
|
+
* GET /repos/{owner}/{repo}/pulls/{pr_number}
|
|
144
|
+
*/
|
|
145
|
+
async getPR(prNumber) {
|
|
146
|
+
return this.get(`/repos/${this.owner}/${this.repo}/pulls/${prNumber}`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 获取 PR 的 diff
|
|
151
|
+
* GET /repos/{owner}/{repo}/pulls/{pr_number}/files
|
|
152
|
+
*/
|
|
153
|
+
async getPRFiles(prNumber) {
|
|
154
|
+
return this.get(`/repos/${this.owner}/${this.repo}/pulls/${prNumber}/files`)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 获取 PR 提交的 diff (未压缩)
|
|
159
|
+
*/
|
|
160
|
+
async getPRDiff(prNumber) {
|
|
161
|
+
const endpoint = `/repos/${this.owner}/${this.repo}/pulls/${prNumber}`
|
|
162
|
+
// 使用统一的 request 方法,但走 diff 类型的 Accept
|
|
163
|
+
// 单独 fetch diff,因为需要 text() 而非 json()
|
|
164
|
+
const diffUrl = `${this.baseUrl}/repos/${this.owner}/${this.repo}/pulls/${prNumber}.diff`
|
|
165
|
+
const diffResponse = await fetch(diffUrl, {
|
|
166
|
+
method: 'GET',
|
|
167
|
+
headers: {
|
|
168
|
+
'Authorization': `Bearer ${this.token}`,
|
|
169
|
+
'Accept': 'application/vnd.github.v3.diff'
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
if (!diffResponse.ok) {
|
|
174
|
+
const errMsg = `Failed to fetch PR diff: ${diffResponse.status} ${diffResponse.statusText}`
|
|
175
|
+
throw new GitHubError(errMsg, diffResponse.status)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return await diffResponse.text()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 创建 PR 评论
|
|
183
|
+
* POST /repos/{owner}/{repo}/pulls/{pr_number}/reviews
|
|
184
|
+
*/
|
|
185
|
+
async createReview(prNumber, body, event = 'COMMENT', comments = []) {
|
|
186
|
+
return this.post(`/repos/${this.owner}/${this.repo}/pulls/${prNumber}/reviews`, {
|
|
187
|
+
body,
|
|
188
|
+
event, // 'COMMENT' | 'APPROVE' | 'REQUEST_CHANGES'
|
|
189
|
+
comments // [{ path, position, body }]
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 在 PR diff 的特定行添加评论
|
|
195
|
+
* POST /repos/{owner}/{repo}/pulls/{pr_number}/comments
|
|
196
|
+
*/
|
|
197
|
+
async createComment(prNumber, body, { path, position, commitId }) {
|
|
198
|
+
return this.post(`/repos/${this.owner}/${this.repo}/pulls/${prNumber}/comments`, {
|
|
199
|
+
body,
|
|
200
|
+
path,
|
|
201
|
+
position,
|
|
202
|
+
commit_id: commitId
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 更新 PR 评论(回复)
|
|
208
|
+
* PATCH /repos/{owner}/{repo}/pulls/comments/{comment_id}
|
|
209
|
+
*/
|
|
210
|
+
async updateComment(commentId, body) {
|
|
211
|
+
return this.patch(`/repos/${this.owner}/${this.repo}/pulls/comments/${commentId}`, { body })
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 删除 PR 评论
|
|
216
|
+
* DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}
|
|
217
|
+
*/
|
|
218
|
+
async deleteComment(commentId) {
|
|
219
|
+
return this.delete(`/repos/${this.owner}/${this.repo}/pulls/comments/${commentId}`)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 获取 PR 的评论线程
|
|
224
|
+
* GET /repos/{owner}/{repo}/pulls/{pr_number}/comments
|
|
225
|
+
*/
|
|
226
|
+
async listPRComments(prNumber) {
|
|
227
|
+
return this.get(`/repos/${this.owner}/${this.repo}/pulls/${prNumber}/comments`)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Approve PR
|
|
232
|
+
*/
|
|
233
|
+
async approvePR(prNumber, body) {
|
|
234
|
+
return this.createReview(prNumber, body || 'Approved', 'APPROVE')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Request changes on PR
|
|
239
|
+
*/
|
|
240
|
+
async requestChanges(prNumber, body) {
|
|
241
|
+
return this.createReview(prNumber, body || 'Changes requested', 'REQUEST_CHANGES')
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 合并 PR
|
|
246
|
+
* POST /repos/{owner}/{repo}/pulls/{pr_number}/merge
|
|
247
|
+
*/
|
|
248
|
+
async mergePR(prNumber, options = {}) {
|
|
249
|
+
const { commitTitle, commitMessage, mergeMethod = 'merge' } = options
|
|
250
|
+
return this.post(`/repos/${this.owner}/${this.repo}/pulls/${prNumber}/merge`, {
|
|
251
|
+
commit_title: commitTitle,
|
|
252
|
+
commit_message: commitMessage,
|
|
253
|
+
merge_method: mergeMethod // 'merge' | 'squash' | 'rebase'
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 检查 PR 是否可合并
|
|
259
|
+
* GET /repos/{owner}/{repo}/pulls/{pr_number}/mergeable
|
|
260
|
+
*/
|
|
261
|
+
async isMergeable(prNumber) {
|
|
262
|
+
const pr = await this.getPR(prNumber)
|
|
263
|
+
// GitHub 返回 null 表示还在计算中,不视为不可合并
|
|
264
|
+
return pr.mergeable !== false
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 获取 PR 的状态检查(CI checks)
|
|
269
|
+
* GET /repos/{owner}/{repo}/commits/{commit_sha}/status
|
|
270
|
+
*/
|
|
271
|
+
/**
|
|
272
|
+
* 获取 PR 的合并状态(combine status)
|
|
273
|
+
* GET /repos/{owner}/{repo}/commits/{commit_sha}/status
|
|
274
|
+
*/
|
|
275
|
+
async getCombinedStatus(commitSha) {
|
|
276
|
+
if (!commitSha) {
|
|
277
|
+
throw new Error('commitSha is required for getCombinedStatus')
|
|
278
|
+
}
|
|
279
|
+
return this.get(`/repos/${this.owner}/${this.repo}/commits/${commitSha}/status`)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 列出 PR 的审查评论(reviews)
|
|
284
|
+
* GET /repos/{owner}/{repo}/pulls/{pr_number}/reviews
|
|
285
|
+
*/
|
|
286
|
+
async listReviews(prNumber) {
|
|
287
|
+
return this.get(`/repos/${this.owner}/${this.repo}/pulls/${prNumber}/reviews`)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ============ 辅助方法 ============
|
|
291
|
+
|
|
292
|
+
_updateRateLimitInfo(response) {
|
|
293
|
+
this.rateLimitRemaining = parseInt(response.headers.get('X-RateLimit-Remaining'), 10)
|
|
294
|
+
this.rateLimitReset = parseInt(response.headers.get('X-RateLimit-Reset'), 10) // Unix timestamp
|
|
295
|
+
this.rateLimitLimit = parseInt(response.headers.get('X-RateLimit-Limit'), 10)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
_isRateLimited(response) {
|
|
299
|
+
return response.status === 403 && response.headers.get('X-RateLimit-Remaining') === '0'
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
_shouldRetryAfter(options) {
|
|
303
|
+
if (options._retryCount && options._retryCount > 3) {
|
|
304
|
+
return { retry: false }
|
|
305
|
+
}
|
|
306
|
+
if (this.rateLimitRemaining === 0 && this.rateLimitReset) {
|
|
307
|
+
const now = Math.floor(Date.now() / 1000)
|
|
308
|
+
const waitSeconds = Math.max(this.rateLimitReset - now + 1, 1)
|
|
309
|
+
return { retry: true, after: waitSeconds * 1000 }
|
|
310
|
+
}
|
|
311
|
+
return { retry: false }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
_delay(ms) {
|
|
315
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async _parseError(response) {
|
|
319
|
+
let message = `${response.status} ${response.statusText}`
|
|
320
|
+
let documentationUrl = null
|
|
321
|
+
try {
|
|
322
|
+
const data = await response.json()
|
|
323
|
+
message = data.message || message
|
|
324
|
+
documentationUrl = data.documentation_url || null
|
|
325
|
+
} catch {
|
|
326
|
+
// ignore parse error, keep fallback message
|
|
327
|
+
}
|
|
328
|
+
return { message, documentation_url: documentationUrl }
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* GitHub API 错误
|
|
334
|
+
*/
|
|
335
|
+
export class GitHubError extends Error {
|
|
336
|
+
constructor(message, status, documentationUrl = null) {
|
|
337
|
+
super(message)
|
|
338
|
+
this.name = 'GitHubError'
|
|
339
|
+
this.status = status
|
|
340
|
+
this.documentationUrl = documentationUrl
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* 速率限制错误
|
|
346
|
+
*/
|
|
347
|
+
export class GitHubRateLimitError extends GitHubError {
|
|
348
|
+
constructor(message, response) {
|
|
349
|
+
super(message, response.status)
|
|
350
|
+
this.name = 'GitHubRateLimitError'
|
|
351
|
+
this.resetAt = parseInt(response.headers.get('X-RateLimit-Reset'), 10)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* 创建 GitHub API 实例的工厂函数
|
|
357
|
+
*/
|
|
358
|
+
export function createGitHubAPI(options) {
|
|
359
|
+
return new GitHubAPI(options)
|
|
360
|
+
}
|
package/src/git/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git 模块统一导出
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { GitHubAPI, createGitHubAPI, GitHubError, GitHubRateLimitError } from './github-api.js'
|
|
6
|
+
export { PRReviewer } from './pr-reviewer.js'
|
|
7
|
+
export { PRMergePolicy, DEFAULT_MERGE_POLICY, findEligiblePRs, isPRMergeable } from './pr-merge-policy.js'
|
|
8
|
+
|
|
9
|
+
// Diff 解析工具
|
|
10
|
+
export {
|
|
11
|
+
parseDiff,
|
|
12
|
+
splitDiffByFile,
|
|
13
|
+
getPositionInDiff,
|
|
14
|
+
buildCommentPayload,
|
|
15
|
+
getHunksForFileRaw,
|
|
16
|
+
fileExistsInDiff,
|
|
17
|
+
countFileChanges,
|
|
18
|
+
mapLineToPosition,
|
|
19
|
+
findAddedLines,
|
|
20
|
+
debugPrintHunks
|
|
21
|
+
} from './utils/diff-parser.js'
|
|
22
|
+
|
|
23
|
+
// LLM 助手
|
|
24
|
+
export { callLLM, buildPRReviewPrompt } from './llm-assistant.js'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 快速创建 GitTool
|
|
28
|
+
*/
|
|
29
|
+
import { gitTool } from '../tools/git-tool.js'
|
|
30
|
+
|
|
31
|
+
export function createGitTool(config = {}) {
|
|
32
|
+
// 这里返回的是一个配置对象,实际 ToolDef 已注册
|
|
33
|
+
return {
|
|
34
|
+
config,
|
|
35
|
+
tool: gitTool
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM 助手 - 调用 LLM API 进行智能分析
|
|
3
|
+
* 复用 cc-node 的 LLM 调用模式
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 调用 LLM 分析 PR
|
|
8
|
+
* @param {string} systemPrompt
|
|
9
|
+
* @param {string} userPrompt
|
|
10
|
+
* @param {Object} apiConfig - { apiKey, apiBase, model }
|
|
11
|
+
*/
|
|
12
|
+
export async function callLLM(systemPrompt, userPrompt, apiConfig = {}) {
|
|
13
|
+
const {
|
|
14
|
+
apiKey = process.env.LLM_API_KEY || process.env.DEEPSEEK_API_KEY,
|
|
15
|
+
apiBase = process.env.LLM_API_BASE || 'https://api.deepseek.com/v1',
|
|
16
|
+
model = process.env.LLM_MODEL || 'deepseek-chat'
|
|
17
|
+
} = apiConfig
|
|
18
|
+
|
|
19
|
+
if (!apiKey) {
|
|
20
|
+
throw new Error('LLM API key required (set LLM_API_KEY or DEEPSEEK_API_KEY)')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const response = await fetch(`${apiBase}/chat/completions`, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
'Authorization': `Bearer ${apiKey}`
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify({
|
|
30
|
+
model,
|
|
31
|
+
messages: [
|
|
32
|
+
{ role: 'system', content: systemPrompt },
|
|
33
|
+
{ role: 'user', content: userPrompt }
|
|
34
|
+
],
|
|
35
|
+
temperature: 0.3,
|
|
36
|
+
max_tokens: 2000
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
const error = await response.json().catch(() => ({}))
|
|
42
|
+
throw new Error(`LLM API error: ${response.status} ${error.message || response.statusText}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const data = await response.json()
|
|
46
|
+
return data.choices[0].message.content
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* PR 智能审查 prompt
|
|
51
|
+
* @param {Object} pr - PR 信息
|
|
52
|
+
* @param {string} diff - diff 内容
|
|
53
|
+
* @param {Array} findings - 已发现的规则检查问题
|
|
54
|
+
*/
|
|
55
|
+
export function buildPRReviewPrompt(pr, diff, findings) {
|
|
56
|
+
const findingsText = findings.map(f => `- ${f.file}:${f.line} [${f.severity}] ${f.message}`).join('\n')
|
|
57
|
+
|
|
58
|
+
return `
|
|
59
|
+
You are an experienced code reviewer. Analyze the following PR and provide a concise review summary.
|
|
60
|
+
|
|
61
|
+
## PR Information
|
|
62
|
+
- Title: ${pr.title}
|
|
63
|
+
- Author: ${pr.user?.login}
|
|
64
|
+
- Files changed: ${pr.changed_files}
|
|
65
|
+
- Additions: ${pr.additions}, Deletions: ${pr.deletions}
|
|
66
|
+
|
|
67
|
+
## Automated Findings
|
|
68
|
+
${findingsText || 'No issues found by automated checks.'}
|
|
69
|
+
|
|
70
|
+
## Diff
|
|
71
|
+
${diff.substring(0, 5000)}... (truncated)
|
|
72
|
+
|
|
73
|
+
## Instructions
|
|
74
|
+
Provide a review summary with:
|
|
75
|
+
1. Overall assessment (POSITIVE / NEEDS_WORK / REJECT)
|
|
76
|
+
2. Key strengths (if any)
|
|
77
|
+
3. Critical issues that must be fixed
|
|
78
|
+
4. Suggestions for improvement
|
|
79
|
+
5. Merging recommendation (YES / NO)
|
|
80
|
+
|
|
81
|
+
Keep it concise (3-5 bullet points).
|
|
82
|
+
`
|
|
83
|
+
}
|