From 24d7b05c0d4e1eb07b2c6b11dd19c0aba4f9a603 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Fri, 4 Oct 2024 16:55:00 -0700 Subject: [PATCH] refactor and optimize git operations for commit messages and failure analysis (#7412) --- .gitignore.genai | 3 + genaisrc/gai.genai.mts | 17 +++ genaisrc/gcm.genai.mts | 30 ++--- genaisrc/genaiscript.d.ts | 247 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 264 insertions(+), 33 deletions(-) create mode 100644 .gitignore.genai create mode 100644 genaisrc/gai.genai.mts diff --git a/.gitignore.genai b/.gitignore.genai new file mode 100644 index 000000000..254ca5bfe --- /dev/null +++ b/.gitignore.genai @@ -0,0 +1,3 @@ +**/genaiscript.d.ts +**/package-lock.json +**/yarn.lock diff --git a/genaisrc/gai.genai.mts b/genaisrc/gai.genai.mts new file mode 100644 index 000000000..9de3cf11a --- /dev/null +++ b/genaisrc/gai.genai.mts @@ -0,0 +1,17 @@ +script({ + tools: ["agent_fs", "agent_git", "agent_github"], +}) + +const { + workflow = "latest failed", + failure_run_id = "latest", + branch = await git.defaultBranch(), +} = env.vars + +$`Investigate the status of the ${workflow} workflow and identify the root cause of the failure of run ${failure_run_id} in branch ${branch}. + +- Correlate the failure with the relevant commits, pull requests or issues. +- Compare the source code between the failed run commit and the last successful run commit before that run. + +In your report, include html links to the relevant runs, commits, pull requests or issues. +` diff --git a/genaisrc/gcm.genai.mts b/genaisrc/gcm.genai.mts index e1ebd1197..d1f97174f 100644 --- a/genaisrc/gcm.genai.mts +++ b/genaisrc/gcm.genai.mts @@ -6,28 +6,16 @@ script({ description: "Generate a commit message for all staged changes", }) -// TODO: update this diff command to match your workspace -const diffCmd = "git diff --cached -- . :!**/genaiscript.d.ts" - // Check for staged changes and stage all changes if none are staged -let diff = await host.exec(diffCmd) -if (!diff.stdout) { - /** - * Ask user to stage all changes if none are staged - */ - const stage = await host.confirm("No staged changes. Stage all changes?", { - default: true, - }) - if (stage) { - // Stage all changes and recompute diff - await host.exec("git add .") - diff = await host.exec(diffCmd) - } - if (!diff.stdout) cancel("no staged changes") -} +const diff = await git.diff({ + staged: true, + excludedPaths: "**/genaiscript.d.ts", + askStageOnEmpty: true, +}) +if (!diff) cancel("no staged changes") // show diff in the console -console.log(diff.stdout) +console.log(diff) let choice let message @@ -79,9 +67,9 @@ Please generate a concise, one-line commit message for these changes. } // Regenerate message if (choice === "commit" && message) { - console.log((await host.exec("git", ["commit", "-m", message])).stdout) + console.log(await git.exec(["commit", "-m", message])) if (await host.confirm("Push changes?", { default: true })) - console.log((await host.exec("git push")).stdout) + console.log(await git.exec("push")) break } } while (choice !== "commit") diff --git a/genaisrc/genaiscript.d.ts b/genaisrc/genaiscript.d.ts index c0a6426d2..e11a3edd7 100644 --- a/genaisrc/genaiscript.d.ts +++ b/genaisrc/genaiscript.d.ts @@ -71,6 +71,11 @@ interface PromptLike extends PromptDefinition { type SystemPromptId = OptionsOrString< | "system" + | "system.agent_fs" + | "system.agent_git" + | "system.agent_github" + | "system.agent_interpreter" + | "system.agent_user_input" | "system.annotations" | "system.changelog" | "system.diagrams" @@ -78,8 +83,15 @@ type SystemPromptId = OptionsOrString< | "system.explanations" | "system.files" | "system.files_schema" + | "system.fs_diff_files" | "system.fs_find_files" | "system.fs_read_file" + | "system.git" + | "system.github_actions" + | "system.github_files" + | "system.github_info" + | "system.github_issues" + | "system.github_pulls" | "system.math" | "system.md_frontmatter" | "system.python" @@ -92,18 +104,47 @@ type SystemPromptId = OptionsOrString< | "system.technical" | "system.tools" | "system.typescript" + | "system.user_input" | "system.zero_shot_cot" > type SystemToolId = OptionsOrString< + | "agent_fs" + | "agent_git" + | "agent_github" + | "agent_interpreter" + | "agent_user_input" + | "fs_diff_files" | "fs_find_files" | "fs_read_file" + | "git_branch_current" + | "git_branch_list" + | "git_diff" + | "git_last_tag" + | "git_log" + | "git_status" + | "github_actions_job_logs_diff" + | "github_actions_job_logs_get" + | "github_actions_jobs_list" + | "github_actions_workflows_list" + | "github_files_get" + | "github_files_list" + | "github_issues_comments_list" + | "github_issues_get" + | "github_issues_list" + | "github_pulls_get" + | "github_pulls_list" + | "github_pulls_review_comments_list" | "math_eval" | "md_read_frontmatter" - | "python_code_interpreter" + | "python_code_interpreter_copy_files" + | "python_code_interpreter_run" | "retrieval_fuzz_search" | "retrieval_vector_search" | "retrieval_web_search" + | "user_input_confirm" + | "user_input_select" + | "user_input_text" > type FileMergeHandler = ( @@ -419,6 +460,11 @@ interface PromptScript * Set if this is a system prompt. */ isSystem?: boolean + + /** + * List of tools defined in the script + */ + defTools?: { id: string, description: string }[] } /** @@ -607,10 +653,25 @@ interface WorkspaceFileSystem { */ readJSON(path: string | Awaitable): Promise + /** + * Reads the content of a file and parses to YAML. + * @param path + */ + readYAML(path: string | Awaitable): Promise + /** * Reads the content of a file and parses to XML, using the XML parser. */ - readXML(path: string | Awaitable): Promise + readXML(path: string | Awaitable, options?: XMLParseOptions): Promise + + /** + * Reads the content of a CSV file. + * @param path + */ + readCSV( + path: string | Awaitable, + options?: CSVParseOptions + ): Promise /** * Writes a file as text to the file system @@ -630,6 +691,7 @@ interface WorkspaceFileSystem { } interface ToolCallContext { + log(message: string): void trace: ToolCallTrace } @@ -701,7 +763,7 @@ interface ExpansionVariables { type MakeOptional = Partial> & Omit -type PromptArgs = Omit +type PromptArgs = Omit type PromptSystemArgs = Omit< PromptArgs, @@ -717,6 +779,7 @@ type PromptSystemArgs = Omit< | "responseSchema" | "files" | "modelConcurrency" + | "parameters" > type StringLike = string | WorkspaceFile | WorkspaceFile[] @@ -764,18 +827,22 @@ interface ContextExpansionOptions { * It defaults to 1 on all elements. */ flex?: number + /** + * This text is likely to change and will probably break the prefix cache. + */ + ephemeral?: boolean } interface DefOptions extends FenceOptions, ContextExpansionOptions, DataFilter { /** * Filename filter based on file suffix. Case insensitive. */ - endsWith?: string + endsWith?: ElementOrArray /** * Filename filter using glob syntax. */ - glob?: string + glob?: ElementOrArray /** * By default, throws an error if the value in def is empty. @@ -832,6 +899,7 @@ type JSONSchemaType = interface JSONSchemaString { type: "string" + enum?: string[] description?: string default?: string } @@ -900,6 +968,7 @@ interface RunPromptResult { | "content_filter" | "cancel" | "fail" + usages?: ChatCompletionUsages } /** @@ -1002,6 +1071,11 @@ interface ParseZipOptions { type TokenEncoder = (text: string) => number[] +interface CSVParseOptions { + delimiter?: string + headers?: string[] +} + interface Parsers { /** * Parses text as a JSON5 payload @@ -1067,7 +1141,7 @@ interface Parsers { */ CSV( content: string | WorkspaceFile, - options?: { delimiter?: string; headers?: string[] } + options?: CSVParseOptions ): object[] | undefined /** @@ -1177,6 +1251,11 @@ interface Parsers { * Renders a jinja template */ jinja(text: string | WorkspaceFile, data: Record): string + + /** + * Computes a diff between two files + */ + diff(left: WorkspaceFile, right: WorkspaceFile, options?: DefDiffOptions): string } interface AICIGenOptions { @@ -1292,6 +1371,90 @@ interface HTML { convertToMarkdown(html: string): Promise } +interface GitCommit { + sha: string + message: string +} + +interface Git { + /** + * Resolves the default branch for this repository + */ + defaultBranch(): Promise + + /** + * Gets the last tag in the repository + */ + lastTag(): Promise + + /** + * Gets the current branch of the repository + */ + branch(): Promise + + /** + * Executes a git command in the repository and returns the stdout + * @param cmd + */ + exec(args: string[] | string, options?: { label?: string }): Promise + + /** + * Lists the branches in the git repository + */ + listBranches(): Promise + + /** + * Finds specific files in the git repository. + * By default, work + * @param options + */ + listFiles( + scope: "modified-base" | "staged" | "modified", + options?: { + base?: string + /** + * Ask the user to stage the changes if the diff is empty. + */ + askStageOnEmpty?: boolean + paths?: ElementOrArray + excludedPaths?: ElementOrArray + } + ): Promise + + /** + * + * @param options + */ + diff(options?: { + staged?: boolean + /** + * Ask the user to stage the changes if the diff is empty. + */ + askStageOnEmpty?: boolean + base?: string + head?: string + paths?: ElementOrArray + excludedPaths?: ElementOrArray + unified?: number + /** + * Modifies the diff to be in a more LLM friendly format + */ + llmify?: boolean + }): Promise + + /** + * Lists the commits in the git repository + */ + log(options?: { + base?: string + head?: string + merges?: boolean + excludedGrep?: string | RegExp + paths?: ElementOrArray + excludedPaths?: ElementOrArray + }): Promise +} + interface GitHubOptions { owner: string repo: string @@ -1350,7 +1513,22 @@ interface GitHubIssue { number: number state: string state_reason?: "completed" | "reopened" | "not_planned" | null - html_url: string + html_url: string + draft?: boolean + reactions?: GitHubReactions +} + +interface GitHubReactions { + url: string + total_count: number + "+1": number + "-1": number + laugh: number + confused: number + heart: number + hooray: number + eyes: number + rocket: number } interface GitHubComment { @@ -1359,9 +1537,11 @@ interface GitHubComment { created_at: string updated_at: string html_url: string + reactions?: GitHubReactions } -interface GitHubPullRequest extends GitHubIssue {} +interface GitHubPullRequest extends GitHubIssue { +} interface GitHubCodeSearchResult { name: string @@ -1379,8 +1559,10 @@ interface GitHubWorkflow { } interface GitHubPaginationOptions { - page?: number - per_page?: number + /** + * Default number of items to fetch, default is 50. + */ + count?: number } interface GitHubFile extends WorkspaceFile { @@ -1388,6 +1570,10 @@ interface GitHubFile extends WorkspaceFile { size: number } +interface GitHubUser { + login: string +} + interface GitHub { /** * Gets connection information for octokit @@ -1422,6 +1608,20 @@ interface GitHub { options?: GitHubPaginationOptions ): Promise + /** + * Downloads a GitHub Action workflow run log + * @param jobId + */ + downloadWorkflowJobLog( + jobId: number, + options?: { llmify?: boolean } + ): Promise + + /** + * Diffs two GitHub Action workflow job logs + */ + diffWorkflowJobLogs(job_id: number, other_job_id: number): Promise + /** * Lists issues for a given repository * @param options @@ -1435,6 +1635,13 @@ interface GitHub { } & GitHubPaginationOptions ): Promise + + /** + * Gets the details of a GitHub issue + * @param number issue number (not the issue id!) + */ + async getIssue(number: number): Promise + /** * Lists comments for a given issue * @param issue_number @@ -1457,6 +1664,12 @@ interface GitHub { } & GitHubPaginationOptions ): Promise + /** + * Gets the details of a GitHub pull request + * @param pull_number pull request number (not the pull request id!) + */ + getPullRequest(pull_number: number): Promise + /** * Lists comments for a given pull request * @param pull_number @@ -1511,6 +1724,11 @@ interface GitHub { type?: (typeof GitHubFile)["type"] } ): Promise + + /** + * Gets the underlying Octokit client + */ + client(): Promise } interface MD { @@ -1882,7 +2100,7 @@ interface ChatGenerationContext extends ChatTurnGenerationContext { options?: ChatParticipantOptions ): void defFileOutput( - pattern: string | string[], + pattern: ElementOrArray, description?: string, options?: FileOutputOptions ): void @@ -2765,7 +2983,7 @@ declare function def( * @param options expectations about the generated file content */ declare function defFileOutput( - pattern: string | string[], + pattern: ElementOrArray, description?: string, options?: FileOutputOptions ): void @@ -2869,6 +3087,11 @@ declare var host: PromptHost */ declare var github: GitHub +/** + * Access to Git operations for the current repository + */ +declare var git: Git + /** * Fetches a given URL and returns the response. * @param url