@@ -118,7 +118,7 @@ main().catch(error => {
118118"use strict";
119119
120120Object.defineProperty(exports, "__esModule", ({ value: true }));
121- exports.baseCodeReviewPrompt = exports.outputFormat = void 0;
121+ exports.updateReviewPrompt = exports. baseCodeReviewPrompt = exports.outputFormat = void 0;
122122exports.outputFormat = `
123123{
124124 "summary": "",
@@ -162,7 +162,15 @@ For the "comments" field, provide a list of comments. Each comment should have t
162162- path: The path to the file that the comment is about
163163- line: The line number in the file that the comment is about
164164- comment: The comment text
165- Comments should ONLY be added to lines or blocks of code that have issues.
165+ Other rules for "comments" field:
166+ - Comments should ONLY be added to lines or blocks of code that have issues.
167+ - ONLY use line numbers that appear in the "diff" property of each file
168+ - Each diff line starts with a prefix:
169+ * "normal" for unchanged lines
170+ * "del" for removed lines
171+ * "add" for added lines
172+ - Extract the line number that appears after the prefix
173+ - DO NOT use line number 0 or line numbers not present in the diff
166174
167175For the "suggestedAction" field, provide a single word that indicates the action to be taken. Options are:
168176- "approve"
@@ -171,6 +179,15 @@ For the "suggestedAction" field, provide a single word that indicates the action
171179
172180For the "confidence" field, provide a number between 0 and 100 that indicates the confidence in the verdict.
173181`;
182+ exports.updateReviewPrompt = `
183+ When reviewing updates to a PR:
184+ 1. Focus on the modified sections but consider their context
185+ 2. Reference previous comments if they're still relevant
186+ 3. Acknowledge fixed issues from previous reviews
187+ 4. Only comment on new issues or unresolved previous issues
188+ 5. Consider the cumulative impact of changes
189+ 6. IMPORTANT: Only use line numbers that appear in the current "diff" field
190+ `;
174191exports["default"] = exports.baseCodeReviewPrompt;
175192
176193
@@ -256,16 +273,15 @@ class AnthropicProvider {
256273 }
257274 async review(request) {
258275 var _a;
259- const prompt = this.buildPrompt(request);
260- core.info(`Sending request to Anthropic with prompt structure: ${JSON.stringify(request, null, 2)}`);
276+ core.debug(`Sending request to Anthropic with prompt structure: ${JSON.stringify(request, null, 2)}`);
261277 const response = await this.client.messages.create({
262278 model: this.config.model,
263279 max_tokens: 4000,
264- system: prompts_1.baseCodeReviewPrompt ,
280+ system: this.buildSystemPrompt(request) ,
265281 messages: [
266282 {
267283 role: 'user',
268- content: prompt ,
284+ content: this.buildPullRequestPrompt(request) ,
269285 },
270286 {
271287 role: 'user',
@@ -274,19 +290,35 @@ class AnthropicProvider {
274290 ],
275291 temperature: (_a = this.config.temperature) !== null && _a !== void 0 ? _a : 0.3,
276292 });
277- core.info (`Raw Anthropic response: ${JSON.stringify(response.content[0].text, null, 2)}`);
293+ core.debug (`Raw Anthropic response: ${JSON.stringify(response.content[0].text, null, 2)}`);
278294 const parsedResponse = this.parseResponse(response);
279295 core.info(`Parsed response: ${JSON.stringify(parsedResponse, null, 2)}`);
280296 return parsedResponse;
281297 }
282- buildPrompt(request) {
298+ buildPullRequestPrompt(request) {
299+ var _a;
283300 return JSON.stringify({
284301 type: 'code_review',
285302 files: request.files,
286303 pr: request.pullRequest,
287304 context: request.context,
305+ previousReviews: (_a = request.previousReviews) === null || _a === void 0 ? void 0 : _a.map(review => ({
306+ summary: review.summary,
307+ lineComments: review.lineComments.map(comment => ({
308+ path: comment.path,
309+ line: comment.line,
310+ comment: comment.comment
311+ }))
312+ }))
288313 });
289314 }
315+ buildSystemPrompt(request) {
316+ const isUpdate = request.context.isUpdate;
317+ return `
318+ ${prompts_1.baseCodeReviewPrompt}
319+ ${isUpdate ? prompts_1.updateReviewPrompt : ''}
320+ `;
321+ }
290322 parseResponse(response) {
291323 try {
292324 const content = JSON.parse(response.content[0].text);
@@ -368,33 +400,50 @@ class GeminiProvider {
368400 });
369401 }
370402 async review(request) {
371- const prompt = this.buildPrompt(request);
372- core.info(`Sending request to Gemini with prompt structure: ${JSON.stringify(request, null, 2)}`);
403+ core.debug(`Sending request to Gemini with prompt structure: ${JSON.stringify(request, null, 2)}`);
373404 const result = await this.model.generateContent({
374- systemInstruction: prompts_1.baseCodeReviewPrompt ,
405+ systemInstruction: this.buildSystemPrompt(request) ,
375406 contents: [
376407 {
377408 role: 'user',
378409 parts: [
379410 {
380- text: prompt ,
411+ text: this.buildPullRequestPrompt(request) ,
381412 }
382413 ]
383414 }
384415 ]
385416 });
386417 const response = result.response;
387- core.info(`Raw Gemini response: ${JSON.stringify(response.text(), null, 2)}`);
388- return this.parseResponse(response);
418+ core.debug(`Raw Gemini response: ${JSON.stringify(response.text(), null, 2)}`);
419+ const parsedResponse = this.parseResponse(response);
420+ core.info(`Parsed response: ${JSON.stringify(parsedResponse, null, 2)}`);
421+ return parsedResponse;
389422 }
390- buildPrompt(request) {
423+ buildPullRequestPrompt(request) {
424+ var _a;
391425 return JSON.stringify({
392426 type: 'code_review',
393427 files: request.files,
394428 pr: request.pullRequest,
395429 context: request.context,
430+ previousReviews: (_a = request.previousReviews) === null || _a === void 0 ? void 0 : _a.map(review => ({
431+ summary: review.summary,
432+ lineComments: review.lineComments.map(comment => ({
433+ path: comment.path,
434+ line: comment.line,
435+ comment: comment.comment
436+ }))
437+ }))
396438 });
397439 }
440+ buildSystemPrompt(request) {
441+ const isUpdate = request.context.isUpdate;
442+ return `
443+ ${prompts_1.baseCodeReviewPrompt}
444+ ${isUpdate ? prompts_1.updateReviewPrompt : ''}
445+ `;
446+ }
398447 parseResponse(response) {
399448 try {
400449 const content = JSON.parse(response.text());
@@ -474,36 +523,51 @@ class OpenAIProvider {
474523 }
475524 async review(request) {
476525 var _a;
477- const prompt = this.buildPrompt(request);
478526 core.info(`Sending request to OpenAI with prompt structure: ${JSON.stringify(request, null, 2)}`);
479527 const response = await this.client.chat.completions.create({
480528 model: this.config.model,
481529 messages: [
482530 {
483531 role: 'system',
484- content: prompts_1.baseCodeReviewPrompt ,
532+ content: this.buildSystemPrompt(request) ,
485533 },
486534 {
487535 role: 'user',
488- content: prompt ,
536+ content: this.buildPullRequestPrompt(request) ,
489537 },
490538 ],
491539 temperature: (_a = this.config.temperature) !== null && _a !== void 0 ? _a : 0.3,
492540 response_format: { type: 'json_object' },
493541 });
494- core.info (`Raw OpenAI response: ${JSON.stringify(response.choices[0].message.content, null, 2)}`);
542+ core.debug (`Raw OpenAI response: ${JSON.stringify(response.choices[0].message.content, null, 2)}`);
495543 const parsedResponse = this.parseResponse(response);
496544 core.info(`Parsed response: ${JSON.stringify(parsedResponse, null, 2)}`);
497545 return parsedResponse;
498546 }
499- buildPrompt(request) {
547+ buildPullRequestPrompt(request) {
548+ var _a;
500549 return JSON.stringify({
501550 type: 'code_review',
502551 files: request.files,
503552 pr: request.pullRequest,
504553 context: request.context,
554+ previousReviews: (_a = request.previousReviews) === null || _a === void 0 ? void 0 : _a.map(review => ({
555+ summary: review.summary,
556+ lineComments: review.lineComments.map(comment => ({
557+ path: comment.path,
558+ line: comment.line,
559+ comment: comment.comment
560+ }))
561+ }))
505562 });
506563 }
564+ buildSystemPrompt(request) {
565+ const isUpdate = request.context.isUpdate;
566+ return `
567+ ${prompts_1.baseCodeReviewPrompt}
568+ ${isUpdate ? prompts_1.updateReviewPrompt : ''}
569+ `;
570+ }
507571 parseResponse(response) {
508572 var _a;
509573 // Implement response parsing
@@ -575,23 +639,26 @@ class DiffService {
575639 .map(p => p.trim());
576640 }
577641 async getRelevantFiles(prDetails, lastReviewedCommit) {
578- const baseUrl = `https://api.github.com/repos/${prDetails.owner}/${prDetails.repo}/pulls/${prDetails.number} `;
642+ const baseUrl = `https://api.github.com/repos/${prDetails.owner}/${prDetails.repo}`;
579643 const diffUrl = lastReviewedCommit ?
580- `${baseUrl}/compare/${lastReviewedCommit}...${prDetails.head}.diff ` :
581- `${baseUrl}.diff `;
644+ `${baseUrl}/compare/${lastReviewedCommit}...${prDetails.head}` :
645+ `${baseUrl}/pulls/${prDetails.number} `;
582646 const response = await fetch(diffUrl, {
583647 headers: {
584648 'Authorization': `Bearer ${this.githubToken}`,
585- 'Accept': 'application/vnd.github.v3.diff'
649+ 'Accept': 'application/vnd.github.v3.diff',
650+ 'X-GitHub-Api-Version': '2022-11-28'
586651 }
587652 });
588653 if (!response.ok) {
589- core.error(`Failed to fetch diff: ${await response.text()}`);
654+ const errorText = await response.text();
655+ core.error(`Failed to fetch diff from ${diffUrl}: ${errorText}`);
590656 throw new Error(`Failed to fetch diff: ${response.statusText}`);
591657 }
592658 const diffText = await response.text();
593- core.info(`Diff text length: ${diffText.length}`);
659+ core.debug(`Full diff text length: ${diffText.length}`);
594660 const files = (0, parse_diff_1.default)(diffText);
661+ core.info(`Found ${files.length} files in diff`);
595662 return this.filterRelevantFiles(files);
596663 }
597664 filterRelevantFiles(files) {
@@ -805,6 +872,34 @@ class GitHubService {
805872 });
806873 return (lastCommit === null || lastCommit === void 0 ? void 0 : lastCommit.sha) || null;
807874 }
875+ async getPreviousReviews(prNumber) {
876+ const { data: reviews } = await this.octokit.pulls.listReviews({
877+ owner: this.owner,
878+ repo: this.repo,
879+ pull_number: prNumber,
880+ });
881+ // Filter to bot reviews and fetch their comments
882+ const botReviews = reviews.filter(review => { var _a; return ((_a = review.user) === null || _a === void 0 ? void 0 : _a.login) === 'github-actions[bot]'; });
883+ core.debug(`Found ${botReviews.length} bot reviews`);
884+ const botReviewsWithComments = await Promise.all(botReviews.map(async (review) => {
885+ const { data: comments } = await this.octokit.pulls.listReviewComments({
886+ owner: this.owner,
887+ repo: this.repo,
888+ pull_number: prNumber,
889+ review_id: review.id
890+ });
891+ return {
892+ commit: review.commit_id,
893+ summary: review.body || '',
894+ lineComments: comments.map(comment => ({
895+ path: comment.path,
896+ line: comment.line || 0,
897+ comment: comment.body
898+ }))
899+ };
900+ }));
901+ return botReviewsWithComments;
902+ }
808903}
809904exports.GitHubService = GitHubService;
810905
@@ -865,21 +960,33 @@ class ReviewService {
865960 const prDetails = await this.githubService.getPRDetails(prNumber);
866961 core.info(`PR title: ${prDetails.title}`);
867962 // Get modified files from diff
868- const modifiedFiles = await this.diffService.getRelevantFiles(prDetails);
963+ const lastReviewedCommit = await this.githubService.getLastReviewedCommit(prNumber);
964+ const isUpdate = !!lastReviewedCommit;
965+ // If this is an update, get previous reviews
966+ let previousReviews;
967+ if (isUpdate) {
968+ previousReviews = await this.githubService.getPreviousReviews(prNumber);
969+ core.debug(`Found ${previousReviews.length} previous reviews`);
970+ }
971+ const modifiedFiles = await this.diffService.getRelevantFiles(prDetails, lastReviewedCommit);
869972 core.info(`Modified files length: ${modifiedFiles.length}`);
870973 // Get full content for each modified file
871- const filesWithContent = await Promise.all(modifiedFiles.map(async (file) => ({
872- path: file.path,
873- content: await this.githubService.getFileContent(file.path, prDetails.head),
874- originalContent: await this.githubService.getFileContent(file.path, prDetails.base),
875- diff: file.diff,
876- })));
974+ const filesWithContent = await Promise.all(modifiedFiles.map(async (file) => {
975+ const fullContent = await this.githubService.getFileContent(file.path, prDetails.head);
976+ return {
977+ path: file.path,
978+ content: fullContent,
979+ originalContent: await this.githubService.getFileContent(file.path, prDetails.base),
980+ diff: file.diff,
981+ };
982+ }));
877983 // Get repository context (package.json, readme, etc)
878984 const contextFiles = await this.getRepositoryContext();
879985 // Perform AI review
880986 const review = await this.aiProvider.review({
881987 files: filesWithContent,
882988 contextFiles,
989+ previousReviews,
883990 pullRequest: {
884991 title: prDetails.title,
885992 description: prDetails.description,
@@ -890,6 +997,7 @@ class ReviewService {
890997 repository: (_a = process.env.GITHUB_REPOSITORY) !== null && _a !== void 0 ? _a : '',
891998 owner: (_b = process.env.GITHUB_REPOSITORY_OWNER) !== null && _b !== void 0 ? _b : '',
892999 projectContext: process.env.INPUT_PROJECT_CONTEXT,
1000+ isUpdate,
8931001 },
8941002 });
8951003 // Add model name to summary
0 commit comments