scai 0.1.75 → 0.1.77
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.
|
@@ -13,15 +13,20 @@ function truncate(str, length) {
|
|
|
13
13
|
return str.length > length ? str.slice(0, length - 3) + '...' : str;
|
|
14
14
|
}
|
|
15
15
|
// Fetch open PRs with review requested
|
|
16
|
+
import { Spinner } from '../lib/spinner.js'; // adjust path as needed
|
|
16
17
|
export async function getPullRequestsForReview(token, owner, repo, username, branch = 'main', filterForUser = true) {
|
|
17
|
-
const
|
|
18
|
+
const spinner = new Spinner('Fetching pull requests and diffs...');
|
|
19
|
+
spinner.start();
|
|
18
20
|
const filtered = [];
|
|
19
21
|
const failedPRs = [];
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
try {
|
|
23
|
+
const prs = await fetchOpenPullRequests(token, owner, repo);
|
|
24
|
+
for (const pr of prs) {
|
|
25
|
+
const shouldInclude = !pr.draft &&
|
|
26
|
+
!pr.merged_at &&
|
|
27
|
+
(!filterForUser || pr.requested_reviewers?.some(r => r.login === username));
|
|
28
|
+
if (!shouldInclude)
|
|
29
|
+
continue;
|
|
25
30
|
try {
|
|
26
31
|
const prNumber = pr.number;
|
|
27
32
|
const prRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}`, {
|
|
@@ -50,21 +55,25 @@ export async function getPullRequestsForReview(token, owner, repo, username, bra
|
|
|
50
55
|
failedPRs.push(pr);
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
if (filtered.length === 0) {
|
|
59
|
+
const msg = filterForUser
|
|
60
|
+
? `No open pull requests found for review by '${username}'.`
|
|
61
|
+
: `No open pull requests found.`;
|
|
62
|
+
spinner.succeed(msg);
|
|
58
63
|
}
|
|
59
64
|
else {
|
|
60
|
-
|
|
65
|
+
spinner.succeed(`Fetched ${filtered.length} PR(s) with diffs.`);
|
|
66
|
+
}
|
|
67
|
+
if (failedPRs.length > 0) {
|
|
68
|
+
const failedList = failedPRs.map(pr => `#${pr.number}`).join(', ');
|
|
69
|
+
console.warn(`⚠️ Skipped ${failedPRs.length} PR(s): ${failedList}`);
|
|
61
70
|
}
|
|
71
|
+
return filtered;
|
|
62
72
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
catch (err) {
|
|
74
|
+
spinner.fail(`Error fetching pull requests: ${err.message}`);
|
|
75
|
+
return [];
|
|
66
76
|
}
|
|
67
|
-
return filtered;
|
|
68
77
|
}
|
|
69
78
|
// Prompt user to select PR
|
|
70
79
|
function askUserToPickPR(prs) {
|
|
@@ -119,7 +128,7 @@ function askReviewMethod() {
|
|
|
119
128
|
});
|
|
120
129
|
}
|
|
121
130
|
// Prompt for review approval
|
|
122
|
-
function askReviewApproval(
|
|
131
|
+
function askReviewApproval() {
|
|
123
132
|
return new Promise((resolve) => {
|
|
124
133
|
console.log('\n---');
|
|
125
134
|
console.log('1) ✅ Approve');
|
|
@@ -143,6 +152,24 @@ function askReviewApproval(suggestion) {
|
|
|
143
152
|
});
|
|
144
153
|
});
|
|
145
154
|
}
|
|
155
|
+
function askFinalReviewApproval() {
|
|
156
|
+
return new Promise((resolve) => {
|
|
157
|
+
console.log('\n---');
|
|
158
|
+
console.log('1) ✅ Approve');
|
|
159
|
+
console.log('2) ❌ Request Changes');
|
|
160
|
+
console.log('3) 🚫 Cancel');
|
|
161
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
162
|
+
rl.question(`\n👉 Choose an option [1-3]: `, (answer) => {
|
|
163
|
+
rl.close();
|
|
164
|
+
if (answer === '1')
|
|
165
|
+
resolve('approve');
|
|
166
|
+
else if (answer === '2')
|
|
167
|
+
resolve('request-changes');
|
|
168
|
+
else
|
|
169
|
+
resolve('cancel');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
146
173
|
// Prompt for custom review
|
|
147
174
|
function promptCustomReview() {
|
|
148
175
|
return new Promise((resolve) => {
|
|
@@ -167,15 +194,63 @@ export async function promptEditReview(suggestedReview) {
|
|
|
167
194
|
.trim() || suggestedReview;
|
|
168
195
|
}
|
|
169
196
|
// Split diff into file-based chunks
|
|
170
|
-
function chunkDiff(diff) {
|
|
197
|
+
function chunkDiff(diff, review_id) {
|
|
171
198
|
const rawChunks = diff.split(/^diff --git /m).filter(Boolean);
|
|
172
199
|
return rawChunks.map(chunk => {
|
|
173
200
|
const fullChunk = 'diff --git ' + chunk;
|
|
174
201
|
const filePathMatch = fullChunk.match(/^diff --git a\/(.+?) b\//);
|
|
175
202
|
const filePath = filePathMatch ? filePathMatch[1] : 'unknown';
|
|
203
|
+
// Now we extract hunks and lines as per the DiffHunk type
|
|
204
|
+
const hunks = [];
|
|
205
|
+
let currentHunk = null;
|
|
206
|
+
// Split chunk into lines
|
|
207
|
+
const lines = fullChunk.split('\n');
|
|
208
|
+
lines.forEach(line => {
|
|
209
|
+
const hunkHeaderMatch = line.match(/^@@ -(\d+),(\d+) \+(\d+),(\d+) @@/);
|
|
210
|
+
if (hunkHeaderMatch) {
|
|
211
|
+
// When we encounter a new hunk, process the previous one (if it exists) and start a new one
|
|
212
|
+
if (currentHunk) {
|
|
213
|
+
hunks.push(currentHunk);
|
|
214
|
+
}
|
|
215
|
+
// Parse the hunk header
|
|
216
|
+
const oldStart = parseInt(hunkHeaderMatch[1], 10);
|
|
217
|
+
const newStart = parseInt(hunkHeaderMatch[3], 10);
|
|
218
|
+
const oldLines = parseInt(hunkHeaderMatch[2], 10);
|
|
219
|
+
const newLines = parseInt(hunkHeaderMatch[4], 10);
|
|
220
|
+
currentHunk = {
|
|
221
|
+
oldStart,
|
|
222
|
+
newStart,
|
|
223
|
+
oldLines,
|
|
224
|
+
newLines,
|
|
225
|
+
lines: [],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
else if (currentHunk) {
|
|
229
|
+
// Process the lines inside the hunk
|
|
230
|
+
let lineType = 'context';
|
|
231
|
+
if (line.startsWith('+'))
|
|
232
|
+
lineType = 'add';
|
|
233
|
+
if (line.startsWith('-'))
|
|
234
|
+
lineType = 'del';
|
|
235
|
+
// Create the DiffLine object
|
|
236
|
+
currentHunk.lines.push({
|
|
237
|
+
line,
|
|
238
|
+
type: lineType,
|
|
239
|
+
lineNumberOld: lineType === 'del' ? currentHunk.oldStart++ : undefined,
|
|
240
|
+
lineNumberNew: lineType === 'add' ? currentHunk.newStart++ : undefined,
|
|
241
|
+
review_id, // Assign the review_id here
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
// Push the last hunk (if any)
|
|
246
|
+
if (currentHunk) {
|
|
247
|
+
hunks.push(currentHunk);
|
|
248
|
+
}
|
|
176
249
|
return {
|
|
177
250
|
filePath,
|
|
178
251
|
content: fullChunk,
|
|
252
|
+
hunks, // Return hunks, which now contain DiffLine objects with line numbers and review_id
|
|
253
|
+
review_id, // Assign the review_id here for each chunk
|
|
179
254
|
};
|
|
180
255
|
});
|
|
181
256
|
}
|
|
@@ -191,18 +266,99 @@ function colorDiffLine(line) {
|
|
|
191
266
|
}
|
|
192
267
|
// Review a single chunk
|
|
193
268
|
export async function reviewChunk(chunk, chunkIndex, totalChunks) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
269
|
+
const lines = chunk.content.split('\n');
|
|
270
|
+
const coloredDiff = lines.map(colorDiffLine).join('\n');
|
|
271
|
+
console.log(chalk.gray('\n' + '━'.repeat(60)));
|
|
272
|
+
console.log(`📄 ${chalk.bold('File')}: ${chalk.cyan(chunk.filePath)}`);
|
|
273
|
+
console.log(`🔢 ${chalk.bold('Chunk')}: ${chunkIndex + 1} of ${totalChunks}`);
|
|
274
|
+
const suggestion = await reviewModule.run({
|
|
275
|
+
content: chunk.content,
|
|
276
|
+
filepath: chunk.filePath
|
|
277
|
+
});
|
|
278
|
+
const summary = suggestion.content?.trim() || 'AI review summary not available.';
|
|
279
|
+
console.log(`🔍 ${chalk.bold('Summary')}: ${summary}`);
|
|
280
|
+
console.log(`\n${chalk.bold('--- Diff ---')}\n`);
|
|
197
281
|
console.log(coloredDiff);
|
|
198
|
-
|
|
199
|
-
console.log(
|
|
200
|
-
console.log(
|
|
201
|
-
const
|
|
202
|
-
if (
|
|
203
|
-
|
|
282
|
+
console.log(`\n${chalk.bold('--- AI Review ---')}\n`);
|
|
283
|
+
console.log(chalk.blue(`💬 ${summary}`));
|
|
284
|
+
console.log(chalk.gray('━'.repeat(60)));
|
|
285
|
+
const choice = await promptChunkReviewMenu();
|
|
286
|
+
if (choice === 'edit') {
|
|
287
|
+
const edited = await promptEditReview(summary); // edit based on the suggestion
|
|
288
|
+
return { choice: edited, summary: edited };
|
|
204
289
|
}
|
|
205
|
-
|
|
290
|
+
else if (choice === 'skip') {
|
|
291
|
+
await waitForSpaceOrQ(); // pause between chunks
|
|
292
|
+
return { choice: 'cancel', summary }; // skip this one
|
|
293
|
+
}
|
|
294
|
+
return { choice, summary };
|
|
295
|
+
}
|
|
296
|
+
function waitForSpaceOrQ() {
|
|
297
|
+
return new Promise(resolve => {
|
|
298
|
+
process.stdin.setRawMode(true);
|
|
299
|
+
process.stdin.resume();
|
|
300
|
+
process.stdout.write('\n⏭️ (Press [space] to skip, [q] to quit, or any other key to show menu)\n');
|
|
301
|
+
function onKeyPress(chunk) {
|
|
302
|
+
const key = chunk.toString();
|
|
303
|
+
if (key === ' ' || key === 'q' || key === 'Q') {
|
|
304
|
+
process.stdin.setRawMode(false);
|
|
305
|
+
process.stdin.pause();
|
|
306
|
+
process.stdin.removeListener('data', onKeyPress);
|
|
307
|
+
resolve();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
process.stdin.on('data', onKeyPress);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
export async function promptChunkReviewMenu() {
|
|
314
|
+
return new Promise((resolve) => {
|
|
315
|
+
console.log('\nReview options for this chunk:');
|
|
316
|
+
console.log(' 1) 💬 Approve and post AI review as comment');
|
|
317
|
+
console.log(' 2) ✍️ Edit the review before posting');
|
|
318
|
+
console.log(' 3) ⌨️ Write a custom comment');
|
|
319
|
+
console.log(' 4) ❌ Mark this chunk as needing changes');
|
|
320
|
+
console.log(' 5) ⏭️ Skip this chunk without commenting');
|
|
321
|
+
console.log(chalk.gray(' (Press [space] to skip chunk, [q] to quit review, or any other key to show menu)\n'));
|
|
322
|
+
// Fallback to menu input if key was not space/q
|
|
323
|
+
function askWithReadline() {
|
|
324
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
325
|
+
rl.question('👉 Choose [1–5]: ', (answer) => {
|
|
326
|
+
rl.close();
|
|
327
|
+
switch (answer.trim()) {
|
|
328
|
+
case '1': return resolve('approve');
|
|
329
|
+
case '2': return resolve('edit');
|
|
330
|
+
case '3': return resolve('custom');
|
|
331
|
+
case '4': return resolve('reject');
|
|
332
|
+
case '5': return resolve('skip');
|
|
333
|
+
default:
|
|
334
|
+
console.log('⚠️ Invalid option. Skipping chunk.');
|
|
335
|
+
return resolve('skip');
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
// Raw key listener for quick actions
|
|
340
|
+
function onKeyPress(key) {
|
|
341
|
+
const keyStr = key.toString().toLowerCase();
|
|
342
|
+
process.stdin.setRawMode(false);
|
|
343
|
+
process.stdin.pause();
|
|
344
|
+
if (keyStr === ' ') {
|
|
345
|
+
return resolve('skip');
|
|
346
|
+
}
|
|
347
|
+
else if (keyStr === 'q') {
|
|
348
|
+
console.log('\n👋 Exiting review.');
|
|
349
|
+
process.exit(0);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
// flush any remaining input
|
|
353
|
+
process.stdin.removeAllListeners('data');
|
|
354
|
+
askWithReadline();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Prepare for keypress
|
|
358
|
+
process.stdin.setRawMode(true);
|
|
359
|
+
process.stdin.resume();
|
|
360
|
+
process.stdin.once('data', onKeyPress);
|
|
361
|
+
});
|
|
206
362
|
}
|
|
207
363
|
// Main command to review PR
|
|
208
364
|
export async function reviewPullRequestCmd(branch = 'main', showAll = false) {
|
|
@@ -220,19 +376,21 @@ export async function reviewPullRequestCmd(branch = 'main', showAll = false) {
|
|
|
220
376
|
if (pr.body) {
|
|
221
377
|
console.log(chalk.magentaBright('\n📝 PR Description:\n') + chalk.gray(pr.body));
|
|
222
378
|
}
|
|
223
|
-
const
|
|
379
|
+
const chunks = chunkDiff(diff, pr.number.toString());
|
|
380
|
+
const reviewMethod = chunks.length > 1 ? await askReviewMethod() : 'chunk';
|
|
381
|
+
let reviewComments = [];
|
|
224
382
|
if (reviewMethod === 'whole') {
|
|
225
383
|
const suggestion = await reviewModule.run({ content: diff, filepath: 'Whole PR Diff' });
|
|
226
|
-
console.log(suggestion
|
|
227
|
-
const finalReviewChoice = await askReviewApproval(
|
|
384
|
+
console.log(chalk.yellowBright("Suggestion: ", suggestion));
|
|
385
|
+
const finalReviewChoice = await askReviewApproval();
|
|
228
386
|
let reviewText = '';
|
|
229
387
|
if (finalReviewChoice === 'approve') {
|
|
230
388
|
reviewText = 'PR approved';
|
|
231
|
-
await submitReview(pr.number,
|
|
389
|
+
await submitReview(pr.number, suggestion.content, 'APPROVE');
|
|
232
390
|
}
|
|
233
391
|
else if (finalReviewChoice === 'reject') {
|
|
234
392
|
reviewText = 'Changes requested';
|
|
235
|
-
await submitReview(pr.number,
|
|
393
|
+
await submitReview(pr.number, suggestion.content, 'REQUEST_CHANGES');
|
|
236
394
|
}
|
|
237
395
|
else if (finalReviewChoice === 'custom') {
|
|
238
396
|
reviewText = await promptCustomReview();
|
|
@@ -244,24 +402,72 @@ export async function reviewPullRequestCmd(branch = 'main', showAll = false) {
|
|
|
244
402
|
}
|
|
245
403
|
}
|
|
246
404
|
else {
|
|
247
|
-
const chunks = chunkDiff(diff);
|
|
248
405
|
console.log(chalk.cyan(`🔍 Total Chunks: ${chunks.length}`));
|
|
406
|
+
let allApproved = true;
|
|
249
407
|
for (let i = 0; i < chunks.length; i++) {
|
|
250
408
|
const chunk = chunks[i];
|
|
251
|
-
const
|
|
252
|
-
if (
|
|
253
|
-
|
|
409
|
+
const { choice, summary } = await reviewChunk(chunk, i, chunks.length);
|
|
410
|
+
if (choice === 'approve') {
|
|
411
|
+
reviewComments.push({
|
|
412
|
+
path: chunk.filePath,
|
|
413
|
+
body: summary,
|
|
414
|
+
line: chunk.hunks[0]?.newStart || 1,
|
|
415
|
+
side: 'RIGHT',
|
|
416
|
+
});
|
|
417
|
+
console.log(`💬 Posted AI review for chunk ${i + 1}`);
|
|
254
418
|
}
|
|
255
|
-
else if (
|
|
256
|
-
|
|
419
|
+
else if (choice === 'reject') {
|
|
420
|
+
allApproved = false;
|
|
421
|
+
reviewComments.push({
|
|
422
|
+
path: chunk.filePath,
|
|
423
|
+
body: summary,
|
|
424
|
+
line: chunk.hunks[0]?.newStart || 1,
|
|
425
|
+
side: 'RIGHT',
|
|
426
|
+
});
|
|
257
427
|
}
|
|
258
|
-
else if (
|
|
428
|
+
else if (choice === 'custom') {
|
|
259
429
|
const customReview = await promptCustomReview();
|
|
260
|
-
|
|
430
|
+
reviewComments.push({
|
|
431
|
+
path: chunk.filePath,
|
|
432
|
+
body: customReview,
|
|
433
|
+
line: chunk.hunks[0]?.newStart || 1,
|
|
434
|
+
side: 'RIGHT',
|
|
435
|
+
});
|
|
261
436
|
}
|
|
262
|
-
else {
|
|
263
|
-
|
|
437
|
+
else if (choice === 'cancel' || choice === 'skip') {
|
|
438
|
+
console.log(chalk.gray(`⏭️ Skipped chunk ${i + 1}`));
|
|
264
439
|
}
|
|
440
|
+
else if (typeof choice === 'string') {
|
|
441
|
+
reviewComments.push({
|
|
442
|
+
path: chunk.filePath,
|
|
443
|
+
body: choice,
|
|
444
|
+
line: chunk.hunks[0]?.newStart || 1,
|
|
445
|
+
side: 'RIGHT',
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
console.log(chalk.blueBright('\n📝 Review Comments Preview:'));
|
|
450
|
+
reviewComments.forEach((comment, idx) => {
|
|
451
|
+
console.log(`${idx + 1}. ${comment.path}:${comment.line} [${comment.side}] — ${comment.body}`);
|
|
452
|
+
});
|
|
453
|
+
const shouldApprove = allApproved;
|
|
454
|
+
const hasInlineComments = reviewComments.length > 0;
|
|
455
|
+
// We always submit comments first if any
|
|
456
|
+
const initialReviewState = shouldApprove ? 'APPROVE' : 'REQUEST_CHANGES';
|
|
457
|
+
const initialReviewBody = shouldApprove
|
|
458
|
+
? 'Reviewed all chunks.'
|
|
459
|
+
: 'Requested changes based on chunk reviews.';
|
|
460
|
+
console.log(shouldApprove && !hasInlineComments
|
|
461
|
+
? chalk.green('✔️ All chunks approved. Submitting final PR approval.')
|
|
462
|
+
: !shouldApprove
|
|
463
|
+
? chalk.red('❌ Not all chunks were approved. Changes requested.')
|
|
464
|
+
: chalk.green('📝 Submitting inline comments before approval.') // ✅ NEW
|
|
465
|
+
);
|
|
466
|
+
// ✅ Submit review with inline comments or direct approval/request
|
|
467
|
+
await submitReview(pr.number, initialReviewBody, initialReviewState, reviewComments);
|
|
468
|
+
// ✅ Then submit separate approval if needed
|
|
469
|
+
if (shouldApprove && hasInlineComments) {
|
|
470
|
+
await submitReview(pr.number, 'PR approved after inline comments.', 'APPROVE');
|
|
265
471
|
}
|
|
266
472
|
}
|
|
267
473
|
}
|
package/dist/github/github.js
CHANGED
|
@@ -48,7 +48,7 @@ export async function fetchPullRequestDiff(pr, token) {
|
|
|
48
48
|
}
|
|
49
49
|
return await res.text();
|
|
50
50
|
}
|
|
51
|
-
export async function submitReview(prNumber, body, event
|
|
51
|
+
export async function submitReview(prNumber, body, event, comments) {
|
|
52
52
|
const token = await ensureGitHubAuth();
|
|
53
53
|
const { owner, repo } = getRepoDetails();
|
|
54
54
|
const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews`;
|
|
@@ -61,6 +61,7 @@ export async function submitReview(prNumber, body, event = 'COMMENT') {
|
|
|
61
61
|
body: JSON.stringify({
|
|
62
62
|
body,
|
|
63
63
|
event,
|
|
64
|
+
comments
|
|
64
65
|
}),
|
|
65
66
|
});
|
|
66
67
|
if (!res.ok) {
|
|
@@ -69,3 +70,52 @@ export async function submitReview(prNumber, body, event = 'COMMENT') {
|
|
|
69
70
|
}
|
|
70
71
|
console.log(`✅ Submitted ${event} review for PR #${prNumber}`);
|
|
71
72
|
}
|
|
73
|
+
export async function postInlineComment(prNumber, commitId, path, body, line, side = 'RIGHT', reviewId = null // Associate with a review if available
|
|
74
|
+
) {
|
|
75
|
+
const token = await ensureGitHubAuth();
|
|
76
|
+
const { owner, repo } = getRepoDetails();
|
|
77
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/comments`;
|
|
78
|
+
const res = await fetch(url, {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: {
|
|
81
|
+
Authorization: `token ${token}`,
|
|
82
|
+
Accept: 'application/vnd.github.v3+json',
|
|
83
|
+
},
|
|
84
|
+
body: JSON.stringify({
|
|
85
|
+
body,
|
|
86
|
+
commit_id: commitId,
|
|
87
|
+
path,
|
|
88
|
+
line,
|
|
89
|
+
side,
|
|
90
|
+
review_id: reviewId, // Include review_id if available
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
const errorText = await res.text();
|
|
95
|
+
throw new Error(`Failed to post inline comment: ${res.status} ${res.statusText} - ${errorText}`);
|
|
96
|
+
}
|
|
97
|
+
console.log(`💬 Posted inline comment on ${path}:${line}`);
|
|
98
|
+
}
|
|
99
|
+
export async function createReviewForPR(prNumber, body, event = 'COMMENT') {
|
|
100
|
+
const token = await ensureGitHubAuth();
|
|
101
|
+
const { owner, repo } = getRepoDetails();
|
|
102
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews`;
|
|
103
|
+
const res = await fetch(url, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: {
|
|
106
|
+
Authorization: `token ${token}`,
|
|
107
|
+
Accept: 'application/vnd.github.v3+json',
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
body,
|
|
111
|
+
event,
|
|
112
|
+
}),
|
|
113
|
+
});
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
const errorText = await res.text();
|
|
116
|
+
throw new Error(`Failed to create review: ${res.status} ${res.statusText} - ${errorText}`);
|
|
117
|
+
}
|
|
118
|
+
const review = await res.json();
|
|
119
|
+
console.log(`✅ Created review for PR #${prNumber}`);
|
|
120
|
+
return review.id; // Return the review ID to be used for inline comments
|
|
121
|
+
}
|
|
@@ -7,8 +7,12 @@ export const reviewModule = {
|
|
|
7
7
|
const model = Config.getModel();
|
|
8
8
|
const prompt = `
|
|
9
9
|
You are a senior software engineer reviewing a pull request.
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
ALWAYS make 3 concise suggestions for improvements based on the input code diff.
|
|
11
|
+
Use this format ONLY and output ONLY those suggestions:
|
|
12
|
+
|
|
13
|
+
1. ...
|
|
14
|
+
2. ...
|
|
15
|
+
3. ...
|
|
12
16
|
|
|
13
17
|
Changes:
|
|
14
18
|
${content}
|