diff --git a/README.md b/README.md index 1b09677f..a9267f60 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
- - GitHub + + GitHub
@@ -47,7 +47,7 @@ FAQs, you can refer to the sections below. - [Overview](#overview) - [Install instructions](#install-instructions) -- [Conversation with OpenAI](#conversation-with-openai) +- [Conversation with CodeRabbit](#conversation-with-coderabbit) - [Examples](#examples) - [Contribute](#contribute) - [FAQs](#faqs) @@ -55,7 +55,7 @@ FAQs, you can refer to the sections below. ## Install instructions `ai-pr-reviewer` runs as a GitHub Action. Add the below file to your repository -at `.github/workflows/openai-pr-reviewer.yml` +at `.github/workflows/ai-pr-reviewer.yml` ```yaml name: Code Review @@ -123,7 +123,7 @@ value. For example, to review docs/blog posts, you can use the following prompt: ```yaml system_message: | - You are `@openai` (aka `github-actions[bot]`), a language model + You are `@coderabbitai` (aka `github-actions[bot]`), a language model trained by OpenAI. Your purpose is to act as a highly experienced DevRel (developer relations) professional with focus on cloud-native infrastructure. @@ -150,15 +150,15 @@ system_message: | -## Conversation with OpenAI +## Conversation with CodeRabbit You can reply to a review comment made by this action and get a response based on the diff context. Additionally, you can invite the bot to a conversation by -tagging it in the comment (`@openai`). +tagging it in the comment (`@coderabbitai`). Example: -> @openai Please generate a test plan for this file. +> @coderabbitai Please generate a test plan for this file. Note: A review comment is a comment made on a diff or a file in the pull request. @@ -170,7 +170,7 @@ to review documentation, you can ignore PRs that only change the documentation. To ignore a PR, add the following keyword in the PR description: ```text -@openai: ignore +@coderabbitai: ignore ``` ## Examples diff --git a/action.yml b/action.yml index 06fbea67..c66695cf 100644 --- a/action.yml +++ b/action.yml @@ -1,9 +1,9 @@ -name: 'OpenAI ChatGPT based PR Reviewer & Summarizer' -description: 'OpenAI ChatGPT based PR Reviewer and Summarizer' +name: 'AI-based PR Reviewer & Summarizer with Chat Capabilities' +description: 'AI-based PR Reviewer & Summarizer with Chat Capabilities' branding: - icon: 'aperture' + icon: 'git-merge' color: 'orange' -author: 'FluxNinja, Inc.' +author: 'CodeRabbit LLC' inputs: debug: required: false @@ -148,7 +148,7 @@ inputs: required: false description: 'System message to be sent to OpenAI' default: | - You are `@openai` (aka `github-actions[bot]`), a language model + You are `@coderabbitai` (aka `github-actions[bot]`), a language model trained by OpenAI. Your purpose is to act as a highly experienced software engineer and provide a thorough review of the code hunks and suggest code snippets to improve key areas such as: diff --git a/dist/index.js b/dist/index.js index 55ab8cdd..2cc2b1fb 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3895,7 +3895,7 @@ Current date: ${currentDate}`; /* harmony export */ "oi": () => (/* binding */ RAW_SUMMARY_START_TAG), /* harmony export */ "rV": () => (/* binding */ RAW_SUMMARY_END_TAG) /* harmony export */ }); -/* unused harmony exports COMMENT_GREETING, DESCRIPTION_START_TAG, DESCRIPTION_END_TAG, COMMIT_ID_START_TAG, COMMIT_ID_END_TAG */ +/* unused harmony exports COMMENT_GREETING, IN_PROGRESS_START_TAG, IN_PROGRESS_END_TAG, DESCRIPTION_START_TAG, DESCRIPTION_END_TAG, COMMIT_ID_START_TAG, COMMIT_ID_END_TAG */ /* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(2186); /* harmony import */ var _actions_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nccwpck_require__.n(_actions_core__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var _actions_github__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(5438); @@ -3908,23 +3908,25 @@ Current date: ${currentDate}`; // eslint-disable-next-line camelcase const context = _actions_github__WEBPACK_IMPORTED_MODULE_1__.context; const repo = context.repo; -const COMMENT_GREETING = ':robot: OpenAI'; -const COMMENT_TAG = ''; -const COMMENT_REPLY_TAG = ''; -const SUMMARIZE_TAG = ''; +const COMMENT_GREETING = `Image description CodeRabbit`; +const COMMENT_TAG = ''; +const COMMENT_REPLY_TAG = ''; +const SUMMARIZE_TAG = ''; +const IN_PROGRESS_START_TAG = ''; +const IN_PROGRESS_END_TAG = ''; const DESCRIPTION_START_TAG = ` -`; -const DESCRIPTION_END_TAG = ''; -const RAW_SUMMARY_START_TAG = ` +`; +const DESCRIPTION_END_TAG = ''; +const RAW_SUMMARY_START_TAG = ` -`; -const SHORT_SUMMARY_START_TAG = ` +`; +const SHORT_SUMMARY_START_TAG = ` -`; +`; const COMMIT_ID_START_TAG = ''; const COMMIT_ID_END_TAG = ''; class Commenter { @@ -4315,13 +4317,20 @@ ${chain} async create(body, target) { try { // get comment ID from the response - await _octokit__WEBPACK_IMPORTED_MODULE_2__/* .octokit.issues.createComment */ .K.issues.createComment({ + const response = await _octokit__WEBPACK_IMPORTED_MODULE_2__/* .octokit.issues.createComment */ .K.issues.createComment({ owner: repo.owner, repo: repo.repo, // eslint-disable-next-line camelcase issue_number: target, body }); + // add comment to issueCommentsCache + if (this.issueCommentsCache[target]) { + this.issueCommentsCache[target].push(response.data); + } + else { + this.issueCommentsCache[target] = [response.data]; + } } catch (e) { (0,_actions_core__WEBPACK_IMPORTED_MODULE_0__.warning)(`Failed to create comment: ${e}`); @@ -4461,6 +4470,42 @@ ${chain} } return allCommits; } + // add in-progress status to the comment body + addInProgressStatus(commentBody, headCommitId, highestReviewedCommitId) { + const start = commentBody.indexOf(IN_PROGRESS_START_TAG); + const end = commentBody.indexOf(IN_PROGRESS_END_TAG); + // add to the beginning of the comment body if the marker doesn't exist + // otherwise do nothing + if (start === -1 || end === -1) { + return `${IN_PROGRESS_START_TAG} + +Currently reviewing new changes in this PR... + +
+Details +The files that changed from the \`base\` of the PR and between \`${highestReviewedCommitId}\` and \`${headCommitId}\` commits are being reviewed. +
+ +${IN_PROGRESS_END_TAG} + +--- + +${commentBody}`; + } + return commentBody; + } + // remove in-progress status from the comment body + removeInProgressStatus(commentBody) { + const start = commentBody.indexOf(IN_PROGRESS_START_TAG); + const end = commentBody.indexOf(IN_PROGRESS_END_TAG); + // remove the in-progress status if the marker exists + // otherwise do nothing + if (start !== -1 && end !== -1) { + return (commentBody.substring(0, start) + + commentBody.substring(end + IN_PROGRESS_END_TAG.length)); + } + return commentBody; + } } @@ -6981,7 +7026,7 @@ $comment // eslint-disable-next-line camelcase const context = _actions_github__WEBPACK_IMPORTED_MODULE_1__.context; const repo = context.repo; -const ASK_BOT = '@openai'; +const ASK_BOT = '@coderabbitai'; const handleReviewComment = async (heavyBot, options, prompts) => { const commenter = new _commenter__WEBPACK_IMPORTED_MODULE_2__/* .Commenter */ .Es(); const inputs = new _inputs__WEBPACK_IMPORTED_MODULE_5__/* .Inputs */ .k(); @@ -7277,7 +7322,7 @@ var tokenizer = __nccwpck_require__(652); // eslint-disable-next-line camelcase const context = github.context; const repo = context.repo; -const ignoreKeyword = '@openai: ignore'; +const ignoreKeyword = '@coderabbitai: ignore'; const codeReview = async (lightBot, heavyBot, options, prompts) => { const commenter = new lib_commenter/* Commenter */.Es(); const openaiConcurrencyLimit = pLimit(options.openaiConcurrencyLimit); @@ -7306,10 +7351,12 @@ const codeReview = async (lightBot, heavyBot, options, prompts) => { // get SUMMARIZE_TAG message const existingSummarizeCmt = await commenter.findCommentWithTag(lib_commenter/* SUMMARIZE_TAG */.Rp, context.payload.pull_request.number); let existingCommitIdsBlock = ''; + let existingSummarizeCmtBody = ''; if (existingSummarizeCmt != null) { - inputs.rawSummary = commenter.getRawSummary(existingSummarizeCmt.body); - inputs.shortSummary = commenter.getShortSummary(existingSummarizeCmt.body); - existingCommitIdsBlock = commenter.getReviewedCommitIdsBlock(existingSummarizeCmt.body); + existingSummarizeCmtBody = existingSummarizeCmt.body; + inputs.rawSummary = commenter.getRawSummary(existingSummarizeCmtBody); + inputs.shortSummary = commenter.getShortSummary(existingSummarizeCmtBody); + existingCommitIdsBlock = commenter.getReviewedCommitIdsBlock(existingSummarizeCmtBody); } const allCommitIds = await commenter.getAllCommitIds(); // find highest reviewed commit id @@ -7351,11 +7398,6 @@ const codeReview = async (lightBot, heavyBot, options, prompts) => { (0,core.warning)('Skipped: files is null'); return; } - const commits = incrementalDiff.data.commits; - if (commits.length === 0) { - (0,core.warning)('Skipped: ommits is null'); - return; - } // skip files if they are filtered out const filterSelectedFiles = []; const filterIgnoredFiles = []; @@ -7368,6 +7410,15 @@ const codeReview = async (lightBot, heavyBot, options, prompts) => { filterSelectedFiles.push(file); } } + if (filterSelectedFiles.length === 0) { + (0,core.warning)('Skipped: filterSelectedFiles is null'); + return; + } + const commits = incrementalDiff.data.commits; + if (commits.length === 0) { + (0,core.warning)('Skipped: commits is null'); + return; + } // find hunks to review const filteredFiles = await Promise.all(filterSelectedFiles.map(file => githubConcurrencyLimit(async () => { // retrieve file contents @@ -7439,6 +7490,10 @@ ${hunks.oldHunk} (0,core.error)('Skipped: no files to review'); return; } + // update the existing comment with in progress status + const inProgressSummarizeCmt = commenter.addInProgressStatus(existingSummarizeCmtBody, context.payload.pull_request.head.sha, highestReviewedCommitId); + // add in progress status to the summarize comment + await commenter.comment(`${inProgressSummarizeCmt}`, lib_commenter/* SUMMARIZE_TAG */.Rp, 'replace'); const summariesFailed = []; const doSummary = async (filename, fileContent, fileDiff) => { (0,core.info)(`summarize: ${filename}`); @@ -7536,7 +7591,7 @@ ${filename}: ${summary} (0,core.info)('release notes: nothing obtained from openai'); } else { - let message = '### Summary by OpenAI\n\n'; + let message = '### Summary by CodeRabbit\n\n'; message += releaseNotesResponse; try { await commenter.updateDescription(context.payload.pull_request.number, message); @@ -7556,20 +7611,24 @@ ${lib_commenter/* RAW_SUMMARY_END_TAG */.rV} ${lib_commenter/* SHORT_SUMMARY_START_TAG */.O$} ${inputs.shortSummary} ${lib_commenter/* SHORT_SUMMARY_END_TAG */.Zb} + --- -### Chat with 🤖 OpenAI Bot (\`@openai\`) +
+Notes + +### Chat with Image description CodeRabbit Bot (\`@coderabbitai\`) - Reply on review comments left by this bot to ask follow-up questions. A review comment is a comment on a diff or a file. -- Invite the bot into a review comment chain by tagging \`@openai\` in a reply. +- Invite the bot into a review comment chain by tagging \`@coderabbitai\` in a reply. ### Code suggestions - The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned. - You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off. ### Ignoring further reviews -- Type \`@openai: ignore\` anywhere in the PR description to ignore further reviews from the bot. +- Type \`@coderabbitai: ignore\` anywhere in the PR description to ignore further reviews from the bot. ---- +
${filterIgnoredFiles.length > 0 ? ` @@ -7735,9 +7794,6 @@ ${commentChain} } await Promise.all(reviewPromises); summarizeComment += ` ---- -In the recent run, only the files that changed from the \`base\` of the PR and between \`${highestReviewedCommitId}\` and \`${context.payload.pull_request.head.sha}\` commits were reviewed. - ${reviewsFailed.length > 0 ? `
Files not reviewed due to errors in the recent run (${reviewsFailed.length}) diff --git a/src/commenter.ts b/src/commenter.ts index 1b8a4770..9aa036f5 100644 --- a/src/commenter.ts +++ b/src/commenter.ts @@ -7,34 +7,40 @@ import {octokit} from './octokit' const context = github_context const repo = context.repo -export const COMMENT_GREETING = ':robot: OpenAI' +export const COMMENT_GREETING = `Image description CodeRabbit` export const COMMENT_TAG = - '' + '' export const COMMENT_REPLY_TAG = - '' + '' export const SUMMARIZE_TAG = - '' + '' + +export const IN_PROGRESS_START_TAG = + '' + +export const IN_PROGRESS_END_TAG = + '' export const DESCRIPTION_START_TAG = ` -` +` export const DESCRIPTION_END_TAG = - '' + '' -export const RAW_SUMMARY_START_TAG = ` +export const RAW_SUMMARY_START_TAG = ` -` +` -export const SHORT_SUMMARY_START_TAG = ` +export const SHORT_SUMMARY_START_TAG = ` -` +` export const COMMIT_ID_START_TAG = '' export const COMMIT_ID_END_TAG = '' @@ -557,13 +563,19 @@ ${chain} async create(body: string, target: number) { try { // get comment ID from the response - await octokit.issues.createComment({ + const response = await octokit.issues.createComment({ owner: repo.owner, repo: repo.repo, // eslint-disable-next-line camelcase issue_number: target, body }) + // add comment to issueCommentsCache + if (this.issueCommentsCache[target]) { + this.issueCommentsCache[target].push(response.data) + } else { + this.issueCommentsCache[target] = [response.data] + } } catch (e) { warning(`Failed to create comment: ${e}`) } @@ -718,4 +730,48 @@ ${chain} return allCommits } + + // add in-progress status to the comment body + addInProgressStatus( + commentBody: string, + headCommitId: string, + highestReviewedCommitId: string + ): string { + const start = commentBody.indexOf(IN_PROGRESS_START_TAG) + const end = commentBody.indexOf(IN_PROGRESS_END_TAG) + // add to the beginning of the comment body if the marker doesn't exist + // otherwise do nothing + if (start === -1 || end === -1) { + return `${IN_PROGRESS_START_TAG} + +Currently reviewing new changes in this PR... + +
+Details +The files that changed from the \`base\` of the PR and between \`${highestReviewedCommitId}\` and \`${headCommitId}\` commits are being reviewed. +
+ +${IN_PROGRESS_END_TAG} + +--- + +${commentBody}` + } + return commentBody + } + + // remove in-progress status from the comment body + removeInProgressStatus(commentBody: string): string { + const start = commentBody.indexOf(IN_PROGRESS_START_TAG) + const end = commentBody.indexOf(IN_PROGRESS_END_TAG) + // remove the in-progress status if the marker exists + // otherwise do nothing + if (start !== -1 && end !== -1) { + return ( + commentBody.substring(0, start) + + commentBody.substring(end + IN_PROGRESS_END_TAG.length) + ) + } + return commentBody + } } diff --git a/src/review-comment.ts b/src/review-comment.ts index 1a5b0b17..f42c53db 100644 --- a/src/review-comment.ts +++ b/src/review-comment.ts @@ -17,7 +17,7 @@ import {getTokenCount} from './tokenizer' // eslint-disable-next-line camelcase const context = github_context const repo = context.repo -const ASK_BOT = '@openai' +const ASK_BOT = '@coderabbitai' export const handleReviewComment = async ( heavyBot: Bot, diff --git a/src/review.ts b/src/review.ts index 9d73d34b..867f7b0a 100644 --- a/src/review.ts +++ b/src/review.ts @@ -22,7 +22,7 @@ import {getTokenCount} from './tokenizer' const context = github_context const repo = context.repo -const ignoreKeyword = '@openai: ignore' +const ignoreKeyword = '@coderabbitai: ignore' export const codeReview = async ( lightBot: Bot, @@ -72,11 +72,13 @@ export const codeReview = async ( context.payload.pull_request.number ) let existingCommitIdsBlock = '' + let existingSummarizeCmtBody = '' if (existingSummarizeCmt != null) { - inputs.rawSummary = commenter.getRawSummary(existingSummarizeCmt.body) - inputs.shortSummary = commenter.getShortSummary(existingSummarizeCmt.body) + existingSummarizeCmtBody = existingSummarizeCmt.body + inputs.rawSummary = commenter.getRawSummary(existingSummarizeCmtBody) + inputs.shortSummary = commenter.getShortSummary(existingSummarizeCmtBody) existingCommitIdsBlock = commenter.getReviewedCommitIdsBlock( - existingSummarizeCmt.body + existingSummarizeCmtBody ) } @@ -140,13 +142,6 @@ export const codeReview = async ( return } - const commits = incrementalDiff.data.commits - - if (commits.length === 0) { - warning('Skipped: ommits is null') - return - } - // skip files if they are filtered out const filterSelectedFiles = [] const filterIgnoredFiles = [] @@ -159,6 +154,18 @@ export const codeReview = async ( } } + if (filterSelectedFiles.length === 0) { + warning('Skipped: filterSelectedFiles is null') + return + } + + const commits = incrementalDiff.data.commits + + if (commits.length === 0) { + warning('Skipped: commits is null') + return + } + // find hunks to review const filteredFiles: Array< [string, string, string, Array<[number, number, string]>] | null @@ -255,6 +262,16 @@ ${hunks.oldHunk} return } + // update the existing comment with in progress status + const inProgressSummarizeCmt = commenter.addInProgressStatus( + existingSummarizeCmtBody, + context.payload.pull_request.head.sha, + highestReviewedCommitId + ) + + // add in progress status to the summarize comment + await commenter.comment(`${inProgressSummarizeCmt}`, SUMMARIZE_TAG, 'replace') + const summariesFailed: string[] = [] const doSummary = async ( @@ -385,7 +402,7 @@ ${filename}: ${summary} if (releaseNotesResponse === '') { info('release notes: nothing obtained from openai') } else { - let message = '### Summary by OpenAI\n\n' + let message = '### Summary by CodeRabbit OSS\n\n' message += releaseNotesResponse try { await commenter.updateDescription( @@ -412,20 +429,24 @@ ${RAW_SUMMARY_END_TAG} ${SHORT_SUMMARY_START_TAG} ${inputs.shortSummary} ${SHORT_SUMMARY_END_TAG} + --- -### Chat with 🤖 OpenAI Bot (\`@openai\`) +
+Notes + +### Chat with Image description CodeRabbit Bot (\`@coderabbitai\`) - Reply on review comments left by this bot to ask follow-up questions. A review comment is a comment on a diff or a file. -- Invite the bot into a review comment chain by tagging \`@openai\` in a reply. +- Invite the bot into a review comment chain by tagging \`@coderabbitai\` in a reply. ### Code suggestions - The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned. - You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off. ### Ignoring further reviews -- Type \`@openai: ignore\` anywhere in the PR description to ignore further reviews from the bot. +- Type \`@coderabbitai: ignore\` anywhere in the PR description to ignore further reviews from the bot. ---- +
${ filterIgnoredFiles.length > 0 @@ -653,11 +674,6 @@ ${commentChain} await Promise.all(reviewPromises) summarizeComment += ` ---- -In the recent run, only the files that changed from the \`base\` of the PR and between \`${highestReviewedCommitId}\` and \`${ - context.payload.pull_request.head.sha - }\` commits were reviewed. - ${ reviewsFailed.length > 0 ? `