diff --git a/.cspell.json b/.cspell.json index 65d0f95..06b57b6 100644 --- a/.cspell.json +++ b/.cspell.json @@ -34,7 +34,11 @@ "Typeguard", "typeguards", "OPENROUTER_API_KEY", - "Openrouter" + "Openrouter", + "flac", + "dylib", + "mobileprovision", + "icns" ], "dictionaries": ["typescript", "node", "software-terms"], "import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"], diff --git a/package.json b/package.json index ece6959..d3a72c2 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "@supabase/supabase-js": "^2.45.4", "@ubiquity-os/ubiquity-os-logger": "^1.3.2", "dotenv": "^16.4.5", - "github-diff-tool": "^1.0.6", "gpt-tokenizer": "^2.5.1", "openai": "^4.63.0", "typebox-validators": "0.3.5", diff --git a/src/adapters/openai/helpers/completions.ts b/src/adapters/openai/helpers/completions.ts index 3a5f24a..42b61db 100644 --- a/src/adapters/openai/helpers/completions.ts +++ b/src/adapters/openai/helpers/completions.ts @@ -3,6 +3,7 @@ import { Context } from "../../../types"; import { SuperOpenAi } from "./openai"; import { CompletionsModelHelper, ModelApplications } from "../../../types/llm"; import { encode } from "gpt-tokenizer"; +import { logger } from "../../../helpers/errors"; export interface CompletionsType { answer: string; @@ -22,8 +23,44 @@ export class Completions extends SuperOpenAi { this.context = context; } + getModelMaxTokenLimit(model: string): number { + // could be made more robust, unfortunately, there's no endpoint to get the model token limit + const tokenLimits = new Map([ + ["o1-mini", 128_000], + ["o1-preview", 128_000], + ["gpt-4-turbo", 128_000], + ["gpt-4o", 128_000], + ["gpt-4o-mini", 128_000], + ["gpt-4", 8_192], + ["gpt-3.5-turbo-0125", 16_385], + ["gpt-3.5-turbo", 16_385], + ]); + + return tokenLimits.get(model) || 128_000; + } + + getModelMaxOutputLimit(model: string): number { + // could be made more robust, unfortunately, there's no endpoint to get the model token limit + const tokenLimits = new Map([ + ["o1-mini", 65_536], + ["o1-preview", 32_768], + ["gpt-4-turbo", 4_096], + ["gpt-4o-mini", 16_384], + ["gpt-4o", 16_384], + ["gpt-4", 8_192], + ["gpt-3.5-turbo-0125", 4_096], + ["gpt-3.5-turbo", 4_096], + ]); + + return tokenLimits.get(model) || 16_384; + } + + async getModelTokenLimit(): Promise { + return this.getModelMaxTokenLimit("o1-mini"); + } + async createCompletion( - prompt: string, + query: string, model: string = "o1-mini", additionalContext: string[], localContext: string[], @@ -31,6 +68,24 @@ export class Completions extends SuperOpenAi { botName: string, maxTokens: number ): Promise { + const numTokens = await this.findTokenLength(query, additionalContext, localContext, groundTruths); + logger.info(`Number of tokens: ${numTokens}`); + + const sysMsg = [ + "You Must obey the following ground truths: ", + JSON.stringify(groundTruths) + "\n", + "You are tasked with assisting as a GitHub bot by generating responses based on provided chat history and similar responses, focusing on using available knowledge within the provided corpus, which may contain code, documentation, or incomplete information. Your role is to interpret and use this knowledge effectively to answer user questions.\n\n# Steps\n\n1. **Understand Context**: Review the chat history and any similar provided responses to understand the context.\n2. **Extract Relevant Information**: Identify key pieces of information, even if they are incomplete, from the available corpus.\n3. **Apply Knowledge**: Use the extracted information and relevant documentation to construct an informed response.\n4. **Draft Response**: Compile the gathered insights into a coherent and concise response, ensuring it's clear and directly addresses the user's query.\n5. **Review and Refine**: Check for accuracy and completeness, filling any gaps with logical assumptions where necessary.\n\n# Output Format\n\n- Concise and coherent responses in paragraphs that directly address the user's question.\n- Incorporate inline code snippets or references from the documentation if relevant.\n\n# Examples\n\n**Example 1**\n\n*Input:*\n- Chat History: \"What was the original reason for moving the LP tokens?\"\n- Corpus Excerpts: \"It isn't clear to me if we redid the staking yet and if we should migrate. If so, perhaps we should make a new issue instead. We should investigate whether the missing LP tokens issue from the MasterChefV2.1 contract is critical to the decision of migrating or not.\"\n\n*Output:*\n\"It was due to missing LP tokens issue from the MasterChefV2.1 Contract.\n\n# Notes\n\n- Ensure the response is crafted from the corpus provided, without introducing information outside of what's available or relevant to the query.\n- Consider edge cases where the corpus might lack explicit answers, and justify responses with logical reasoning based on the existing information.", + `Your name is: ${botName}`, + "\n", + "Main Context (Provide additional precedence in terms of information): ", + localContext.join("\n"), + "Secondary Context: ", + additionalContext.join("\n"), + ].join("\n"); + + logger.info(`System message: ${sysMsg}`); + logger.info(`Query: ${query}`); + const res: OpenAI.Chat.Completions.ChatCompletion = await this.client.chat.completions.create({ model: model, messages: [ @@ -39,18 +94,7 @@ export class Completions extends SuperOpenAi { content: [ { type: "text", - text: - "You Must obey the following ground truths: [" + - groundTruths.join(":") + - "]\n" + - "You are tasked with assisting as a GitHub bot by generating responses based on provided chat history and similar responses, focusing on using available knowledge within the provided corpus, which may contain code, documentation, or incomplete information. Your role is to interpret and use this knowledge effectively to answer user questions.\n\n# Steps\n\n1. **Understand Context**: Review the chat history and any similar provided responses to understand the context.\n2. **Extract Relevant Information**: Identify key pieces of information, even if they are incomplete, from the available corpus.\n3. **Apply Knowledge**: Use the extracted information and relevant documentation to construct an informed response.\n4. **Draft Response**: Compile the gathered insights into a coherent and concise response, ensuring it's clear and directly addresses the user's query.\n5. **Review and Refine**: Check for accuracy and completeness, filling any gaps with logical assumptions where necessary.\n\n# Output Format\n\n- Concise and coherent responses in paragraphs that directly address the user's question.\n- Incorporate inline code snippets or references from the documentation if relevant.\n\n# Examples\n\n**Example 1**\n\n*Input:*\n- Chat History: \"What was the original reason for moving the LP tokens?\"\n- Corpus Excerpts: \"It isn't clear to me if we redid the staking yet and if we should migrate. If so, perhaps we should make a new issue instead. We should investigate whether the missing LP tokens issue from the MasterChefV2.1 contract is critical to the decision of migrating or not.\"\n\n*Output:*\n\"It was due to missing LP tokens issue from the MasterChefV2.1 Contract.\n\n# Notes\n\n- Ensure the response is crafted from the corpus provided, without introducing information outside of what's available or relevant to the query.\n- Consider edge cases where the corpus might lack explicit answers, and justify responses with logical reasoning based on the existing information." + - "Your name is : " + - botName + - "\n" + - "Main Context (Provide additional precedence in terms of information): " + - localContext.join("\n") + - "Secondary Context: " + - additionalContext.join("\n"), + text: sysMsg, }, ], }, @@ -59,7 +103,7 @@ export class Completions extends SuperOpenAi { content: [ { type: "text", - text: prompt, + text: query, }, ], }, @@ -73,6 +117,7 @@ export class Completions extends SuperOpenAi { type: "text", }, }); + const answer = res.choices[0].message; if (answer && answer.content && res.usage) { return { @@ -120,6 +165,7 @@ export class Completions extends SuperOpenAi { } async findTokenLength(prompt: string, additionalContext: string[] = [], localContext: string[] = [], groundTruths: string[] = []): Promise { - return encode(prompt + additionalContext.join("\n") + localContext.join("\n") + groundTruths.join("\n")).length; + // disallowedSpecial: new Set() because we pass the entire diff as the prompt we should account for all special characters + return encode(prompt + additionalContext.join("\n") + localContext.join("\n") + groundTruths.join("\n"), { disallowedSpecial: new Set() }).length; } } diff --git a/src/handlers/ask-llm.ts b/src/handlers/ask-llm.ts index 801f8e7..8f6790b 100644 --- a/src/handlers/ask-llm.ts +++ b/src/handlers/ask-llm.ts @@ -6,37 +6,26 @@ import { recursivelyFetchLinkedIssues } from "../helpers/issue-fetching"; import { formatChatHistory } from "../helpers/format-chat-history"; import { fetchRepoDependencies, fetchRepoLanguageStats } from "./ground-truths/chat-bot"; import { findGroundTruths } from "./ground-truths/find-ground-truths"; -import { bubbleUpErrorComment } from "../helpers/errors"; +import { bubbleUpErrorComment, logger } from "../helpers/errors"; -/** - * Asks a question to GPT and returns the response - * @param context - The context object containing environment and configuration details - * @param question - The question to ask GPT - * @returns The response from GPT - * @throws If no question is provided - */ export async function askQuestion(context: Context, question: string) { if (!question) { - throw context.logger.error("No question provided"); + throw logger.error("No question provided"); } + // using any links in comments or issue/pr bodies to fetch more context const { specAndBodies, streamlinedComments } = await recursivelyFetchLinkedIssues({ context, owner: context.payload.repository.owner.login, repo: context.payload.repository.name, }); + // build a nicely structure system message containing a streamlined chat history + // includes the current issue, any linked issues, and any linked PRs const formattedChat = await formatChatHistory(context, streamlinedComments, specAndBodies); - context.logger.info(`${formattedChat.join("")}`); - return await askGpt(context, question, formattedChat); + logger.info(`${formattedChat.join("")}`); + return await askLlm(context, question, formattedChat); } -/** - * Asks GPT a question and returns the completions - * @param context - The context object containing environment and configuration details - * @param question - The question to ask GPT - * @param formattedChat - The formatted chat history to provide context to GPT - * @returns completions - The completions generated by GPT - **/ -export async function askGpt(context: Context, question: string, formattedChat: string[]): Promise { +export async function askLlm(context: Context, question: string, formattedChat: string[]): Promise { const { env: { UBIQUITY_OS_APP_NAME }, config: { model, similarityThreshold, maxTokens }, @@ -45,31 +34,49 @@ export async function askGpt(context: Context, question: string, formattedChat: voyage: { reranker }, openai: { completions }, }, - logger, } = context; try { + // using db functions to find similar comments and issues const [similarComments, similarIssues] = await Promise.all([ comment.findSimilarComments(question, 1 - similarityThreshold, ""), issue.findSimilarIssues(question, 1 - similarityThreshold, ""), ]); + // combine the similar comments and issues into a single array const similarText = [ ...(similarComments?.map((comment: CommentSimilaritySearchResult) => comment.comment_plaintext) || []), ...(similarIssues?.map((issue: IssueSimilaritySearchResult) => issue.issue_plaintext) || []), ]; + // filter out any empty strings formattedChat = formattedChat.filter((text) => text); + // rerank the similar text using voyageai const rerankedText = similarText.length > 0 ? await reranker.reRankResults(similarText, question) : []; + // gather structural data about the payload repository const [languages, { dependencies, devDependencies }] = await Promise.all([fetchRepoLanguageStats(context), fetchRepoDependencies(context)]); - const groundTruths = await findGroundTruths(context, "chat-bot", { languages, dependencies, devDependencies }); + let groundTruths: string[] = []; - const numTokens = await completions.findTokenLength(question, rerankedText, formattedChat, groundTruths); - logger.info(`Number of tokens: ${numTokens}`); + if (!languages.length) { + groundTruths.push("No languages found in the repository"); + } - return completions.createCompletion(question, model, rerankedText, formattedChat, groundTruths, UBIQUITY_OS_APP_NAME, maxTokens); + if (!Reflect.ownKeys(dependencies).length) { + groundTruths.push("No dependencies found in the repository"); + } + + if (!Reflect.ownKeys(devDependencies).length) { + groundTruths.push("No devDependencies found in the repository"); + } + + if (groundTruths.length === 3) { + return await completions.createCompletion(question, model, rerankedText, formattedChat, groundTruths, UBIQUITY_OS_APP_NAME, maxTokens); + } + + groundTruths = await findGroundTruths(context, "chat-bot", { languages, dependencies, devDependencies }); + return await completions.createCompletion(question, model, rerankedText, formattedChat, groundTruths, UBIQUITY_OS_APP_NAME, maxTokens); } catch (error) { throw bubbleUpErrorComment(context, error, false); } diff --git a/src/handlers/comment-created-callback.ts b/src/handlers/comment-created-callback.ts index ae44fbe..a45770d 100644 --- a/src/handlers/comment-created-callback.ts +++ b/src/handlers/comment-created-callback.ts @@ -27,24 +27,25 @@ export async function issueCommentCreatedCallback( return { status: 204, reason: logger.info("Comment is from a bot. Skipping.").logMessage.raw }; } - logger.info(`Asking question: ${question}`); - let commentToPost; try { const response = await askQuestion(context, question); const { answer, tokenUsage, groundTruths } = response; if (!answer) { throw logger.error(`No answer from OpenAI`); } - logger.info(`Answer: ${answer}`, { tokenUsage }); - const metadata = { - groundTruths, - tokenUsage, - }; + const metadataString = createStructuredMetadata( + // don't change this header, it's used for tracking + "ubiquity-os-llm-response", + logger.info(`Answer: ${answer}`, { + metadata: { + groundTruths, + tokenUsage, + }, + }) + ); - const metadataString = createStructuredMetadata("LLM Ground Truths and Token Usage", logger.info(`Answer: ${answer}`, { metadata })); - commentToPost = answer + metadataString; - await addCommentToIssue(context, commentToPost); + await addCommentToIssue(context, answer + metadataString); return { status: 200, reason: logger.info("Comment posted successfully").logMessage.raw }; } catch (error) { throw await bubbleUpErrorComment(context, error, false); diff --git a/src/handlers/comments.ts b/src/handlers/comments.ts index b033686..8d1418e 100644 --- a/src/handlers/comments.ts +++ b/src/handlers/comments.ts @@ -10,11 +10,14 @@ import { StreamlinedComment } from "../types/llm"; */ export async function getAllStreamlinedComments(linkedIssues: LinkedIssues[]) { const streamlinedComments: Record = {}; + for (const issue of linkedIssues) { const linkedIssueComments = issue.comments || []; if (linkedIssueComments.length === 0) continue; + const linkedStreamlinedComments = streamlineComments(linkedIssueComments); if (!linkedStreamlinedComments) continue; + for (const [key, value] of Object.entries(linkedStreamlinedComments)) { streamlinedComments[key] = [...(streamlinedComments[key] || []), ...value]; } @@ -74,15 +77,15 @@ export function createKey(issueUrl: string, issue?: number) { */ export function streamlineComments(comments: SimplifiedComment[]) { const streamlined: Record = {}; + for (const comment of comments) { const { user, issueUrl: url, body } = comment; - // Skip bot comments if (user?.type === "Bot") continue; + const key = createKey(url); const [owner, repo] = splitKey(key); - if (!streamlined[key]) { - streamlined[key] = []; - } + streamlined[key] ??= []; + if (user && body) { streamlined[key].push({ user: user.login, diff --git a/src/handlers/ground-truths/chat-bot.ts b/src/handlers/ground-truths/chat-bot.ts index 6d087d2..de32e8c 100644 --- a/src/handlers/ground-truths/chat-bot.ts +++ b/src/handlers/ground-truths/chat-bot.ts @@ -68,6 +68,7 @@ export async function fetchRepoLanguageStats(context: Context) { return Array.from(Object.entries(stats)).sort((a, b) => b[1] - a[1]); } catch (err) { - throw logger.error(`Error fetching language stats for ${owner}/${repo}`, { err }); + logger.error(`Error fetching language stats for ${owner}/${repo}`, { err }); + return []; } } diff --git a/src/helpers/format-chat-history.ts b/src/helpers/format-chat-history.ts index ecb2b38..bad5d7f 100644 --- a/src/helpers/format-chat-history.ts +++ b/src/helpers/format-chat-history.ts @@ -1,79 +1,103 @@ import { Context } from "../types"; -import { StreamlinedComment, StreamlinedComments } from "../types/llm"; +import { StreamlinedComment, StreamlinedComments, TokenLimits } from "../types/llm"; import { createKey, streamlineComments } from "../handlers/comments"; -import { fetchPullRequestDiff, fetchIssue, fetchIssueComments, fetchLinkedPullRequests } from "./issue-fetching"; -import { splitKey } from "./issue"; - -/** - * Formats the chat history by combining streamlined comments and specifications or bodies for issues and pull requests. - * - * @param context - The context object containing information about the current GitHub event. - * @param streamlined - A record of streamlined comments for each issue or pull request. - * @param specAndBodies - A record of specifications or bodies for each issue or pull request. - * @returns A promise that resolves to a formatted string representing the chat history. - */ +import { fetchPullRequestDiff, fetchIssue, fetchIssueComments } from "./issue-fetching"; +import { pullReadmeFromRepoForIssue, splitKey } from "./issue"; +import { logger } from "./errors"; + export async function formatChatHistory( context: Context, streamlined: Record, specAndBodies: Record ): Promise { + // At this point really we should have all the context we can obtain but we try again just in case const keys = new Set([...Object.keys(streamlined), ...Object.keys(specAndBodies), createKey(context.payload.issue.html_url)]); - let runningTokenCount = 0; + const tokenLimits: TokenLimits = { + modelMaxTokenLimit: context.adapters.openai.completions.getModelMaxTokenLimit(context.config.model), + maxCompletionTokens: context.config.maxTokens || context.adapters.openai.completions.getModelMaxOutputLimit(context.config.model), + runningTokenCount: 0, + tokensRemaining: 0, + }; + + // what we start out with + tokenLimits.tokensRemaining = tokenLimits.modelMaxTokenLimit - tokenLimits.maxCompletionTokens; + + // careful adding any more API calls here as it's likely to hit the secondary rate limit const chatHistory = await Promise.all( - Array.from(keys).map(async (key) => { - const isCurrentIssue = key === createKey(context.payload.issue.html_url); - const [currentTokenCount, result] = await createContextBlockSection(context, key, streamlined, specAndBodies, isCurrentIssue, runningTokenCount); - runningTokenCount += currentTokenCount; + // keys are owner/repo/issueNum; so for each issue, we want to create a block + Array.from(keys).map(async (key, i) => { + // if we run out of tokens, we should stop + if (tokenLimits.tokensRemaining < 0) { + logger.error(`Ran out of tokens at block ${i}`); + return ""; + } + const [currentTokenCount, result] = await createContextBlockSection({ + context, + key, + streamlined, + specAndBodies, + isCurrentIssue: key === createKey(context.payload.issue.html_url), + tokenLimits, + }); + // update the token count + tokenLimits.runningTokenCount = currentTokenCount; + tokenLimits.tokensRemaining = tokenLimits.modelMaxTokenLimit - tokenLimits.maxCompletionTokens - currentTokenCount; return result; }) ); - return Array.from(new Set(chatHistory)); + + return Array.from(new Set(chatHistory)).filter((x): x is string => !!x); } -/** - * Generates the correct header string based on the provided parameters. - * - * @param prDiff - The pull request diff string, if available. - * @param issueNumber - The issue number. - * @param isCurrentIssue - A boolean indicating if this is the current issue. - * @param isBody - A boolean indicating if this is for the body of the issue. - * @returns The formatted header string. - */ -function getCorrectHeaderString(prDiff: string | null, issueNumber: number, isCurrentIssue: boolean, isBody: boolean) { - const headerTemplates = { - pull: `Pull #${issueNumber} Request`, - issue: `Issue #${issueNumber} Specification`, - convo: `Issue #${issueNumber} Conversation`, +// These give structure and provide the distinction between the different sections of the chat history +function getCorrectHeaderString(prDiff: string | null, isCurrentIssue: boolean, isConvo: boolean) { + const strings = { + convo: { + pull: { + linked: `Linked Pull Request Conversation`, + current: `Current Pull Request Conversation`, + }, + issue: { + linked: `Linked Task Conversation`, + current: `Current Task Conversation`, + }, + }, + spec: { + pull: { + linked: `Linked Pull Request Specification`, + current: `Current Pull Request Specification`, + }, + issue: { + linked: `Linked Task Specification`, + current: `Current Task Specification`, + }, + }, }; - const type = prDiff ? "pull" : "issue"; - const context = isCurrentIssue ? "current" : "linked"; - const bodyContext = isBody ? "convo" : type; - - return `${context.charAt(0).toUpperCase() + context.slice(1)} ${headerTemplates[bodyContext]}`; + const category = isConvo ? "convo" : "spec"; + const issueType = prDiff ? "pull" : "issue"; + const issueStatus = isCurrentIssue ? "current" : "linked"; + return strings[category][issueType][issueStatus]; } -/** - * Creates a context block section for the given issue or pull request. - * - * @param context - The context object containing information about the current GitHub event. - * @param key - The unique key representing the issue or pull request. - * @param streamlined - A record of streamlined comments for each issue or pull request. - * @param specAndBodies - A record of specifications or bodies for each issue or pull request. - * @param isCurrentIssue - A boolean indicating whether the key represents the current issue. - * @returns A formatted string representing the context block section. - */ -async function createContextBlockSection( - context: Context, - key: string, - streamlined: Record, - specAndBodies: Record, - isCurrentIssue: boolean, - currentContextTokenCount: number = 0 -): Promise<[number, string]> { - const maxTokens = context.config.maxTokens; +async function createContextBlockSection({ + context, + key, + streamlined, + specAndBodies, + isCurrentIssue, + tokenLimits, +}: { + context: Context; + key: string; + streamlined: Record; + specAndBodies: Record; + isCurrentIssue: boolean; + tokenLimits: TokenLimits; +}): Promise<[number, string]> { let comments = streamlined[key]; - if (!comments || comments.length === 0) { + // just in case we try again but we should already have the comments + if (!comments || !comments.length) { const [owner, repo, number] = splitKey(key); const { comments: fetchedComments } = await fetchIssueComments({ context, @@ -83,25 +107,18 @@ async function createContextBlockSection( }); comments = streamlineComments(fetchedComments)[key]; } + const [org, repo, issueNum] = key.split("/"); const issueNumber = parseInt(issueNum); if (!issueNumber || isNaN(issueNumber)) { throw context.logger.error("Issue number is not valid"); } - const pulls = (await fetchLinkedPullRequests(org, repo, issueNumber, context)) || []; - const prDiffs = await Promise.all(pulls.map((pull) => fetchPullRequestDiff(context, org, repo, pull.number))); - let prDiff: string | null = null; - for (const pullDiff of prDiffs.flat()) { - if (currentContextTokenCount > maxTokens) break; - if (pullDiff) { - const tokenLength = await context.adapters.openai.completions.findTokenLength(pullDiff.diff); - if (currentContextTokenCount + tokenLength > maxTokens) break; - currentContextTokenCount += tokenLength; - prDiff = (prDiff ? prDiff + "\n" : "") + pullDiff.diff; - } - } - const specHeader = getCorrectHeaderString(prDiff, issueNumber, isCurrentIssue, false); + + // Fetch our diff if we have one; this excludes the largest of files to keep within token limits + const { diff } = await fetchPullRequestDiff(context, org, repo, issueNumber, tokenLimits); + // specification or pull request body let specOrBody = specAndBodies[key]; + // we should have it already but just in case if (!specOrBody) { specOrBody = ( @@ -113,61 +130,75 @@ async function createContextBlockSection( }) )?.body || "No specification or body available"; } - const specOrBodyBlock = [createHeader(specHeader, key), createSpecOrBody(specOrBody), createFooter(specHeader)]; - currentContextTokenCount += await context.adapters.openai.completions.findTokenLength(specOrBody); - const header = getCorrectHeaderString(prDiff, issueNumber, isCurrentIssue, true); - const repoString = `${org}/${repo} #${issueNumber}`; - const block = [specOrBodyBlock.join(""), createHeader(header, repoString), createComment({ issueNumber, repo, org, comments }), createFooter(header)]; - currentContextTokenCount += await context.adapters.openai.completions.findTokenLength(block.join(" ")); - if (!prDiff) { - return [currentContextTokenCount, block.join("")]; + + const specHeader = getCorrectHeaderString(diff, isCurrentIssue, false); //E.g: === Current Task Specification === + const blockHeader = getCorrectHeaderString(diff, isCurrentIssue, true); //E.g: === Linked Task Conversation === + + // contains the actual spec or body + const specBlock = [createHeader(specHeader, key), createSpecOrBody(specOrBody), createFooter(specHeader, key)]; + // contains the conversation + const commentSection = createComment({ issueNumber, repo, org, comments }, specOrBody); + + let block; + // if we have a conversation, we should include it + if (commentSection) { + block = [specBlock.join("\n"), createHeader(blockHeader, key), commentSection, createFooter(blockHeader, key)]; + } else { + // No need for empty sections in the chat history + block = [specBlock.join("\n")]; + } + + // only inject the README if this is the current issue as that's likely most relevant + if (isCurrentIssue) { + const readme = await pullReadmeFromRepoForIssue({ context, owner: org, repo }); + // give the readme it's own clear section + if (readme) { + const readmeBlock = readme ? [createHeader("README", key), createSpecOrBody(readme), createFooter("README", key)] : []; + block = block.concat(readmeBlock); + } + } + + if (!diff) { + // the diff was already encoded etc but we have added more to the block so we need to re-encode + return [await context.adapters.openai.completions.findTokenLength(block.join("")), block.join("\n")]; } - const diffBlock = [createHeader("Linked Pull Request Code Diff", repoString), prDiff, createFooter("\nLinked Pull Request Code Diff")]; - return [currentContextTokenCount, block.join("") + diffBlock.join("")]; + + // Build the block with the diff in it's own section + const blockWithDiff = [block.join("\n"), createHeader(`Pull Request Diff`, key), diff, createFooter(`Pull Request Diff`, key)]; + return [await context.adapters.openai.completions.findTokenLength(blockWithDiff.join("")), blockWithDiff.join("\n")]; } -/** - * Creates a header string for the given content and repository string. - * - * @param content - The content to include in the header. - * @param repoString - The repository string to include in the header. - * @returns A formatted header string. - */ function createHeader(content: string, repoString: string) { - return `=== ${content} === ${repoString} ===\n\n`; + return `=== ${content} === ${repoString} ===\n`; } -/** - * Creates a footer string for the given content. - * - * @param content - The content to include in the footer. - * @returns A formatted footer string. - */ -function createFooter(content: string) { - return `=== End ${content} ===\n\n`; +function createFooter(content: string, repoString: string) { + return `=== End ${content} === ${repoString} ===\n`; } -/** - * Creates a comment string from the StreamlinedComments object. - * - * @param comment - The StreamlinedComments object. - * @returns A string representing the comments. - */ -function createComment(comment: StreamlinedComments) { +function createSpecOrBody(specOrBody: string) { + return `${specOrBody}\n`; +} + +function createComment(comment: StreamlinedComments, specOrBody: string) { if (!comment.comments) { - return ""; + return null; } - // Format comments + + const seen = new Set(); + comment.comments = comment.comments.filter((c) => { + // Do not include the same comment twice or the spec/body + if (seen.has(c.id) || c.body === specOrBody) { + return false; + } + seen.add(c.id); + return true; + }); + const formattedComments = comment.comments.map((c) => `${c.id} ${c.user}: ${c.body}\n`); - return formattedComments.join(""); -} -/** - * Creates a formatted string for the specification or body of an issue. - * - * @param specOrBody - The specification or body content. - * @returns A formatted string representing the specification or body. - */ -function createSpecOrBody(specOrBody: string) { - return `${specOrBody}\n`; + if (formattedComments.length === 0) { + return; + } + return formattedComments.join(""); } diff --git a/src/helpers/issue-fetching.ts b/src/helpers/issue-fetching.ts index 6f01340..3c8d73a 100644 --- a/src/helpers/issue-fetching.ts +++ b/src/helpers/issue-fetching.ts @@ -1,44 +1,30 @@ -import { GithubDiff } from "github-diff-tool"; import { createKey, getAllStreamlinedComments } from "../handlers/comments"; import { Context } from "../types"; -import { IssueComments, FetchParams, Issue, LinkedIssues, LinkedPullsToIssue, ReviewComments, SimplifiedComment } from "../types/github-types"; -import { StreamlinedComment } from "../types/llm"; +import { IssueComments, FetchParams, Issue, LinkedIssues, ReviewComments, SimplifiedComment } from "../types/github-types"; +import { StreamlinedComment, TokenLimits } from "../types/llm"; import { logger } from "./errors"; -import { - dedupeStreamlinedComments, - fetchCodeLinkedFromIssue, - idIssueFromComment, - mergeStreamlinedComments, - pullReadmeFromRepoForIssue, - splitKey, -} from "./issue"; +import { dedupeStreamlinedComments, fetchCodeLinkedFromIssue, idIssueFromComment, mergeStreamlinedComments, splitKey } from "./issue"; import { handleIssue, handleSpec, handleSpecAndBodyKeys, throttlePromises } from "./issue-handling"; +import { processPullRequestDiff } from "./pull-request-parsing"; -/** - * Recursively fetches linked issues and processes them, including fetching comments and specifications. - * - * @param params - The parameters required to fetch the linked issues, including context and other details. - * @returns A promise that resolves to an object containing linked issues, specifications, streamlined comments, and seen issue keys. - */ export async function recursivelyFetchLinkedIssues(params: FetchParams) { + // take a first run at gathering everything we need and package it up const { linkedIssues, seen, specAndBodies, streamlinedComments } = await fetchLinkedIssues(params); + // build promises and throttle them; this calls handleSpec which is a recursive function potentially to great depth const fetchPromises = linkedIssues.map(async (linkedIssue) => await mergeCommentsAndFetchSpec(params, linkedIssue, streamlinedComments, specAndBodies, seen)); await throttlePromises(fetchPromises, 10); + // handle the keys that have been gathered const linkedIssuesKeys = linkedIssues.map((issue) => createKey(`${issue.owner}/${issue.repo}/${issue.issueNumber}`)); + // exhaustive list of unique keys from the first full pass const specAndBodyKeys = Array.from(new Set([...Object.keys(specAndBodies), ...Object.keys(streamlinedComments), ...linkedIssuesKeys])); + // this fn throttles from within but again, be weary of the rate limit await handleSpecAndBodyKeys(specAndBodyKeys, params, dedupeStreamlinedComments(streamlinedComments), seen); return { linkedIssues, specAndBodies, streamlinedComments }; } -/** - * Fetches linked issues recursively and processes them. - * - * @param params - The parameters required to fetch the linked issues, including context and other details. - * @returns A promise that resolves to an object containing linked issues, specifications, streamlined comments, and seen issue keys. - */ export async function fetchLinkedIssues(params: FetchParams) { - const { comments, issue } = await fetchIssueComments(params); - if (!issue) { + const fetchedIssueAndComments = await fetchIssueComments(params); + if (!fetchedIssueAndComments.issue) { return { streamlinedComments: {}, linkedIssues: [], specAndBodies: {}, seen: new Set() }; } @@ -46,14 +32,17 @@ export async function fetchLinkedIssues(params: FetchParams) { throw logger.error("Owner or repo not found"); } + const issue = fetchedIssueAndComments.issue; + const comments = fetchedIssueAndComments.comments.filter((comment) => comment.body !== undefined); + const issueKey = createKey(issue.html_url); const [owner, repo, issueNumber] = splitKey(issueKey); - const linkedIssues: LinkedIssues[] = [{ body: issue.body || "", comments, issueNumber: parseInt(issueNumber), owner, repo, url: issue.html_url }]; + const linkedIssues: LinkedIssues[] = [{ body: issue.body, comments, issueNumber: parseInt(issueNumber), owner, repo, url: issue.html_url }]; const specAndBodies: Record = {}; const seen = new Set([issueKey]); comments.push({ - body: issue.body || "", + body: issue.body, user: issue.user, id: issue.id.toString(), org: params.owner, @@ -61,37 +50,16 @@ export async function fetchLinkedIssues(params: FetchParams) { issueUrl: issue.html_url, }); - //Fetch the README of the repository - try { - const readme = await pullReadmeFromRepoForIssue(params); - if (readme) { - comments.push({ - body: readme, - user: issue.user, - id: issue.id.toString(), - org: params.owner, - repo: params.repo, - issueUrl: issue.html_url, - }); - } - } catch (error) { - params.context.logger.error(`Error fetching README`, { - err: error, - owner, - repo, - issue, - }); - } - for (const comment of comments) { - const foundIssues = idIssueFromComment(comment.body); + const foundIssues = idIssueFromComment(comment.body, params); const foundCodes = comment.body ? await fetchCodeLinkedFromIssue(comment.body, params.context, comment.issueUrl) : []; + if (foundIssues) { for (const linkedIssue of foundIssues) { const linkedKey = createKey(linkedIssue.url, linkedIssue.issueNumber); if (seen.has(linkedKey)) continue; - seen.add(linkedKey); + const { comments: fetchedComments, issue: fetchedIssue } = await fetchIssueComments({ context: params.context, issueNum: linkedIssue.issueNumber, @@ -128,15 +96,6 @@ export async function fetchLinkedIssues(params: FetchParams) { return { streamlinedComments, linkedIssues, specAndBodies, seen }; } -/** - * Merges comments and fetches the specification for a linked issue. - * - * @param params - The parameters required to fetch the linked issue, including context and other details. - * @param linkedIssue - The linked issue for which comments and specifications need to be fetched. - * @param streamlinedComments - A record of streamlined comments associated with issues. - * @param specOrBodies - A record of specifications or bodies associated with issues. - * @param seen - A set of issue keys that have already been processed to avoid duplication. - */ export async function mergeCommentsAndFetchSpec( params: FetchParams, linkedIssue: LinkedIssues, @@ -149,66 +108,33 @@ export async function mergeCommentsAndFetchSpec( const merged = mergeStreamlinedComments(streamlinedComments, streamed); streamlinedComments = { ...streamlinedComments, ...merged }; } + if (linkedIssue.body) { await handleSpec(params, linkedIssue.body, specOrBodies, createKey(linkedIssue.url, linkedIssue.issueNumber), seen, streamlinedComments); } } -/** - * Fetches the diff of a pull request. - * - * @param context - The context containing the octokit instance and logger. - * @param org - The organization or owner of the repository. - * @param repo - The name of the repository. - * @param issue - The pull request number. - * @returns A promise that resolves to the diff of the pull request as a string, or null if an error occurs. - */ -export async function fetchPullRequestDiff(context: Context, org: string, repo: string, issue: number): Promise<{ diff: string; diffSize: number }[] | null> { - const { octokit, logger } = context; +export async function fetchPullRequestDiff(context: Context, org: string, repo: string, issue: number, tokenLimits: TokenLimits) { + const { octokit } = context; + let diff: string; + try { - const githubDiff = new GithubDiff(octokit); - //Fetch the statistics of the pull request - const stats = await githubDiff.getPullRequestStats(org, repo, issue); - const files = stats.map((file) => ({ filename: file.filename, diffSizeInBytes: file.diffSizeInBytes })); - //Fetch the diff of the files - const prDiffs = await Promise.all( - files.map(async (file) => { - let diff = null; - try { - diff = await githubDiff.getPullRequestDiff({ - owner: org, - repo, - pullNumber: issue, - filePath: file.filename, - }); - } catch { - logger.error(`Error fetching pull request diff for the file`, { - owner: org, - repo, - pull_number: issue, - file: file.filename, - }); - } - return diff ? { diff: file.filename + diff, diffSize: file.diffSizeInBytes } : null; - }) - ); - return prDiffs.filter((diff): diff is { diff: string; diffSize: number } => diff !== null).sort((a, b) => a.diffSize - b.diffSize); - } catch (error) { - logger.error(`Error fetching pull request diff`, { - err: error, + const diffResponse = await octokit.pulls.get({ owner: org, repo, pull_number: issue, + mediaType: { format: "diff" }, }); - return null; + + diff = diffResponse.data as unknown as string; + } catch (e) { + logger.error(`Error fetching PR data`, { owner: org, repo, issue, err: String(e) }); + return { diff: null }; } + + return await processPullRequestDiff(diff, tokenLimits); } -/** - * Fetches an issue from the GitHub API. - * @param params - Context - * @returns A promise that resolves to an issue object or null if an error occurs. - */ export async function fetchIssue(params: FetchParams): Promise { const { octokit, payload, logger } = params.context; const { issueNum, owner, repo } = params; @@ -250,6 +176,14 @@ export async function fetchIssueComments(params: FetchParams) { pull_number: issueNum || payload.issue.number, }); reviewComments = response.data; + + const response2 = await octokit.rest.issues.listComments({ + owner: owner || payload.repository.owner.login, + repo: repo || payload.repository.name, + issue_number: issueNum || payload.issue.number, + }); + + issueComments = response2.data; } else { const response = await octokit.rest.issues.listComments({ owner: owner || payload.repository.owner.login, @@ -275,15 +209,6 @@ export async function fetchIssueComments(params: FetchParams) { }; } -/** - * Fetches and handles an issue based on the provided key and parameters. - * - * @param key - The unique key representing the issue in the format "owner/repo/issueNumber". - * @param params - The parameters required to fetch the issue, including context and other details. - * @param streamlinedComments - A record of streamlined comments associated with issues. - * @param seen - A set of issue keys that have already been processed to avoid duplication. - * @returns A promise that resolves to an array of streamlined comments for the specified issue. - */ export async function fetchAndHandleIssue( key: string, params: FetchParams, @@ -315,54 +240,17 @@ function castCommentsToSimplifiedComments(comments: (IssueComments | ReviewComme }; } - if ("issue_url" in comment) { + if ("html_url" in comment) { return { body: comment.body, user: comment.user, id: comment.id.toString(), org: params.owner || params.context.payload.repository.owner.login, repo: params.repo || params.context.payload.repository.name, - issueUrl: comment.issue_url, + issueUrl: comment.html_url, }; } throw logger.error("Comment type not recognized", { comment, params }); }); } - -export async function fetchLinkedPullRequests(owner: string, repo: string, issueNumber: number, context: Context) { - const query = ` - query($owner: String!, $repo: String!, $issueNumber: Int!) { - repository(owner: $owner, name: $repo) { - issue(number: $issueNumber) { - closedByPullRequestsReferences(first: 100) { - nodes { - number - title - state - merged - url - } - } - } - } - } - `; - - try { - const { repository } = await context.octokit.graphql(query, { - owner, - repo, - issueNumber, - }); - return repository.issue.closedByPullRequestsReferences.nodes; - } catch (error) { - context.logger.error(`Error fetching linked PRs from issue`, { - err: error, - owner, - repo, - issueNumber, - }); - return null; - } -} diff --git a/src/helpers/issue-handling.ts b/src/helpers/issue-handling.ts index 3f44225..779cb26 100644 --- a/src/helpers/issue-handling.ts +++ b/src/helpers/issue-handling.ts @@ -4,14 +4,6 @@ import { StreamlinedComment } from "../types/llm"; import { idIssueFromComment, mergeStreamlinedComments, splitKey } from "./issue"; import { fetchLinkedIssues, fetchIssue, fetchAndHandleIssue, mergeCommentsAndFetchSpec } from "./issue-fetching"; -/** - * Handles the processing of an issue. - * - * @param params - The parameters required to fetch and handle issues. - * @param streamlinedComments - A record of streamlined comments indexed by keys. - * @param alreadySeen - A set of keys that have already been processed to avoid duplication. - * @returns A promise that resolves when the issue has been handled. - */ export async function handleIssue(params: FetchParams, streamlinedComments: Record, alreadySeen: Set) { if (alreadySeen.has(createKey(`${params.owner}/${params.repo}/${params.issueNum}`))) { return; @@ -22,17 +14,6 @@ export async function handleIssue(params: FetchParams, streamlinedComments: Reco return mergeStreamlinedComments(streamlinedComments, streamlined); } -/** - * Handles the processing of a specification or body text. - * - * @param params - The parameters required to fetch and handle issues. - * @param specOrBody - The specification or body text to be processed. - * @param specAndBodies - A record of specifications and bodies indexed by keys. - * @param key - The key associated with the current specification or body. - * @param seen - A set of keys that have already been processed to avoid duplication. - * @param streamlinedComments - A record of streamlined comments indexed by keys. - * @returns A promise that resolves to the updated record of specifications and bodies. - */ export async function handleSpec( params: FetchParams, specOrBody: string, @@ -42,7 +23,7 @@ export async function handleSpec( streamlinedComments: Record ) { specAndBodies[key] = specOrBody; - const otherReferences = idIssueFromComment(specOrBody); + const otherReferences = idIssueFromComment(specOrBody, params); if (otherReferences) { for (const ref of otherReferences) { const anotherKey = createKey(ref.url, ref.issueNumber); @@ -73,21 +54,13 @@ export async function handleSpec( return specAndBodies; } -/** - * Handles the processing of a comment. - * - * @param params - The parameters required to fetch and handle issues. - * @param comment - The comment to be processed. - * @param streamlinedComments - A record of streamlined comments indexed by keys. - * @param seen - A set of keys that have already been processed to avoid duplication. - */ export async function handleComment( params: FetchParams, comment: StreamlinedComment, streamlinedComments: Record, seen: Set ) { - const otherReferences = idIssueFromComment(comment.body); + const otherReferences = idIssueFromComment(comment.body, params); if (otherReferences) { for (const ref of otherReferences) { const key = createKey(ref.url); @@ -100,15 +73,8 @@ export async function handleComment( } } -/** - * Handles the processing of specification and body keys. - * - * @param keys - An array of keys representing issues or comments to be processed. - * @param params - The parameters required to fetch and handle issues. - * @param streamlinedComments - A record of streamlined comments indexed by keys. - * @param seen - A set of keys that have already been processed to avoid duplication. - */ export async function handleSpecAndBodyKeys(keys: string[], params: FetchParams, streamlinedComments: Record, seen: Set) { + // Make one last sweep just to be sure we have everything const commentProcessingPromises = keys.map(async (key) => { let comments = streamlinedComments[key]; if (!comments || comments.length === 0) { @@ -122,12 +88,6 @@ export async function handleSpecAndBodyKeys(keys: string[], params: FetchParams, await throttlePromises(commentProcessingPromises, 10); } -/** - * Throttles the execution of promises to ensure that no more than the specified limit are running concurrently. - * - * @param promises - An array of promises to be executed. - * @param limit - The maximum number of promises to run concurrently. - */ export async function throttlePromises(promises: Promise[], limit: number) { const executing: Promise[] = []; for (const promise of promises) { diff --git a/src/helpers/issue.ts b/src/helpers/issue.ts index b63b7d5..2c4697b 100644 --- a/src/helpers/issue.ts +++ b/src/helpers/issue.ts @@ -58,8 +58,8 @@ export function splitKey(key: string): [string, string, string] { * @param params - Additional parameters that may include context information. * @returns An array of linked issues or null if no issues are found. */ -export function idIssueFromComment(comment?: string | null): LinkedIssues[] | null { - const urlMatch = comment?.match(/https?:\/\/(?:www\.)?github\.com\/([^/]+)\/([^/]+)\/(pull|issues?)\/(\d+)/g); +export function idIssueFromComment(comment?: string | null, params?: FetchParams): LinkedIssues[] | null { + const urlMatch = comment?.match(/https:\/\/(?:www\.)?github.com\/([^/]+)\/([^/]+)\/(pull|issue|issues)\/(\d+)/g); const response: LinkedIssues[] = []; if (urlMatch) { @@ -68,6 +68,24 @@ export function idIssueFromComment(comment?: string | null): LinkedIssues[] | nu }); } + /** + * These can only reference issues within the same repository + * so params works here + */ + const hashMatch = comment?.match(/#(\d+)/g); + if (hashMatch && hashMatch.length > 0) { + hashMatch.forEach((hash) => { + const issueNumber = hash.replace("#", ""); + // the HTML comment in the PR template + if (issueNumber === "1234" && comment?.includes("You must link the issue number e.g.")) { + return; + } + const owner = params?.context.payload.repository?.owner?.login || ""; + const repo = params?.context.payload.repository?.name || ""; + response.push({ body: undefined, owner, repo, issueNumber: parseInt(issueNumber), url: `https://github.com/${owner}/${repo}/issues/${issueNumber}` }); + }); + } + return response.length > 0 ? response : null; } @@ -151,7 +169,7 @@ export async function fetchCodeLinkedFromIssue( return { body: content, id: parsedUrl.path }; } } catch (error) { - console.error(`Error fetching content from ${url}:`, error); + logger.error(`Error fetching content from ${url}:`, { er: error }); } return null; }) diff --git a/src/helpers/pull-request-parsing.ts b/src/helpers/pull-request-parsing.ts new file mode 100644 index 0000000..87a86e6 --- /dev/null +++ b/src/helpers/pull-request-parsing.ts @@ -0,0 +1,104 @@ +import { encode } from "gpt-tokenizer"; +import { TokenLimits } from "../types/llm"; +import { logger } from "./errors"; +import { EncodeOptions } from "gpt-tokenizer/esm/GptEncoding"; + +export async function processPullRequestDiff(diff: string, tokenLimits: TokenLimits) { + const { runningTokenCount, tokensRemaining } = tokenLimits; + + // parse the diff into per-file diffs for quicker processing + const perFileDiffs = parsePerFileDiffs(diff); + + // quick estimate using a simple heuristic; 3.5 characters per token + const estimatedFileDiffStats = perFileDiffs.map(({ filename, diffContent }) => { + const estimatedTokenCount = Math.ceil(diffContent.length / 3.5); + return { filename, estimatedTokenCount, diffContent }; + }); + + estimatedFileDiffStats.sort((a, b) => a.estimatedTokenCount - b.estimatedTokenCount); // Smallest first + + let currentTokenCount = runningTokenCount; + const includedFileDiffs = []; + + // Using the quick estimate, include as many files as possible without exceeding token limits + for (const file of estimatedFileDiffStats) { + if (currentTokenCount + file.estimatedTokenCount > tokensRemaining) { + logger.info(`Skipping ${file.filename} to stay within token limits.`); + continue; + } + includedFileDiffs.push(file); + currentTokenCount += file.estimatedTokenCount; + } + + // If no files can be included, return null + if (includedFileDiffs.length === 0) { + logger.error(`Cannot include any files from diff without exceeding token limits.`); + return { diff: null }; + } + + // Accurately calculate token count for included files we have approximated to be under the limit + const accurateFileDiffStats = await Promise.all( + includedFileDiffs.map(async (file) => { + const tokenCountArray = await encodeAsync(file.diffContent, { disallowedSpecial: new Set() }); + const tokenCount = tokenCountArray.length; + return { ...file, tokenCount }; + }) + ); + + // Take an accurate reading of our current collection of files within the diff + currentTokenCount = accurateFileDiffStats.reduce((sum, file) => sum + file.tokenCount, runningTokenCount); + + // Remove files from the end of the list until we are within token limits + while (currentTokenCount > tokensRemaining && accurateFileDiffStats.length > 0) { + const removedFile = accurateFileDiffStats.pop(); + currentTokenCount -= removedFile?.tokenCount || 0; + logger.info(`Excluded ${removedFile?.filename || "Unknown filename"} after accurate token count exceeded limits.`); + } + + if (accurateFileDiffStats.length === 0) { + logger.error(`Cannot include any files from diff after accurate token count calculation.`); + return { diff: null }; + } + + // Build the diff with the included files + const currentDiff = accurateFileDiffStats.map((file) => file.diffContent).join("\n"); + + return { diff: currentDiff }; +} + +// Helper to speed up tokenization +export async function encodeAsync(text: string, options: EncodeOptions): Promise { + return new Promise((resolve) => { + const result = encode(text, options); + resolve(result); + }); +} + +// Helper to parse a diff into per-file diffs +export function parsePerFileDiffs(diff: string): { filename: string; diffContent: string }[] { + // regex to capture diff sections, including the last file + const diffPattern = /^diff --git a\/(.*?) b\/.*$/gm; + let match: RegExpExecArray | null; + const perFileDiffs = []; + let lastIndex = 0; + + // iterate over each file in the diff + while ((match = diffPattern.exec(diff)) !== null) { + const filename = match[1]; + const startIndex = match.index; + + // if we have pushed a file into the array, "append" the diff content + if (perFileDiffs.length > 0) { + perFileDiffs[perFileDiffs.length - 1].diffContent = diff.substring(lastIndex, startIndex).trim(); + } + + perFileDiffs.push({ filename, diffContent: "" }); + lastIndex = startIndex; + } + // append the last file's diff content + if (perFileDiffs.length > 0 && lastIndex < diff.length) { + perFileDiffs[perFileDiffs.length - 1].diffContent = diff.substring(lastIndex).trim(); + } + + return perFileDiffs; +} diff --git a/src/plugin.ts b/src/plugin.ts index 284b3cf..3bc13c8 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,13 +1,13 @@ import { Octokit } from "@octokit/rest"; import { PluginInputs } from "./types"; import { Context } from "./types"; -import { LogLevel, Logs } from "@ubiquity-os/ubiquity-os-logger"; import { Env } from "./types/env"; import { createAdapters } from "./adapters"; import { createClient } from "@supabase/supabase-js"; import { VoyageAIClient } from "voyageai"; import OpenAI from "openai"; import { proxyCallbacks } from "./helpers/callback-proxy"; +import { logger } from "./helpers/errors"; export async function plugin(inputs: PluginInputs, env: Env) { const octokit = new Octokit({ auth: inputs.authToken }); @@ -26,7 +26,7 @@ export async function plugin(inputs: PluginInputs, env: Env) { config: inputs.settings, octokit, env, - logger: new Logs("info" as LogLevel), + logger, adapters: {} as ReturnType, }; context.adapters = createAdapters(supabase, voyageClient, openaiClient, context); diff --git a/src/types/github-types.ts b/src/types/github-types.ts index 2830da7..af351f3 100644 --- a/src/types/github-types.ts +++ b/src/types/github-types.ts @@ -19,12 +19,12 @@ export type LinkedIssues = { owner: string; url: string; comments?: SimplifiedComment[] | null | undefined; - body: string | undefined; + body: string | undefined | null; }; export type SimplifiedComment = { user: Partial | null; - body: string | undefined; + body: string | undefined | null; id: string; org: string; repo: string; @@ -39,22 +39,4 @@ export type FetchedCodes = { org: string; repo: string; issueNumber: number; -}; - -export type FetchedPulls = { - number: number; - title: string; - state: string; - merged: boolean; - url: string; -}; - -export type LinkedPullsToIssue = { - repository: { - issue: { - closedByPullRequestsReferences: { - nodes: FetchedPulls[]; - }; - }; - }; -}; +}; \ No newline at end of file diff --git a/src/types/llm.ts b/src/types/llm.ts index f01a70d..7d5bedf 100644 --- a/src/types/llm.ts +++ b/src/types/llm.ts @@ -4,8 +4,8 @@ export type ModelApplications = "code-review" | "chat-bot"; type ChatBotAppParams = { languages: [string, number][]; - dependencies: Record; - devDependencies: Record; + dependencies: Record | null; + devDependencies: Record | null; }; type CodeReviewAppParams = { @@ -51,3 +51,10 @@ export type StreamlinedComments = { org: string; comments: StreamlinedComment[]; }; + +export type TokenLimits = { + modelMaxTokenLimit: number; + maxCompletionTokens: number; + runningTokenCount: number; + tokensRemaining: number; +}; diff --git a/tests/__mocks__/handlers.ts b/tests/__mocks__/handlers.ts index be7ba62..2c2141b 100644 --- a/tests/__mocks__/handlers.ts +++ b/tests/__mocks__/handlers.ts @@ -85,9 +85,7 @@ export const handlers = [ db.pull.findFirst({ where: { owner: { equals: owner as string }, repo: { equals: repo as string }, number: { equals: Number(pullNumber) } } }) ) ), - http.get("https://api.github.com/repos/:owner/:repo/languages", ({ params: { owner, repo } }) => - HttpResponse.json(db.repo.findFirst({ where: { owner: { login: { equals: owner as string } }, name: { equals: repo as string } } })) - ), + http.get("https://api.github.com/repos/:owner/:repo/languages", () => HttpResponse.json(["JavaScript", "TypeScript", "Python"])), http.get("https://api.github.com/repos/:owner/:repo/contents/:path", () => HttpResponse.json({ type: "file", @@ -97,4 +95,19 @@ export const handlers = [ content: Buffer.from(JSON.stringify({ content: "This is a mock README file" })).toString("base64"), }) ), + // [MSW] Warning: intercepted a request without a matching request handler: + + // • GET https://api.github.com/repos/ubiquity/test-repo/pulls/3/files?per_page=100?per_page=100 + http.get("https://api.github.com/repos/:owner/:repo/pulls/:pull_number/files", () => + HttpResponse.json([ + { + sha: "abc123", + filename: "file1.txt", + status: "modified", + additions: 10, + deletions: 5, + changes: 15, + }, + ]) + ), ]; diff --git a/tests/main.test.ts b/tests/main.test.ts index 3ceaeb5..64c1063 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -2,7 +2,6 @@ import { db } from "./__mocks__/db"; import { server } from "./__mocks__/node"; import usersGet from "./__mocks__/users-get.json"; import { expect, describe, beforeAll, beforeEach, afterAll, afterEach, it } from "@jest/globals"; -import { Logs } from "@ubiquity-os/ubiquity-os-logger"; import { Context, SupportedEventsU } from "../src/types"; import { drop } from "@mswjs/data"; import issueTemplate from "./__mocks__/issue-template"; @@ -12,6 +11,7 @@ import { runPlugin } from "../src/plugin"; import { TransformDecodeCheckError, Value } from "@sinclair/typebox/value"; import { envSchema } from "../src/types/env"; import { CompletionsType } from "../src/adapters/openai/helpers/completions"; +import { logger } from "../src/helpers/errors"; const TEST_QUESTION = "what is pi?"; const TEST_SLASH_COMMAND = "@UbiquityOS what is pi?"; @@ -39,6 +39,23 @@ type Comment = { const octokit = jest.requireActual("@octokit/rest"); jest.requireActual("openai"); +// extractDependencies + +jest.mock("../src/handlers/ground-truths/chat-bot", () => { + return { + fetchRepoDependencies: jest.fn().mockReturnValue({ + dependencies: {}, + devDependencies: {}, + }), + extractDependencies: jest.fn(), + // [string, number][] + fetchRepoLanguageStats: jest.fn().mockReturnValue([ + ["JavaScript", 100], + ["TypeScript", 200], + ]), + }; +}); + beforeAll(() => { server.listen(); }); @@ -52,6 +69,7 @@ afterAll(() => server.close()); describe("Ask plugin tests", () => { beforeEach(async () => { + jest.clearAllMocks(); await setupTests(); }); @@ -102,23 +120,6 @@ describe("Ask plugin tests", () => { }); it("should construct the chat history correctly", async () => { - const ctx = createContext(TEST_SLASH_COMMAND); - const infoSpy = jest.spyOn(ctx.logger, "info"); - createComments([transformCommentTemplate(1, 1, TEST_QUESTION, "ubiquity", "test-repo", true)]); - await runPlugin(ctx); - - expect(infoSpy).toHaveBeenNthCalledWith(1, `Asking question: @UbiquityOS ${TEST_QUESTION}`); - expect(infoSpy).toHaveBeenNthCalledWith(4, "Answer: This is a mock answer for the chat", { - caller: LOG_CALLER, - tokenUsage: { - input: 1000, - output: 150, - total: 1150, - }, - }); - }); - - it("should collect the linked issues correctly", async () => { const ctx = createContext(TEST_SLASH_COMMAND); const infoSpy = jest.spyOn(ctx.logger, "info"); createComments([ @@ -129,46 +130,59 @@ describe("Ask plugin tests", () => { ]); await runPlugin(ctx); - - expect(infoSpy).toHaveBeenNthCalledWith(1, `Asking question: @UbiquityOS ${TEST_QUESTION}`); - - const prompt = `=== Current Issue #1 Specification === ubiquity/test-repo/1 === + const prompt = `=== Current Task Specification === ubiquity/test-repo/1 === This is a demo spec for a demo task just perfect for testing. - === End Current Issue #1 Specification === - === Current Issue #1 Conversation === ubiquity/test-repo #1 === + === End Current Task Specification === ubiquity/test-repo/1 === + + === Current Task Conversation === ubiquity/test-repo/1 === 1 ubiquity: ${ISSUE_ID_2_CONTENT} [#2](https://www.github.com/ubiquity/test-repo/issues/2) 2 ubiquity: ${TEST_QUESTION} [#1](https://www.github.com/ubiquity/test-repo/issues/1) - === End Current Issue #1 Conversation === + === End Current Task Conversation === ubiquity/test-repo/1 === - === Linked Issue #2 Specification === ubiquity/test-repo/2 === + === README === ubiquity/test-repo/1 === + + {"content":"This is a mock README file"} + + === End README === ubiquity/test-repo/1 === + + === Linked Task Specification === ubiquity/test-repo/2 === Related to issue #3 - === End Linked Issue #2 Specification === + === End Linked Task Specification === ubiquity/test-repo/2 === - === Linked Issue #2 Conversation === ubiquity/test-repo #2 === + === Linked Task Conversation === ubiquity/test-repo/2 === 3 ubiquity: ${ISSUE_ID_3_CONTENT} [#3](https://www.github.com/ubiquity/test-repo/issues/3) - === End Linked Issue #2 Conversation === + === End Linked Task Conversation === ubiquity/test-repo/2 === - === Linked Issue #3 Specification === ubiquity/test-repo/3 === + === Linked Task Specification === ubiquity/test-repo/3 === Just another issue - === End Linked Issue #3 Specification === + === End Linked Task Specification === ubiquity/test-repo/3 === - === Linked Issue #3 Conversation === ubiquity/test-repo #3 === + === Linked Task Conversation === ubiquity/test-repo/3 === 4 ubiquity: Just a comment [#1](https://www.github.com/ubiquity/test-repo/issues/1) - 4 ubiquity: Just a comment [#1](https://www.github.com/ubiquity/test-repo/issues/1) - === End Linked Issue #3 Conversation ===\n - `; + === End Linked Task Conversation === ubiquity/test-repo/3 ===`; const normalizedExpected = normalizeString(prompt); - const normalizedReceived = normalizeString(infoSpy.mock.calls[1][0] as string); + const normalizedReceived = normalizeString(infoSpy.mock.calls[0][0] as string); expect(normalizedReceived).toEqual(normalizedExpected); + expect(infoSpy).toHaveBeenNthCalledWith(2, "Answer: This is a mock answer for the chat", { + caller: LOG_CALLER, + metadata: { + tokenUsage: { + input: 1000, + output: 150, + total: 1150, + }, + groundTruths: ["This is a mock answer for the chat"], + }, + }); }); }); @@ -266,7 +280,7 @@ function createContext(body = TEST_SLASH_COMMAND) { }, owner: "ubiquity", repo: "test-repo", - logger: new Logs("debug"), + logger: logger, config: {}, env: { UBIQUITY_OS_APP_NAME: "UbiquityOS", @@ -391,6 +405,12 @@ function createContext(body = TEST_SLASH_COMMAND) { }, openai: { completions: { + getModelMaxTokenLimit: () => { + return 50000; + }, + getModelMaxOutputLimit: () => { + return 50000; + }, createCompletion: async (): Promise => { return { answer: MOCK_ANSWER, diff --git a/yarn.lock b/yarn.lock index ad622f5..6588153 100644 --- a/yarn.lock +++ b/yarn.lock @@ -979,11 +979,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== -"@esbuild/aix-ppc64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz#b57697945b50e99007b4c2521507dc613d4a648c" - integrity sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw== - "@esbuild/android-arm64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd" @@ -994,11 +989,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== -"@esbuild/android-arm64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz#1add7e0af67acefd556e407f8497e81fddad79c0" - integrity sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w== - "@esbuild/android-arm@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d" @@ -1009,11 +999,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== -"@esbuild/android-arm@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz#ab7263045fa8e090833a8e3c393b60d59a789810" - integrity sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew== - "@esbuild/android-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1" @@ -1024,11 +1009,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== -"@esbuild/android-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz#e8f8b196cfdfdd5aeaebbdb0110983460440e705" - integrity sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ== - "@esbuild/darwin-arm64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz#584c34c5991b95d4d48d333300b1a4e2ff7be276" @@ -1039,11 +1019,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== -"@esbuild/darwin-arm64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz#2d0d9414f2acbffd2d86e98253914fca603a53dd" - integrity sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw== - "@esbuild/darwin-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb" @@ -1054,11 +1029,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== -"@esbuild/darwin-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz#33087aab31a1eb64c89daf3d2cf8ce1775656107" - integrity sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA== - "@esbuild/freebsd-arm64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2" @@ -1069,11 +1039,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== -"@esbuild/freebsd-arm64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz#bb76e5ea9e97fa3c753472f19421075d3a33e8a7" - integrity sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA== - "@esbuild/freebsd-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4" @@ -1084,11 +1049,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== -"@esbuild/freebsd-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz#e0e2ce9249fdf6ee29e5dc3d420c7007fa579b93" - integrity sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ== - "@esbuild/linux-arm64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb" @@ -1099,11 +1059,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== -"@esbuild/linux-arm64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz#d1b2aa58085f73ecf45533c07c82d81235388e75" - integrity sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g== - "@esbuild/linux-arm@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a" @@ -1114,11 +1069,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== -"@esbuild/linux-arm@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz#8e4915df8ea3e12b690a057e77a47b1d5935ef6d" - integrity sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw== - "@esbuild/linux-ia32@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a" @@ -1129,11 +1079,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== -"@esbuild/linux-ia32@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz#8200b1110666c39ab316572324b7af63d82013fb" - integrity sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA== - "@esbuild/linux-loong64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72" @@ -1144,11 +1089,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== -"@esbuild/linux-loong64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz#6ff0c99cf647504df321d0640f0d32e557da745c" - integrity sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g== - "@esbuild/linux-mips64el@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289" @@ -1159,11 +1099,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== -"@esbuild/linux-mips64el@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz#3f720ccd4d59bfeb4c2ce276a46b77ad380fa1f3" - integrity sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA== - "@esbuild/linux-ppc64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7" @@ -1174,11 +1109,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== -"@esbuild/linux-ppc64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz#9d6b188b15c25afd2e213474bf5f31e42e3aa09e" - integrity sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ== - "@esbuild/linux-riscv64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09" @@ -1189,11 +1119,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== -"@esbuild/linux-riscv64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz#f989fdc9752dfda286c9cd87c46248e4dfecbc25" - integrity sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw== - "@esbuild/linux-s390x@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829" @@ -1204,11 +1129,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== -"@esbuild/linux-s390x@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz#29ebf87e4132ea659c1489fce63cd8509d1c7319" - integrity sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g== - "@esbuild/linux-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4" @@ -1219,11 +1139,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== -"@esbuild/linux-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz#4af48c5c0479569b1f359ffbce22d15f261c0cef" - integrity sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA== - "@esbuild/netbsd-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462" @@ -1234,16 +1149,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== -"@esbuild/netbsd-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz#1ae73d23cc044a0ebd4f198334416fb26c31366c" - integrity sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg== - -"@esbuild/openbsd-arm64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz#5d904a4f5158c89859fd902c427f96d6a9e632e2" - integrity sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg== - "@esbuild/openbsd-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691" @@ -1254,11 +1159,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== -"@esbuild/openbsd-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz#4c8aa88c49187c601bae2971e71c6dc5e0ad1cdf" - integrity sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q== - "@esbuild/sunos-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273" @@ -1269,11 +1169,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== -"@esbuild/sunos-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz#8ddc35a0ea38575fa44eda30a5ee01ae2fa54dd4" - integrity sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA== - "@esbuild/win32-arm64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f" @@ -1284,11 +1179,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== -"@esbuild/win32-arm64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz#6e79c8543f282c4539db684a207ae0e174a9007b" - integrity sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA== - "@esbuild/win32-ia32@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03" @@ -1299,11 +1189,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== -"@esbuild/win32-ia32@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz#057af345da256b7192d18b676a02e95d0fa39103" - integrity sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw== - "@esbuild/win32-x64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061" @@ -1314,11 +1199,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== -"@esbuild/win32-x64@0.24.0": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz#168ab1c7e1c318b922637fad8f339d48b01e1244" - integrity sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA== - "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -1748,83 +1628,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@octokit/app@^15.0.0": - version "15.1.0" - resolved "https://registry.yarnpkg.com/@octokit/app/-/app-15.1.0.tgz#b330d8826be088ec8d1d43a59dc27ef57d1232b2" - integrity sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg== - dependencies: - "@octokit/auth-app" "^7.0.0" - "@octokit/auth-unauthenticated" "^6.0.0" - "@octokit/core" "^6.1.2" - "@octokit/oauth-app" "^7.0.0" - "@octokit/plugin-paginate-rest" "^11.0.0" - "@octokit/types" "^13.0.0" - "@octokit/webhooks" "^13.0.0" - -"@octokit/auth-app@^7.0.0": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@octokit/auth-app/-/auth-app-7.1.1.tgz#d8916ad01e6ffb0a0a50507aa613e91fe7a49b93" - integrity sha512-kRAd6yelV9OgvlEJE88H0VLlQdZcag9UlLr7dV0YYP37X8PPDvhgiTy66QVhDXdyoT0AleFN2w/qXkPdrSzINg== - dependencies: - "@octokit/auth-oauth-app" "^8.1.0" - "@octokit/auth-oauth-user" "^5.1.0" - "@octokit/request" "^9.1.1" - "@octokit/request-error" "^6.1.1" - "@octokit/types" "^13.4.1" - lru-cache "^10.0.0" - universal-github-app-jwt "^2.2.0" - universal-user-agent "^7.0.0" - -"@octokit/auth-oauth-app@^8.0.0", "@octokit/auth-oauth-app@^8.1.0": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz#6204affa6e86f535016799cadf2af9befe5e893c" - integrity sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg== - dependencies: - "@octokit/auth-oauth-device" "^7.0.0" - "@octokit/auth-oauth-user" "^5.0.1" - "@octokit/request" "^9.0.0" - "@octokit/types" "^13.0.0" - universal-user-agent "^7.0.0" - -"@octokit/auth-oauth-device@^7.0.0", "@octokit/auth-oauth-device@^7.0.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz#7b4f8f97cbcadbe9894d48cde4406dbdef39875a" - integrity sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg== - dependencies: - "@octokit/oauth-methods" "^5.0.0" - "@octokit/request" "^9.0.0" - "@octokit/types" "^13.0.0" - universal-user-agent "^7.0.0" - -"@octokit/auth-oauth-user@^5.0.1", "@octokit/auth-oauth-user@^5.1.0": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz#4f1570c6ee15bb9ddc3dcca83308dcaa159e3848" - integrity sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw== - dependencies: - "@octokit/auth-oauth-device" "^7.0.1" - "@octokit/oauth-methods" "^5.0.0" - "@octokit/request" "^9.0.1" - "@octokit/types" "^13.0.0" - universal-user-agent "^7.0.0" - "@octokit/auth-token@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-4.0.0.tgz#40d203ea827b9f17f42a29c6afb93b7745ef80c7" integrity sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA== -"@octokit/auth-token@^5.0.0": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-5.1.1.tgz#3bbfe905111332a17f72d80bd0b51a3e2fa2cf07" - integrity sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA== - -"@octokit/auth-unauthenticated@^6.0.0", "@octokit/auth-unauthenticated@^6.0.0-beta.1": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@octokit/auth-unauthenticated/-/auth-unauthenticated-6.1.0.tgz#de0fe923bb06ed93aea526ab99972a98c546d0bf" - integrity sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ== - dependencies: - "@octokit/request-error" "^6.0.1" - "@octokit/types" "^13.0.0" - "@octokit/core@^5.0.1", "@octokit/core@^5.0.2": version "5.2.0" resolved "https://registry.yarnpkg.com/@octokit/core/-/core-5.2.0.tgz#ddbeaefc6b44a39834e1bb2e58a49a117672a7ea" @@ -1838,27 +1646,6 @@ before-after-hook "^2.2.0" universal-user-agent "^6.0.0" -"@octokit/core@^6.0.0", "@octokit/core@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@octokit/core/-/core-6.1.2.tgz#20442d0a97c411612da206411e356014d1d1bd17" - integrity sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg== - dependencies: - "@octokit/auth-token" "^5.0.0" - "@octokit/graphql" "^8.0.0" - "@octokit/request" "^9.0.0" - "@octokit/request-error" "^6.0.1" - "@octokit/types" "^13.0.0" - before-after-hook "^3.0.2" - universal-user-agent "^7.0.0" - -"@octokit/endpoint@^10.0.0": - version "10.1.1" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-10.1.1.tgz#1a9694e7aef6aa9d854dc78dd062945945869bcc" - integrity sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q== - dependencies: - "@octokit/types" "^13.0.0" - universal-user-agent "^7.0.2" - "@octokit/endpoint@^9.0.1": version "9.0.5" resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-9.0.5.tgz#e6c0ee684e307614c02fc6ac12274c50da465c44" @@ -1876,44 +1663,6 @@ "@octokit/types" "^13.0.0" universal-user-agent "^6.0.0" -"@octokit/graphql@^8.0.0": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-8.1.1.tgz#3cacab5f2e55d91c733e3bf481d3a3f8a5f639c4" - integrity sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg== - dependencies: - "@octokit/request" "^9.0.0" - "@octokit/types" "^13.0.0" - universal-user-agent "^7.0.0" - -"@octokit/oauth-app@^7.0.0": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@octokit/oauth-app/-/oauth-app-7.1.3.tgz#a0f256dd185e7c00bfbc3e6bc3c5aad66e42c609" - integrity sha512-EHXbOpBkSGVVGF1W+NLMmsnSsJRkcrnVmDKt0TQYRBb6xWfWzoi9sBD4DIqZ8jGhOWO/V8t4fqFyJ4vDQDn9bg== - dependencies: - "@octokit/auth-oauth-app" "^8.0.0" - "@octokit/auth-oauth-user" "^5.0.1" - "@octokit/auth-unauthenticated" "^6.0.0-beta.1" - "@octokit/core" "^6.0.0" - "@octokit/oauth-authorization-url" "^7.0.0" - "@octokit/oauth-methods" "^5.0.0" - "@types/aws-lambda" "^8.10.83" - universal-user-agent "^7.0.0" - -"@octokit/oauth-authorization-url@^7.0.0": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz#0e17c2225eb66b58ec902d02b6f1315ffe9ff04b" - integrity sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA== - -"@octokit/oauth-methods@^5.0.0": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@octokit/oauth-methods/-/oauth-methods-5.1.2.tgz#fd31d2a69f4c91d1abc1ed1814dda5252c697e02" - integrity sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g== - dependencies: - "@octokit/oauth-authorization-url" "^7.0.0" - "@octokit/request" "^9.1.0" - "@octokit/request-error" "^6.1.0" - "@octokit/types" "^13.0.0" - "@octokit/openapi-types@^20.0.0": version "20.0.0" resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-20.0.0.tgz#9ec2daa0090eeb865ee147636e0c00f73790c6e5" @@ -1929,16 +1678,6 @@ resolved "https://registry.yarnpkg.com/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-8.2.1.tgz#08b974f1e83a75c4d3ce23f798c7667b433bf4cd" integrity sha512-msAU1oTSm0ZmvAE0xDemuF4tVs5i0xNnNGtNmr4EuATi+1Rn8cZDetj6NXioSf5LwnxEc209COa/WOSbjuhLUA== -"@octokit/openapi-webhooks-types@8.3.0": - version "8.3.0" - resolved "https://registry.yarnpkg.com/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-8.3.0.tgz#a7a4da00c0f27f7f5708eb3fcebefa08f8d51125" - integrity sha512-vKLsoR4xQxg4Z+6rU/F65ItTUz/EXbD+j/d4mlq2GW8TsA4Tc8Kdma2JTAAJ5hrKWUQzkR/Esn2fjsqiVRYaQg== - -"@octokit/plugin-paginate-graphql@^5.0.0": - version "5.2.4" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-5.2.4.tgz#b6afda7b3f24cb93d2ab822ec8eac664a5d325d0" - integrity sha512-pLZES1jWaOynXKHOqdnwZ5ULeVR6tVVCMm+AUbp0htdcyXDU95WbkYdU4R2ej1wKj5Tu94Mee2Ne0PjPO9cCyA== - "@octokit/plugin-paginate-rest@11.3.1": version "11.3.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.1.tgz#fe92d04b49f134165d6fbb716e765c2f313ad364" @@ -1946,13 +1685,6 @@ dependencies: "@octokit/types" "^13.5.0" -"@octokit/plugin-paginate-rest@^11.0.0": - version "11.3.5" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.5.tgz#a1929b3ba3dc7b63bc73bb6d3c7a3faf2a9c7649" - integrity sha512-cgwIRtKrpwhLoBi0CUNuY83DPGRMaWVjqVI/bGKsLJ4PzyWZNaEmhHroI2xlrVXkk6nFv0IsZpOp+ZWSWUS2AQ== - dependencies: - "@octokit/types" "^13.6.0" - "@octokit/plugin-paginate-rest@^9.0.0": version "9.2.1" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.1.tgz#2e2a2f0f52c9a4b1da1a3aa17dabe3c459b9e401" @@ -1979,30 +1711,6 @@ dependencies: "@octokit/types" "^12.6.0" -"@octokit/plugin-rest-endpoint-methods@^13.0.0": - version "13.2.6" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.6.tgz#b9d343dbe88a6cb70cc7fa16faa98f0a29ffe654" - integrity sha512-wMsdyHMjSfKjGINkdGKki06VEkgdEldIGstIEyGX0wbYHGByOwN/KiM+hAAlUwAtPkP3gvXtVQA9L3ITdV2tVw== - dependencies: - "@octokit/types" "^13.6.1" - -"@octokit/plugin-retry@^7.0.0": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@octokit/plugin-retry/-/plugin-retry-7.1.2.tgz#242e2d19a72a50b5113bb25d7d2c622ce0373fa0" - integrity sha512-XOWnPpH2kJ5VTwozsxGurw+svB2e61aWlmk5EVIYZPwFK5F9h4cyPyj9CIKRyMXMHSwpIsI3mPOdpMmrRhe7UQ== - dependencies: - "@octokit/request-error" "^6.0.0" - "@octokit/types" "^13.0.0" - bottleneck "^2.15.3" - -"@octokit/plugin-throttling@^9.0.0": - version "9.3.2" - resolved "https://registry.yarnpkg.com/@octokit/plugin-throttling/-/plugin-throttling-9.3.2.tgz#cc05180e45e769d6726c5faed157e9ad3b6ab8c0" - integrity sha512-FqpvcTpIWFpMMwIeSoypoJXysSAQ3R+ALJhXXSG1HTP3YZOIeLmcNcimKaXxTcws+Sh6yoRl13SJ5r8sXc1Fhw== - dependencies: - "@octokit/types" "^13.0.0" - bottleneck "^2.15.3" - "@octokit/request-error@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-5.1.0.tgz#ee4138538d08c81a60be3f320cd71063064a3b30" @@ -2012,13 +1720,6 @@ deprecation "^2.0.0" once "^1.4.0" -"@octokit/request-error@^6.0.0", "@octokit/request-error@^6.1.0", "@octokit/request-error@^6.1.1": - version "6.1.5" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-6.1.5.tgz#907099e341c4e6179db623a0328d678024f54653" - integrity sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ== - dependencies: - "@octokit/types" "^13.0.0" - "@octokit/request-error@^6.0.1": version "6.1.1" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-6.1.1.tgz#bed1b5f52ce7fefb1077a92bf42124ff36f73f2c" @@ -2036,16 +1737,6 @@ "@octokit/types" "^13.1.0" universal-user-agent "^6.0.0" -"@octokit/request@^9.0.0", "@octokit/request@^9.0.1", "@octokit/request@^9.1.0", "@octokit/request@^9.1.1": - version "9.1.3" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-9.1.3.tgz#42b693bc06238f43af3c037ebfd35621c6457838" - integrity sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA== - dependencies: - "@octokit/endpoint" "^10.0.0" - "@octokit/request-error" "^6.0.1" - "@octokit/types" "^13.1.0" - universal-user-agent "^7.0.2" - "@octokit/rest@20.1.1": version "20.1.1" resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-20.1.1.tgz#ec775864f53fb42037a954b9a40d4f5275b3dc95" @@ -2070,13 +1761,6 @@ dependencies: "@octokit/openapi-types" "^22.2.0" -"@octokit/types@^13.4.1", "@octokit/types@^13.6.0", "@octokit/types@^13.6.1": - version "13.6.1" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.1.tgz#432fc6c0aaae54318e5b2d3e15c22ac97fc9b15f" - integrity sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g== - dependencies: - "@octokit/openapi-types" "^22.2.0" - "@octokit/webhooks-methods@^5.0.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@octokit/webhooks-methods/-/webhooks-methods-5.1.0.tgz#13b6c08f89902c1ab0ddf31c6eeeec9c2772cfe6" @@ -2092,15 +1776,6 @@ "@octokit/webhooks-methods" "^5.0.0" aggregate-error "^5.0.0" -"@octokit/webhooks@^13.0.0": - version "13.3.0" - resolved "https://registry.yarnpkg.com/@octokit/webhooks/-/webhooks-13.3.0.tgz#fd5d54d47c789c75d60a00eb04e982152d7c654a" - integrity sha512-TUkJLtI163Bz5+JK0O+zDkQpn4gKwN+BovclUvCj6pI/6RXrFqQvUMRS2M+Rt8Rv0qR3wjoMoOPmpJKeOh0nBg== - dependencies: - "@octokit/openapi-webhooks-types" "8.3.0" - "@octokit/request-error" "^6.0.1" - "@octokit/webhooks-methods" "^5.0.0" - "@open-draft/deferred-promise@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz#4a822d10f6f0e316be4d67b4d4f8c9a124b073bd" @@ -2214,11 +1889,6 @@ "@supabase/realtime-js" "2.10.2" "@supabase/storage-js" "2.7.0" -"@types/aws-lambda@^8.10.83": - version "8.10.145" - resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.145.tgz#b2d31a987f4888e5553ff1819f57cafa475594d9" - integrity sha512-dtByW6WiFk5W5Jfgz1VM+YPA21xMXTuSFoLYIDY0L44jDLLflVPtZkYuu3/YxpGcvjzKFBZLU+GyKjR0HOYtyw== - "@types/babel__core@^7.1.14": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -2795,11 +2465,6 @@ before-after-hook@^2.2.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== -before-after-hook@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-3.0.2.tgz#d5665a5fa8b62294a5aa0a499f933f4a1016195d" - integrity sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A== - binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" @@ -2810,11 +2475,6 @@ blake3-wasm@^2.1.5: resolved "https://registry.yarnpkg.com/blake3-wasm/-/blake3-wasm-2.1.5.tgz#b22dbb84bc9419ed0159caa76af4b1b132e6ba52" integrity sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g== -bottleneck@^2.15.3: - version "2.19.5" - resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" - integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw== - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3477,7 +3137,7 @@ dot-prop@^5.1.0: dependencies: is-obj "^2.0.0" -dotenv@^16.3.1, dotenv@^16.4.5: +dotenv@^16.4.5: version "16.4.5" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== @@ -3645,36 +3305,6 @@ esbuild@0.17.19: "@esbuild/win32-ia32" "0.17.19" "@esbuild/win32-x64" "0.17.19" -esbuild@^0.24.0: - version "0.24.0" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.0.tgz#f2d470596885fcb2e91c21eb3da3b3c89c0b55e7" - integrity sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ== - optionalDependencies: - "@esbuild/aix-ppc64" "0.24.0" - "@esbuild/android-arm" "0.24.0" - "@esbuild/android-arm64" "0.24.0" - "@esbuild/android-x64" "0.24.0" - "@esbuild/darwin-arm64" "0.24.0" - "@esbuild/darwin-x64" "0.24.0" - "@esbuild/freebsd-arm64" "0.24.0" - "@esbuild/freebsd-x64" "0.24.0" - "@esbuild/linux-arm" "0.24.0" - "@esbuild/linux-arm64" "0.24.0" - "@esbuild/linux-ia32" "0.24.0" - "@esbuild/linux-loong64" "0.24.0" - "@esbuild/linux-mips64el" "0.24.0" - "@esbuild/linux-ppc64" "0.24.0" - "@esbuild/linux-riscv64" "0.24.0" - "@esbuild/linux-s390x" "0.24.0" - "@esbuild/linux-x64" "0.24.0" - "@esbuild/netbsd-x64" "0.24.0" - "@esbuild/openbsd-arm64" "0.24.0" - "@esbuild/openbsd-x64" "0.24.0" - "@esbuild/sunos-x64" "0.24.0" - "@esbuild/win32-arm64" "0.24.0" - "@esbuild/win32-ia32" "0.24.0" - "@esbuild/win32-x64" "0.24.0" - esbuild@~0.21.4: version "0.21.5" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" @@ -4182,15 +3812,6 @@ git-raw-commits@^4.0.0: meow "^12.0.1" split2 "^4.0.0" -github-diff-tool@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/github-diff-tool/-/github-diff-tool-1.0.6.tgz#e633b46397db850ad3dc0d500450357cb7ee26f9" - integrity sha512-DOqKck+WUj3HsfOwef5cjS32qqOkKWFncIl4erBtp2+dfccrkSi6Ee14mKGnrQaAhMrx/9LWFh8X5KGivZVY8A== - dependencies: - dotenv "^16.3.1" - esbuild "^0.24.0" - octokit "^4.0.2" - glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -5389,11 +5010,6 @@ log-update@^6.0.0: strip-ansi "^7.1.0" wrap-ansi "^9.0.0" -lru-cache@^10.0.0: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -5716,22 +5332,6 @@ object.assign@^4.1.5: has-symbols "^1.0.3" object-keys "^1.1.1" -octokit@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/octokit/-/octokit-4.0.2.tgz#775d68d363cdaec69d7b73d3dc82ae909d30f59b" - integrity sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg== - dependencies: - "@octokit/app" "^15.0.0" - "@octokit/core" "^6.0.0" - "@octokit/oauth-app" "^7.0.0" - "@octokit/plugin-paginate-graphql" "^5.0.0" - "@octokit/plugin-paginate-rest" "^11.0.0" - "@octokit/plugin-rest-endpoint-methods" "^13.0.0" - "@octokit/plugin-retry" "^7.0.0" - "@octokit/plugin-throttling" "^9.0.0" - "@octokit/request-error" "^6.0.0" - "@octokit/types" "^13.0.0" - ohash@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.4.tgz#ae8d83014ab81157d2c285abf7792e2995fadd72" @@ -6932,21 +6532,11 @@ unicorn-magic@^0.1.0: resolved "https://registry.yarnpkg.com/unicorn-magic/-/unicorn-magic-0.1.0.tgz#1bb9a51c823aaf9d73a8bfcd3d1a23dde94b0ce4" integrity sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ== -universal-github-app-jwt@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz#dc6c8929e76f1996a766ba2a08fb420f73365d77" - integrity sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ== - universal-user-agent@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.1.tgz#15f20f55da3c930c57bddbf1734c6654d5fd35aa" integrity sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ== -universal-user-agent@^7.0.0, universal-user-agent@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-7.0.2.tgz#52e7d0e9b3dc4df06cc33cb2b9fd79041a54827e" - integrity sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q== - universalify@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"