From 746e58415f882ccf09efae3e49f378315eafb17b Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 21:52:31 +0200 Subject: [PATCH 1/8] fix: optimize diff parse --- lib/joinStringsUntilMaxLength.ts | 12 +++++++----- utils/getCodeDiff.ts | 10 +++++++--- utils/parseDiff.ts | 19 ++++++++++++++++--- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/lib/joinStringsUntilMaxLength.ts b/lib/joinStringsUntilMaxLength.ts index d96277b..3206b06 100644 --- a/lib/joinStringsUntilMaxLength.ts +++ b/lib/joinStringsUntilMaxLength.ts @@ -1,22 +1,24 @@ export function joinStringsUntilMaxLength( parsedFiles: string[], maxLength: number -): string { - let combinedString = "" +) { + let codeDiff = "" let currentLength = 0 + let maxLengthExceeded = false for (const file of parsedFiles) { const fileLength = file.length if (currentLength + fileLength <= maxLength) { - combinedString += file + codeDiff += file currentLength += fileLength } else { + maxLengthExceeded = true const remainingLength = maxLength - currentLength - combinedString += file.slice(0, remainingLength) + codeDiff += file.slice(0, remainingLength) break } } - return combinedString + return { codeDiff, maxLengthExceeded } } diff --git a/utils/getCodeDiff.ts b/utils/getCodeDiff.ts index cacbf02..74396d7 100644 --- a/utils/getCodeDiff.ts +++ b/utils/getCodeDiff.ts @@ -3,7 +3,7 @@ import { joinStringsUntilMaxLength } from "../lib/joinStringsUntilMaxLength" import { parseDiff } from "./parseDiff" const maxChanges = 1000 -const maxCodeDiff = 10000 +const maxCodeDiff = 11500 export const getCodeDiff = async ( owner: string, @@ -26,7 +26,11 @@ export const getCodeDiff = async ( // If the number of changes in a file is greater than `maxChanges` changes, the file will be skipped. // The codeDiff is the joined string of parsed files, up to a max length of `maxCodeDiff`. const { parsedFiles, skippedFiles } = parseDiff(diffContent, maxChanges) - const codeDiff = joinStringsUntilMaxLength(parsedFiles, maxCodeDiff) - return { codeDiff, skippedFiles } + const { codeDiff, maxLengthExceeded } = joinStringsUntilMaxLength( + parsedFiles, + maxCodeDiff + ) + + return { codeDiff, skippedFiles, maxLengthExceeded } } diff --git a/utils/parseDiff.ts b/utils/parseDiff.ts index 317a47c..1a011a7 100644 --- a/utils/parseDiff.ts +++ b/utils/parseDiff.ts @@ -3,18 +3,31 @@ type FileChange = { parsedFiles: string[]; skippedFiles: string[] } export function parseDiff(diff: string, maxChanges: number): FileChange { let skippedFiles: string[] = [] const files = diff.split(/diff --git /).slice(1) - const parsedFiles = files.map((file) => { + + const parsedFiles = files.flatMap((file) => { const lines = file.split("\n") + const filepath = lines[0].split(" ")[1] - const mainContent = lines.slice(4) + + // Don't consider diff in deleted files + if (lines[1].startsWith("deleted")) return `deleted ${filepath}` + + const mainContent = lines.slice(6).map((line) => { + if (line.startsWith("+") || line.startsWith("-")) { + const trimContent = line.slice(1).trim() + return line[0] + trimContent + } else return line.trim() + }) const changes = mainContent.filter( (line) => line.startsWith("+") || line.startsWith("-") ).length if (changes <= maxChanges) { - return file + return `${filepath}\n${mainContent.join("\n")}` } skippedFiles.push(`\`${filepath.slice(2)}\``) + return [] }) + return { parsedFiles, skippedFiles } } From cde80595310f0bc4259e64f68597a25d7a4f2881 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 21:57:10 +0200 Subject: [PATCH 2/8] fix: prompts and logic --- lib/replyIssueComment.ts | 4 ++-- lib/summarizePullRequest.ts | 13 +++++++++---- utils/github/testPayloadComment.ts | 2 +- utils/github/testPayloadSyncPr.ts | 4 ++-- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/replyIssueComment.ts b/lib/replyIssueComment.ts index 07386ad..f4ca8cd 100644 --- a/lib/replyIssueComment.ts +++ b/lib/replyIssueComment.ts @@ -29,11 +29,11 @@ export async function replyIssueComment(payload: any, octokit: Octokit) { const messages: ChatCompletionRequestMessage[] = [ { role: "system", - content: systemPrompt + content: `${systemPrompt}\n\nHere is the code diff:\n\n${codeDiff}` }, { role: "user", - content: `${question}\n\nHere is the code diff:\n\n${codeDiff}` + content: `${question}` } ] diff --git a/lib/summarizePullRequest.ts b/lib/summarizePullRequest.ts index 679a612..43b7d9f 100644 --- a/lib/summarizePullRequest.ts +++ b/lib/summarizePullRequest.ts @@ -6,7 +6,7 @@ import { getCodeDiff } from "../utils/getCodeDiff" export const startDescription = "\n\n" export const endDescription = "" const systemPrompt = - 'You are a Git diff assistant. Always begin with "This PR". Given a code diff, you provide a simple description in prose, in less than 300 chars, which sums up the changes. Continue with "\n\n### Detailed summary\n" and make a comprehensive list of all changes, excluding any eventual skipped files. Be concise. Always wrap file names, functions, objects and similar in backticks (`).' + "You are a Git diff assistant. Given a code diff, you provide a clear and concise description of its content. Always wrap file names, functions, objects and similar in backticks (`)." export async function summarizePullRequest(payload: any, octokit: Octokit) { // Get relevant PR information @@ -18,7 +18,7 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { } // Get the diff content using Octokit and GitHub API - const { codeDiff, skippedFiles } = await getCodeDiff( + const { codeDiff, skippedFiles, maxLengthExceeded } = await getCodeDiff( owner, repo, pull_number, @@ -30,11 +30,12 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { const messages: ChatCompletionRequestMessage[] = [ { role: "system", - content: systemPrompt + content: `${systemPrompt}\n\nHere is the code diff:\n\n${codeDiff}` }, { role: "user", - content: `Here is the code diff:\n\n${codeDiff}` + content: + 'Starting with "This PR", clearly explain the focus of this PR in prose, in less than 300 characters. Then follow up with "\n\n### Detailed summary\n" and make a comprehensive list of all changes.' } ] @@ -55,6 +56,10 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { ", " )}` : "" + }${ + maxLengthExceeded + ? "\n\n> The code diff exceeds the max number of characters, so this overview may be incomplete." + : "" }\n\n${endDescription}` const description = hasCodexCommented diff --git a/utils/github/testPayloadComment.ts b/utils/github/testPayloadComment.ts index 8adf62c..0dfadb7 100644 --- a/utils/github/testPayloadComment.ts +++ b/utils/github/testPayloadComment.ts @@ -6,7 +6,7 @@ export const testPayloadComment = { }, comment: { - body: "/ask-codex Describe the changes in the homepage UI" + body: "/ask-codex what changes have been done in the homepage?" }, sender: { login: "jjranalli" diff --git a/utils/github/testPayloadSyncPr.ts b/utils/github/testPayloadSyncPr.ts index 00ca963..f1512cc 100644 --- a/utils/github/testPayloadSyncPr.ts +++ b/utils/github/testPayloadSyncPr.ts @@ -6,9 +6,9 @@ export const testPayloadSyncPr = { pull_request: { diff_url: "https://github.com/decentralizedlabs/pr-codex/pull/4.diff", number: 4, - // body: null, + body: null, // body: "\n\n\n\n## PR-Codex overview\nThis PR adds a new feature to the project: a GitHub app that explains and summarizes PR code diffs. It includes a new `github/route.ts` file and updates several existing files, including `README.md`, `Homepage.tsx`, `DefaultHead.tsx`, `AppLayout.tsx`, `Footer.tsx`, and `Navbar.tsx`.\n\n> The following files were skipped due to too many changes: `package-lock.json`.\n\n", - body: "\n\nthis is a test", + // body: "\n\nthis is a test", // body: // "\n\nthis is a test" + // startDescription + From b2bd02083303a2c19be07e8fe6dee80291e52aba Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 22:17:25 +0200 Subject: [PATCH 3/8] fix: prompt --- lib/summarizePullRequest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/summarizePullRequest.ts b/lib/summarizePullRequest.ts index 43b7d9f..5565ef6 100644 --- a/lib/summarizePullRequest.ts +++ b/lib/summarizePullRequest.ts @@ -58,9 +58,9 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { : "" }${ maxLengthExceeded - ? "\n\n> The code diff exceeds the max number of characters, so this overview may be incomplete." + ? "\n\n> The code diff exceeds the max number of characters, so this overview may be incomplete. Keep PRs small to avoid this issue." : "" - }\n\n${endDescription}` + }\n\n✨ Ask PR-Codex anything about this PR by commenting below with \`/ask-codex {your question}\`\n\n${endDescription}` const description = hasCodexCommented ? pr.body.split(startDescription)[0] + From 5b51202c37c824ff3d3a30ea8d946317f9fabaa4 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 22:29:26 +0200 Subject: [PATCH 4/8] fix: comment prompt --- lib/replyIssueComment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/replyIssueComment.ts b/lib/replyIssueComment.ts index f4ca8cd..540c5c3 100644 --- a/lib/replyIssueComment.ts +++ b/lib/replyIssueComment.ts @@ -6,7 +6,7 @@ import { getCodeDiff } from "../utils/getCodeDiff" export const startDescription = "\n\n" export const endDescription = "" const systemPrompt = - "You are a Git diff assistant. Given a code diff, you answer any question related to it. Be concise. Always wrap file names, functions, objects and similar in backticks (`)." + "You are a Git diff assistant. Given a code diff, you answer any question related to it. Be concise. Use line breaks and lists to improve readability. Always wrap file names, functions, objects and similar in backticks (`)." export async function replyIssueComment(payload: any, octokit: Octokit) { // Get relevant PR information From 7af47a291d11c46f8c1b7caff61a0eddcca390c4 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 22:49:15 +0200 Subject: [PATCH 5/8] feat: add review comment response --- lib/replyIssueComment.ts | 48 +++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/lib/replyIssueComment.ts b/lib/replyIssueComment.ts index 540c5c3..c946fad 100644 --- a/lib/replyIssueComment.ts +++ b/lib/replyIssueComment.ts @@ -10,19 +10,21 @@ const systemPrompt = export async function replyIssueComment(payload: any, octokit: Octokit) { // Get relevant PR information - const { repository, issue, sender, comment } = payload + const { repository, issue, sender, comment, pull_request } = payload const question = comment.body.split("/ask-codex")[1].trim() if (question) { - const { owner, repo, issue_number } = { + const { owner, repo, number, diff_hunk } = { owner: repository.owner.login, repo: repository.name, - issue_number: issue.number + number: issue.number ?? pull_request.number, + diff_hunk: comment?.diff_hunk } // Get the diff content using Octokit and GitHub API - const { codeDiff } = await getCodeDiff(owner, repo, issue_number, octokit) + const { codeDiff } = + diff_hunk ?? (await getCodeDiff(owner, repo, number, octokit)) // If there are changes, trigger workflow if (codeDiff?.length != 0) { @@ -41,12 +43,38 @@ export async function replyIssueComment(payload: any, octokit: Octokit) { const description = `> ${question}\n\n@${sender.login} ${codexResponse}` - await octokit.issues.createComment({ - owner, - repo, - issue_number, - body: description - }) + if (diff_hunk) { + const { commit_id, path, line, side, start_line, start_side, id } = { + commit_id: comment.commit_id, + path: comment.path, + line: comment.line, + side: comment.side, + start_line: comment.start_line, + start_side: comment.start_side, + id: comment.id + } + + await octokit.pulls.createReviewComment({ + owner, + repo, + pull_number: number, + body: description, + commit_id, + path, + line, + side, + start_line, + start_side, + in_reply_to: id + }) + } else { + await octokit.issues.createComment({ + owner, + repo, + issue_number: number, + body: description + }) + } return codexResponse } From 4ac648d3d02437f88d1b15d3d41e9ddec062813d Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 22:51:56 +0200 Subject: [PATCH 6/8] fix: bug --- lib/replyIssueComment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/replyIssueComment.ts b/lib/replyIssueComment.ts index c946fad..a5f50f9 100644 --- a/lib/replyIssueComment.ts +++ b/lib/replyIssueComment.ts @@ -18,7 +18,7 @@ export async function replyIssueComment(payload: any, octokit: Octokit) { const { owner, repo, number, diff_hunk } = { owner: repository.owner.login, repo: repository.name, - number: issue.number ?? pull_request.number, + number: issue?.number ?? pull_request.number, diff_hunk: comment?.diff_hunk } From 0e4bf9f3de1cb0ac074d0be44818f6a9a80caeb2 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 16 Apr 2023 22:58:24 +0200 Subject: [PATCH 7/8] fix: diff_hunk --- lib/replyIssueComment.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/replyIssueComment.ts b/lib/replyIssueComment.ts index a5f50f9..6307458 100644 --- a/lib/replyIssueComment.ts +++ b/lib/replyIssueComment.ts @@ -22,9 +22,10 @@ export async function replyIssueComment(payload: any, octokit: Octokit) { diff_hunk: comment?.diff_hunk } - // Get the diff content using Octokit and GitHub API - const { codeDiff } = - diff_hunk ?? (await getCodeDiff(owner, repo, number, octokit)) + // Get the diff content + const { codeDiff } = diff_hunk + ? { codeDiff: diff_hunk } + : await getCodeDiff(owner, repo, number, octokit) // If there are changes, trigger workflow if (codeDiff?.length != 0) { From f7b4e3aaeaec1f39085ca7423dbfd8007639a135 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Mon, 17 Apr 2023 00:57:27 +0200 Subject: [PATCH 8/8] refactor: constants --- app/github/route.ts | 3 ++- constants.json | 7 ------- lib/replyIssueComment.ts | 5 ++--- lib/summarizePullRequest.ts | 11 +++++++---- utils/constants.ts | 16 +++++----------- utils/getCodeDiff.ts | 4 +--- utils/github/testPayloadComment.ts | 4 +++- utils/github/testPayloadSyncPr.ts | 2 +- 8 files changed, 21 insertions(+), 31 deletions(-) delete mode 100644 constants.json diff --git a/app/github/route.ts b/app/github/route.ts index 8af196b..fe0b076 100644 --- a/app/github/route.ts +++ b/app/github/route.ts @@ -1,6 +1,7 @@ import { handleGithubAuth } from "@lib/handleGithubAuth" import { replyIssueComment } from "@lib/replyIssueComment" import { summarizePullRequest } from "@lib/summarizePullRequest" +import { codexCommand } from "@utils/constants" import { NextRequest, NextResponse } from "next/server" export const fetchCache = "force-no-store" @@ -15,7 +16,7 @@ export async function POST(req: NextRequest) { await summarizePullRequest(payload, octokit) } else if (payload.action == "created") { - if (payload.comment.body.includes("/ask-codex")) { + if (payload.comment.body.includes(codexCommand)) { // If a comment is created, reply to it const octokit = await handleGithubAuth(payload) diff --git a/constants.json b/constants.json deleted file mode 100644 index 575c1bf..0000000 --- a/constants.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "values": {}, - "addresses": { - "1": {}, - "5": {} - } -} diff --git a/lib/replyIssueComment.ts b/lib/replyIssueComment.ts index 6307458..5274780 100644 --- a/lib/replyIssueComment.ts +++ b/lib/replyIssueComment.ts @@ -1,10 +1,9 @@ import { Octokit } from "@octokit/rest" import { ChatCompletionRequestMessage } from "openai-streams" +import { codexCommand } from "../utils/constants" import { generateChatGpt } from "../utils/generateChatGpt" import { getCodeDiff } from "../utils/getCodeDiff" -export const startDescription = "\n\n" -export const endDescription = "" const systemPrompt = "You are a Git diff assistant. Given a code diff, you answer any question related to it. Be concise. Use line breaks and lists to improve readability. Always wrap file names, functions, objects and similar in backticks (`)." @@ -12,7 +11,7 @@ export async function replyIssueComment(payload: any, octokit: Octokit) { // Get relevant PR information const { repository, issue, sender, comment, pull_request } = payload - const question = comment.body.split("/ask-codex")[1].trim() + const question = comment.body.split(codexCommand)[1].trim() if (question) { const { owner, repo, number, diff_hunk } = { diff --git a/lib/summarizePullRequest.ts b/lib/summarizePullRequest.ts index 5565ef6..453cfa6 100644 --- a/lib/summarizePullRequest.ts +++ b/lib/summarizePullRequest.ts @@ -1,10 +1,13 @@ import { Octokit } from "@octokit/rest" import { ChatCompletionRequestMessage } from "openai-streams" +import { + codexCommand, + endDescription, + startDescription +} from "../utils/constants" import { generateChatGpt } from "../utils/generateChatGpt" import { getCodeDiff } from "../utils/getCodeDiff" -export const startDescription = "\n\n" -export const endDescription = "" const systemPrompt = "You are a Git diff assistant. Given a code diff, you provide a clear and concise description of its content. Always wrap file names, functions, objects and similar in backticks (`)." @@ -58,9 +61,9 @@ export async function summarizePullRequest(payload: any, octokit: Octokit) { : "" }${ maxLengthExceeded - ? "\n\n> The code diff exceeds the max number of characters, so this overview may be incomplete. Keep PRs small to avoid this issue." + ? "\n\n> The code diff in this PR exceeds the max number of characters, so this overview may be incomplete." : "" - }\n\n✨ Ask PR-Codex anything about this PR by commenting below with \`/ask-codex {your question}\`\n\n${endDescription}` + }\n\n✨ Ask PR-Codex anything about this PR by commenting with \`${codexCommand}{your question}\`\n\n${endDescription}` const description = hasCodexCommented ? pr.body.split(startDescription)[0] + diff --git a/utils/constants.ts b/utils/constants.ts index bc87cb5..20a8135 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -1,11 +1,5 @@ -import envConstants from "constants.json" - -type Addresses = {} -type Constants = {} - -export const constants: Constants = envConstants.values - -export const addresses: Addresses = - envConstants.addresses[process.env.NEXT_PUBLIC_CHAIN_ID] - -export default constants +export const startDescription = "\n\n" +export const endDescription = "" +export const codexCommand = "/codex " +export const maxChanges = 1000 +export const maxCodeDiff = 11500 diff --git a/utils/getCodeDiff.ts b/utils/getCodeDiff.ts index 74396d7..08875d5 100644 --- a/utils/getCodeDiff.ts +++ b/utils/getCodeDiff.ts @@ -1,10 +1,8 @@ import { Octokit } from "@octokit/rest" import { joinStringsUntilMaxLength } from "../lib/joinStringsUntilMaxLength" +import { maxChanges, maxCodeDiff } from "./constants" import { parseDiff } from "./parseDiff" -const maxChanges = 1000 -const maxCodeDiff = 11500 - export const getCodeDiff = async ( owner: string, repo: string, diff --git a/utils/github/testPayloadComment.ts b/utils/github/testPayloadComment.ts index 0dfadb7..25e8de6 100644 --- a/utils/github/testPayloadComment.ts +++ b/utils/github/testPayloadComment.ts @@ -1,3 +1,5 @@ +import { codexCommand } from "../../utils/constants" + export const testPayloadComment = { installation: { id: 35293807 }, action: "created", @@ -6,7 +8,7 @@ export const testPayloadComment = { }, comment: { - body: "/ask-codex what changes have been done in the homepage?" + body: `${codexCommand}what changes have been done in the homepage?` }, sender: { login: "jjranalli" diff --git a/utils/github/testPayloadSyncPr.ts b/utils/github/testPayloadSyncPr.ts index f1512cc..9124c58 100644 --- a/utils/github/testPayloadSyncPr.ts +++ b/utils/github/testPayloadSyncPr.ts @@ -1,4 +1,4 @@ -import { endDescription, startDescription } from "@lib/summarizePullRequest" +import { startDescription, endDescription } from "../../utils/constants" export const testPayloadSyncPr = { installation: { id: 35293807 },