@@ -150453,18 +150453,56 @@ const robot = (app) => {
150453150453 head: context.payload.pull_request.head.sha,
150454150454 });
150455150455 let { files: changedFiles, commits } = data.data;
150456- loglevel_1.default.debug("compareCommits, base:", context.payload.pull_request.base.sha, "head:", context.payload.pull_request.head.sha);
150457- loglevel_1.default.debug("compareCommits.commits:", commits);
150458- loglevel_1.default.debug("compareCommits.files", changedFiles);
150459- if (context.payload.action === 'synchronize' && commits.length >= 2) {
150460- const { data: { files }, } = await context.octokit.repos.compareCommits({
150461- owner: repo.owner,
150462- repo: repo.repo,
150463- base: commits[commits.length - 2].sha,
150464- head: commits[commits.length - 1].sha,
150465- });
150466- changedFiles = files;
150456+ if (context.payload.action === 'synchronize') {
150457+ // Try to detect the last commit we reviewed (by looking for our previous review)
150458+ try {
150459+ const reviewsResp = await context.octokit.pulls.listReviews({
150460+ owner: repo.owner,
150461+ repo: repo.repo,
150462+ pull_number: context.pullRequest().pull_number,
150463+ });
150464+ const reviews = reviewsResp.data || [];
150465+ // Find the most recent review created by this bot (we mark our reviews with a body)
150466+ const botReview = reviews
150467+ .slice()
150468+ .reverse()
150469+ .find((r) => r.body && (r.body.startsWith('Code review by ChatGPT') || r.body.startsWith('LGTM')));
150470+ if (botReview?.commit_id) {
150471+ const { data: { files, commits: newCommits }, } = await context.octokit.repos.compareCommits({
150472+ owner: repo.owner,
150473+ repo: repo.repo,
150474+ base: botReview.commit_id,
150475+ head: context.payload.pull_request.head.sha,
150476+ });
150477+ changedFiles = files;
150478+ commits = newCommits;
150479+ }
150480+ else if (commits.length >= 2) {
150481+ // fallback: compare last two commits in the PR
150482+ const { data: { files }, } = await context.octokit.repos.compareCommits({
150483+ owner: repo.owner,
150484+ repo: repo.repo,
150485+ base: commits[commits.length - 2].sha,
150486+ head: commits[commits.length - 1].sha,
150487+ });
150488+ changedFiles = files;
150489+ }
150490+ }
150491+ catch (err) {
150492+ loglevel_1.default.debug('failed to detect previous bot review, falling back', err);
150493+ if (commits.length >= 2) {
150494+ const { data: { files }, } = await context.octokit.repos.compareCommits({
150495+ owner: repo.owner,
150496+ repo: repo.repo,
150497+ base: commits[commits.length - 2].sha,
150498+ head: commits[commits.length - 1].sha,
150499+ });
150500+ changedFiles = files;
150501+ }
150502+ }
150467150503 }
150504+ loglevel_1.default.debug('changedFiles:', changedFiles);
150505+ loglevel_1.default.debug;
150468150506 const ignoreList = (process.env.IGNORE || process.env.ignore || '')
150469150507 .split('\n')
150470150508 .filter((v) => v !== '');
@@ -150507,16 +150545,36 @@ const robot = (app) => {
150507150545 }
150508150546 try {
150509150547 const res = await chat?.codeReview(patch);
150510- if (!res.lgtm && !!res.review_comment) {
150511- ress.push({
150512- path: file.filename,
150513- body: res.review_comment,
150514- position: patch.split('\n').length - 1,
150515- });
150548+ // res can be a single review or an array of reviews (one for each hunk)
150549+ const reviews = Array.isArray(res) ? res : [res];
150550+ for (const review of reviews) {
150551+ if (!review.lgtm && !!review.review_comment) {
150552+ let line;
150553+ let side = 'RIGHT';
150554+ // Extract line number from hunk header if available
150555+ if (review.hunk_header) {
150556+ const c = review.hunk_header.match(/\+\s*(\d+),(\d+)/);
150557+ if (c) {
150558+ const [, start, count] = c;
150559+ line = Number(start) + Number(count) - 1;
150560+ }
150561+ else {
150562+ loglevel_1.default.error(`Failed to parse hunk header: ${review.hunk_header}`);
150563+ continue;
150564+ }
150565+ }
150566+ ress.push({
150567+ path: file.filename,
150568+ body: review.review_comment,
150569+ line: line,
150570+ side: side,
150571+ });
150572+ }
150516150573 }
150517150574 }
150518150575 catch (e) {
150519150576 loglevel_1.default.info(`review ${file.filename} failed`, e);
150577+ throw e;
150520150578 }
150521150579 }
150522150580 try {
@@ -150526,12 +150584,13 @@ const robot = (app) => {
150526150584 pull_number: context.pullRequest().pull_number,
150527150585 body: ress.length ? "Code review by ChatGPT" : "LGTM 👍",
150528150586 event: 'COMMENT',
150529- commit_id: commits[commits.length - 1] .sha,
150587+ commit_id: context.payload.pull_request.head .sha,
150530150588 comments: ress,
150531150589 });
150532150590 }
150533150591 catch (e) {
150534150592 loglevel_1.default.info(`Failed to create review`, e);
150593+ throw e;
150535150594 }
150536150595 console.timeEnd('gpt cost');
150537150596 loglevel_1.default.info('successfully reviewed', context.payload.pull_request.html_url);
@@ -150598,10 +150657,16 @@ class Chat {
150598150657 const userPrompt = process.env.PROMPT || 'Please review the following code patch. Focus on potential bugs, risks, and improvement suggestions.';
150599150658 const jsonFormatRequirement = '\nProvide your feedback in a strict JSON format with the following structure:\n' +
150600150659 '{\n' +
150601- ' "lgtm": boolean, // true if the code looks good to merge, false if there are concerns\n' +
150602- ' "review_comment": string // Your detailed review comments. You can use markdown syntax in this string, but the overall response must be a valid JSON\n' +
150660+ ' "reviews": [\n' +
150661+ ' {\n' +
150662+ ' "hunk_header": string, // The @@ hunk header (e.g., "@@ -10,5 +10,7 @@"), optional\n' +
150663+ ' "lgtm": boolean, // true if this hunk looks good, false if there are concerns\n' +
150664+ ' "review_comment": string // Your detailed review comments for this hunk. Can use markdown syntax. Empty string if lgtm is true.\n' +
150665+ ' }\n' +
150666+ ' ]\n' +
150603150667 '}\n' +
150604- 'Ensure your response is a valid JSON object.\n';
150668+ 'Review each hunk (marked by @@) separately and provide feedback for hunks that need improvement.\n' +
150669+ 'Ensure your response is a valid JSON object with a reviews array.\n';
150605150670 return `${userPrompt}${jsonFormatRequirement} ${answerLanguage}:
150606150671 ${patch}
150607150672 `;
@@ -150634,11 +150699,17 @@ class Chat {
150634150699 if (res.choices.length) {
150635150700 try {
150636150701 const json = JSON.parse(res.choices[0].message.content || "");
150702+ // If response has a 'reviews' array, return it directly
150703+ if (json.reviews && Array.isArray(json.reviews)) {
150704+ return json.reviews;
150705+ }
150706+ // Otherwise, treat as a single review response
150637150707 return json;
150638150708 }
150639150709 catch (e) {
150640150710 return {
150641150711 lgtm: false,
150712+ hunk_header: patch.split('\n')[0].startsWith('@@') ? patch.split('\n')[0] : undefined,
150642150713 review_comment: res.choices[0].message.content || ""
150643150714 };
150644150715 }
0 commit comments