diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 827ec4e7..00000000 --- a/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "arrowParens": "always", - "bracketSameLine": true, - "printWidth": 80, - "proseWrap": "always", - "trailingComma": "all" -} diff --git a/.prettierrc.json b/.prettierrc.json index c34bafcb..7a6245d1 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -6,5 +6,6 @@ "singleQuote": true, "trailingComma": "none", "bracketSpacing": false, - "arrowParens": "avoid" + "arrowParens": "avoid", + "proseWrap": "always" } diff --git a/action.yml b/action.yml index 72b30ce6..4b5abf5a 100644 --- a/action.yml +++ b/action.yml @@ -1,22 +1,22 @@ -name: "OpenAI-based PR Reviewer & Summarizer" -description: "OpenAI-based PR Reviewer and Summarizer" +name: 'OpenAI-based PR Reviewer & Summarizer' +description: 'OpenAI-based PR Reviewer and Summarizer' branding: - icon: "aperture" - color: "orange" -author: "FluxNinja, Inc." + icon: 'aperture' + color: 'orange' +author: 'FluxNinja, Inc.' inputs: debug: required: false - description: "Enable debug mode" - default: "false" + description: 'Enable debug mode' + default: 'false' temperature: required: false - description: "Temperature for GPT model" - default: "0.0" + description: 'Temperature for GPT model' + default: '0.0' review_comment_lgtm: required: false - description: "Leave comments even if the patch is LGTM" - default: "false" + description: 'Leave comments even if the patch is LGTM' + default: 'false' path_filters: required: false description: | @@ -44,7 +44,7 @@ inputs: !**/vendor/** system_message: required: false - description: "System message to be sent to OpenAI" + description: 'System message to be sent to OpenAI' default: | You are a very experienced software engineer. You are able to thoroughly review code changes and uncover issues (if there are any) such as logic errors, syntax errors, out of bound errors, potential data races, @@ -53,7 +53,7 @@ inputs: We will be doing code reviews today. summarize_beginning: required: false - description: "The prompt for the whole pull request" + description: 'The prompt for the whole pull request' default: | $system_message @@ -64,7 +64,9 @@ inputs: I have a pull request with title "$title" and the description is as follows, - > $description. + ``` + $description + ``` Next, I will be providing you diffs for each file for you to summarize. In every response, you will update the high-level summary of the entire pull request, the table for each file's @@ -74,11 +76,17 @@ inputs: Reply "OK" to confirm that you are ready to receive the diffs for summarization. summarize_file_diff: required: false - description: "The prompt for each file diff" + description: 'The prompt for each file diff' default: | - Providing diff for `$filename`. + Summary generated by you for other files, is as follows and you can use + this summary as a basis for your response. - I would like you to summarize the diff and add the summary for this file in the table. + ``` + $summary + ``` + + I am providing diff for `$filename` below. I would like you to summarize the diff and add the summary + for this file in the table. In response, please provide the updated high-level summary, the summary table of the files we have summarized so far and your overall feedback to the developer. @@ -88,9 +96,16 @@ inputs: ``` summarize: required: false - description: "The prompt for final summarization response" + description: 'The prompt for final summarization response' default: | - This is the end of summarization session. Please provide the final response as follows in + This is the end of summarization session. Below is the summary you have generated so far in our previous + conversations. Please use this as a basis for your final response. + + ``` + $summary + ``` + + Please provide the final response as follows in the `markdown` format with the following content: - Thank the user for letting you participate in the code review. - High-level summary (focus on the purpose and intent within 80 words) @@ -101,7 +116,7 @@ inputs: request. summarize_release_notes: required: false - description: "The prompt for generating release notes" + description: 'The prompt for generating release notes' default: | Next, release notes in `markdown` format for this pull request that focuses on the purpose of this PR. If needed, you can classify the changes as "New Feature", "Bug fix", @@ -111,7 +126,7 @@ inputs: used as is in our release notes. review_beginning: required: false - description: "The beginning prompt of a code review dialog" + description: 'The beginning prompt of a code review dialog' default: | $system_message @@ -121,16 +136,20 @@ inputs: I have a pull request with title "$title" and the description is as follows, - > $description. + ``` + $description + ``` OpenAI generated summary is as follows, - > $summary + ``` + $summary + ``` Reply "OK" to confirm that you are ready for further instructions. review_file: required: false - description: "The prompt for each file" + description: 'The prompt for each file' default: | Providing `$filename` content as context. Please use this context when reviewing patches. @@ -139,7 +158,7 @@ inputs: ``` review_file_diff: required: false - description: "The prompt for each file diff" + description: 'The prompt for each file diff' default: | Providing entire diff for `$filename` as context. Please use this context when reviewing patches. @@ -148,7 +167,7 @@ inputs: ``` review_patch_begin: required: false - description: "The prompt for each file diff" + description: 'The prompt for each file diff' default: | Next, I will send you a series of patches, each of them consists of a diff snippet, and you need to do a brief code review for every patch, and tell me any bug risk or improvement @@ -159,7 +178,7 @@ inputs: preferred for your responses. Reply "OK" to confirm. review_patch: required: false - description: "The prompt for each chunks/patches" + description: 'The prompt for each chunks/patches' default: | $filename @@ -167,5 +186,5 @@ inputs: $patch ``` runs: - using: "node16" - main: "dist/index.js" + using: 'node16' + main: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index 27af8e40..4d91b38c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -28596,7 +28596,7 @@ class Prompts { summarize_file_diff; summarize; summarize_release_notes; - constructor(review_beginning = "", review_file = "", review_file_diff = "", review_patch_begin = "", review_patch = "", summarize_beginning = "", summarize_file_diff = "", summarize = "", summarize_release_notes = "") { + constructor(review_beginning = '', review_file = '', review_file_diff = '', review_patch_begin = '', review_patch = '', summarize_beginning = '', summarize_file_diff = '', summarize = '', summarize_release_notes = '') { this.review_beginning = review_beginning; this.review_file = review_file; this.review_file_diff = review_file_diff; @@ -28645,7 +28645,7 @@ class Inputs { file_diff; patch; diff; - constructor(system_message = "", title = "", description = "", summary = "", filename = "", file_content = "", file_diff = "", patch = "", diff = "") { + constructor(system_message = '', title = 'no title provided', description = 'no description provided', summary = 'no summary so far', filename = '', file_content = '', file_diff = '', patch = '', diff = '') { this.system_message = system_message; this.title = title; this.description = description; @@ -28658,34 +28658,34 @@ class Inputs { } render(content) { if (!content) { - return ""; + return ''; } if (this.system_message) { - content = content.replace("$system_message", this.system_message); + content = content.replace('$system_message', this.system_message); } if (this.title) { - content = content.replace("$title", this.title); + content = content.replace('$title', this.title); } if (this.description) { - content = content.replace("$description", this.description); + content = content.replace('$description', this.description); } if (this.summary) { - content = content.replace("$summary", this.summary); + content = content.replace('$summary', this.summary); } if (this.filename) { - content = content.replace("$filename", this.filename); + content = content.replace('$filename', this.filename); } if (this.file_content) { - content = content.replace("$file_content", this.file_content); + content = content.replace('$file_content', this.file_content); } if (this.file_diff) { - content = content.replace("$file_diff", this.file_diff); + content = content.replace('$file_diff', this.file_diff); } if (this.patch) { - content = content.replace("$patch", this.patch); + content = content.replace('$patch', this.patch); } if (this.diff) { - content = content.replace("$diff", this.diff); + content = content.replace('$diff', this.diff); } return content; } @@ -28696,7 +28696,7 @@ class Options { path_filters; system_message; temperature; - constructor(debug, review_comment_lgtm = false, path_filters = null, system_message = "", temperature = "0.0") { + constructor(debug, review_comment_lgtm = false, path_filters = null, system_message = '', temperature = '0.0') { this.debug = debug; this.review_comment_lgtm = review_comment_lgtm; this.path_filters = new PathFilter(path_filters); @@ -28718,7 +28718,7 @@ class PathFilter { for (const rule of rules) { const trimmed = rule?.trim(); if (trimmed) { - if (trimmed.startsWith("!")) { + if (trimmed.startsWith('!')) { this.rules.push([trimmed.substring(1).trim(), true]); } else { @@ -29030,16 +29030,16 @@ function get_token_count(input) { -const review_token = core.getInput("token") - ? core.getInput("token") +const review_token = core.getInput('token') + ? core.getInput('token') : process.env.GITHUB_TOKEN; const review_octokit = new dist_node/* Octokit */.v({ auth: `token ${review_token}` }); const review_context = github.context; const review_repo = review_context.repo; const MAX_TOKENS_FOR_EXTRA_CONTENT = 2500; const codeReview = async (bot, options, prompts) => { - if (review_context.eventName !== "pull_request" && - review_context.eventName !== "pull_request_target") { + if (review_context.eventName !== 'pull_request' && + review_context.eventName !== 'pull_request_target') { core.warning(`Skipped: current event is ${review_context.eventName}, only support pull_request event`); return; } @@ -29060,7 +29060,7 @@ const codeReview = async (bot, options, prompts) => { owner: review_repo.owner, repo: review_repo.repo, base: review_context.payload.pull_request.base.sha, - head: review_context.payload.pull_request.head.sha, + head: review_context.payload.pull_request.head.sha }); const { files, commits } = diff.data; if (!files) { @@ -29075,18 +29075,18 @@ const codeReview = async (bot, options, prompts) => { continue; } // retrieve file contents - let file_content = ""; + let file_content = ''; try { const contents = await review_octokit.repos.getContent({ owner: review_repo.owner, repo: review_repo.repo, path: file.filename, - ref: review_context.payload.pull_request.base.sha, + ref: review_context.payload.pull_request.base.sha }); if (contents.data) { if (!Array.isArray(contents.data)) { - if (contents.data.type === "file" && contents.data.content) { - file_content = Buffer.from(contents.data.content, "base64").toString(); + if (contents.data.type === 'file' && contents.data.content) { + file_content = Buffer.from(contents.data.content, 'base64').toString(); } } } @@ -29094,7 +29094,7 @@ const codeReview = async (bot, options, prompts) => { catch (error) { core.warning(`Failed to get file contents: ${error}, skipping.`); } - let file_diff = ""; + let file_diff = ''; if (file.patch) { core.info(`diff for ${file.filename}: ${file.patch}`); file_diff = file.patch; @@ -29113,6 +29113,8 @@ const codeReview = async (bot, options, prompts) => { const [, summarize_begin_ids] = await bot.chat(prompts.render_summarize_beginning(inputs), {}); let next_summarize_ids = summarize_begin_ids; for (const [filename, file_content, file_diff] of files_to_review) { + // reset chat session for each file while summarizing + next_summarize_ids = summarize_begin_ids; inputs.filename = filename; inputs.file_content = file_content; inputs.file_diff = file_diff; @@ -29122,10 +29124,11 @@ const codeReview = async (bot, options, prompts) => { // summarize diff const [summarize_resp, summarize_diff_ids] = await bot.chat(prompts.render_summarize_file_diff(inputs), next_summarize_ids); if (!summarize_resp) { - core.info("summarize: nothing obtained from openai"); + core.info('summarize: nothing obtained from openai'); } else { next_summarize_ids = summarize_diff_ids; + inputs.summary = summarize_resp; } } } @@ -29133,30 +29136,30 @@ const codeReview = async (bot, options, prompts) => { // final summary const [summarize_final_response, summarize_final_response_ids] = await bot.chat(prompts.render_summarize(inputs), next_summarize_ids); if (!summarize_final_response) { - core.info("summarize: nothing obtained from openai"); + core.info('summarize: nothing obtained from openai'); } else { inputs.summary = summarize_final_response; next_summarize_ids = summarize_final_response_ids; - const tag = ""; - await commenter.comment(`${summarize_final_response}`, tag, "replace"); + const tag = ''; + await commenter.comment(`${summarize_final_response}`, tag, 'replace'); } // final release notes const [release_notes_response, release_notes_ids] = await bot.chat(prompts.render_summarize_release_notes(inputs), next_summarize_ids); if (!release_notes_response) { - core.info("release notes: nothing obtained from openai"); + core.info('release notes: nothing obtained from openai'); } else { next_summarize_ids = release_notes_ids; const description = inputs.description; - let message = "### Summary by OpenAI\n\n"; + let message = '### Summary by OpenAI\n\n'; message += release_notes_response; commenter.update_description(review_context.payload.pull_request.number, description, message); } // Review Stage const [, review_begin_ids] = await bot.chat(prompts.render_review_beginning(inputs), {}); let next_review_ids = review_begin_ids; - for (const [filename, file_content, file_diff, patches,] of files_to_review) { + for (const [filename, file_content, file_diff, patches] of files_to_review) { inputs.filename = filename; inputs.file_content = file_content; inputs.file_diff = file_diff; @@ -29168,7 +29171,7 @@ const codeReview = async (bot, options, prompts) => { // review file const [resp, review_file_ids] = await bot.chat(prompts.render_review_file(inputs), next_review_ids); if (!resp) { - core.info("review: nothing obtained from openai"); + core.info('review: nothing obtained from openai'); } else { next_review_ids = review_file_ids; @@ -29184,7 +29187,7 @@ const codeReview = async (bot, options, prompts) => { // review diff const [resp, review_diff_ids] = await bot.chat(prompts.render_review_file_diff(inputs), next_review_ids); if (!resp) { - core.info("review: nothing obtained from openai"); + core.info('review: nothing obtained from openai'); } else { next_review_ids = review_diff_ids; @@ -29202,11 +29205,11 @@ const codeReview = async (bot, options, prompts) => { inputs.patch = patch; const [response, patch_ids] = await bot.chat(prompts.render_review_patch(inputs), next_review_ids); if (!response) { - core.info("review: nothing obtained from openai"); + core.info('review: nothing obtained from openai'); continue; } next_review_ids = patch_ids; - if (!options.review_comment_lgtm && response.includes("LGTM")) { + if (!options.review_comment_lgtm && response.includes('LGTM')) { continue; } try { diff --git a/src/options.ts b/src/options.ts index 9a37f5b8..ec0d1e51 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,186 +1,186 @@ -import * as core from "@actions/core"; -import { minimatch } from "minimatch"; +import * as core from '@actions/core' +import {minimatch} from 'minimatch' export class Prompts { - review_beginning: string; - review_file: string; - review_file_diff: string; - review_patch_begin: string; - review_patch: string; - summarize_beginning: string; - summarize_file_diff: string; - summarize: string; - summarize_release_notes: string; + review_beginning: string + review_file: string + review_file_diff: string + review_patch_begin: string + review_patch: string + summarize_beginning: string + summarize_file_diff: string + summarize: string + summarize_release_notes: string constructor( - review_beginning = "", - review_file = "", - review_file_diff = "", - review_patch_begin = "", - review_patch = "", - summarize_beginning = "", - summarize_file_diff = "", - summarize = "", - summarize_release_notes = "", + review_beginning = '', + review_file = '', + review_file_diff = '', + review_patch_begin = '', + review_patch = '', + summarize_beginning = '', + summarize_file_diff = '', + summarize = '', + summarize_release_notes = '' ) { - this.review_beginning = review_beginning; - this.review_file = review_file; - this.review_file_diff = review_file_diff; - this.review_patch_begin = review_patch_begin; - this.review_patch = review_patch; - this.summarize_beginning = summarize_beginning; - this.summarize_file_diff = summarize_file_diff; - this.summarize = summarize; - this.summarize_release_notes = summarize_release_notes; + this.review_beginning = review_beginning + this.review_file = review_file + this.review_file_diff = review_file_diff + this.review_patch_begin = review_patch_begin + this.review_patch = review_patch + this.summarize_beginning = summarize_beginning + this.summarize_file_diff = summarize_file_diff + this.summarize = summarize + this.summarize_release_notes = summarize_release_notes } render_review_beginning(inputs: Inputs): string { - return inputs.render(this.review_beginning); + return inputs.render(this.review_beginning) } render_review_file(inputs: Inputs): string { - return inputs.render(this.review_file); + return inputs.render(this.review_file) } render_review_file_diff(inputs: Inputs): string { - return inputs.render(this.review_file_diff); + return inputs.render(this.review_file_diff) } render_review_patch_begin(inputs: Inputs): string { - return inputs.render(this.review_patch_begin); + return inputs.render(this.review_patch_begin) } render_review_patch(inputs: Inputs): string { - return inputs.render(this.review_patch); + return inputs.render(this.review_patch) } render_summarize_beginning(inputs: Inputs): string { - return inputs.render(this.summarize_beginning); + return inputs.render(this.summarize_beginning) } render_summarize_file_diff(inputs: Inputs): string { - return inputs.render(this.summarize_file_diff); + return inputs.render(this.summarize_file_diff) } render_summarize(inputs: Inputs): string { - return inputs.render(this.summarize); + return inputs.render(this.summarize) } render_summarize_release_notes(inputs: Inputs): string { - return inputs.render(this.summarize_release_notes); + return inputs.render(this.summarize_release_notes) } } export class Inputs { - system_message: string; - title: string; - description: string; - summary: string; - filename: string; - file_content: string; - file_diff: string; - patch: string; - diff: string; + system_message: string + title: string + description: string + summary: string + filename: string + file_content: string + file_diff: string + patch: string + diff: string constructor( - system_message = "", - title = "", - description = "", - summary = "", - filename = "", - file_content = "", - file_diff = "", - patch = "", - diff = "", + system_message = '', + title = 'no title provided', + description = 'no description provided', + summary = 'no summary so far', + filename = '', + file_content = '', + file_diff = '', + patch = '', + diff = '' ) { - this.system_message = system_message; - this.title = title; - this.description = description; - this.summary = summary; - this.filename = filename; - this.file_content = file_content; - this.file_diff = file_diff; - this.patch = patch; - this.diff = diff; + this.system_message = system_message + this.title = title + this.description = description + this.summary = summary + this.filename = filename + this.file_content = file_content + this.file_diff = file_diff + this.patch = patch + this.diff = diff } render(content: string): string { if (!content) { - return ""; + return '' } if (this.system_message) { - content = content.replace("$system_message", this.system_message); + content = content.replace('$system_message', this.system_message) } if (this.title) { - content = content.replace("$title", this.title); + content = content.replace('$title', this.title) } if (this.description) { - content = content.replace("$description", this.description); + content = content.replace('$description', this.description) } if (this.summary) { - content = content.replace("$summary", this.summary); + content = content.replace('$summary', this.summary) } if (this.filename) { - content = content.replace("$filename", this.filename); + content = content.replace('$filename', this.filename) } if (this.file_content) { - content = content.replace("$file_content", this.file_content); + content = content.replace('$file_content', this.file_content) } if (this.file_diff) { - content = content.replace("$file_diff", this.file_diff); + content = content.replace('$file_diff', this.file_diff) } if (this.patch) { - content = content.replace("$patch", this.patch); + content = content.replace('$patch', this.patch) } if (this.diff) { - content = content.replace("$diff", this.diff); + content = content.replace('$diff', this.diff) } - return content; + return content } } export class Options { - debug: boolean; - review_comment_lgtm: boolean; - path_filters: PathFilter; - system_message: string; - temperature: number; + debug: boolean + review_comment_lgtm: boolean + path_filters: PathFilter + system_message: string + temperature: number constructor( debug: boolean, review_comment_lgtm = false, path_filters: string[] | null = null, - system_message = "", - temperature = "0.0", + system_message = '', + temperature = '0.0' ) { - this.debug = debug; - this.review_comment_lgtm = review_comment_lgtm; - this.path_filters = new PathFilter(path_filters); - this.system_message = system_message; + this.debug = debug + this.review_comment_lgtm = review_comment_lgtm + this.path_filters = new PathFilter(path_filters) + this.system_message = system_message // convert temperature to number - this.temperature = parseFloat(temperature); + this.temperature = parseFloat(temperature) } check_path(path: string): boolean { - const ok = this.path_filters.check(path); - core.info(`checking path: ${path} => ${ok}`); - return ok; + const ok = this.path_filters.check(path) + core.info(`checking path: ${path} => ${ok}`) + return ok } } export class PathFilter { - private rules: [string /* rule */, boolean /* exclude */][]; + private rules: [string /* rule */, boolean /* exclude */][] constructor(rules: string[] | null = null) { - this.rules = []; + this.rules = [] if (rules) { for (const rule of rules) { - const trimmed = rule?.trim(); + const trimmed = rule?.trim() if (trimmed) { - if (trimmed.startsWith("!")) { - this.rules.push([trimmed.substring(1).trim(), true]); + if (trimmed.startsWith('!')) { + this.rules.push([trimmed.substring(1).trim(), true]) } else { - this.rules.push([trimmed, false]); + this.rules.push([trimmed, false]) } } } @@ -188,23 +188,23 @@ export class PathFilter { } check(path: string): boolean { - let include_all = this.rules.length == 0; - let matched = false; + let include_all = this.rules.length == 0 + let matched = false for (const [rule, exclude] of this.rules) { if (exclude) { if (minimatch(path, rule)) { - return false; + return false } - include_all = true; + include_all = true } else { if (minimatch(path, rule)) { - matched = true; - include_all = false; + matched = true + include_all = false } else { - return false; + return false } } } - return include_all || matched; + return include_all || matched } } diff --git a/src/review.ts b/src/review.ts index 2bfdddf3..11ecfc6b 100644 --- a/src/review.ts +++ b/src/review.ts @@ -1,108 +1,108 @@ -import * as core from "@actions/core"; -import * as github from "@actions/github"; -import { Octokit } from "@octokit/action"; -import { Bot } from "./bot.js"; -import { Commenter } from "./commenter.js"; -import { Inputs, Options, Prompts } from "./options.js"; -import * as tokenizer from "./tokenizer.js"; +import * as core from '@actions/core' +import * as github from '@actions/github' +import {Octokit} from '@octokit/action' +import {Bot} from './bot.js' +import {Commenter} from './commenter.js' +import {Inputs, Options, Prompts} from './options.js' +import * as tokenizer from './tokenizer.js' -const token = core.getInput("token") - ? core.getInput("token") - : process.env.GITHUB_TOKEN; -const octokit = new Octokit({ auth: `token ${token}` }); -const context = github.context; -const repo = context.repo; +const token = core.getInput('token') + ? core.getInput('token') + : process.env.GITHUB_TOKEN +const octokit = new Octokit({auth: `token ${token}`}) +const context = github.context +const repo = context.repo -const MAX_TOKENS_FOR_EXTRA_CONTENT = 2500; +const MAX_TOKENS_FOR_EXTRA_CONTENT = 2500 export const codeReview = async ( bot: Bot, options: Options, - prompts: Prompts, + prompts: Prompts ) => { if ( - context.eventName !== "pull_request" && - context.eventName !== "pull_request_target" + context.eventName !== 'pull_request' && + context.eventName !== 'pull_request_target' ) { core.warning( - `Skipped: current event is ${context.eventName}, only support pull_request event`, - ); - return; + `Skipped: current event is ${context.eventName}, only support pull_request event` + ) + return } if (!context.payload.pull_request) { - core.warning(`Skipped: context.payload.pull_request is null`); - return; + core.warning(`Skipped: context.payload.pull_request is null`) + return } - const commenter: Commenter = new Commenter(); + const commenter: Commenter = new Commenter() - const inputs: Inputs = new Inputs(); - inputs.title = context.payload.pull_request.title; + const inputs: Inputs = new Inputs() + inputs.title = context.payload.pull_request.title if (context.payload.pull_request.body) { inputs.description = commenter.get_description( - context.payload.pull_request.body, - ); + context.payload.pull_request.body + ) } // as gpt-3.5-turbo isn't paying attention to system message, add to inputs for now - inputs.system_message = options.system_message; + inputs.system_message = options.system_message // collect diff chunks const diff = await octokit.repos.compareCommits({ owner: repo.owner, repo: repo.repo, base: context.payload.pull_request.base.sha, - head: context.payload.pull_request.head.sha, - }); - const { files, commits } = diff.data; + head: context.payload.pull_request.head.sha + }) + const {files, commits} = diff.data if (!files) { - core.warning(`Skipped: diff.data.files is null`); - return; + core.warning(`Skipped: diff.data.files is null`) + return } // find patches to review - const files_to_review: [string, string, string, [number, string][]][] = []; + const files_to_review: [string, string, string, [number, string][]][] = [] for (const file of files) { if (!options.check_path(file.filename)) { - core.info(`skip for excluded path: ${file.filename}`); - continue; + core.info(`skip for excluded path: ${file.filename}`) + continue } // retrieve file contents - let file_content = ""; + let file_content = '' try { const contents = await octokit.repos.getContent({ owner: repo.owner, repo: repo.repo, path: file.filename, - ref: context.payload.pull_request.base.sha, - }); + ref: context.payload.pull_request.base.sha + }) if (contents.data) { if (!Array.isArray(contents.data)) { - if (contents.data.type === "file" && contents.data.content) { + if (contents.data.type === 'file' && contents.data.content) { file_content = Buffer.from( contents.data.content, - "base64", - ).toString(); + 'base64' + ).toString() } } } } catch (error) { - core.warning(`Failed to get file contents: ${error}, skipping.`); + core.warning(`Failed to get file contents: ${error}, skipping.`) } - let file_diff = ""; + let file_diff = '' if (file.patch) { - core.info(`diff for ${file.filename}: ${file.patch}`); - file_diff = file.patch; + core.info(`diff for ${file.filename}: ${file.patch}`) + file_diff = file.patch } - const patches: [number, string][] = []; + const patches: [number, string][] = [] for (const patch of split_patch(file.patch)) { - const line = patch_comment_line(patch); - patches.push([line, patch]); + const line = patch_comment_line(patch) + patches.push([line, patch]) } if (patches.length > 0) { - files_to_review.push([file.filename, file_content, file_diff, patches]); + files_to_review.push([file.filename, file_content, file_diff, patches]) } } @@ -110,143 +110,146 @@ export const codeReview = async ( // Summary Stage const [, summarize_begin_ids] = await bot.chat( prompts.render_summarize_beginning(inputs), - {}, - ); - let next_summarize_ids = summarize_begin_ids; + {} + ) + let next_summarize_ids = summarize_begin_ids for (const [filename, file_content, file_diff] of files_to_review) { - inputs.filename = filename; - inputs.file_content = file_content; - inputs.file_diff = file_diff; + // reset chat session for each file while summarizing + next_summarize_ids = summarize_begin_ids + inputs.filename = filename + inputs.file_content = file_content + inputs.file_diff = file_diff if (file_diff.length > 0) { - const file_diff_tokens = tokenizer.get_token_count(file_diff); + const file_diff_tokens = tokenizer.get_token_count(file_diff) if (file_diff_tokens < MAX_TOKENS_FOR_EXTRA_CONTENT) { // summarize diff const [summarize_resp, summarize_diff_ids] = await bot.chat( prompts.render_summarize_file_diff(inputs), - next_summarize_ids, - ); + next_summarize_ids + ) if (!summarize_resp) { - core.info("summarize: nothing obtained from openai"); + core.info('summarize: nothing obtained from openai') } else { - next_summarize_ids = summarize_diff_ids; + next_summarize_ids = summarize_diff_ids + inputs.summary = summarize_resp } } } } // final summary const [summarize_final_response, summarize_final_response_ids] = - await bot.chat(prompts.render_summarize(inputs), next_summarize_ids); + await bot.chat(prompts.render_summarize(inputs), next_summarize_ids) if (!summarize_final_response) { - core.info("summarize: nothing obtained from openai"); + core.info('summarize: nothing obtained from openai') } else { - inputs.summary = summarize_final_response; + inputs.summary = summarize_final_response - next_summarize_ids = summarize_final_response_ids; + next_summarize_ids = summarize_final_response_ids const tag = - ""; - await commenter.comment(`${summarize_final_response}`, tag, "replace"); + '' + await commenter.comment(`${summarize_final_response}`, tag, 'replace') } // final release notes const [release_notes_response, release_notes_ids] = await bot.chat( prompts.render_summarize_release_notes(inputs), - next_summarize_ids, - ); + next_summarize_ids + ) if (!release_notes_response) { - core.info("release notes: nothing obtained from openai"); + core.info('release notes: nothing obtained from openai') } else { - next_summarize_ids = release_notes_ids; - const description = inputs.description; - let message = "### Summary by OpenAI\n\n"; - message += release_notes_response; + next_summarize_ids = release_notes_ids + const description = inputs.description + let message = '### Summary by OpenAI\n\n' + message += release_notes_response commenter.update_description( context.payload.pull_request.number, description, - message, - ); + message + ) } // Review Stage const [, review_begin_ids] = await bot.chat( prompts.render_review_beginning(inputs), - {}, - ); - let next_review_ids = review_begin_ids; + {} + ) + let next_review_ids = review_begin_ids for (const [ filename, file_content, file_diff, - patches, + patches ] of files_to_review) { - inputs.filename = filename; - inputs.file_content = file_content; - inputs.file_diff = file_diff; + inputs.filename = filename + inputs.file_content = file_content + inputs.file_diff = file_diff // reset chat session for each file while reviewing - next_review_ids = review_begin_ids; + next_review_ids = review_begin_ids if (file_content.length > 0) { - const file_content_tokens = tokenizer.get_token_count(file_content); + const file_content_tokens = tokenizer.get_token_count(file_content) if (file_content_tokens < MAX_TOKENS_FOR_EXTRA_CONTENT) { // review file const [resp, review_file_ids] = await bot.chat( prompts.render_review_file(inputs), - next_review_ids, - ); + next_review_ids + ) if (!resp) { - core.info("review: nothing obtained from openai"); + core.info('review: nothing obtained from openai') } else { - next_review_ids = review_file_ids; + next_review_ids = review_file_ids } } else { core.info( - `skip sending content of file: ${inputs.filename} due to token count: ${file_content_tokens}`, - ); + `skip sending content of file: ${inputs.filename} due to token count: ${file_content_tokens}` + ) } } if (file_diff.length > 0) { - const file_diff_tokens = tokenizer.get_token_count(file_diff); + const file_diff_tokens = tokenizer.get_token_count(file_diff) if (file_diff_tokens < MAX_TOKENS_FOR_EXTRA_CONTENT) { // review diff const [resp, review_diff_ids] = await bot.chat( prompts.render_review_file_diff(inputs), - next_review_ids, - ); + next_review_ids + ) if (!resp) { - core.info("review: nothing obtained from openai"); + core.info('review: nothing obtained from openai') } else { - next_review_ids = review_diff_ids; + next_review_ids = review_diff_ids } } else { core.info( - `skip sending diff of file: ${inputs.filename} due to token count: ${file_diff_tokens}`, - ); + `skip sending diff of file: ${inputs.filename} due to token count: ${file_diff_tokens}` + ) } } // review_patch_begin const [, patch_begin_ids] = await bot.chat( prompts.render_review_patch_begin(inputs), - next_review_ids, - ); - next_review_ids = patch_begin_ids; + next_review_ids + ) + next_review_ids = patch_begin_ids for (const [line, patch] of patches) { - core.info(`Reviewing ${filename}:${line} with openai ...`); - inputs.patch = patch; + core.info(`Reviewing ${filename}:${line} with openai ...`) + inputs.patch = patch const [response, patch_ids] = await bot.chat( prompts.render_review_patch(inputs), - next_review_ids, - ); + next_review_ids + ) if (!response) { - core.info("review: nothing obtained from openai"); - continue; + core.info('review: nothing obtained from openai') + continue } - next_review_ids = patch_ids; - if (!options.review_comment_lgtm && response.includes("LGTM")) { - continue; + next_review_ids = patch_ids + if (!options.review_comment_lgtm && response.includes('LGTM')) { + continue } try { await commenter.review_comment( @@ -254,55 +257,55 @@ export const codeReview = async ( commits[commits.length - 1].sha, filename, line, - `${response}`, - ); + `${response}` + ) } catch (e: any) { core.warning(`Failed to comment: ${e}, skipping. backtrace: ${e.stack} filename: ${filename} line: ${line} - patch: ${patch}`); + patch: ${patch}`) } } } } -}; +} // Write a function that takes diff for a single file as a string // and splits the diff into separate patches const split_patch = (patch: string | null | undefined): string[] => { if (!patch) { - return []; + return [] } - const pattern = /(^@@ -(\d+),(\d+) \+(\d+),(\d+) @@).*$/gm; + const pattern = /(^@@ -(\d+),(\d+) \+(\d+),(\d+) @@).*$/gm - const result: string[] = []; - let last = -1; - let match: RegExpExecArray | null; + const result: string[] = [] + let last = -1 + let match: RegExpExecArray | null while ((match = pattern.exec(patch)) !== null) { if (last === -1) { - last = match.index; + last = match.index } else { - result.push(patch.substring(last, match.index)); - last = match.index; + result.push(patch.substring(last, match.index)) + last = match.index } } if (last !== -1) { - result.push(patch.substring(last)); + result.push(patch.substring(last)) } - return result; -}; + return result +} const patch_comment_line = (patch: string): number => { - const pattern = /(^@@ -(\d+),(\d+) \+(\d+),(\d+) @@)/gm; - const match = pattern.exec(patch); + const pattern = /(^@@ -(\d+),(\d+) \+(\d+),(\d+) @@)/gm + const match = pattern.exec(patch) if (match) { - const begin = parseInt(match[4]); - const diff = parseInt(match[5]); - return begin + diff - 1; + const begin = parseInt(match[4]) + const diff = parseInt(match[5]) + return begin + diff - 1 } else { - return -1; + return -1 } -}; +}