From 82710c2d15c2f549ab80904a6924a21ec5d29ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Sz=C3=A9csi?= Date: Sun, 13 Aug 2023 22:56:50 +0200 Subject: [PATCH 01/97] add AzureDevopsProvider to __init__.py --- pr_agent/git_providers/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pr_agent/git_providers/__init__.py b/pr_agent/git_providers/__init__.py index e7c2aa0f..f65553b0 100644 --- a/pr_agent/git_providers/__init__.py +++ b/pr_agent/git_providers/__init__.py @@ -3,12 +3,14 @@ from pr_agent.git_providers.bitbucket_provider import BitbucketProvider from pr_agent.git_providers.github_provider import GithubProvider from pr_agent.git_providers.gitlab_provider import GitLabProvider from pr_agent.git_providers.local_git_provider import LocalGitProvider +from pr_agent.git_providers.azuredevops_provider import AzureDevopsProvider _GIT_PROVIDERS = { 'github': GithubProvider, 'gitlab': GitLabProvider, 'bitbucket': BitbucketProvider, - 'local' : LocalGitProvider + 'local': LocalGitProvider, + 'azure': AzureDevopsProvider } def get_git_provider(): From 524faadffb1dc013542be96fd22e562b0265d52e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Sz=C3=A9csi?= Date: Sun, 13 Aug 2023 23:00:45 +0200 Subject: [PATCH 02/97] init AzureDevopsProvider --- .../git_providers/azuredevops_provider.py | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 pr_agent/git_providers/azuredevops_provider.py diff --git a/pr_agent/git_providers/azuredevops_provider.py b/pr_agent/git_providers/azuredevops_provider.py new file mode 100644 index 00000000..1af45cd4 --- /dev/null +++ b/pr_agent/git_providers/azuredevops_provider.py @@ -0,0 +1,191 @@ +import logging +from typing import Optional, Tuple +from urllib.parse import urlparse + +import os + +import requests + +from msrest.authentication import BasicAuthentication +from azure.devops.connection import Connection + +from ..algo.pr_processing import clip_tokens +from ..config_loader import get_settings +from .git_provider import FilePatchInfo + +class AzureDevopsProvider: + def __init__(self, pr_url: Optional[str] = None, incremental: Optional[bool] = False): + + self.azure_devops_client = self._get_azure_devops_client() + logging.info(self.azure_devops_client) + + self.workspace_slug = None + self.repo_slug = None + self.repo = None + self.pr_num = None + self.pr = None + self.temp_comments = [] + self.incremental = incremental + if pr_url: + self.set_pr(pr_url) + + def is_supported(self, capability: str) -> bool: + if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments', 'get_labels']: + return False + return True + + def set_pr(self, pr_url: str): + self.workspace_slug, self.repo_slug, self.pr_num = self._parse_pr_url(pr_url) + self.pr = self._get_pr() + + def get_repo_settings(self): + try: + contents = self.azure_devops_client.get_item_content(repository_id=self.repo_slug, project=self.workspace_slug, download=False, include_content_metadata=False, include_content=True, path=".pr_agent.toml") + logging.info("get repo settings") + logging.info(contents) + return contents + except Exception as e: + logging.info("get repo settings error") + logging.info(e) + return "" + + def get_files(self): + files = [] + for i in self.azure_devops_client.get_pull_request_commits(project=self.workspace_slug, repository_id=self.repo_slug, pull_request_id=self.pr_num): + #logging.info(i) + changes_obj = self.azure_devops_client.get_changes(project=self.workspace_slug, repository_id=self.repo_slug, commit_id=i.commit_id) + #logging.info(changes_obj) + #logging.info("***********") + for c in changes_obj.changes: + files.append(c['item']['path']) + #logging.info("###########") + return files + + def get_diff_files(self) -> list[FilePatchInfo]: + diffs = self.pr.diffstat() + diff_split = ['diff --git%s' % x for x in self.pr.diff().split('diff --git') if x.strip()] + + diff_files = [] + for index, diff in enumerate(diffs): + original_file_content_str = self._get_pr_file_content(diff.old.get_data('links')) + new_file_content_str = self._get_pr_file_content(diff.new.get_data('links')) + diff_files.append(FilePatchInfo(original_file_content_str, new_file_content_str, + diff_split[index], diff.new.path)) + return diff_files + + def publish_comment(self, pr_comment: str, is_temporary: bool = False): + comment = self.pr.comment(pr_comment) + if is_temporary: + self.temp_comments.append(comment['id']) + + def remove_initial_comment(self): + try: + for comment in self.temp_comments: + self.pr.delete(f'comments/{comment}') + except Exception as e: + logging.exception(f"Failed to remove temp comments, error: {e}") + + def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str): + pass + + def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str): + raise NotImplementedError("Azure DevOps provider does not support creating inline comments yet") + + def publish_inline_comments(self, comments: list[dict]): + raise NotImplementedError("Azure DevOps provider does not support publishing inline comments yet") + + def get_title(self): + return self.pr.title + + def get_languages(self): + languages = [] + files = self.azure_devops_client.get_items(project=self.workspace_slug, repository_id=self.repo_slug, recursion_level="Full", include_content_metadata=True, include_links=False, download=False) + for f in files: + if f.git_object_type == 'blob': + file_name, file_extension = os.path.splitext(f.path) + languages.append(file_extension[1:]) + + extension_counts = {} + for ext in languages: + if ext != '': + extension_counts[ext] = extension_counts.get(ext, 0) + 1 + + total_extensions = sum(extension_counts.values()) + + extension_percentages = {ext: (count / total_extensions) * 100 for ext, count in extension_counts.items()} + logging.info(extension_percentages) + + return extension_percentages + + def get_pr_branch(self): + return self.pr.source_branch + + def get_pr_description(self): + max_tokens = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None) + if max_tokens: + return clip_tokens(self.pr.description, max_tokens) + return self.pr.description + + def get_user_id(self): + return 0 + + def get_issue_comments(self): + raise NotImplementedError("Azure DevOps provider does not support issue comments yet") + + def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]: + return True + + def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool: + return True + + @staticmethod + def _parse_pr_url(pr_url: str) -> Tuple[str, int]: + parsed_url = urlparse(pr_url) + + if 'azure.com' not in parsed_url.netloc: + raise ValueError("The provided URL is not a valid Azure DevOps URL") + + path_parts = parsed_url.path.strip('/').split('/') + logging.info(path_parts) + + if len(path_parts) < 6 or path_parts[4] != 'pullrequest': + raise ValueError("The provided URL does not appear to be a Azure DevOps PR URL") + + workspace_slug = path_parts[1] + repo_slug = path_parts[3] + try: + pr_number = int(path_parts[5]) + except ValueError as e: + raise ValueError("Unable to convert PR number to integer") from e + + return workspace_slug, repo_slug, pr_number + + def _get_azure_devops_client(self): + try: + pat = get_settings().azure_devops.pat + org = get_settings().azure_devops.org + except AttributeError as e: + raise ValueError( + "Azure DevOps PAT token is required ") from e + + credentials = BasicAuthentication('', pat) + azure_devops_connection = Connection(base_url=org, creds=credentials) + azure_devops_client = azure_devops_connection.clients.get_git_client() + + return azure_devops_client + + def _get_repo(self): + if self.repo is None: + self.repo = self.azure_devops_client.get_repository(project=self.workspace_slug, repository_id=self.repo_slug) + #logging.info(self.repo) + return self.repo + + def _get_pr(self): + logging.info(self.azure_devops_client.get_pull_request_by_id(pull_request_id=self.pr_num, project=self.workspace_slug)) + return self.azure_devops_client.get_pull_request_by_id(pull_request_id=self.pr_num, project=self.workspace_slug) + + def _get_pr_file_content(self, remote_link: str): + return "" + + def get_commit_messages(self): + return "" # not implemented yet From 9a84b4b1843192de8ec45704f704e91d358f06d2 Mon Sep 17 00:00:00 2001 From: idavidov Date: Wed, 16 Aug 2023 12:56:15 +0300 Subject: [PATCH 03/97] + digest usage for docker in Istallation part of readme --- INSTALL.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/INSTALL.md b/INSTALL.md index 4589e30f..7b714bf9 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -18,6 +18,18 @@ docker run --rm -it -e OPENAI.KEY= -e GITHUB.USER_TOKEN= c ``` docker run --rm -it -e OPENAI.KEY= -e GITHUB.USER_TOKEN= codiumai/pr-agent --pr_url ask "" ``` +Note: If you want to ensure you're running a specific version of the Docker image, consider using the image's digest. +The digest is a unique identifier for a specific version of an image. You can pull and run an image using its digest by referencing it like so: repository@sha256:digest. Always ensure you're using the correct and trusted digest for your operations. + +1. To request a review for a PR using a specific digest, run the following command: +``` +docker run --rm -it -e OPENAI.KEY= -e GITHUB.USER_TOKEN= codiumai/pr-agent@sha256:71b5ee15df59c745d352d84752d01561ba64b6d51327f97d46152f0c58a5f678 --pr_url review +``` + +2. To ask a question about a PR using the same digest, run the following command: +``` +docker run --rm -it -e OPENAI.KEY= -e GITHUB.USER_TOKEN= codiumai/pr-agent@sha256:71b5ee15df59c745d352d84752d01561ba64b6d51327f97d46152f0c58a5f678 --pr_url ask "" +``` Possible questions you can ask include: From 7803d8eec47b8ebea3573a4a04c4f2b2b839429d Mon Sep 17 00:00:00 2001 From: idavidov Date: Wed, 16 Aug 2023 14:22:14 +0300 Subject: [PATCH 04/97] + pin to specific commit with gihub actions in Istallation part of readme --- INSTALL.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 7b714bf9..b7d860a5 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -22,12 +22,12 @@ Note: If you want to ensure you're running a specific version of the Docker imag The digest is a unique identifier for a specific version of an image. You can pull and run an image using its digest by referencing it like so: repository@sha256:digest. Always ensure you're using the correct and trusted digest for your operations. 1. To request a review for a PR using a specific digest, run the following command: -``` +```bash docker run --rm -it -e OPENAI.KEY= -e GITHUB.USER_TOKEN= codiumai/pr-agent@sha256:71b5ee15df59c745d352d84752d01561ba64b6d51327f97d46152f0c58a5f678 --pr_url review ``` 2. To ask a question about a PR using the same digest, run the following command: -``` +```bash docker run --rm -it -e OPENAI.KEY= -e GITHUB.USER_TOKEN= codiumai/pr-agent@sha256:71b5ee15df59c745d352d84752d01561ba64b6d51327f97d46152f0c58a5f678 --pr_url ask "" ``` @@ -63,7 +63,24 @@ jobs: OPENAI_KEY: ${{ secrets.OPENAI_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` +** if you want to pin your action to a specific commit for stability reasons +```yaml +on: + pull_request: + issue_comment: +jobs: + pr_agent_job: + runs-on: ubuntu-latest + name: Run pr agent on every pull request, respond to user comments + steps: + - name: PR Agent action step + id: pragent + uses: Codium-ai/pr-agent@ + env: + OPENAI_KEY: ${{ secrets.OPENAI_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` 2. Add the following secret to your repository under `Settings > Secrets`: ``` From 631fb93b2853f29ed0d0b11e8f076b1385e628e9 Mon Sep 17 00:00:00 2001 From: Tim Perkins Date: Wed, 16 Aug 2023 16:17:00 -0400 Subject: [PATCH 05/97] Implement Automatic Review Configuration for GitHub app --- pr_agent/agent/pr_agent.py | 5 +++++ pr_agent/servers/github_app.py | 2 +- pr_agent/settings/configuration.toml | 1 + pr_agent/tools/pr_reviewer.py | 11 ++++++++--- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/pr_agent/agent/pr_agent.py b/pr_agent/agent/pr_agent.py index 2ab13d69..2b901391 100644 --- a/pr_agent/agent/pr_agent.py +++ b/pr_agent/agent/pr_agent.py @@ -15,6 +15,7 @@ from pr_agent.tools.pr_update_changelog import PRUpdateChangelog from pr_agent.tools.pr_config import PRConfig command2class = { + "auto_review": PRReviewer, "answer": PRReviewer, "review": PRReviewer, "review_pr": PRReviewer, @@ -43,8 +44,10 @@ class PRAgent: repo_settings_file = None try: git_provider = get_git_provider()(pr_url) + logging.info(f'Fetching repo settings {git_provider.repo}') repo_settings = git_provider.get_repo_settings() if repo_settings: + logging.debug(f'Found settings for repo {git_provider.repo}\n{repo_settings}') repo_settings_file = None fd, repo_settings_file = tempfile.mkstemp(suffix='.toml') os.write(fd, repo_settings) @@ -70,6 +73,8 @@ class PRAgent: if notify: notify() await PRReviewer(pr_url, is_answer=True, args=args).run() + elif action == "auto_review": + await PRReviewer(pr_url, is_auto=True, args=args).run() elif action in command2class: if notify: notify() diff --git a/pr_agent/servers/github_app.py b/pr_agent/servers/github_app.py index 18943ae8..498bb81f 100644 --- a/pr_agent/servers/github_app.py +++ b/pr_agent/servers/github_app.py @@ -93,7 +93,7 @@ async def handle_request(body: Dict[str, Any]): api_url = pull_request.get("url") if not api_url: return {} - await agent.handle_request(api_url, "/review") + await agent.handle_request(api_url, "/auto_review") return {} diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index 0c502df9..ce920efd 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -19,6 +19,7 @@ require_security_review=true num_code_suggestions=3 inline_code_comments = false ask_and_reflect=false +automatic_review=true extra_instructions = "" [pr_description] # /describe # diff --git a/pr_agent/tools/pr_reviewer.py b/pr_agent/tools/pr_reviewer.py index fd6479ae..a89c27a3 100644 --- a/pr_agent/tools/pr_reviewer.py +++ b/pr_agent/tools/pr_reviewer.py @@ -23,7 +23,7 @@ class PRReviewer: """ The PRReviewer class is responsible for reviewing a pull request and generating feedback using an AI model. """ - def __init__(self, pr_url: str, is_answer: bool = False, args: list = None): + def __init__(self, pr_url: str, is_answer: bool = False, is_auto: bool = False, args: list = None): """ Initialize the PRReviewer object with the necessary attributes and objects to review a pull request. @@ -40,6 +40,7 @@ class PRReviewer: ) self.pr_url = pr_url self.is_answer = is_answer + self.is_auto = is_auto if self.is_answer and not self.git_provider.is_supported("get_issue_comments"): raise Exception(f"Answer mode is not supported for {get_settings().config.git_provider} for now") @@ -93,8 +94,12 @@ class PRReviewer: """ Review the pull request and generate feedback. """ - logging.info('Reviewing PR...') - + if self.is_auto and not get_settings().pr_reviewer.automatic_review: + logging.info(f'Automatic review is disabled {self.pr_url}') + return None + + logging.info(f'Reviewing PR: {self.pr_url} ...') + if get_settings().config.publish_output: self.git_provider.publish_comment("Preparing review...", is_temporary=True) From dff46469206be0b9f5f010f97c6d372bd56ead6c Mon Sep 17 00:00:00 2001 From: sarbjitgrewal Date: Fri, 18 Aug 2023 17:48:45 +0530 Subject: [PATCH 06/97] fix bitbucket improve issue --- pr_agent/git_providers/bitbucket_provider.py | 80 ++++++++++++++++++-- pr_agent/tools/pr_code_suggestions.py | 2 +- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index ddb70666..3596f4bf 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -1,3 +1,4 @@ +import json import logging from typing import Optional, Tuple from urllib.parse import urlparse @@ -14,8 +15,9 @@ class BitbucketProvider: def __init__(self, pr_url: Optional[str] = None, incremental: Optional[bool] = False): s = requests.Session() s.headers['Authorization'] = f'Bearer {get_settings().get("BITBUCKET.BEARER_TOKEN", None)}' + s.headers['Content-Type'] = 'application/json' + self.headers = s.headers self.bitbucket_client = Cloud(session=s) - self.workspace_slug = None self.repo_slug = None self.repo = None @@ -25,6 +27,7 @@ class BitbucketProvider: self.incremental = incremental if pr_url: self.set_pr(pr_url) + self.bitbucket_comment_api_url = self.pr._BitbucketBase__data['links']['comments']['href'] def get_repo_settings(self): try: @@ -32,6 +35,56 @@ class BitbucketProvider: return contents except Exception: return "" + + def publish_code_suggestions(self, code_suggestions: list): + """ + Publishes code suggestions as comments on the PR. + """ + post_parameters_list = [] + for suggestion in code_suggestions: + body = suggestion['body'] + relevant_file = suggestion['relevant_file'] + relevant_lines_start = suggestion['relevant_lines_start'] + relevant_lines_end = suggestion['relevant_lines_end'] + + if not relevant_lines_start or relevant_lines_start == -1: + if get_settings().config.verbosity_level >= 2: + logging.exception( + f"Failed to publish code suggestion, relevant_lines_start is {relevant_lines_start}") + continue + + if relevant_lines_end < relevant_lines_start: + if get_settings().config.verbosity_level >= 2: + logging.exception(f"Failed to publish code suggestion, " + f"relevant_lines_end is {relevant_lines_end} and " + f"relevant_lines_start is {relevant_lines_start}") + continue + + if relevant_lines_end > relevant_lines_start: + post_parameters = { + "body": body, + "path": relevant_file, + "line": relevant_lines_end, + "start_line": relevant_lines_start, + "start_side": "RIGHT", + } + else: # API is different for single line comments + post_parameters = { + "body": body, + "path": relevant_file, + "line": relevant_lines_start, + "side": "RIGHT", + } + post_parameters_list.append(post_parameters) + + + try: + self.publish_inline_comments(post_parameters_list) + return True + except Exception as e: + if get_settings().config.verbosity_level >= 2: + logging.error(f"Failed to publish code suggestion, error: {e}") + return False def is_supported(self, capability: str) -> bool: if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments', 'get_labels']: @@ -69,14 +122,29 @@ class BitbucketProvider: except Exception as e: logging.exception(f"Failed to remove temp comments, error: {e}") - def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str): - pass + def publish_inline_comment(self, comment: str, from_line: int, to_line: int, file: str): + payload = json.dumps( { + "content": { + "raw": comment, + }, + "inline": { + "to": from_line, + "path": file + }, + }) + response = requests.request( + "POST", + self.bitbucket_comment_api_url, + data=payload, + headers=self.headers + ) + return response + - def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str): - raise NotImplementedError("Bitbucket provider does not support creating inline comments yet") def publish_inline_comments(self, comments: list[dict]): - raise NotImplementedError("Bitbucket provider does not support publishing inline comments yet") + for comment in comments: + self.publish_inline_comment(comment['body'], comment['start_line'], comment['line'], comment['path']) def get_title(self): return self.pr.title diff --git a/pr_agent/tools/pr_code_suggestions.py b/pr_agent/tools/pr_code_suggestions.py index e759d61d..ebb88b21 100644 --- a/pr_agent/tools/pr_code_suggestions.py +++ b/pr_agent/tools/pr_code_suggestions.py @@ -42,7 +42,7 @@ class PRCodeSuggestions: get_settings().pr_code_suggestions_prompt.user) async def run(self): - assert type(self.git_provider) != BitbucketProvider, "Bitbucket is not supported for now" + # assert type(self.git_provider) != BitbucketProvider, "Bitbucket is not supported for now" logging.info('Generating code suggestions for PR...') if get_settings().config.publish_output: From 5477469a91f1d39dce1364ac1d1063c494f224ba Mon Sep 17 00:00:00 2001 From: idavidov Date: Sat, 19 Aug 2023 15:06:22 +0300 Subject: [PATCH 07/97] in order to have exact sha's we have to find correct diff for this change otherwise gitlab web doesn't able show diff on page and return 500 or 400 errors based on different scenarios --- pr_agent/git_providers/gitlab_provider.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pr_agent/git_providers/gitlab_provider.py b/pr_agent/git_providers/gitlab_provider.py index 73a3a2f9..94b40c92 100644 --- a/pr_agent/git_providers/gitlab_provider.py +++ b/pr_agent/git_providers/gitlab_provider.py @@ -155,7 +155,8 @@ class GitLabProvider(GitProvider): if not found: logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}") else: - d = self.last_diff + # in order to have exact sha's we have to find correct diff for this change + d = self.get_relevant_diff(relevant_file, relevant_line_in_file) pos_obj = {'position_type': 'text', 'new_path': target_file.filename, 'old_path': target_file.old_filename if target_file.old_filename else target_file.filename, @@ -171,6 +172,14 @@ class GitLabProvider(GitProvider): self.mr.discussions.create({'body': body, 'position': pos_obj}) + def get_relevant_diff(self, relevant_file, relevant_line_in_file): + for d in self.mr.diffs.list(get_all=True): + changes = self.mr.changes() # Retrieve the changes for the merge request + for change in changes['changes']: + if change['new_path'] == relevant_file and relevant_line_in_file in change['diff']: + return d + return self.last_diff # fallback to last_diff if no relevant diff is found + def publish_code_suggestions(self, code_suggestions: list): for suggestion in code_suggestions: try: From 7b7e91319594a5dd793e7ba80d488d0195fb6f81 Mon Sep 17 00:00:00 2001 From: idavidov Date: Sat, 19 Aug 2023 15:31:02 +0300 Subject: [PATCH 08/97] to changes suggested by /improve with my small touch --- pr_agent/git_providers/gitlab_provider.py | 8 ++++++-- pr_agent/settings/configuration.toml | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pr_agent/git_providers/gitlab_provider.py b/pr_agent/git_providers/gitlab_provider.py index 94b40c92..ef7210ee 100644 --- a/pr_agent/git_providers/gitlab_provider.py +++ b/pr_agent/git_providers/gitlab_provider.py @@ -173,11 +173,15 @@ class GitLabProvider(GitProvider): 'position': pos_obj}) def get_relevant_diff(self, relevant_file, relevant_line_in_file): - for d in self.mr.diffs.list(get_all=True): - changes = self.mr.changes() # Retrieve the changes for the merge request + changes = self.mr.changes() # Retrieve the changes for the merge request once + all_diffs = self.mr.diffs.list(get_all=True) + + for d in all_diffs: for change in changes['changes']: if change['new_path'] == relevant_file and relevant_line_in_file in change['diff']: return d + logging.debug( + f'No relevant diff found for {relevant_file} {relevant_line_in_file}. Falling back to last diff.') return self.last_diff # fallback to last_diff if no relevant diff is found def publish_code_suggestions(self, code_suggestions: list): diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index 0c502df9..777d7209 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -1,7 +1,7 @@ [config] -model="gpt-4" +model="gpt-3.5-turbo-16k" fallback_models=["gpt-3.5-turbo-16k"] -git_provider="github" +git_provider="gitlab" publish_output=true publish_output_progress=true verbosity_level=0 # 0,1,2 From fdd16f6c75aa4f88baf9183410b68a85949c01de Mon Sep 17 00:00:00 2001 From: idavidov Date: Sat, 19 Aug 2023 15:40:40 +0300 Subject: [PATCH 09/97] raize exception when no diffs in MR --- pr_agent/git_providers/gitlab_provider.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pr_agent/git_providers/gitlab_provider.py b/pr_agent/git_providers/gitlab_provider.py index ef7210ee..6a9f9fe2 100644 --- a/pr_agent/git_providers/gitlab_provider.py +++ b/pr_agent/git_providers/gitlab_provider.py @@ -174,8 +174,13 @@ class GitLabProvider(GitProvider): def get_relevant_diff(self, relevant_file, relevant_line_in_file): changes = self.mr.changes() # Retrieve the changes for the merge request once + if not changes: + logging.error('No changes found for the merge request.') + return None all_diffs = self.mr.diffs.list(get_all=True) - + if not all_diffs: + logging.error('No diffs found for the merge request.') + raise ValueError(f"Could not get diff for merge request {self.id_mr}") for d in all_diffs: for change in changes['changes']: if change['new_path'] == relevant_file and relevant_line_in_file in change['diff']: From 6595c3e0c9a23faa5f0e169363e96617624bc3b5 Mon Sep 17 00:00:00 2001 From: idavidov Date: Sat, 19 Aug 2023 15:47:45 +0300 Subject: [PATCH 10/97] 2 more /improve good suggestions --- pr_agent/git_providers/gitlab_provider.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pr_agent/git_providers/gitlab_provider.py b/pr_agent/git_providers/gitlab_provider.py index 6a9f9fe2..6a04f6bf 100644 --- a/pr_agent/git_providers/gitlab_provider.py +++ b/pr_agent/git_providers/gitlab_provider.py @@ -172,7 +172,7 @@ class GitLabProvider(GitProvider): self.mr.discussions.create({'body': body, 'position': pos_obj}) - def get_relevant_diff(self, relevant_file, relevant_line_in_file): + def get_relevant_diff(self, relevant_file: str, relevant_line_in_file: int) -> Optional[dict]: changes = self.mr.changes() # Retrieve the changes for the merge request once if not changes: logging.error('No changes found for the merge request.') @@ -181,10 +181,10 @@ class GitLabProvider(GitProvider): if not all_diffs: logging.error('No diffs found for the merge request.') raise ValueError(f"Could not get diff for merge request {self.id_mr}") - for d in all_diffs: + for diff in all_diffs: for change in changes['changes']: if change['new_path'] == relevant_file and relevant_line_in_file in change['diff']: - return d + return diff logging.debug( f'No relevant diff found for {relevant_file} {relevant_line_in_file}. Falling back to last diff.') return self.last_diff # fallback to last_diff if no relevant diff is found From 50125ae57f8c3f18e787064e28d32e02955f9e04 Mon Sep 17 00:00:00 2001 From: idavidov Date: Sat, 19 Aug 2023 16:12:48 +0300 Subject: [PATCH 11/97] various changes as outcomes from AI review --- pr_agent/git_providers/gitlab_provider.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pr_agent/git_providers/gitlab_provider.py b/pr_agent/git_providers/gitlab_provider.py index 6a04f6bf..82024375 100644 --- a/pr_agent/git_providers/gitlab_provider.py +++ b/pr_agent/git_providers/gitlab_provider.py @@ -156,11 +156,14 @@ class GitLabProvider(GitProvider): logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}") else: # in order to have exact sha's we have to find correct diff for this change - d = self.get_relevant_diff(relevant_file, relevant_line_in_file) + diff = self.get_relevant_diff(relevant_file, relevant_line_in_file) + if diff is None: + logger.error(f"Could not get diff for merge request {self.id_mr}") + raise ValueError(f"Could not get diff for merge request {self.id_mr}") pos_obj = {'position_type': 'text', 'new_path': target_file.filename, 'old_path': target_file.old_filename if target_file.old_filename else target_file.filename, - 'base_sha': d.base_commit_sha, 'start_sha': d.start_commit_sha, 'head_sha': d.head_commit_sha} + 'base_sha': diff.base_commit_sha, 'start_sha': diff.start_commit_sha, 'head_sha': diff.head_commit_sha} if edit_type == 'deletion': pos_obj['old_line'] = source_line_no - 1 elif edit_type == 'addition': @@ -180,7 +183,7 @@ class GitLabProvider(GitProvider): all_diffs = self.mr.diffs.list(get_all=True) if not all_diffs: logging.error('No diffs found for the merge request.') - raise ValueError(f"Could not get diff for merge request {self.id_mr}") + return None for diff in all_diffs: for change in changes['changes']: if change['new_path'] == relevant_file and relevant_line_in_file in change['diff']: From 35afe758e94d0a092050b04fb820885c2ccf6a4c Mon Sep 17 00:00:00 2001 From: idavidov Date: Sat, 19 Aug 2023 16:16:16 +0300 Subject: [PATCH 12/97] revert back conf --- pr_agent/settings/configuration.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index 777d7209..0c502df9 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -1,7 +1,7 @@ [config] -model="gpt-3.5-turbo-16k" +model="gpt-4" fallback_models=["gpt-3.5-turbo-16k"] -git_provider="gitlab" +git_provider="github" publish_output=true publish_output_progress=true verbosity_level=0 # 0,1,2 From 9770f4709a2b3e50638f0f5820a654a02334b799 Mon Sep 17 00:00:00 2001 From: idavidov Date: Sat, 19 Aug 2023 16:26:15 +0300 Subject: [PATCH 13/97] few more changes suggested by AI implemented --- pr_agent/git_providers/gitlab_provider.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pr_agent/git_providers/gitlab_provider.py b/pr_agent/git_providers/gitlab_provider.py index 82024375..ec6f236d 100644 --- a/pr_agent/git_providers/gitlab_provider.py +++ b/pr_agent/git_providers/gitlab_provider.py @@ -14,6 +14,9 @@ from .git_provider import EDIT_TYPE, FilePatchInfo, GitProvider logger = logging.getLogger() +class DiffNotFoundError(Exception): + """Raised when the diff for a merge request cannot be found.""" + pass class GitLabProvider(GitProvider): @@ -56,7 +59,7 @@ class GitLabProvider(GitProvider): self.last_diff = self.mr.diffs.list(get_all=True)[-1] except IndexError as e: logger.error(f"Could not get diff for merge request {self.id_mr}") - raise ValueError(f"Could not get diff for merge request {self.id_mr}") from e + raise DiffNotFoundError(f"Could not get diff for merge request {self.id_mr}") from e def _get_pr_file_content(self, file_path: str, branch: str) -> str: @@ -150,8 +153,8 @@ class GitLabProvider(GitProvider): def create_inline_comments(self, comments: list[dict]): raise NotImplementedError("Gitlab provider does not support publishing inline comments yet") - def send_inline_comment(self, body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no, - target_file, target_line_no): + def send_inline_comment(self,body: str,edit_type: str,found: bool,relevant_file: str,relevant_line_in_file: int, + source_line_no: int, target_file: str,target_line_no: int) -> None: if not found: logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}") else: @@ -159,7 +162,7 @@ class GitLabProvider(GitProvider): diff = self.get_relevant_diff(relevant_file, relevant_line_in_file) if diff is None: logger.error(f"Could not get diff for merge request {self.id_mr}") - raise ValueError(f"Could not get diff for merge request {self.id_mr}") + raise DiffNotFoundError(f"Could not get diff for merge request {self.id_mr}") pos_obj = {'position_type': 'text', 'new_path': target_file.filename, 'old_path': target_file.old_filename if target_file.old_filename else target_file.filename, From c6f8d985c2269b7614be443b42ddb1985725df44 Mon Sep 17 00:00:00 2001 From: zmeir Date: Sun, 20 Aug 2023 10:03:57 +0300 Subject: [PATCH 14/97] Safe parse key value in config override --- pr_agent/algo/utils.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py index 725d75ec..87e206cf 100644 --- a/pr_agent/algo/utils.py +++ b/pr_agent/algo/utils.py @@ -245,14 +245,12 @@ def update_settings_from_args(args: List[str]) -> List[str]: arg = arg.strip() if arg.startswith('--'): arg = arg.strip('-').strip() - vals = arg.split('=') + vals = arg.split('=', 1) if len(vals) != 2: logging.error(f'Invalid argument format: {arg}') other_args.append(arg) continue - key, value = vals - key = key.strip().upper() - value = value.strip() + key, value = _fix_key_value(*vals) get_settings().set(key, value) logging.info(f'Updated setting {key} to: "{value}"') else: @@ -260,6 +258,16 @@ def update_settings_from_args(args: List[str]) -> List[str]: return other_args +def _fix_key_value(key: str, value: str): + key = key.strip().upper() + value = value.strip() + try: + value = yaml.safe_load(value) + except Exception as e: + logging.error(f"Failed to parse YAML for config override {key}={value}", exc_info=e) + return key, value + + def load_yaml(review_text: str) -> dict: review_text = review_text.removeprefix('```yaml').rstrip('`') try: From 2d5b0fa37f200d61f99a07f463fbd206c64f2036 Mon Sep 17 00:00:00 2001 From: Ori Kotek Date: Sun, 20 Aug 2023 14:39:05 +0300 Subject: [PATCH 15/97] Fix repo settings bug in Gitlab --- pr_agent/agent/pr_agent.py | 2 -- requirements.txt | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pr_agent/agent/pr_agent.py b/pr_agent/agent/pr_agent.py index 2b901391..70121f3c 100644 --- a/pr_agent/agent/pr_agent.py +++ b/pr_agent/agent/pr_agent.py @@ -44,10 +44,8 @@ class PRAgent: repo_settings_file = None try: git_provider = get_git_provider()(pr_url) - logging.info(f'Fetching repo settings {git_provider.repo}') repo_settings = git_provider.get_repo_settings() if repo_settings: - logging.debug(f'Found settings for repo {git_provider.repo}\n{repo_settings}') repo_settings_file = None fd, repo_settings_file = tempfile.mkstemp(suffix='.toml') os.write(fd, repo_settings) diff --git a/requirements.txt b/requirements.txt index ebea2b71..470fc6ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,4 @@ GitPython~=3.1.32 litellm~=0.1.351 PyYAML==6.0 starlette-context==0.3.6 -litellm~=0.1.351 \ No newline at end of file +litellm~=0.1.351 From 31e91edebc2cae8d06f941c6153d2017e18d8731 Mon Sep 17 00:00:00 2001 From: zmeir Date: Thu, 17 Aug 2023 15:40:24 +0300 Subject: [PATCH 16/97] Allow keeping the original user description --- pr_agent/git_providers/bitbucket_provider.py | 10 +++------- pr_agent/git_providers/git_provider.py | 19 ++++++++++++++++++- pr_agent/git_providers/github_provider.py | 5 +---- pr_agent/git_providers/gitlab_provider.py | 5 +---- pr_agent/git_providers/local_git_provider.py | 2 +- pr_agent/settings/configuration.toml | 1 + pr_agent/tools/pr_description.py | 11 +++++++++-- 7 files changed, 34 insertions(+), 19 deletions(-) diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index 3596f4bf..e6cc74c9 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -6,12 +6,11 @@ from urllib.parse import urlparse import requests from atlassian.bitbucket import Cloud -from ..algo.pr_processing import clip_tokens from ..config_loader import get_settings -from .git_provider import FilePatchInfo +from .git_provider import FilePatchInfo, GitProvider -class BitbucketProvider: +class BitbucketProvider(GitProvider): def __init__(self, pr_url: Optional[str] = None, incremental: Optional[bool] = False): s = requests.Session() s.headers['Authorization'] = f'Bearer {get_settings().get("BITBUCKET.BEARER_TOKEN", None)}' @@ -156,10 +155,7 @@ class BitbucketProvider: def get_pr_branch(self): return self.pr.source_branch - def get_pr_description(self): - max_tokens = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None) - if max_tokens: - return clip_tokens(self.pr.description, max_tokens) + def get_pr_description_full(self): return self.pr.description def get_user_id(self): diff --git a/pr_agent/git_providers/git_provider.py b/pr_agent/git_providers/git_provider.py index 4d711a14..e7796d1e 100644 --- a/pr_agent/git_providers/git_provider.py +++ b/pr_agent/git_providers/git_provider.py @@ -82,9 +82,26 @@ class GitProvider(ABC): pass @abstractmethod - def get_pr_description(self): + def get_pr_description_full(self): pass + def get_pr_description(self): + from pr_agent.config_loader import get_settings + from pr_agent.algo.pr_processing import clip_tokens + max_tokens = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None) + description = self.get_pr_description_full() + if max_tokens: + return clip_tokens(description, max_tokens) + return description + + def get_user_description(self): + description = (self.get_pr_description_full() or "").strip() + if not description.startswith("## PR Type"): + return description + if "## User Description:" not in description: + return "" + return description.split("## User Description:", 1)[1].strip() + @abstractmethod def get_issue_comments(self): pass diff --git a/pr_agent/git_providers/github_provider.py b/pr_agent/git_providers/github_provider.py index be0fa645..2e07e969 100644 --- a/pr_agent/git_providers/github_provider.py +++ b/pr_agent/git_providers/github_provider.py @@ -233,10 +233,7 @@ class GithubProvider(GitProvider): def get_pr_branch(self): return self.pr.head.ref - def get_pr_description(self): - max_tokens = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None) - if max_tokens: - return clip_tokens(self.pr.body, max_tokens) + def get_pr_description_full(self): return self.pr.body def get_user_id(self): diff --git a/pr_agent/git_providers/gitlab_provider.py b/pr_agent/git_providers/gitlab_provider.py index ec6f236d..1d4d1680 100644 --- a/pr_agent/git_providers/gitlab_provider.py +++ b/pr_agent/git_providers/gitlab_provider.py @@ -299,10 +299,7 @@ class GitLabProvider(GitProvider): def get_pr_branch(self): return self.mr.source_branch - def get_pr_description(self): - max_tokens = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None) - if max_tokens: - return clip_tokens(self.mr.description, max_tokens) + def get_pr_description_full(self): return self.mr.description def get_issue_comments(self): diff --git a/pr_agent/git_providers/local_git_provider.py b/pr_agent/git_providers/local_git_provider.py index a4f21969..59b97a85 100644 --- a/pr_agent/git_providers/local_git_provider.py +++ b/pr_agent/git_providers/local_git_provider.py @@ -158,7 +158,7 @@ class LocalGitProvider(GitProvider): def get_user_id(self): return -1 # Not used anywhere for the local provider, but required by the interface - def get_pr_description(self): + def get_pr_description_full(self): commits_diff = list(self.repo.iter_commits(self.target_branch_name + '..HEAD')) # Get the commit messages and concatenate commit_messages = " ".join([commit.message for commit in commits_diff]) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index ce920efd..ef684f2c 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -24,6 +24,7 @@ extra_instructions = "" [pr_description] # /describe # publish_description_as_comment=false +keep_user_description=true extra_instructions = "" [pr_questions] # /ask # diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index d55dd55a..13898e8f 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -42,6 +42,8 @@ class PRDescription: "extra_instructions": get_settings().pr_description.extra_instructions, "commit_messages_str": self.git_provider.get_commit_messages() } + + self.user_description = self.git_provider.get_user_description() # Initialize the token handler self.token_handler = TokenHandler( @@ -145,6 +147,9 @@ class PRDescription: # Load the AI prediction data into a dictionary data = load_yaml(self.prediction.strip()) + if get_settings().pr_description.keep_user_description and self.user_description: + data["User Description"] = self.user_description + # Initialization pr_types = [] @@ -167,7 +172,7 @@ class PRDescription: # Iterate over the remaining dictionary items and append the key and value to 'pr_body' in a markdown format, # except for the items containing the word 'walkthrough' pr_body = "" - for key, value in data.items(): + for idx, (key, value) in enumerate(data.items()): pr_body += f"## {key}:\n" if 'walkthrough' in key.lower(): # for filename, description in value.items(): @@ -179,7 +184,9 @@ class PRDescription: # if the value is a list, join its items by comma if type(value) == list: value = ', '.join(v for v in value) - pr_body += f"{value}\n\n___\n" + pr_body += f"{value}\n" + if idx < len(data) - 1: + pr_body += "\n___\n" if get_settings().config.verbosity_level >= 2: logging.info(f"title:\n{title}\n{pr_body}") From b3749d08e2655e40977e4f14b904c584978994af Mon Sep 17 00:00:00 2001 From: zmeir Date: Sun, 20 Aug 2023 19:00:56 +0300 Subject: [PATCH 17/97] Set default configuration to false to allow users to opt-in --- pr_agent/settings/configuration.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index ef684f2c..a881c500 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -24,7 +24,7 @@ extra_instructions = "" [pr_description] # /describe # publish_description_as_comment=false -keep_user_description=true +keep_user_description=false extra_instructions = "" [pr_questions] # /ask # From 542bc9586a248a312ccce8588be25948ccdf22ce Mon Sep 17 00:00:00 2001 From: Phill Zarfos Date: Sun, 20 Aug 2023 12:58:44 -0400 Subject: [PATCH 18/97] Remove duplicate get_repo_settings() in bitbucket_provider --- pr_agent/git_providers/bitbucket_provider.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index 3596f4bf..97275742 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -168,13 +168,6 @@ class BitbucketProvider: def get_issue_comments(self): raise NotImplementedError("Bitbucket provider does not support issue comments yet") - def get_repo_settings(self): - try: - contents = self.repo_obj.get_contents(".pr_agent.toml", ref=self.pr.head.sha).decoded_content - return contents - except Exception: - return "" - def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]: return True From 81c38f9646c43a068e7618c25723ce3d4fa39ec0 Mon Sep 17 00:00:00 2001 From: zmeir Date: Mon, 21 Aug 2023 09:22:58 +0300 Subject: [PATCH 19/97] Added type hints --- pr_agent/git_providers/git_provider.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pr_agent/git_providers/git_provider.py b/pr_agent/git_providers/git_provider.py index e7796d1e..9db0a5bd 100644 --- a/pr_agent/git_providers/git_provider.py +++ b/pr_agent/git_providers/git_provider.py @@ -82,10 +82,10 @@ class GitProvider(ABC): pass @abstractmethod - def get_pr_description_full(self): + def get_pr_description_full(self) -> str: pass - def get_pr_description(self): + def get_pr_description(self) -> str: from pr_agent.config_loader import get_settings from pr_agent.algo.pr_processing import clip_tokens max_tokens = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None) @@ -94,7 +94,7 @@ class GitProvider(ABC): return clip_tokens(description, max_tokens) return description - def get_user_description(self): + def get_user_description(self) -> str: description = (self.get_pr_description_full() or "").strip() if not description.startswith("## PR Type"): return description From fb9335f4247514ec15c3ee1a4ce3a8200d4bf28b Mon Sep 17 00:00:00 2001 From: mrT23 Date: Mon, 21 Aug 2023 09:07:21 +0300 Subject: [PATCH 20/97] extended improve --- pr_agent/agent/pr_agent.py | 2 + pr_agent/algo/ai_handler.py | 2 +- pr_agent/algo/git_patch_processing.py | 2 +- pr_agent/algo/pr_processing.py | 100 +++++++- pr_agent/config_loader.py | 1 + pr_agent/settings/configuration.toml | 8 + .../settings/pr_code_suggestions_prompts.toml | 67 +++--- pr_agent/settings/pr_reviewer_prompts.toml | 4 +- .../pr_sort_code_suggestions_prompts.toml | 46 ++++ .../tools/pr_extended_code_suggestions.py | 215 ++++++++++++++++++ 10 files changed, 406 insertions(+), 41 deletions(-) create mode 100644 pr_agent/settings/pr_sort_code_suggestions_prompts.toml create mode 100644 pr_agent/tools/pr_extended_code_suggestions.py diff --git a/pr_agent/agent/pr_agent.py b/pr_agent/agent/pr_agent.py index 70121f3c..f722695c 100644 --- a/pr_agent/agent/pr_agent.py +++ b/pr_agent/agent/pr_agent.py @@ -11,6 +11,7 @@ from pr_agent.tools.pr_description import PRDescription from pr_agent.tools.pr_information_from_user import PRInformationFromUser from pr_agent.tools.pr_questions import PRQuestions from pr_agent.tools.pr_reviewer import PRReviewer +from pr_agent.tools.pr_extended_code_suggestions import PRExtendedCodeSuggestions from pr_agent.tools.pr_update_changelog import PRUpdateChangelog from pr_agent.tools.pr_config import PRConfig @@ -25,6 +26,7 @@ command2class = { "describe_pr": PRDescription, "improve": PRCodeSuggestions, "improve_code": PRCodeSuggestions, + "extended_improve": PRExtendedCodeSuggestions, "ask": PRQuestions, "ask_question": PRQuestions, "update_changelog": PRUpdateChangelog, diff --git a/pr_agent/algo/ai_handler.py b/pr_agent/algo/ai_handler.py index fb5f64fe..0bc879d1 100644 --- a/pr_agent/algo/ai_handler.py +++ b/pr_agent/algo/ai_handler.py @@ -55,7 +55,7 @@ class AiHandler: @retry(exceptions=(APIError, Timeout, TryAgain, AttributeError, RateLimitError), tries=OPENAI_RETRIES, delay=2, backoff=2, jitter=(1, 3)) - async def chat_completion(self, model: str, temperature: float, system: str, user: str): + async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2): """ Performs a chat completion using the OpenAI ChatCompletion API. Retries in case of API errors or timeouts. diff --git a/pr_agent/algo/git_patch_processing.py b/pr_agent/algo/git_patch_processing.py index 1aec0006..230df41e 100644 --- a/pr_agent/algo/git_patch_processing.py +++ b/pr_agent/algo/git_patch_processing.py @@ -176,7 +176,7 @@ def convert_to_hunks_with_lines_numbers(patch: str, file) -> str: ... """ - patch_with_lines_str = f"## {file.filename}\n" + patch_with_lines_str = f"\n\n## {file.filename}\n" import re patch_lines = patch.splitlines() RE_HUNK_HEADER = re.compile( diff --git a/pr_agent/algo/pr_processing.py b/pr_agent/algo/pr_processing.py index adab9506..1003f456 100644 --- a/pr_agent/algo/pr_processing.py +++ b/pr_agent/algo/pr_processing.py @@ -57,7 +57,7 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler, model: s pr_languages = sort_files_by_main_languages(git_provider.get_languages(), diff_files) # generate a standard diff string, with patch extension - patches_extended, total_tokens = pr_generate_extended_diff(pr_languages, token_handler, + patches_extended, total_tokens, patches_extended_tokens = pr_generate_extended_diff(pr_languages, token_handler, add_line_numbers_to_hunks) # if we are under the limit, return the full diff @@ -78,9 +78,9 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler, model: s return final_diff -def pr_generate_extended_diff(pr_languages: list, token_handler: TokenHandler, - add_line_numbers_to_hunks: bool) -> \ - Tuple[list, int]: +def pr_generate_extended_diff(pr_languages: list, + token_handler: TokenHandler, + add_line_numbers_to_hunks: bool) -> Tuple[list, int, list]: """ Generate a standard diff string with patch extension, while counting the number of tokens used and applying diff minimization techniques if needed. @@ -90,13 +90,10 @@ def pr_generate_extended_diff(pr_languages: list, token_handler: TokenHandler, files. - token_handler: An object of the TokenHandler class used for handling tokens in the context of the pull request. - add_line_numbers_to_hunks: A boolean indicating whether to add line numbers to the hunks in the diff. - - Returns: - - patches_extended: A list of extended patches for each file in the pull request. - - total_tokens: The total number of tokens used in the extended patches. """ total_tokens = token_handler.prompt_tokens # initial tokens patches_extended = [] + patches_extended_tokens = [] for lang in pr_languages: for file in lang['files']: original_file_content_str = file.base_file @@ -108,15 +105,16 @@ def pr_generate_extended_diff(pr_languages: list, token_handler: TokenHandler, extended_patch = extend_patch(original_file_content_str, patch, num_lines=PATCH_EXTRA_LINES) full_extended_patch = f"## {file.filename}\n\n{extended_patch}\n" - if add_line_numbers_to_hunks: + if add_line_numbers_to_hunks and PATCH_EXTRA_LINES > 0: full_extended_patch = convert_to_hunks_with_lines_numbers(extended_patch, file) patch_tokens = token_handler.count_tokens(full_extended_patch) file.tokens = patch_tokens total_tokens += patch_tokens + patches_extended_tokens.append(patch_tokens) patches_extended.append(full_extended_patch) - return patches_extended, total_tokens + return patches_extended, total_tokens, patches_extended_tokens def pr_generate_compressed_diff(top_langs: list, token_handler: TokenHandler, model: str, @@ -337,4 +335,84 @@ def clip_tokens(text: str, max_tokens: int) -> str: return clipped_text except Exception as e: logging.warning(f"Failed to clip tokens: {e}") - return text \ No newline at end of file + return text + + +def get_pr_multi_diffs(git_provider: GitProvider, + token_handler: TokenHandler, + model: str, + max_calls: int = 5) -> List[str]: + """ + Retrieves the diff files from a Git provider, sorts them by main language, and generates patches for each file. + The patches are split into multiple groups based on the maximum number of tokens allowed for the given model. + + Args: + git_provider (GitProvider): An object that provides access to Git provider APIs. + token_handler (TokenHandler): An object that handles tokens in the context of a pull request. + model (str): The name of the model. + max_calls (int, optional): The maximum number of calls to retrieve diff files. Defaults to 5. + + Returns: + List[str]: A list of final diff strings, split into multiple groups based on the maximum number of tokens allowed for the given model. + + Raises: + RateLimitExceededException: If the rate limit for the Git provider API is exceeded. + """ + try: + diff_files = git_provider.get_diff_files() + except RateLimitExceededException as e: + logging.error(f"Rate limit exceeded for git provider API. original message {e}") + raise + + # Sort files by main language + pr_languages = sort_files_by_main_languages(git_provider.get_languages(), diff_files) + + # Sort files within each language group by tokens in descending order + sorted_files = [] + for lang in pr_languages: + sorted_files.extend(sorted(lang['files'], key=lambda x: x.tokens, reverse=True)) + + patches = [] + final_diff_list = [] + total_tokens = token_handler.prompt_tokens + call_number = 1 + for file in sorted_files: + if call_number > max_calls: + if get_settings().config.verbosity_level >= 2: + logging.info(f"Reached max calls ({max_calls})") + break + + original_file_content_str = file.base_file + new_file_content_str = file.head_file + patch = file.patch + if not patch: + continue + + # Remove delete-only hunks + patch = handle_patch_deletions(patch, original_file_content_str, new_file_content_str, file.filename) + if patch is None: + continue + + patch = convert_to_hunks_with_lines_numbers(patch, file) + new_patch_tokens = token_handler.count_tokens(patch) + if patch and (total_tokens + new_patch_tokens > MAX_TOKENS[model] - OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD): + final_diff = "\n".join(patches) + final_diff_list.append(final_diff) + patches = [] + total_tokens = token_handler.prompt_tokens + call_number += 1 + if get_settings().config.verbosity_level >= 2: + logging.info(f"Call number: {call_number}") + + if patch: + patches.append(patch) + total_tokens += new_patch_tokens + if get_settings().config.verbosity_level >= 2: + logging.info(f"Tokens: {total_tokens}, last filename: {file.filename}") + + # Add the last chunk + if patches: + final_diff = "\n".join(patches) + final_diff_list.append(final_diff) + + return final_diff_list diff --git a/pr_agent/config_loader.py b/pr_agent/config_loader.py index 3075e8dc..47edfd97 100644 --- a/pr_agent/config_loader.py +++ b/pr_agent/config_loader.py @@ -19,6 +19,7 @@ global_settings = Dynaconf( "settings/pr_questions_prompts.toml", "settings/pr_description_prompts.toml", "settings/pr_code_suggestions_prompts.toml", + "settings/pr_sort_code_suggestions_prompts.toml", "settings/pr_information_from_user_prompts.toml", "settings/pr_update_changelog_prompts.toml", "settings_prod/.secrets.toml" diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index ce920efd..00c9b453 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -32,6 +32,14 @@ extra_instructions = "" num_code_suggestions=4 extra_instructions = "" +[pr_extendeted_code_suggestions] # /extended_improve # +num_code_suggestions_per_chunk=8 +extra_instructions = "" +max_number_of_calls = 5 +rank_suggestions = true +final_clip_factor = 0.5 + + [pr_update_changelog] # /update_changelog # push_changelog_changes=false extra_instructions = "" diff --git a/pr_agent/settings/pr_code_suggestions_prompts.toml b/pr_agent/settings/pr_code_suggestions_prompts.toml index 76a3cb6b..be90c840 100644 --- a/pr_agent/settings/pr_code_suggestions_prompts.toml +++ b/pr_agent/settings/pr_code_suggestions_prompts.toml @@ -1,19 +1,47 @@ [pr_code_suggestions_prompt] -system="""You are a language model called CodiumAI-PR-Code-Reviewer. -Your task is to provide meaningfull non-trivial code suggestions to improve the new code in a PR (the '+' lines). -- Try to give important suggestions like fixing code problems, issues and bugs. As a second priority, provide suggestions for meaningfull code improvements, like performance, vulnerability, modularity, and best practices. -- Suggestions should refer only to the 'new hunk' code, and focus on improving the new added code lines, with '+'. +system="""You are a language model called PR-Code-Reviewer. +Your task is to provide meaningful actionable code suggestions, to improve the new code presented in a PR. + +Example PR Diff input: +' +## src/file1.py + +--new hunk-- +12 code line that already existed in the file... +13 code line that already existed in the file.... +14 +new code line added in the PR +15 code line that already existed in the file... +16 code line that already existed in the file... + +--old hunk-- + code line that already existed in the file... +-code line that was removed in the PR + code line that already existed in the file... + + +--new hunk-- +... +--old hunk-- +... + + +## src/file2.py +... +' + +Specific instructions: +- Focus on important suggestions like fixing code problems, issues and bugs. As a second priority, provide suggestions for meaningful code improvements, like performance, vulnerability, modularity, and best practices. +- Suggestions should refer only to code from the '--new hunk--' sections, and focus on new lines of code (lines starting with '+'). - Provide the exact line number range (inclusive) for each issue. -- Assume there is additional code in the relevant file that is not included in the diff. +- Assume there is additional relevant code, that is not included in the diff. - Provide up to {{ num_code_suggestions }} code suggestions. -- Make sure not to provide suggestions repeating modifications already implemented in the new PR code (the '+' lines). -- Don't output line numbers in the 'improved code' snippets. +- Avoid making suggestions that have already been implemented in the PR code. For example, if you propose adding a docstring, type hint, or anything else, make sure it isn't already in the '--new hunk--' code. {%- if extra_instructions %} Extra instructions from the user: {{ extra_instructions }} -{% endif %} +{%- endif %} You must use the following JSON schema to format your answer: ```json @@ -30,39 +58,26 @@ You must use the following JSON schema to format your answer: }, "suggestion content": { "type": "string", - "description": "a concrete suggestion for meaningfully improving the new PR code." + "description": "a concrete suggestion for meaningfully improving the new PR code (lines from the '--new hunk--' sections, starting with '+')." }, "existing code": { "type": "string", - "description": "a code snippet showing authentic relevant code lines from a 'new hunk' section. It must be continuous, correctly formatted and indented, and without line numbers." + "description": "a code snippet showing the relevant code lines from a '--new hunk--' section. It must be continuous, correctly formatted and indented, and without line numbers." }, "relevant lines": { "type": "string", - "description": "the relevant lines in the 'new hunk' sections, in the format of 'start_line-end_line'. For example: '10-15'. They should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above." + "description": "the relevant lines from a '--new hunk--' section, in the format of 'start_line-end_line'. For example: '10-15'. They should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above." }, "improved code": { "type": "string", - "description": "a new code snippet that can be used to replace the relevant lines in 'new hunk' code. Replacement suggestions should be complete, correctly formatted and indented, and without line numbers." + "description": "a new code snippet that can be used to replace the relevant lines in '--new hunk--' code. Replacement suggestions should be complete, correctly formatted and indented, and without line numbers." } } } } ``` -Example input: -' -## src/file1.py ----new_hunk--- -``` -[new hunk code, annotated with line numbers] -``` ----old_hunk--- -``` -[old hunk code] -``` -... -' - +Don't output line numbers in the 'improved code' snippets. Don't repeat the prompt in the answer, and avoid outputting the 'type' and 'description' fields. """ diff --git a/pr_agent/settings/pr_reviewer_prompts.toml b/pr_agent/settings/pr_reviewer_prompts.toml index cdf7f731..b65d62e4 100644 --- a/pr_agent/settings/pr_reviewer_prompts.toml +++ b/pr_agent/settings/pr_reviewer_prompts.toml @@ -1,9 +1,9 @@ [pr_review_prompt] system="""You are CodiumAI-PR-Reviewer, a language model designed to review git pull requests. -Your task is to provide constructive and concise feedback for the PR, and also provide meaningfull code suggestions to improve the new PR code (the '+' lines). +Your task is to provide constructive and concise feedback for the PR, and also provide meaningful code suggestions to improve the new PR code (the '+' lines). {%- if num_code_suggestions > 0 %} - Provide up to {{ num_code_suggestions }} code suggestions. -- Try to focus on the most important suggestions, like fixing code problems, issues and bugs. As a second priority, provide suggestions for meaningfull code improvements, like performance, vulnerability, modularity, and best practices. +- Try to focus on the most important suggestions, like fixing code problems, issues and bugs. As a second priority, provide suggestions for meaningful code improvements, like performance, vulnerability, modularity, and best practices. - Suggestions should focus on improving the new added code lines. - Make sure not to provide suggestions repeating modifications already implemented in the new PR code (the '+' lines). {%- endif %} diff --git a/pr_agent/settings/pr_sort_code_suggestions_prompts.toml b/pr_agent/settings/pr_sort_code_suggestions_prompts.toml new file mode 100644 index 00000000..16b6e861 --- /dev/null +++ b/pr_agent/settings/pr_sort_code_suggestions_prompts.toml @@ -0,0 +1,46 @@ +[pr_sort_code_suggestions_prompt] +system=""" +""" + +user="""You are given a list of code suggestions to improve a PR: + +{{ suggestion_str|trim }} + + +Your task is to sort the code suggestions by their order of importance, and return a list with sorting order. +The sorting order is a list of pairs, where each pair contains the index of the suggestion in the original list. +Rank the suggestions based on their importance to improving the PR, with critical issues first and minor issues last. + +You must use the following YAML schema to format your answer: +```yaml +Sort Order: + type: array + maxItems: {{ suggestion_list|length }} + uniqueItems: true + items: + suggestion number: + type: integer + minimum: 1 + maximum: {{ suggestion_list|length }} + importance order: + type: integer + minimum: 1 + maximum: {{ suggestion_list|length }} +``` + +Example output: +```yaml +Sort Order: + - suggestion number: 1 + importance order: 2 + - suggestion number: 2 + importance order: 3 + - suggestion number: 3 + importance order: 1 +``` + +Make sure to output a valid YAML. Use multi-line block scalar ('|') if needed. +Don't repeat the prompt in the answer, and avoid outputting the 'type' and 'description' fields. +Response (should be a valid YAML, and nothing else): +```yaml +""" diff --git a/pr_agent/tools/pr_extended_code_suggestions.py b/pr_agent/tools/pr_extended_code_suggestions.py new file mode 100644 index 00000000..17f7b570 --- /dev/null +++ b/pr_agent/tools/pr_extended_code_suggestions.py @@ -0,0 +1,215 @@ +from typing import List +import copy +import json +import logging +import textwrap +from typing import Dict, Any + +import yaml +from jinja2 import Environment, StrictUndefined + +from pr_agent.algo.ai_handler import AiHandler +from pr_agent.algo.pr_processing import get_pr_multi_diffs, retry_with_fallback_models +from pr_agent.algo.token_handler import TokenHandler +from pr_agent.algo.utils import try_fix_json +from pr_agent.config_loader import get_settings +from pr_agent.git_providers import get_git_provider +from pr_agent.git_providers.git_provider import get_main_pr_language + + +class PRExtendedCodeSuggestions: + def __init__(self, pr_url: str, cli_mode=False, args: list = None): + + self.git_provider = get_git_provider()(pr_url) + self.main_language = get_main_pr_language( + self.git_provider.get_languages(), self.git_provider.get_files() + ) + + self.ai_handler = AiHandler() + self.patches_diff = None + self.prediction = None + self.cli_mode = cli_mode + self.vars = { + "title": self.git_provider.pr.title, + "branch": self.git_provider.get_pr_branch(), + "description": self.git_provider.get_pr_description(), + "language": self.main_language, + "diff": "", # empty diff for initial calculation + "num_code_suggestions": get_settings().pr_extendeted_code_suggestions.num_code_suggestions_per_chunk, + "extra_instructions": get_settings().pr_extendeted_code_suggestions.extra_instructions, + "commit_messages_str": self.git_provider.get_commit_messages(), + } + self.token_handler = TokenHandler(self.git_provider.pr, + self.vars, + get_settings().pr_code_suggestions_prompt.system, + get_settings().pr_code_suggestions_prompt.user) + + async def run(self): + logging.info('Generating code suggestions for PR...') + if get_settings().config.publish_output: + self.git_provider.publish_comment("Preparing review...", is_temporary=True) + data = await retry_with_fallback_models(self._prepare_prediction) + + if get_settings().pr_extendeted_code_suggestions.rank_suggestions: + logging.info('Ranking Suggestions...') + data['Code suggestions'] = await self.rank_suggestions(data['Code suggestions']) + + logging.info('Preparing PR review...') + if get_settings().config.publish_output: + logging.info('Pushing PR review...') + self.git_provider.remove_initial_comment() + logging.info('Pushing inline code comments...') + self.push_inline_code_suggestions(data) + + async def _prepare_prediction(self, model: str) -> dict: + logging.info('Getting PR diff...') + patches_diff_list = get_pr_multi_diffs(self.git_provider, self.token_handler, model, + max_calls=get_settings().pr_extendeted_code_suggestions.max_number_of_calls) + + logging.info('Getting multi AI predictions...') + prediction_list = [] + for i, patches_diff in enumerate(patches_diff_list): + logging.info(f"Processing chunk {i + 1} of {len(patches_diff_list)}") + self.patches_diff = patches_diff + prediction = await self._get_prediction(model) + prediction_list.append(prediction) + self.prediction_list = prediction_list + + data = {} + for prediction in prediction_list: + self.prediction = prediction + data_per_chunk = self._prepare_pr_code_suggestions() + if "Code suggestions" in data: + data["Code suggestions"].extend(data_per_chunk["Code suggestions"]) + else: + data.update(data_per_chunk) + self.data = data + return data + + async def _get_prediction(self, model: str): + variables = copy.deepcopy(self.vars) + variables["diff"] = self.patches_diff # update diff + environment = Environment(undefined=StrictUndefined) + system_prompt = environment.from_string(get_settings().pr_code_suggestions_prompt.system).render(variables) + user_prompt = environment.from_string(get_settings().pr_code_suggestions_prompt.user).render(variables) + if get_settings().config.verbosity_level >= 2: + logging.info(f"\nSystem prompt:\n{system_prompt}") + logging.info(f"\nUser prompt:\n{user_prompt}") + response, finish_reason = await self.ai_handler.chat_completion(model=model, temperature=0.2, + system=system_prompt, user=user_prompt) + + return response + + def _prepare_pr_code_suggestions(self) -> str: + review = self.prediction.strip() + try: + data = json.loads(review) + except json.decoder.JSONDecodeError: + if get_settings().config.verbosity_level >= 1: + logging.info(f"Could not parse json response: {review}") + data = try_fix_json(review, code_suggestions=True) + return data + + def push_inline_code_suggestions(self, data): + code_suggestions = [] + + if not data['Code suggestions']: + return self.git_provider.publish_comment('No suggestions found to improve this PR.') + + for d in data['Code suggestions']: + try: + if get_settings().config.verbosity_level >= 1: + logging.info(f"suggestion: {d}") + relevant_file = d['relevant file'].strip() + relevant_lines_str = d['relevant lines'].strip() + if ',' in relevant_lines_str: # handling 'relevant lines': '181, 190' or '178-184, 188-194' + relevant_lines_str = relevant_lines_str.split(',')[0] + relevant_lines_start = int(relevant_lines_str.split('-')[0]) # absolute position + relevant_lines_end = int(relevant_lines_str.split('-')[-1]) + content = d['suggestion content'] + new_code_snippet = d['improved code'] + + if new_code_snippet: + new_code_snippet = self.dedent_code(relevant_file, relevant_lines_start, new_code_snippet) + + body = f"**Suggestion:** {content}\n```suggestion\n" + new_code_snippet + "\n```" + code_suggestions.append({'body': body, 'relevant_file': relevant_file, + 'relevant_lines_start': relevant_lines_start, + 'relevant_lines_end': relevant_lines_end}) + except Exception: + if get_settings().config.verbosity_level >= 1: + logging.info(f"Could not parse suggestion: {d}") + + self.git_provider.publish_code_suggestions(code_suggestions) + + def dedent_code(self, relevant_file, relevant_lines_start, new_code_snippet): + try: # dedent code snippet + self.diff_files = self.git_provider.diff_files if self.git_provider.diff_files \ + else self.git_provider.get_diff_files() + original_initial_line = None + for file in self.diff_files: + if file.filename.strip() == relevant_file: + original_initial_line = file.head_file.splitlines()[relevant_lines_start - 1] + break + if original_initial_line: + suggested_initial_line = new_code_snippet.splitlines()[0] + original_initial_spaces = len(original_initial_line) - len(original_initial_line.lstrip()) + suggested_initial_spaces = len(suggested_initial_line) - len(suggested_initial_line.lstrip()) + delta_spaces = original_initial_spaces - suggested_initial_spaces + if delta_spaces > 0: + new_code_snippet = textwrap.indent(new_code_snippet, delta_spaces * " ").rstrip('\n') + except Exception as e: + if get_settings().config.verbosity_level >= 1: + logging.info(f"Could not dedent code snippet for file {relevant_file}, error: {e}") + + return new_code_snippet + + async def rank_suggestions(self, data: List) -> List: + """ + Call a model to rank (sort) code suggestions based on their importance order. + + Args: + data (List): A list of code suggestions to be ranked. + + Returns: + List: The ranked list of code suggestions. + """ + + suggestion_list = [] + # remove invalid suggestions + for i, suggestion in enumerate(data): + if suggestion['existing code'] != suggestion['improved code']: + suggestion_list.append(suggestion) + + data_sorted = [[]] * len(suggestion_list) + + try: + suggestion_str = "" + for i, suggestion in enumerate(suggestion_list): + suggestion_str += f"suggestion {i + 1}: " + str(suggestion) + '\n\n' + + variables = {'suggestion_list': suggestion_list, 'suggestion_str': suggestion_str} + model = get_settings().config.model + environment = Environment(undefined=StrictUndefined) + system_prompt = environment.from_string(get_settings().pr_sort_code_suggestions_prompt.system).render(variables) + user_prompt = environment.from_string(get_settings().pr_sort_code_suggestions_prompt.user).render(variables) + if get_settings().config.verbosity_level >= 2: + logging.info(f"\nSystem prompt:\n{system_prompt}") + logging.info(f"\nUser prompt:\n{user_prompt}") + response, finish_reason = await self.ai_handler.chat_completion(model=model, system=system_prompt, user=user_prompt) + + sort_order = yaml.safe_load(response) + for s in sort_order['Sort Order']: + suggestion_number = s['suggestion number'] + importance_order = s['importance order'] + data_sorted[importance_order - 1] = suggestion_list[suggestion_number - 1] + + if get_settings().pr_extendeted_code_suggestions.final_clip_factor != 1: + new_len = int(0.5 + len(data_sorted) * get_settings().pr_extendeted_code_suggestions.final_clip_factor) + data_sorted = data_sorted[:new_len] + except Exception as e: + if get_settings().config.verbosity_level >= 1: + logging.info(f"Could not sort suggestions, error: {e}") + data_sorted = suggestion_list + + return data_sorted From dcad4905130e8ac4bbbd2947ab77086c3891d521 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Mon, 21 Aug 2023 15:31:51 -0700 Subject: [PATCH 21/97] adding huggingface inference support + litellm debugger --- INSTALL.md | 8 ++++++++ pics/debugger.png | Bin 0 -> 547134 bytes pr_agent/algo/ai_handler.py | 10 +++++++++- pr_agent/settings/configuration.toml | 1 + requirements.txt | 2 +- 5 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 pics/debugger.png diff --git a/INSTALL.md b/INSTALL.md index 73848ade..1dccd80f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -135,6 +135,14 @@ python pr_agent/cli.py --pr_url describe python pr_agent/cli.py --pr_url improve ``` +5. **Debugging LLM API Calls** +If you're testing your codium/pr-agent server, and need to see if calls were made successfully + the exact call logs, you can use the [LiteLLM Debugger tool](https://docs.litellm.ai/docs/debugging/hosted_debugging). + +You can do this by setting `litellm_debugger=true` in configuration.toml. Your Logs will be viewable in real-time @ `admin.litellm.ai/`. Set your email in the `.secrets.toml` under 'user_email'. + + + + --- #### Method 4: Run as a polling server diff --git a/pics/debugger.png b/pics/debugger.png new file mode 100644 index 0000000000000000000000000000000000000000..7d8f201f7b5d999b5a517b5d20d41753832883ec GIT binary patch literal 547134 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�=>xbjE#EPcD=tP9n$b$6~Ou=VcC zoov4YB9K!)Hl*l^C(C%;GFGaT$U5Wtc$Yk8br#vEdC@a5I|dfJ3;`Imi}QCTxQ}^SH?|>4!Bvw>(2vY~!wkJ6xlmx~s=Omtb{C-ad&sT!~jXBkyiTbq! z4q)ckYrZ!*buiUp$;p8&JqgGa5YGy3!Z9z{6|n9*;d(S_jmOqv=R=Ij{9z^f3_(qe z!^Tw!1PfGe`qB+HvwkQs@;TA1TM+0po{4+!E$}8i?r;K@3q%~fnFH6z4mjiHfOnJX z4-&?|YLyZn%Z0R8iz}WacIqHMFxg8hn>DgVBy%#pa6D#KKV}^wLk{~A?geYVqt0di zd^rW~K-GxlwGz6$?Bxl7#Hj@!>fW~PPB}U&KCb5*(@%}P15~lvbEedZLzfy$wdJ&; zNP!ZdhM|4l-K!ldvQ+C0jGRk6FjiPUFkKwGp}VLyGT2prCi3!>I24XK6L6^v<{~s- zsbFKfWu2iPy)$1zKU+sNs2Ong$PbpF2%f=mHPp@dNmIQ;nq_SVl9z}VxpZBAxJyKr z-~a|-j&GR2d`+H5k{+jf;tpfY(Zh}RGdckR?R3w(JC8;g&v=b(NQDf@auOv_)|K;e zUP&zjB%h1Ff!}m%n0S+B^j2`vJWO*kO!ly-qnG!~FZP(@Evq=)8NQL#F|RA{ni>6t9ao1>TUeTDC2Bsdlf z*|I9FLaf}$`!7dVz;X%WvRrB(>?z-XWEpUl-f03wcCWgonE1CZ;nuh!^GXWEvji=v z)wwZ(-TukVg+N{HOJBb?pQT#AsLX^c^r@u9PyA^MQR9`goC*6(m8u=68Mw`-M{Fm~ z@!&ZX41>AE;v@Bzyl|=iS$QZU{t=`@$M40U>#qvP=%Nh25rxdex%8-FJ8u%*hjOBn zLjyl3f0-$8w7fU*-z)$EaqAfvuP14()BJH8|8eJn`CZLS8Q(->wAlWPmdSVcGO~jQj@pcx%;;o?|_rRG;9XzlO%#qaXWlLj~t1*CfOe@ zP9-JVzJ&C=y5DCn`MJpuR36>DlQ7avLMHUExo^tW&L^~XIx(SM@C z*;cO1Q+^F%iC0mXVb*@gp;u{Zs^~JD-Pgz*3(4wyrv1of)YQ&II^mA*9YQk~6I;{I zeib{^SvnDVKvkN^KukkQGfiORDXR5&TB_O8O2Z3_B9ccThz15mwl+?pz3yTa!r~wI zCN*x9Wvk6_q4PAGlK)I=et1Fbw7TIbEVXLLTTD)n@<0N&K5jpx^wknlt*PdBw+6l8 zfGgF<*1(Q*^uQn)Mt&|iE3;VEP$1>)+r5A#*V8}E9fWP)QVwSRc@NQA&+3N8CZqc@ z;%5^8`FjWHeREWqi2(qeDU%;gNpAmHo5K|?sqDLT#gJN45qI9&%9xy!Mbn8YlD$z7PyFzvU6KBY zoI_M{7UZpC$*whdx~A*^a<}VGOkg?=H zmlq(j2FjIqY(<;Dc=rQ%`s za?}D&{$<*cg@Tev^7RtzP-|rU6aM}qdB|9yy6S~5d-qhDpe-C#emcIEsgwfZ={bj$ z{GNLJ^I@Jz)Uwkj&A+-61w||Vr#rT<8Sfj$(wpx`W@b|`yUV5OZQXyk{D(A$zpZ_>^;!6_9y=VK+T~+~_Ge}2zXC&VF?p9Kto<6=M z%l6pdOiI_KGcAVRDVAWHITaM^ZQ!$-1iY(?f&uqgTRrJtF#dRDe>K;wyEkJpmCp~< zl9j$!LF~9r?LdiBK}{o4W$y@;{P-nX!sYb03SCq98AN*P++GzHCI2k6x@Xuv<6>!L zD0K)BZCKdSQG0nxLX0OXM3ViU)t{E1Pcqe zrVw%O)b!FF*Zmxs-@w@cj3`*q%zc3S^epa1`bCmC*CGExs+TMx#a&7{CyoHzIZ=#k z)G6V?4LNCkxR?1+70eo)%NWwpb$hU7#FujemuHj!MKdbzyMz9zBv8K*gipPDu?{|PFS*|~NHtVd8Bl$R2N9ezUF6FI3~kNxFl+UtF5=P(rl(R-a9~t5VG~&<}0K_{hk3*_fwfS!pGV$pcQ|1@X$CK4bbx zits+HO9x(=OZc5WfsK6HFvPJoXik*PjK$^_<2TeG?fZ~fjIQ136v8?S*evqf08gZrW?Fquwg9UPpLyK zr!RR2WlFTo+=bFHsvFh3rTc$)R{w8g>Hly{zo;1g$XLPMDdaJibz407iie#p`iqO< ztuDiUDVw~nDm-9uJ8+c9zJ=|4c9UkA4ZUSx*CmEuCr^d7x#u^QT@^ zlJbyy_Rei;n3qJ7WgoSah`ljUCNpn+Ct|%p@N}k7`TK9SfzQ{ATcvLTc48z!hEXNF z1~9enO6q*GdTZlj)`|X(wMwP}ccGbr{p8%L`2;FLw;U%5c05sIL4e;0Hp6vmco(3s zL#9g2(#*i$Fit_}&A?)t+Ot2Jw0QuiBd}J=e_g>CGcCHws&&YGQS%9#*^=NN z`Fkixp08k8Qdhe`JiQwHq!y4M#oEN7_&*KHo-e-TqEvfuihYKzX z5Ocn?@XcvI-^>{D`Y>|l$Ta2VS?mz-yjU{`ZZEhs9$&=HxYX)ZHeNo>(I&C<0Y+5Y z*AbmIYtMJ51M=C$B1&6db=C9pkZnXz2W)fsgQbk!>u{7D&fAB?I^J=fkE%u$FX9oHodON+;F#Z5SnYm{gm zY#i6VY83Mw?-+tb{6wC6lo)+V3JEb(fL#aZE{@(P;w)1Vf7wjY-^40RCCZjw57X=G zxt|mSJ4 zKChLTQeu^+8RSTvkbR(v%akcAhnkesZSoZQESuVxD$dY9dzM<8*Zn^Ox4sI$eD+$_ z20xg0BIZ(4Tk}WUHb7d_XssP~VP8#(N~A6RxV4&fK1o9x%bXzA6rK^Q+hBL2|DW%;%9jK)A$>tkWj(_!JF8_T6Mk}4%MYAgVpM_^V)tjKjrxtV^4!1x3Gn>o0VZj9u1cBd^CdRqSgUOTizi8FRB(5jMvtM@ zPAz#^YAXo1fCbPA21L*^cg|hsJ^L^D- z6)KzCsd1+qLL>SnNbMEcR)@XmpoT?YYRnEk^Ft_-#AoX3a1n z`aIZN1ngw9l6hRHf}Q&g7%I7?B^z*g+EuF8cCZOo`A8m-P8q{Ii*15*O@=<~n!qBf zo6h@Cy$RTBzGv1b4C$LR1(OrDoxrgoHH6#*@mRugSJ_CgtIDHL6;@m)(fVA- zeX>G+rVGhVpPh}ciEQTt=k36dyTaQu|&*` z6B?v`+IjKP&^rG%AdaG~rR^HmWB&_Juuh~v84*)&0qVMWi3?;b*{#kRhm^C`iP;Ngsy8Th< z=U?%}LF|fz;Q#3=a{Q0Bk$2_ny}HgGtLR^YqE6@MRD3^Gpk~r~QWZ~k61A3m6@y}j zTv-cx?emWb;-AFGHXMZnAz|t z{?#;!J7=U?E%%1(_g)3a*UC2t=g9DqG%%ONfm$t4OD!kNBN}<2Y{uu?!8XPPkUhXU z)c-z{gMED3$8i>XBUdoxP^(lOuf4J>VrTE%c3HKks#Ha+IFXg9mnJZ_RZ4>UL~;l_ z$A~thuQyHfyAWZ;R@>9!Y;8`yCPvkNV6R-nrD28KaesDT9`%8FSAtXdX*jA~Cwd$* zc&fpJ6GlXT+Wrsd@}~OB#$R!bZJOGdn?p|zG1*ghX!YLxo>!70s014~UE87%E~zc5 z`GtLT2O^l&fLRYp4)On1i2X&8f|Bfax>=#UJF6`83k!Ote0SP1sIr`Ws=d3X4mvJN zRR#U(yPCGth5RQgY`ZQp8icIo#5|H@`uG93C0!_uq-JEqv~34q0PFyIOI#^0?%HA0 z83HynF>R+Ax+kVs@ZjIpfu6iuVLxtd`|WGdAoioE%cI_Cv|vdQeUvl-;`0I|KWGaypbnaSQDJizqU6F>RLvdtmND+y`ql!LOX?Ef zBx$QV+KE3=3co+z7`7}QiRG%@oA(QcnDP-qe;{9ks4fN~HCHLhb)Zkw#m~<>h5Z;e zMr~~?@B>l>-iVR0>lkJkD-@$Tn8t!tzXUgOJ@+6DP(rngGjZ)~F$TLY`MI$FS<_z` z46MBnxd_Z7Rp&OpNZz%CmIpg|MDi6ijb79hr@(o4FSRzpJ4N{glhBvw%K679E6U@VGnN2^$7Ua{qusClN5 z<&SGeaMgec=Ld<#*pQj|>Rj(zJ$Iy7OgmK=V_N+K={H7bmFGQv6W>v7kHNMK#g+4z z#^e_$Yw65zjSCfqwUOlOnr0SL>hU#1e1GMQT0nXaShge><)1+sy`0HgF;oh&C#Ccp zzEp428qLdt*iDnTxXNlSuDy6sexjEz_*j_#cR*8>@V`(B4PZ}*CP`DE{muc>-n~kT z0r*aGHS9|flY6BM{=eg0Q=9_G{KV3YbhZl}RX3E=u;#9@@s-ew2 zMAl`nC(-Fc5Iq$`3v!0-ZZ>+!8a505GMGqe}1X zuf#gmiJl)FMts-r75Rv|6O;58Mxy9fMyW-|w`;&U_exN+<*L=~ugCS}q-(Pw1v!L_ z1M%H>73{BZFA)bJ-vsq87zO$Mwz}5N`2+%^^hZ=!P%m^37cjIh2 z_NZz#{7&}@+-UY8CoYRl)~Hqe`7;2#j+P^Hq6{{CD(dizj5n~{C}?B~!dy}=$H&6P z9w)w$^{U8512rDSD^*Ue8VlNYsR!5PKC=agk*=OZfGw6D&c77kQaBd+{3$T{J7y_d z_G*!a1=^ioB%>w2Oq}g^daul=BhpfV%kh>$lm*BoN7h&*HaNF%`)(Rd%mZL{QTau{5oE*!t^#N8Fs0sU{PTb;w(w>0O3A>cSOE6k<~op? z^Ax&cg=CJo_C)=llkk1p#qfC`>r&#{mC_}=;H_m{{8f+ZM|Ki5MP*xo2d1?(QLEdp z*jG!{Qb-MHdbA5ZZNFkn2F1`R(!_EozZR-Qyo*#Ckye+={j0~uM(#;e5f5ADh5*la z&*UY$$!n3w;p7Y^ts09+^Hk=DFQ}=bf<$gLRw~i6p4o2D|apx2BTglhrGJq=qgBLGe`!IF90f4cN~L9{q!Zu0IC#1|Yhb zQlY%*veE9Fec5FMrmm~iU@v*{);+-Q%;f{9c3>P2d4y#unXC9?o?b%)|Dd&(l#Sz5 zu81C7mtp!82!sO{*z6BuX2O;9!`!5G61_6Q*%j|=^ilLiIgX43EXN1B_zv#cWU%#8X2t3~DpF=TW=(Vhhq-S2dyWjG zZ?lUgdK<(bTTEmxye4N}l)YW-h2|KFZfkzkKgt)*DLIb-+e{u48>D80J=l}OCUy=2 zeLHTRAPmw(JJu6)kV_%ZJ#~w`OoS-wl`zwSDXhAyWbphySr(f%9nt)vczgv-WQV-j z!<`OBL-B>tq$U;d2WJ(Muqds`_Lc-E%{E|GhVIxxH-{*xqyx(;i5cUVnB9w~@0t1Z z5p6ygNamKuLA`3pNUHu}!S@yXxweUxu4TR>E6%g;KBRP{E7FfH3$+_~?PjfXp@h2| z<2s!@@P3~KL|!miqB&_Cqfn?*dI!8(En#{3HxnpXBD4g)6wIQwq!dDkw2QJzw+Uj0 zytr$BQnZg{+Zp}bm46o&v%y`h@I26`-<=6mA)G_EZ*VARcyd>u8vp4Sa$X?a!U6C< zG2hN8wF)>)@H!#qvWXk@a}ewpuaMDuPVYZ%z0Qz&Kk>E1oR*oG_o29)dDV3`o!>KJQ&_+) z5rLNT@80C(D%M36Ms-KJjA}i<2UUe`SPYEuv{QHokt{K!NnG(D5Qs#Dy$0-lz#JJJ zQM|_X*bcoUU%xRL^(2Qoq5u>z2EqexwPfP8zO(6i^XqNh27j`+CfRO9{3q>qnzWKH z-Hh_>(77Nv@L*{W94mX42*f=+7NXa>^G;{a47m4Jg!~R(E)K?5>`vsdNT1Hh3plDl zw{%4$qL*)VCqASm|9_FU{*TV}&qEc-zqjDo%bn)x6f*S=lLkA5QCX})8KcdSi;2KM zE9hEGt=}Rjscc5CjS5^s@Ly5u+s0F%>i*LanLM;oIC%l?^!J@(=ZalB(631+-N}@N zWtVj>j13`wVT^Sf1_(E66^i1vg*YNU^Ta5(RvEU$7%To6Qg#xQ(+0aofV>5WiEy56 zHcv{KA;L{d!1G`(azOgRqO9E^B+w4v&gOIE?h1t`_HYILDl8#^JkctC{knAtot+9 zYRz#RB$Y;y251*StMy7y)pNN1XQ?`#6SKNMguTpM-gxkt|4I7m$rnvBZrYWDIo*J{ zCsN~Z?CAmIw^je79j8qr{(!>Q$Ud=jF9BO4hY-0F+0r9`N-gKw*o`Bq)fsChY14-O&+Uc9q^3 z%^9V+TESb)FmM#*_fh2pigGb(^i!mM; zdMRZ+LBOHY{8>UHXEGVmDo|}|BjbqF)JJtcSBl+MZc5>D-o%R3IpapB+K_yLYRte9 z_2Wz;==EU$PV0Y{99FBUXHGVK7J>ttFZhzWd7N@fn6k!lUMZKG%zLSNk6 zOq0m&5%~%*9S$iSkt%W+Jxg*`e8$4a9zj=h9bX{Ff*6gvsC zA1~=;=P7Uh@jEbrCL)qam8I4YbzBtXd9-)S`AID}`K6K>`{%n496g*&4CsQ0YrJ#d9gHFP2I*Rz-Bwxx=7 zvlyqC!T(UVOlUxXVRU=Y3v^7B!1XC!*y>^}GquXZ4rakM{fd*n9P6Jffqcupw-!29 zspz8L_L+{h&~H{GRNlRWhaIbgF)+)AR(CiYJl^|sAhg@>X0CL7xM2CB%nlFC7L%JH zcJUrOk!9}ZX|F~s>)!>Q$(>9WmtC8JfPR&n~;1m^~Z< zC>+eWVJtnUtu6dc;jxoZf=OE&WYK$3n$a4FT9x>dddEHDdjHARi=-<`VYAa3iB|)) z34kQIfN;H6{P6?uH_&;f6x=1EQgocDqM%Vaw(^>H)VXqb@l~42W?d{uWClMVDSBNLbn({dBr&ap`rAK49c{ABfoO z5>5liN9&p4#i2%7?^lbb(!*Tt2W!0#L{cW-Zf5n+k5z~h4$1F+JD`Of)lEtiRRas9 zY{(|lafMN8RGOn4UlI0>`4+a#;VPSL6-?4niUyLj#22(B8=j8C1I_)86^ZI~py&2S zOMQ|Do5(=6o#3ml!LEi0P0#CQt~+H9?_Y@Hf1fhOhc^*$D}J%pfZkNv&NiaQ$GH@dHgcQ_dl`GJG)rp)eS?r0V?KCJKI#L zsVy2TzJ>0rZe{K4j5zb73+LP5t?~S9{zJ#H^Cm$_^D>4e!n^1=oP3iB9)= zACujzVD#2EN+Biwn8%zC4=eyX8oCv^7OQNudVDNFu5(nQpq>=*ohw`1D{@u*+f+sY z>aGTF!Tn-%>>at_iTl5wWMhD%d5;mtqBf&JBbpdV&dpe-W$FfXj8E|l6!W^Aw9 zkgG*gN{@+fv~vU^T60Q$ZdM%PD&lg6k{Hs{q`Zi@O|Ica##-K7Pio#1z;)x1!5cm+ z=+u7SY8o#FRyuwyXOoGO`tR4`^2xL~WdCN1J& z5c^~8J{#s$1DsOju(wH7za&kEfXC)~_Z)&w1uK}*tu0!ioy#mytkAF95BVsOgq#(6 z{f|S*e5=@Hc7G%H8c{mbad6k_ixO}pW#NHapUTtVY+bFWBi47gcV@*Bf!zC&s}{lD zKOXE3y$E!mjWO^NT=)FDnfHNKb}gLE4^%se)V^*BgM0X0`V|lC+EA(gpwPQEIaw|` zCrl|-beL_0kH;EtqJ8GCXri4CDntX8l^tN_lfWVLN<@}B7~#Q=HHFjg^T%=;mZc@f zZex*KghqyqXUe!uqLt$p(Ncg6DZ$b;|yT!OYEnKg>cDG4U6 z?;6*dlHY4d(H@pMX#!z#45^@A3IyC?g59)f#f3`ZNHTJ?m*M%^@pyvu8XVFQub`c# z`tlAd^J~73I*h5HM3q+&BaG~9A9UTP;8sUZ9J8K#2#r^;;O`95YwDajX}?ZZD2m_K zW=wy}Ku%c896xmqjEyn!D>nVfVX*;nx_Un^)UV!pCDSiAS7-}azkXZ-K6W%!B2y2z z+M)Pk+@mJ+wQs+^_`A{T1K0XsLhq@&1GhXZE(ww+B7k~l@5`li-%mQTV5)&J)^lu0 zV4}Mur!v20tpq;FS1h2D@oBD@?f>657MjTkKJNDJxz-XUt#yipL<%N=NLQ?0bvZkX zPwVR>sh)JV^#iVT0ydD?SmmBMt9+TF4(!uZXuj73ndyfP^6L}9 zl}p`L58w_!c#uL(O$`l6MJj)IX^DTWS)gV0g~RPhRU@S+*}yAiS{e7LxqTq{uTbAb zNL}y8e|L~GQKcLPBQ$7icEHbCF?Mb5~LLLnlbJ*&OLo|-q%hB+QUbXi>0y?^sUiK(R*1=y_fGKw@0Gm|IzgC=n zZnf@5c}rHB^&ig>+A}7vw(TE@5oV#`9HQ=!vq2xA^q6f_Cu+T)zuGdTQ)=N#M9>^# zz~N@qg!+?mAZ`499jHP@%5Bt|-B0$aa4|=-2MgHK_QV*0jN?Z1!u4@ zmYMmz1&HK6Lf4;wQ@_fR({=7wwl2!q=Oxkn(XVb8+)Tdh>wYkc2Rke4PcEOqWTdJB z6-R`H@7yo5svnPGHyyoVKM}xpKHS{qbx9iS|HV5RAG_7?X;IgkEWlTWCJc}+y)ju+ zwUs#2AF6ysQ66P_0dZ#4Cf{v`(?t#~PL)~eWP{+qKRO>U<$hb^_bl;mB1taR%u!QI z33!`K&C{Kjpz>8}JBbp9c$JSIOEc5EPn5!`pWZ z(4}n97W11Q23!)Q`erT@qu2hVPb$9kNxOWPPJ-O1Hd5i#Mnyhh-?L+zc*(c1E0c6` zuih-%a68VmHi zlnVIH1o)joHh%BVjK0^E+I-X^oeXqY*jSX zR53>q$C*+5;T{wpnW1Bt#Jb|!6JEZ4iXR7%D16^Y%W+l`f#_!(K-8U=UfdETj6PwQpqK5<~^j>aqumQ@hWii74={wJFH z>@(fJ^&)%G2YI8Cz%_E*;pa)P-6S4>=R*<|ZucVxjquqmz4aKts;N$>*)O)jC7UwiPB8diB??;qtx^>D^s`# zWb7m92A}R7(fK^C83ANp3FpK425xEe7!KaJgAEb>4zyL85aS{R^;%~_Pct89ANTO7 z!5QVOl>JKMFM?dkKPD!B$30fX6Xa;26^2dC^)vrXq$;oqZE)Znf4m_jYq@&!jLE_z zOOhpjY9y;|I&bG@UXu8QYGnQ`EDKSLr^;QDN3XQyzPS+L$&p|T5FER2X}@R($jFP=g8_Es(#n>z&X@JYAAF86)>H?=PJ%mQ1i5&E@X zn_8Wn`Em$LK6Z`vF3lbV6UTZ<9<;l_)9>y%{BoWwz7l@7VeW$fm+4UOVVW{-4v1ur zKk91DnxUwMyiE`nKO1j>XI~%w%rtH50 z4?4Wm^+5k?BGs}1>q<$EuYPUIz7CZoTiWXw*%eQ033cR_l^)-i6XTZoyNJoJZT^x5 zwc*_Dzk|-<3znu}-(}vw9U({cPE-HuG;X9cMpE6p!BNrJU$KthgKYK*-NombA|Yl4(^Af?@lDU>Ino|2GnmtBFpk zim8;?FF!l1j2jrtFP(7Hl?gLnl>+8R$KJ?S8V`0}biCjc3ZN*nxj)%j5-q;a0qgI< ze6gMsUcB6cDeU;Q>8DTJ0)tmCRbf}^NgXn3H|xD@&AyH^mDH~vcH$0f7E@#%V~h@& zNIgvv%kS1S=oQZ5h+$ucW&BQDjHbQ8`A%5n>e*%DmYeP{Ay6np;@R(?)Q8Qc#GOmg zIYKEWF9eDEi351M1V(V9_-rl1T^*$v&(jK*1AOumnps8d=LoPPZ48e%@Z>@U_ks%w ze~$GZN?;H0L{$al&fl{Wmg+PlAi`HhIz*iFB~QL>Az<{hhc+n>w2#%fyA9$^Ic!oV zaLo52C%E)oVqwP12YjHvi#LzFguYL;#qT&I8`*le*nOSiN&kCi@Z`THyS%*pv}m^RTkew~8Vy@w}B_y;e+=-!R(*Is^L2}s|t`+{^R z`k;vIUragP2aJUSQPPsx=>hg%r#@Wn>q{n^oBsI<$|x^SzjuWBOj7hhmP>iy%A|9S z^ltDCTJ0zA`k+$hL-Nsf;ur?t>uoRo`uoxQzwn;@zdU}OV*WBS`+c#`wh=~geh8!Y zmON;L-NRM0?2hi$#d2irmgmmk44F<*gPu}@}f6!*K z&q!fv2X`49rFdk#h%|*}bA8V0iL{qxxlqaSa?K^QtiGJQDN2{cMQ|OPlpZPu;?uc* zy1d-KQQzn3ICBzJ9%g)G`uxJch|eK|XGRj8D~-UH{ZgOBvG1il`bJhNul_w`o@kk- znoylsdi5-xhx0WwZchKPQ&CjHEW%E$Tz|pweU90snX?}GpN}$4-Wl8%F@4uC8yJ=% z@Y@at)|43FW+JRP(YT>Cy^)qiqLJS*$jdNd`s|$3-d5*s-mg+chIZvaOhkxY+$f0N z5uTINygr-K<`L-qCXj;`cKn#LO`dp(DXF9(SQBSof+=n|dN@UAs@v7=)5vq)P(Xwn zt!)10Zd$&4^Q!={?{mf3u!xr2ZHpE$WJ|)l2+rv=zZW=>J0qwmUMwP?zT9+4=c-_j z2xphN2*)Ar(_fa7O$HjDw9SIv=ilX6XzZ<8Y!TMK2_mD$rsxXrpf0FPZNZ4L&bwVE z{&&4reafYF2dMZk!C8^Q#_1bd@9v>@d-OX3yV}sU4Yx$8nrnI*+ggEoQ`NY*zu3-V ze!F-?V(V(E%HVbUw_9|cD41lW=nD@C=fFv%vzKyT{*nLW*HBaMrDDkDEmN_oS<_&n zV@Qs)w`Y0PaoSa)?95lDhfc#y2=C>Y0M#G{lt=)P3|IgUw}RSDFr>JXI-Z*R!^&b#UX$^vtn8G%}J41|3; zn(yP)cG{8btm6%SRtwFi`i{Dq9xmuWWTuuRQeE(NU9bs)zl3|x@H8p$d|8TwN&+U zhnsl*9-E3SmECbHuR5Q44obD=rWEyABUs%;hv$5CAZn__Koplr>tTG}hS_und;Ax@jEnwCKa z`~H)DmuI0)Vc0>cvn)=>U(-mKvj05Qu}+?mE$xn;y-q6i^_2mAqESHO*!_jxd#i%G zBE#P2#1RZ%ASAvqJEPbajxLv|6G(Me4!U==)O_x31_IQnXD;UY$?9lwbP`qUf&Ia6 zn;eLo@O6!_I<2~1b_YHp;8Bw4=B&fd@|H};mX>A?H@=HZ?dO}mb!A^PP#t(ljNRS+ z+3LHMGpYgIf?!Wg))2dEtj)17E0LxhiKvSgZ1h4b#9ymreY>cSW;jP4QuK;i8PWvd zDfSa5C$KP0tF|lb(DA;>QcD^{^ zM6<7>$HOc=Cw#?oiO`$ggfAt& z0dcBF$iy=Q(C9+ogMWcbR&&$e!&{p+QC(yX$zL2^%~P3Q1&ux_Ce#s{oUN<*gc4WE$;`qbypi-MeMi?@D&}G zRZn_!obH#^B~(f*BP1WBw$_vKdD+Q-eG=Hzd|$7tywkq>eB;i7snf|IRjrGD5Lp>z zF>TErwa^Au6vh!N!3`^g9Q>%T3)5e~h!1RnCR6rURH!mA_DXT9A%w#F_ zaXAYUj<9M8NrccdyNvyTsOg4En+&6klN|4pVbSUe`hZ30v)@byL4-VBu>-58t(83A z6G!kLy4O^gEocVxKuHacDSi3Rr3#eWY?)2sJMqkNKHQ{se0_FUU>XXy%?=oWG;WOe zdUf@g2+ChjP(GksojLz$06KOcS0zKen|&QX*7Tc^uyuChL?1Ho^>cZ3N8YJm)8tl< zPVd7I{*vtds@6>{7PyV7=_XFCS(L4vTaF^f?w*SMfYAALb;XLlImSaqJ7}3LU2p4H z_Vw=d#q1*J0yBSx=I?DbvZ2(LK<0eY8f`0OQA%Qw$)@qk$C!02}+;IDza0jgYe#xz1hE=>z>v)p2D>4pKiWYhSwpBK?@#K$D5uH zlwYSBU_1^>Y-3e#k+JT%u$DLrsAFuiPiJ|k4G`B;lcV-4`4~@`Ol~7S+bw6D4+~E`=ZBz{3Xu+`n0KYbD4AWFO-jv-LZ`?TwVBkCQ3{>D2>9% zib1*p!PC60VDuq;Y{0#JUF88PAXZ}{FYG<08@vbgwn2#t!*;^B!07DfGiiBI(dc4W~-HFrXxe;FNP;x@1C zjH~AQ75X5_;(0$EX5p*}(RS1FGr+UTVClE^$-sGDyf!E(mHBaEA&`69xVAK>8r3pO zaq)?IQ%F7$?}e%wLde45>*ed1VeGH6OO8Z`)9cg9J>)Ma$bW_xhLyT%wL?z0Yl-gM3Bitdb9* z6TDvMi{np*BYNpj$)=~bX~8;dBAe(sM0`{i(3PzoJHuI)XU|0b(knCnXnXnqJg|~V zpM`R6KB|VMir}6}um3(aS-!e$Cl7Lky!(Ks9`}ZWVg3KRXZ};HKcs#9YSDr7F#d@> z0KQFv5m_#)?|X3*umi5OD3D#0z42;V+84FQp+YeB&ce8|RYsgV#ay5VT z2fj~riK~V!Hxsyk(At(Z`*2iO-l@d*MDH@f0wE?nSffkdOHR|N#eVOxH@&Tk98obC zjjfBHC&d~R>{*B)GI1V!q;-FViXvzGzT@ z9Ate@c@w;9Z|3LO+Cs=H%u9_R)$?;@QQe*lV?AABbs}xX4Y(-|ACwl*5jOnfH^k|0 z?KI7vr!P93Xk1Jq&W>D~U0j+K;SdDXLsPH^+O4GZrfp{vIBR#Z9CxcuFcTNI0;rpL zR4NvWS-K?WD2qL>&JcWrWhX7p&SFZwei)A9S(&ec#51C)X5-KZG zPpO`Co)DQ7yJOxz0P8V!a5}Q$Km0ioY}GnlB{yD@=~;+3HH4VcrMP>@IohB>`ICw| z@`bs3<-Bg_!vuQ6a0 z?4x>jwqEZC`t@iy&&=B-hzbp@1}85=9rG{yCJL5j^-my|wu5>N- zTaMr_->hp6=Fya5if!F~Tcg90698*n=Y;XD{Lk!I@vuvYlAlsyj z?8}pl-^x(h$l-BWPWM{xi+N`sxq7AHcEMsjINY^1W40UBuy_@&-Hr*NiRWQj{`d`Gq@x4XSr@lH3?l8B1ekgBU-Man@X5n)?C-&_2 zzt4ET@qxz=h=)BVA3AX08-72V6^})TPjfJ_EKm0sEah_gj%~}zalGnc>k&n3DkR|$ zVqm=2bZ92o!rQmF1`!OX?vfdyb^mcx)b!gp3uT6+3;cbn?Q%%vJYxmM29(&1{cerf za@l|@^;+Hy-tej13X+L1hz(41V{M}-p#|Z~`oa4GC7`~Ov6u1l^4E7u#0Pv@y;T=) zk$s=!CJf31&i$-3OS)_g69$}f06bcU`?!B8z=~hihvyfAr$FS zlqM|^5fK5AE+R#xN)_plL{vaQKIakde^(YxxKQK@(p^D0Y!KUs+dh97#7%5=YbWgF4Le3dy9`3SsKeC zyq0jnaQD~!(E}JtzFR7FdBUldyAr9=Yr5iGTG*Ai;VO9W-e8%)GztmgCR}27fAEB%Q!yp@7B!E-2%J#fDQ!FsCsetT ziwTWV;TUeApv1Iy3u2pfPo9#B?T$D;rF}0AWl|tTt81%XGmb;{fy=YB173A#;7+qG zN~;}U!Wm_^hHt)0^eo8FLiEj?GreCd3(uE#3K6v!Hc|i@=i*-Uh-GcN^ws zFf4@Nl*YAW>N$6wPvRWAxcGHPSHTQpuOs)YxKXlYgr8aB`!%XjXytg*zRG>#PYMw-abX&o$%O$ece71{xw7e8L4`t^Q)?;`h$ZU$W znb!}2dCa%N3vQp>O#D=r{3t)4>e6ZR?Bo7VF1O2#9jLRZ9$(qeL*4b5FbGMiv&p~?w;w2d5vgGY^QEeoZL*8mdpIog`}MB4wVXmaoyP2 zI`1=eI+mjtEwe@ems)(J1jl0tO{F0yW4=)XUc%gl#cWSC)3lQHUy3FMOo>7@VAzjq zms^Ys!3E<0zpDOFf6PT8T<;+dO!>t#e>6 zswPA3rL)?UF3X`z1Yy}13K_}$U>TLVwYH<_-5__X=V}UV4QLDI!(^3w=EMpA@bzjp z@OA-tK^>K3wYC7BNkzy=|I&7%JE zsA0-4RQNQiz=8?_uBhOU-6eO85qoiRDAv;hSKhB(JjOqGc%c&P*E@+QXjo1$>l6&Aa7<*8O=6s|ZTgIm zL-y<{OwN`#8O|TJxV$53_C3?>%Ge2Jv~z#s#z!L7PWh8i{>nHzJm$Q$pqh6&HL`@m zeXHsboLV+qi&ZxK;d`H4g{7rGc?K0;QD8PFv$MO~&-I=$_gGyf$ujKpw$T7p8gZBf z`o&Npz=Ey{W-~>YehSB}oPHoyuDD5CZL-$Q<+ER2+uPGd=|Apg&lMR$Cmb6b{R+_& zd*hAhc~;Q!w-FuoAFzNiEsOq$PuE#pix?Z1!9}?H!`W_###x1gZvH0dxCwqy6F9p! z`GiS<8n|7XLh&w8p3aRiXrQ6B!lCjhpMbbW%lnyZ-d&mK5R7Vum72JsA&d2Z!uEo> z+_r|~%6xU7y$L1~V;cW<*?d**eZV_LrAj0%gO_dUI=@ooq>yEkM!?)%(G{&LAm@VW zww6J(yZD^RsI%!+P(bk<&esv11~aapX%SH z7(q+fdTP-oDP0%ux7x4wluhr&J-S02@bXI_W7)9DPHqln>^0+9kb6N1!B0kTP53rl zH-|lJp-g$czd7&aMTfDpb(hj&tAczE6XzA%5YgehcYP{E_S1B}dP_C+B?}?XX4;79 zgI3zSi_B68o8>zJ;7Kwq%ajbKfCMqfTk$pxmsK{u8Dgs%8}GpMT%F8BtkyJNVQC3? znJde&#NzHflOlM6oc4D>F+QeoJ*7C!!w}8ove8@lq!PRnXExxf%8v@o4Rl5z8-zBfSbt8n;%UZ+yZY_L_jFMYfBD1N?j!4z- zbcvrXROxE4-*Kp#4(k>~)Vflf;$Gy@Xt`t;Rkp7~>o3DE*IT#mc^W*EZiIWz*ymR+ zUnDk=jwDf8&5APn4KZk&hO@69#F?8ln!@=S9VZr|3C6o6!(HwZJrp!vtR0Wvk(>LTX!laN~5Nf4a4)3x2nrF>~aREGAJU%suG ze7i)0?n};Ed;TpypnTWG*y6=rW|qIL*p3hvsNxkXF{9KI`)>MO;}mn#;o{-X`t(6T zgHkWv=QWb6h{jcYulgwHP}!I-E`zz7)6lhYD0_$A1&set-iS2Vnoq2mqtBRtT5)9O zlO2iA=Zef4^XuibSC$fLdF%; z&AsCKD;ZZ+q#RH8194v`cGd~*__F71`%L4@`$o^?&cwioKOYs5SMYZ$^)v0B6ZVo1 zNF^gFVpqE~(z(7X zjfqw$01BYXxWPul#OlN7=4}oNJ@o~&2_$=}2{Z-RVAJ(s>Oc69fp&c0CLLA!$$n^U z6?9o{fxt@HO%pNGgZa}q#h&N=bqjFlAPYdgCjw^gic>RtuY!B{d=I}~lD5B~#R3kz zO-M8(I80=Zuatxbc&YU>OV!W9pqqzrd&u=_4R(v~5_^*xX_UGmm*JKDY&l|k6kr{; zS8wGh)vyzm;#91NBAyQ@F*iC7WQnPAR&W7tLi0-R-+$%Q*ZI=v7vh}PE8x(|zgTEV z8Jnx`8!5#&Ck_&!YKM5?ka;s)k4!&N?nNQhlki6IqfG|*t=F3y@51t={3MsHCQ5A$3^d5=Wn`a+r(PUV+XJ;O>YF zR!5}DjmSc2mNxouT8>+DN_HlR1696;r}1M zB1N%;IxB61750{VL>hcN)ew<>)hgpsk~q@Gb&GF~ytmL}5tL?UjI16L5+NfdrPx`v zu;`22EunvAuJx4;DcjrviC*NxOjL~f4=2|T6WiLp=aD%te}ZT%WKn1g*m{?n@bhcb zBw<9ksQZ^QWPhmY;FN%pBc^j*Mt*V`(MW&?U=QO~x3`F)`#Hzl8fNBG)>Wr*3)>hc z4-nFdY-ev6&~OCPZFxCU>fyD!mi710m#-ve1ic{FE zqeHthqDAj8q#NxOfBNK)v*;B%XQ02Nh?adLwo0~wK9n$jA4@JQu^n|HeuOxNxPspgJjEt)h>5M&Xk^@b%(1-&*O`!0t817J zgAQhS@k3oX@b_SPlec!-QqIgWf17d1Op_Zjkuj2U@#4r%UiS8<6v{;U5P~afb$bz) zt@EE6TJi{6b&R3iyGce%`>`>R9$g6Zt&lu#&-ZHok(rau{8d8f%dNk_&d4jh&nSuC zox`TWEfSR`9$I=Ry=VU-r12_nAP=-wKFOOB?tf4nm*Eb&OLoiIwNrX=;=-?5`Gcbl zCKVBgIv3GbX2NFCx`Pr`Xe_HD$yc0~Iih>OYXw$!Y*~4wvyM8nJ?z!VdB&XPOs$@= z!oGMFb=Np$Y}+a1GmR7!8y!w>B8#g9X>~Mv>RNw>Wq?COBT|Rllg^SJ&n-{Be3HLV z^iGA5jU#Fq5gIrNyY@qMqoV*@i3GSyso>pk2)xyE^-#T73TC(Y9$$Fu1>O`(t7CU? zA3nP^kSkL}=nef1JoGx{q)PALyEdbas0GuB#;jOgHNwjenFwo77)6~M3IgTq%038u zbK!G~q$X=(|C$eKZV$95vQj7=+K-H5Jl0Pbs-FOvsoB_qwn^@L?h#tBM2oH}F? z*y*M*QEzsl=N3%<$siL@F|NaeZuoq&!#t92I9u{$l$X zD7gPtbLR(z#0;J3Yy2FDUhTEoA9sg-!e3a>5VIdI@wB?HIt%V=L_r0MH=H)vdZegF z?@=z!P^Kx-T$!zIQ=kpNRVH7^PkitKGf4o>wn;1pOHRNRgkf-B$TWVCq~;Yx3o-`* z#<)IrQYz4&1Uwcf%xfaiwl@LWCr5d70%sr+m zB|4e#{!X~%Gks)#ztOr!UJfn=Z-LayoYD4vY=vA2r+$Opm7p$>kKyc*#4?)WzR!km z!B0~+p!DaRC!djIWXrT&E8rbau#dvjBrimwc zv47~Hi7=13Ol;%(*V!BPN#t=0Tuo-AVrNn1!~#m*Jg_Pg?=zHGhLRgcTN{qMR8K0P zvoXl@IpSIa4!!P;S$R)WD@~Quw25h`&f`vO2inpbChM9@I`xyN?}-5a+hA^_r;asg z|CnoT3UA&$;oI&*DcGW8Gw9dH+^T)DF8gH#CZni8cbbQ*VNVl^-Dv$~|Zt*j!JgyYZO^Fh|bgW>S4aui%+@W(W`;%S4Q0X#`-aKEePojE?6bxHzdQ$=M)fs*b*r)R`#TUaN>`hF zm68lLYcd+meu{4nZLj6_o~ZJlSgRnip_U84h`GSs*ZtDQ$2{vEKsxMR{WER*>$0rO z)u0MQ6awrG$ZRTg-yCJ zqtb3;+StlleUYGFwSuMirZajfbKWQ~s)2VNO#*T&`h0!K%oT3Go(niAwC2aSm2bvj zlwLouW~S2Ub<_1@4)sm6;@5Zb@(@PYn9J>RB+_Qajp%H+WHUE)p!rg!yq39R&j#{z z!_knpNZRmAHnG^(7b-2>Czo6&{T9CP6FJ^AS*xli$mksV%r7hSt&bL%2gdLxklr4X7Lo#WWc%)yA?s%2ZzSR@z@zr2BQKy zxS>1Eu+WOKhKlj6YFh1q%dNiC!W%z|?G%Q~>#spJThDuOx$e7EEwcmH=CVs_tJ?e# z$!|Mt7=Ad739}^T@71N)l+~8B|0uk}k^RkK+lnSVdrhRxpHGkWUx;?{+j*QbS}Q9( z;yq_pWtZY&;xi}(S+(O@z*-~n%Q$;)>cIXH zF#T|6V}H*o=>nA38fWOm2G|z~TCV?G+ErXj@W)MSOV$3chqqYxje1E?D-j zTOXl5XjrbzNJ<0DENqAN1GZs-ltfT2HsaoHeO!(YK3u9{72?&oj{3?hIR*1wcEnS* zG`XTNP{&?lL5>!A3ssC;ObTt`#UXJ3*E zKNJUnX3A#*ht8L^@8xJ6k!MLBa#_lXch7-&j(Bd8Cs(T)B1XGLRXr9~IcjNvsp-TH zUhS%mYU+yg*sA@>L!8x~grtO;?h_;sj zS|J5hpC*-oiOcEgY|jgdznEk{n^#J`1URCny8AA`fRf@h_Q7>W<#}D}d5dqN<98K? zH@}8U;pwW6xgq9+>gAeyf$giVJE-S%Y!GWv1x)?=k`edrUn%hZrxO~GIphR(Q7>6p z7MH{(-938%b{1d1pqLZdFzB&{t!1YQ+PkLOCR|gB5T@cO=k>gQ749S=Po*4uxlaFY z-;)9}vsV5k`1918qOlC_!jXz)Uw-1F6?0N5nhr_>MOB0GT9~)(0Bk&Y;A&nEJ!3Xw z(70s)daWKwf4Umy?BbJLePg^3)D1j)Ic@1-C#9Q-qbE}eq zwi=EX|8O)cAL?&Je-P0qbR>l{Hl4Md9p-{Jq64dx3A5tpx){4>Wj#vaTvr1Vxx~i( z-q?IdR?%X}z7!D4)2(|tQk|4)`CL~~&S*%3g=4EY#brytwYg@}eFl>_HE-qqs0!1p zskPBx2!W7R_Mp`ady_2^($qj;siDAiIbNra{Wo_*lxI-es2kyk@$PH|NDmYXCBdU! z%~IyJ^+~GzOu?Wj{F{DFARwF=w9d3o+X*8E^)mqh`+*|ZUq7a{GpcF7O>GDDhfYHd ze$Wp8<0QNKex?B8!l>!PSt|K+;`%<61fn!n12(9yeo_JczJ&!{A+Ur)rl< z5{%|pkK%ECORu0m^`u;h>(BFGG>B}>Gu*xZr~8I3wSdm;^`UTXITVG_8GPb8Q(>bm z`HM}&Lt4t_x$u1 z$7dy|#G+|Ikp-An^g({%he%X|ON|i-w;&<0G;ze#_Nx7syKEvwCF39Wmi9*S=YeKcJm%Qqse<#CBmpJHV@@?lB+?Rn>t#(Z6v^jNRz^dtPq zv{_4tZ9Q_mNgd4uEDyY~8+k2rSWl@Lz2EL6Sn6k~@c5fMP4?whvtSkY!=(njhh13- z6elSqJaGRD-Rl3GHMA3YL>P~(tiffD1KQ~6kDlNO0#WDOu(?m#VwLs}_{D>&p6l)? z&`JrUdRA#SCv7Z+r|n(DLFCUCKJ=R()O*ry@=Gjx;aZSH<&RNS{OqvS;8$je#dzXk zscV_8g6DRRj8fYA&h>G(C@yM9NwHJaLLzwYZi)fhp;tGi=FO=;`v{#<6Lv8}k#hHz z#kU&3ExU-N`DCZQTjo0&XX5oAJmHd+e(%yIC`}`j;!YT9+=N9roQGvKd>e}8XZ@g8 z7AVvitVFDLLfM+4XJ~OLoJ;JKfqLj2hgeb$tt#6I+5h}#3v&;;YU^RylC{IR5(nUK zHg;>=E#;#0HMov!jH5}+64Qt;eFSU^!0d~PYaV(2^)W2Xq4+>+e){9$FZT2Gt%zF@ zt#J5J6C8o8@|faI?YOz|TxVXV8-{NtSwqSv*(bR|6Agh-C(@C?uM1ZwXQ*#!_%<0- zh~4~6&15XI7ry%ko}OpR5BSB_ynyBs4vWvwAofAM6aQ)%NC+)~JUOG_ICKdYim^C3 zY2&=R0oZ3FF;QA*fE=Svl~%J{Bn)YnRGZm0k1i@Mc&giQzrGAj#9|xYPHT?BmkALd z66Iy(o&v#QMuj`l3Z?O^(He?e0jc)66-#ZKyRIxzFjd-+2e1I`?b@q|Tc8MNOXFtX z!@x--_(=ebW{pakq6~zSw!}*an0vP(FD}ieKG|EMY!5jpCyVyALzP*=63mVlpo48E~%;7zxnF>?wZDrVgHO_$wzRA<#g zM1wky{704bKdqH4@OkoFIIw_1IsV z!T9zy|8dKvo&_ds!#aHx(vm3@bMvlBX-=aQjUYsxlv!5)kNNx`bN@QO`;e%8O+iV` zq)S7q54vg$|5T};{&$SKJEf3du|!iiv%3~am0-PpJX@(`+5B5$2ffr1`;9L|chXbN zuk=!Cbo95ZbM(pjg~IP!Lh;SyAex0|vkPqDPafUCcWl%5>#2ak6sER690ZaXW<5XP z6#n|Ljlgf)r}{Nj0B^-W^PwRB$Xhbj$j`|U3U4v0MH_W{ zhdfAnyf3IO%FzA5$7HS*Dxr$v*7z#Y)8`F*(vY;nZb}YBE(E}$@ZdyDJ06_+$3Hmw zX_;wuBLsYtT=)vpz8BR$K}hL%x>bnoCWf9c+Ar2~)>BiG^s1N0-4@$;O$DO97HJz=q*M#ts7nI_Hx*fh zVGDk+^^oVfjF z-8tT~2P1akG(BmDH%LR`m-+u{?`YD|OKY*$vcrLX~g?%m(3&rl%7DLQ8A8a|2^2=Y) zRA63JbLUe$=M*;UP;4?ox3{i2 zXttw`Zv(XoE|FvECL#y+ue%XNaCq0|MuTucu(J!Fi zeu;bb83bN{4s<@Nu2XnA=)r!^Ws3FccTF^m$b+?nf>Vdn^BQodNfvIgr0k)PUndW( zHLVxCd_14Z79*tV(SFeW1OkHmL;mF>>?SAd6I<^jq2Lb2bkNO|(IwfHzXfnH(cr;>TTfIwiV_EI?UX_0%QlzzN$RYqVX(~$;OR!?S30aA zG@zz)h8EX`zu7h&{nxw+;&|U=t>T0WcBDnNJ0W>|r!(Zu_|N@l3G!NX2} z=nClp4~q!;Nx~ZHbhx%%9?$+^5_U&Xsv2J7+E7hR(rif5++Uh>w*h`V)!63m{HHLR z51HB&hIuqLke0XH_?6vgGuUHOjhgoUgdsRMmM`@0jW6WXH5wU#qD@4V3Jb^9ITW?9 z{3UUKDZx8jvi9@vs+f8xtFFf{(y8YtBkgg{maQH@RxAhyTBJN{2cfxrD9S7@Xg4t0 zr3NpQhw5e^xk@>HWTrhC{bN%(_9Nom)qi@@+44bo_(BT>??c(kx$dn0wrT8t{Q3Sj zXvW=6;<{PVfW4JNweCBBLe_Sd(wj4^9y~XOySTj-oK(m?x>5HkjD3z49ZuY#1F62U zydQG-UzkaH^q5Yk{o1nivBdn-87Kbr!GAJCRbg85pIfcDD><}o1xrM|qBV!Xz0W0))C(kvR876yuFFbzFeE`8v_$F2k}>`y`OmeU-y@`IF)Dut6)i{`32b@HGC%V4QEAL1Ws84-DyP6(Ao>=EGg$ij^C4NuARTBw5q3K!(DERRdzLqqqU}n_Si!yB8#GTg_LLVB4=Brwae=z=T6SmU_CF+9UdQe z7R}W-ee<(&(nZE|s3dNESX)fbTmP;w^R?U$32jKIpR1W!cpn>=wPkmoMom>FeCykj zHx{st%tiGm(ymlQRl5f?>k|WqJ7?)$O6_3gwJ4{xHy%!>xGKxnZ}IeOp3gOosi1yG zc)@}xz0<%s`C=RUEHIbf6`?5cyR!q6s|B)p=a5}U{4wr(LQ>`h%db#EgTAF2F!uPH^X7j&^xxyV$R6YLYmWt96HfjaeTb-!uzi6qJBEzQ8+psUC!pU>$WgH?krB|oM?a4Q?Yv4 zwYkF1=Q+gTd_f=2-*bn5a;JaZrQr)le(Pmi4t3lDI+*!5@cwm>*PaINOa04Cz}Pv+ zU?VkiVMfh4@s2-l5M4x&1VfxTbZ}s#BrPfc;23Qisg<+F>)}>j#!%C5G0Px*Gw(t) zGWw9=$NDMG6N~qGD&#Bs=U{b*rx*t$#zi?4>1x<^9EO2i+o-#7u4UKLd9}8D!C{+~ zAB2^=R*i$**8OHpMn=VP4<++>6laKER?f4$t8<-|sBMUJue{(^0oSo~(u_?p`w~+f zQ?@pz1iD)B*4$aB?naFFqlasr<6GyInaQpCtL@`O-vLN=Z`lq*5O;?RD5IiVWmX5o zP&o3sNmI6s`C{RfnXawFjj~-5gf&HGKFxn)e1AZ?|LiG(B#-LszQ}uyYwRWyrTW>$ zw_SoMBNNQ^<)1;fZhQ(RZ>DP%KcnM=T^GMIANPH-SX1$H24E}f<9TvY;O)AmBW|Yl zLCpDABS6b~GdPC3n`~nN`6cwSNgPY{#;Y<-Z4WMC$F8mp?=!fDx7?2Nz@JRf)8{mr zhiVKA=|$8x#dChM%K)=)*;sXEe=_OON(M(54KZN!lnmpE(Uu8)bAG*^sP^66ChZv( zZXXmdvcG4<`=expQUpj$$KNH;L`Gg#RsZskM|kAy;ERE)R4x|rh$`)xA89@L<^6M$ zta0oR{@ANP$o0ctKFKeMR^`UoD>x{4SeX(N_IkHUx?9RM7EAz(^vrOkXWz6^?}olI zdUr0Y`ppqP4Sj9mL&?1dQ*Xw)d)R{5e!M-e3cP-UQ$wwxelcAG%Qe=mZb9an^?PBM zA>47MUF5?1dTC+S%gpzVp>1a*;J3+budEY9-o2=e4p8RYNol)wYG{A1-cP8Yf?oq?8;is<~6}Dn}ML2pn1J#zc zI&i``m||lpEq>(uXiwB9VUzOB_U%gMAT4lt6#czCH^GFI4{y?ESaFFkRq~p@jAFmJE;QxYd4cNV^R)K@g?J zK4G7&=UBsec1AVYwi8x8*@)S?R-C=34_?F|KltxSbPHCL1!g~7zQ@CGx%^dq1IUEj za6J9Z6DeF~!#BFr)f*|?oY(b_U%#R@V$5#+!Aqd%6@)9P-FP|YdCj^~0k&Hox|p+A zQ++u@tq6r2poQmI*r*yq|sXKPfk{Ys7P(dPbyg}`UarRvI zWSO}u5laoD`*f$`e9ihT!sh*Y*>X1jX?!%dMVCen=3~&Kk>Ibs6F%&RC@(2jxyx?J z`l**~#*O+2x%ohE@y-i(Wg72#WTSVZ_AU&_B9xS6uz6-mCl4JKg36D_?SG4@(Gi#0 zpHoEfW0KM3U;ZBO{dXh0qh*~>gJHf4-Pn;dSN0iC$NHjAa00R19rU=YiMg!I1l!9x z;}FjjT%*EvTm*NBDm=EA(0(k+0XtNvejDyri0HWeV$!( zTI$Qze&nijXLm+TtUV~p1K`mb*BWzsWn@TT^P0A|(JaE`YC#2U-t*H5Tktqp+&8X4 znDCjMV{zrs%)fBNnO=U$Q)OT~ri?D_lS0fw(}USb1Nt$CoXR&_ACr?#;SWz)`mCle z3YY86tlYF*&dCO|d#qKRowAP?R(=Vod%5W;LV$42Dq@_cbN1x69>zz;1$dLi=7U$R zp7g6n$V1C9?iDxme%9Vh7c4$kGai%umR+)psNx#Q)^l2Hi~YZgOiV#}v}V|O9;zp` zX1DdM$jR{^dNOkt`uP@b^n9di*oi^CUnMnRs88~UmjbL_!1x=%}T{5hyvs@ zb86Alh_yT3CbJMv#r|~Zba~cw8(oD@vE|;RXV!$kk+J^P6L2y+&x$lxASFW!ndEEwX_)6Xz=X% z)@3>|FRoqU%Fxfjt3d1<%U)1EW_ zJKOukD*TVU=P%rlz^xe0Vb$JbbH0@H$`7l+^i+f?hk9ckCjE zj@DmdmVR?ied_l4bo~=3fY&9rqwX6*gg2*-@m_iL5h67_gP47GGh&VSUR(kGs2z8v z_P~;lf0hPze!f>=wRI@RAtHT7-()A$yY)uH*V03#4j3sK49x(q-l-Y0|&zM&WDAtNsD>vXYPD6zx_8w`?tpLFF-*0+otzSJpT9NKk3!1 zO~9kPp!=QbdFpE_R_>sR?#a&%_a0OOBy_yTI%0Ir-R83~8Z6T|aWMw-ChEng8`Jw^f&PToh_&&l&8(P5f^<3j zQS;}#S+DbyxDihnsx_V(H9!QvI8)LA=Xs;zmJ&%!27n(@t$k9Vt+to33{HUlZ;tP0 z9U(SbL;FvN-!xgIb`60)OU#vPL=)_S)CPgE>cP{5c8}6)F;zQHx^$~zGlyiUkK;y6 zHHuqxT&k@|Z|_~HkWtN31I_72p%ZH;L(k^E&RsSB(8hk4A-dZ)g8C4U)+$jkqhe>< zZAsKu(dF{kmj)$cBd1_f3~-@C`+6i{O2!hl2|E3|#HD_Q{TptMrstC#+1KZhG`wZ7ohB zq1{mE&6iV;tVLh&-WCsHW9Ydq$ahGBUw-)d`;ogxY+|ZueRA_gI4bI+!q)p| z;y%ifO;eFS8syu#DjONilb6ypCh3(+ep7FdArg`qIr{-R zXhMt-M33$b3)Dmu6iV&_^n~xV`o6r>DenL7VdE@Jli&Iq!ztrMzgW2(1FJ0?`=>wq zW3~M)=5*>MA2iQaR>>TWO0TxsN`~lA3z<)N0f2#s^wh@xE?Fcq$z zZjo>ig0n5Dys9Kb_-qgz=b`t9H*ShqVnb=D$^XjBrk&7<^Q^o?Pky6!qeZ(-bJ?P- zBI8y{CN&o49b>wUGz{r94BR9hx5V$&JS4oG-!IER4t$(CCCl!w6w7`DpBYc{G8$-7 zZ6bG#?93TGD^HOsWWRcU8I8dIqs~+a+(*Eap-@I7%YL*G0JtC=>WcCa;t2+CKjX zsh!hH#=Ktrdj$Kx%f=!YvwcJIz=$n-g-58m&h%;+FxHD)FPW}q&NM z))8jTB6z87jgHgn74s9hCRn$6UdIo_Q|wbIvlDvRLeGwunXQ`g26Ldhi!@}@KWAP{ z|1^3$iN`5#vASCFgOxGQU17oCV)xjtP2RXCB_?|<5}!4cb!u|Q!-CC|ap>?? zr7QF^j@U!%!&X_mUdl-^V!RbQr|9DXoN9`{^Eza?zv|>?gPXFmTkaPxANM{h5$>Oa z5(+a~dEhGXSltQm)iSIp{iXTmtd*t&YuBrvvl>{wJPwtYP6<3GIy)CuUKJ!o*_v%Q z2WC%D=;*Ha1cPmiXQ{#3l->P~HXS=|zAF7df5602N3*Ory@UnX!OUZYEiG+rmR@~Jz*aoW&*n^4QGL{j zCr=$TAzz)t0?@$_fq~QG_wuw{IkG*JhERr&*_h^oeE=x7=`+82p6PM)f1_IczgYgi zC~Yq@W#0|S+l+qn^Z`5w!Uw^MwY>f?>^Lub+El?&XlDizoP6uo)ip^WSx`_?58&lB2_rzXtXf0?(Tq?ZWJJA~)KM7wwv z+Gh05xSqMcOzH7pn7gdC-;wkI39?=MCFR2631eZYA9)jDXS94&ZqozxqdJ*+GQiJ4 z!Q9DOtgAM*GHaMN_ z5U?e7^jP;^(b@BTD^~-M{(Nj4*_W(o+RlORPRzzup9=)()bxnDf+qca9Y9uL#@w^C zWy;S_4YBzxiXCYxuZ%+H(7k~%c6|eSCA57&>L=<)6KjDd)hCQOvmaY+<$gIBHqW?n z^)F?{dZpi@as_Cs2Fo4Hce`@+URH_i##8yL$CW2Kz#tY6!*l0?A9xQD#vonM9->{^ zGoqf0AKXAE{XX5>I7(nv{;J=qd;JAE0^!{036 zKIdm}mi6-cjuYAqood%u*PO`jt1{JKgr)0~8rSUx>?%7Ehjixf@ofvuXV(hvntE`o zZ|JL|wuB|DdsPKmty6wzDxQ3yS#6QT*25(Di_u>Q_EX0){c72N0s2)~qo*`skHBb6 z{=Yi;n~omz`|mTi=x98fLLLA<7%^hlex@kiroNJEUk7Y23J`R1~#?Erb z+YD!ycl;?)w928P>^(8_eqm~b<=3V=~t*zA~ zg)W#36*u#Xp%mo?gq@`eameeKT&J|{x9@*XL-N=8+jT3(z>vJpJ*t<>s=St8RnQDw zD9qy3sCvoH5gyJ96`fddzewQie5sE~r0Fy3p452W{D8-F#;2_HUmtbYH40<@cx5V` z&F^f^i`Y*J6R};K_xXZOMUZc*Gx6g%sslk+5hKr}FC8vEVdNSlz(S}1fVe)^=Voha zOle(owX@f7B}KoK-x_(8Z8bs6&PbACG{$4@YfZ4%i)}qn{)iPBPIH5=I>fK+?vCoX zWeY4GBO{kQw1^*Ml3oUNY*)5EyC&w#s7(+osgPNYVkkVJS!_0FIK>Cm_V>RWa zV>@R#y}RqR{b1}`S3??$M*(MZonU>jV({&s> zd|wZzeJs680q|x@k7@)X;h zvH1fMl(VxC{)DV2a-Js{u1HvmaeVtPQ{o^xT5I!A6VnA9?{297sj4qFF%K+D z@BD~wW!pDokZ>9kB}*}K%tDLW`LBPyA#8B{M&UGrNc|6%HC^L(IFMj zU%emCftD6o(CGk6AJ=C*y8^3^@Azna-2>qJm~3>&$mrRTjzi6bGbYO2EYTL5=~>`K z`P^;qvulRP`|Sx&Lf<}-9Gb zjFM|mam1uAamJc+m$Io^LOcB1_b3@Xp}yat4w zA^52yA7X%WFwYa7Jtw4Snad5HJMx_9b{Vh*)mI}ligP+1HU|<6k;wWd4duA*Bu3xWpN}p4Bu7VJe8DP4U zbZ^h_4&p829hb{yS=l3d+MU<3f(&c%)ko3Y)UC~BmZd6R`w+Tg${ZI>^bJ(=G@g!$ zN%@P6N3M|%-{uptRb^mldc()s>m3s0@AoL%Eh~f(c>2Yuexneses&h+X!g++q(X;k zowaK%@RfXxWepWyZ@Q&W;Kc(ja7pzhp=Hhwx6Yb+Mdeso5Dn?KmH zYaM~-@R>Sd>emaD=iQ3jJnAtCcVT&WGzZz;fM!{nxt|{-^MyD9XG{NFn^!e`^ZAr^ zuFOe4SHEU~WnH0f56-o;m=(o3X|>8C5VL%A?N2)qS@4g-807W2{F_y|@@$2oyO)Cb z$khlnKLY?$Y}}LJkp$NJ1jlL6`xUMbjsUUs9-Zd&mk!s9xPw(Tj_PJAi8A_4UpVYC zmj!t`QS~9nAH&cn|ISSqY2hANeOfSrCHDn4;D zq_O$E>UI=92ZGLKq#UF6EX1H$4kYcr)$5wuZ+?poX)6f*Q+=ocNYtFgzgS>^{n94= z+VQ2y7n@Z0e`^$JD-iujtT=z_K-)+=>j1p`+JUk80;Qumw$-|~n@4C)+>*995L1Q4 z`@C{$ACGuVgm;>US(Y=8369CdH*R=Jl!aIN@&nB;`w~2|Ou*X`UY=Jrcq_#w+Rvan z3^7CU+(f$LYwi9=iAEQ5RrW?cfi?$O$-V{Nm$d8CjrBmYKMr@|19T<48OeDGh}l8X z*S-N>8dK~`<2f%!EyvVb(CH4uJlKa-#JVBC=TCi)K#d!#KOxq;JS)_6Ipj&&joLcx zQ+Bnoii6`^)uLOfW=2Er;xDe4oty3c0b6{6JZ@=aRTWV`Pq1H}q2rur5Az2z#Xb_N zL*!q5Cn|=U8)#H~3EdawV1Z4fKCQy{c$58M%v;3jpS?0#J4Q-#I4HCWT5PDQD{c8|pa zqrO_aD4crKzy9kTUK0%-7dK~kcLVp_-j@Kk3X|RvxK#UIr8q9eqUwHgw;>4KIeNZj zJP@p|$=#`q$_#pDI?Ywr>y-GgJtj{p)!C-iSKSG81bQLz$Yq7hn-&@7QQOh66^R@Z z{QWzNkDzm~(48lwZW9{ep*2n;d(??KFs5t4SCXW;tYp($S!c%IqN1t>xCzHuwoe#_ zz#F!tvISJ8Xj)Im|K*hbKY=iwK5$7oPGKk<#-3yZ|&%TR^>%W{HB zMo~}9Qz#IxEfBg@81+M_>Bd>DBLL=f={PO$yU)^o*v0Z<5T^gc4Mc-PFYc7UqeDq7 z5*{4(h4TfBT+HEtimB!9$Me`{MVmgI@ymlJ(-_V^m-8^;AGrZSPapae~2n*@T?c$XieRwIop9^0e5qrBZxVYgTo?rI?eE|2nEsyT17a(l8* z&o`o(?OekwnNhumE!dK4HtFTpzCy4N0EoPY`x3)0?e$;NKOri#VVx%USZJ*?c!ZUf zmmOY#fZXvwD+H}WOSe!A_IOU$+bU16%;{Igp%c8LVqi=?Hq z&J{KeSu#Qp0-;eoLGgZHNmG=Qp@UbYdaR^DiL}N+EZhI9Dexbi6wUv;Gx>ltmWJL# z|D%&a{r`KvVIWo7>#9yf!U;nUjGUgz|HIpRhc(rG-J)VcP(eXNsTPVfL21$wd@Xc> zgd#OWKzi?86hu%!nsh=-Lg>9iklqC89i&6(C7~pNvw45#d+xdSeCPMwbKd*hXa5nh zv!8@yuQk^gV~#mj&AGZM!PBw8sR%5|lKO2Qu$m~8n(01bFYX)K+)U&&=e2$Je)Jv3 zYf($ypp zyfT|;LUK`!IohDbMC-j=;WLuTd;IH7v^GYtrh?)NtSm} z`QGo8n8!rwu**Nprk*qFGn}XKZjikul=XJw0?p+I;y&q*_KmoUjc!N`S)2>xu^3|L zjdsJ74Ayj-O5c`$vqB{Y#M6QN#bIOX(f<0-+AQXcJs?%2pHQSkz9zfr5kNQ=Un}1_ zifDzFRzRyETCe_KSon~|fH<#ixrT2}STq<+C!2f>NlDFN+`aKAu7#*mF#Q*+Y)6>8 z*c}X6dx2AmZ}<13UjwpFt?a4?3jgwY$Oh3Xara};E;sfl&M@?oWVTBZ@wH++@V%## zsxN6n2{e34RPA_PESzG!%b-gHoR)qBIpHnNI$din90vIxzQFW<+OsVdl0uBs?+_~OWwESjg9`Q{mHcG(4O3x zQSIzQEw#`-@#cNpLQ!D&KzlY2O}J(Iv=~}{)5_>r$st{<%=4m5^IU&0yPsw1=oc1s zJq?sinX9yaU-fwLA*=$5r&JnBH_;Li)IS9+=q$WZ@+# zOPka+cNv@OVOPk{bw*V_*6n%^d1ZL(6=Y>bgsV=Je9_0yChM_h$jM(9tO}NebBhq8 zS{wZ#02|wJgp5Z;f&XiUwrmmBkUMjMeh+Tr)IR_;v4PJOq0t# zgxI;)q!CZc`A=M%=3F>W-nuiOJ#~*KNQr*E3TFH-1^6=;DQ1dx|WUJLSozu ztxN3CW`RN|d{UkQYR;-P!`7-b_d7_%4mWv6%?{)`50y zhKdNQ8tZC3w~oT$we;cD+x)7oZJ#vqqX0ReR6JAxl7?CN#cS%V@klQ_kFp-&jN$FPzb}UTn}V#Z|In=!Z?{fJ9<9FH`W1SWSoia%_w{REUL6$m^3Q>e zEv~0B&54bAToUBHY}sP@@`Jy5%nZ;7W;6AAz8F4TjQ@+>HRU^5lLs0XGD6Hs z*~c1Z%qs-Vc5y;Z9@cklnK%vq=G?jhcg}9Fbyc+2R>+?F5xraex~;@AoH-6W@)V*1T}LbAlW%#4$Tke> zPY|hRQ-^zeJ2NcO_?~1&`*&UYLbUfmW?VrHYT+_>uB+vfN0$p-4fg5V3XU%b+1&+F zBbQJ9PkjYv5nq<_;=9j07>lbrWNKGF?StwM28{jMy?Rx49_>P$6i{E`S4DBRAXbY; zW;W+uu*eylYkcjMyC62&aUN~0JheePc4+C=`>R(mUYsiSDCdvRBvx&Sl{HlnZ9Rs8O{PlXrFT2dvvk@Re|R13M&J67 zHjpz}*$+8g?4vF?wVLp27T60u66Q;i@)!WecILT8U*Frk=DtBP|L&!7j24+bn+Tw3 ziuN-QuiM#8|9CM4@RGggF}Z&E0qotP#}u!gpuwDgj7DdVjBA_H>Ljek^EbP8ceidJ zA#bEbWHyjvR2a;U${Ggdt@GIL8?3`s;#shJI zpSi`o*dbENZ`C{th=#O zcB=JVa}U*<24h`0E`;vTB6qA7Z^))mrde1rhB#L9-}x?G@?vPqb2@wT0^<9huhtVM zO|ggSeUN90rZSIKSM#K^d)<*KU9K$y!4Q@g*{lqUBd{Efs-cRj#*39B$pv~nMQnXE8^;6 z<-;mkS?UBUyqw2lzA${2#5un1Vw%OL-)1vDSB59o8kyeuyHf4{e17HulM_=|I3;LQ zacOs`F7Q|bn3*qtnJM~)zbKIOsRlb&BoOjuLTID=UW}i_5@Iu*ed^{MMW*2OxKa6! zsNoT7sHFac z5AJc8o6|g6eQmoZ?-6I}LA=qhWEEU1W@I^bL&((sNnnbRd$Dl#mb@)%XJGGy7yYO4 zrG}XLg^sYdeatB+gO9&&-HFm&m0bXpdzf6;WGh!I(P6Xlka1*TzP+Kh2z(rv0-DW3 zo6zqF+*scHuZqkIEzUe;Fl(g8jEn;$_RfuMXqJQ(lNy zoq-JDn1&$7b78m&-`sa>4G|`~y_B!-eE*#f2AaAMzy+&Nv$d|57k|i_Mhg?hVJ<6|^$Nx8Wsl!HhG@Mc2eL9$F>xh!Oc{dD3{2XXYT=^g30~_T`SqiB^hjmRZt5B5QpK$z7sGIAiYEl15GC^ecbc5>I=l z6N3!2G~G^!uY4g7ApLUzve6#ApFc9C-fH>q;fktq@Rg8nylR)e9F+wJCvOhF6fX6D zcPn1%ne6#5<&Q$Tp_ksk{^7?vbF*Us) ztF5w;lO(75EqsT8&i}-L{{CP7>$5`Vk&V%2jd`=KHd=9#vx2C=`O@qtqlp(xBVoJw9}lrO{hMOZ@1JLN~M{1g(7LpW4I;pA6NzDy^C%8Bk5`x%JE!i&@s zua!01sjgbIXSv>WTd&fETW-y1^w?Ja+1Va!5?rvV!FnhuNlP3jbsXvX>>knAv)`84 zf7f$kHS|UPGu!x4;{(2TSvHH(DKX^cA+L2dji>u1esk3qyy#FpK-a2WcbTGQdKfoI zFeR&>SuWvwI{VNxy_wa?Ij^uctg8S2*_5u>=4Ym(t^+W7ks`}P%y>P@)pQv#vn6$;tDp1IgnUcZ(!{!SncS8i zBvneT1$ySK;exf}EQ!5qsHj>$KhR{CkZaSA^R~SZ-*@jKudN$baOxfulzGpA?)h7k z9`$IqX$BoQpJrF2$S#ifm@gTxHQP_3HX5j*`D>*|pz%-FY27mqek?z~6?EYL_i>I@ zmhv}+^Z)t7VO_t!3lxqMSBLYmE-sTDfBN;}UR$&oBltTM!ezF-71{4#Pie_5gTOwK z5T29q9hZGZh5kO6pDbO9$O~C9XA(C$0Gx;n*3TnfUpXD48TsK7#SW1clG{y-T{Gq< z7<202;LCrRNjpsk_Ct~;N3r1Gq%Vb0LAHR-t-b#*)M0{m3yCxSDkSxMgb%Q*LEVSNK zBGlkVztT_E&({}}bd|%rw&fayB|$Ch%gv@Ze_Ke`a-WoLZcZc30F3cRFVW-elP$B& z2P(05A0IltTDAj|%RIixfZxG(+6CthJM*a^e2d45cpi$i$>T3RWPYlrwj()YUgfDh zQ z8Ii@IX%%1Xj#`+`iZ5)O0mWAB7|5xDsSsCYVkVg$pIOT&q)iwFU?2;_~IED1K zb#-=ar30bt5o1`icCzEmw0vRj#I#`;Dpt{_w^8EZ?)$S~u`_${wIJawr<|l#_gGy% z-EuR9@9Dhcee1IDR&^SKgTPC4C6|(>7AWdZ;p-3co~6@=X4QfVMy)y4ot_Vd)?VTM zuS3MYQDXc-7dhaTRV0nNljpwpE|)~1B667~FyqM;(Iz~3VGBFqSn_XfbRg~9mTVA* zT-E+U5@VwoNR4;Sds|v1!BWeaX<-iQw^9S2{iI%}5O!>fW)9k=RNsGjreSmSGm#@Lo&j{uH)z#1=sa$`|4^b|FcO zu$U{TJB}YzZbYIR-5whb7Fwteb?3}XSE8t(j0o=n1FNa>?R;T3+AV40iq=Ey=-PW) zy*cwO4jPps-u__1i@#Jv=*>xovANV!QoCVG-_NI8tn3plD#O0-JH-VeaH-)N zvzoSqDAFf&`#l1`O6-S=sqpC$?)BQr6$LxMzj5F^{b%N)lQNj=5l z!P#ZVW5z}6pJly)mqw(QEA-C)h)5T9MdU0%0YgM|~wcwWJT=%QxL>UYn z?)e-7%{bir4=(t~()PXBzais%eucvG1KZ(5!@-69b275Dzb|lqU`wT3@yA~G2A@3z z&f5QPUi9g@678@`ASM=A~E#ibXz%$$NNPXVWH91KFY8#6qiIq#ud?(Gq;jvv&Y? zY1_W7kdPhD-0Rs)he`sz08hMMci^u89=tFy4{Sc!bQf*QPLMJsdQwSYO@~RTnkZ}5 zs^WrgV+RWx-An8VLder}-%$C;!JJ0o-k5B2 ziovvNvWZZccPdN-_<)L4nof;JPSog7DJry?vv#9SAK#9DFIf9z>1D&W?D>PPkmDM! zvV?^0Katz+gf_HCnq>kg>HG?0wT|vh4gOjN!DWlqBAfxm0q+bu{B%gS(2R%twp9Bu zsZm=%Yj*0|XFQt&OG3Dv+I2ygcys-*D?9d0M|BM5xQ-_jolh+s)dR+3vROS5i@rm_ z>YfH0Z7DTwGt^Ls%>G=%(FQLxK?r{oEwtzcov?~cPF1?@Lz)lTyz-TE3<+6dB(P&HLP-nL=_tYcS*(lfAy>BByPRQcHw_&bGjLt}HU4#&_ zzO*r#OVvWem&J7{GxZcVPf$Y8R4CE?V8@e(tnl6&r}Oi2mcsi*-qXE}3}_jn`CqZp z?379_JSqv?iSl`EkruJ2XhrCg zq=8LaE!lz0Y&%3wM~H;J)Cf*pPay_#T)C7=%$?Xe8UE}rW?cWHs)QQKDF4x-zv#8p z!VE%`d~p4A_4CoedAlm`?zOJOKhom@-hZ0*eiHkJFKuPeu3R|&0IFmw)!V1=k!AlK zRQ}kV{p=6Q{h#>Msq}xnUh49b`1ggWwE2`9xBvBeTHe)Zxw8u?eC%b+EB^cIHU`@u z;2l$GS3a#huXzQ$V_7Qg8me*7P8zN_ed?aLD9lDb1fGq83cx$bd5}CV6(5x-)I!i{ zo6kGMwwdrj4@>nN>EJ6nE$oz01?awTmp>zuX8l$$Tx=fPtGlr1w4X2b9 zJE)+ZFV^o+ZAF@3+)|1XVTA-iu9>7<4b7P*fj#y+OuV>_xE zqp=+u`pTt@nWAOg3kzF#q>{9o0XsjWxZ@Pcp>ho+-$<{7{10A+K(Y`0_=M(Q0C-#OM4H6Y!`mpA+z}O zMQ?LM4U;!~(}$3yozd)ahFSAs2eya7qgV&mz7MOB%A(|jjbZ}`Th*}+<_4aRSxRMH zZ-dN>`AgG#C64c8pUr07{oqomVhA)m zb(J5-gceAk9gLLOocu+xq=e&Tvd=HX`0+{FP{w0v3LEAGd(IqT zYm-r}!Sr?p#+n&RHrI~rH^Fp4>Slfo<4C=N?y&g;S5RGVUSI4ez!LkPAmVpbbkwwz zsoW`sU&bpEkH_ub9oRwLRFxYbD zw3PL-MqJ1a5E4UN^MvrN7|#1RIRLJec4}}G@h18B38!`5j-^$JtM(YqpbI>vn$0EB z6c2-!4Cis1Xspk-?gMR0gU2C0zXm# zir?9A8ai3K_38>#bzJMj21-j68i4uyDweT#M^yN0K9rpeD=3sZ#jE^_3!eSHL*k!Q zT<>!i66jIWF4~71UFz^oKQ(Mxd86>j_Kf)V-COG7Tofy2h>OUi`!_ZaWZW#g-^}GWCOR!4wM*drv*>GdiQW{<7fYg%qx6!*Tlt*dH4m7*Uw#@deeNx=L}! z?FNL&RmTj?IGnbD{Pk(qNFf_ydAl$`cH_dhkF{k7Xah{p=`upkNfdE<|>jE+d2t zAUmTuq*nV18l$y-9Q%_7hH&$@6ynBJ>3~ncsuE#UYu`Gs?dfmO3R+@vvCl@mLp}pa#6giy$s!PQ{2QmPYpO_F!^o4`b5XPiB4tq{ zw6S1gB|h6{m2y2?k&}1XWqj%vwRr-+OCk_mpJ*;^?M1wA92BLTT3*^`F>`w&R%Ge! znt3q6rj6yYDMy#|ttZ-w{S}A_VMqTHI{8~gmJap9D&)E7U)5HHr?FO;MvV^Z9kQ~1 zBE1k@+3pL!#Zm5`oQAh^$-r0{uW8I&jTDM$i%)!giu#JUBA$e8pfft=Of`Sxay}Z5 z&yc)4Rt6v*m7%ufFszI;UM=?)Ljn4*>+=gNH56D6eOgboN7(7Oem&BJi@J2|*c;t^A8bI<$pBrXuX8 zUCyL|?;Fd8IfVCgz+RYk-GpMTD{|eFGAzDJn;o~A^MsHwnft#@vg1cYqSOH#HZkL} zlxZJF$_a;Tx;SkvmF>Tr8E&2_VE1gbeSSLBh9;c6URoM)8K)UAWK_lO=J|Gv^Qn5> zGk?X4H=}|AL=%pE%BgYWNgye(puLX-0OT~(Hls@Zaj5VxQH2fVsuX9b8P|E?wM5@b zjF(`n2@l>*dM;p4Wvw8xQ{#zP9te-p*y+fD3@MQY674GTduC2WRN}^q#DbW65*U3C z+XcyGSAp*(hgJ@zL6>3s;{q!^i9 zewC;-FOsT6Q(01dTD;RN0jJS}Oiix)JzX49m%wS3Q|1W`fq)yL*TOi!SfV~6iUrj_tbD{+FIHZv27oUzlr320~J6dDLnt!U6}b7 zk(8xPItQ@LR7&6?^1s;T|BH);VKQ&U8Bo3t8BqOw?lJ)byj8!0t*#NUr^h$p2jp^P znjvG|4#vWKDSaa0lrTHd)JTaCrxt%KWifbYTfeZgjrWTP;E@o~l~Nx~4!)W(HWkXj zwO=3$jsi8{E=y726l5|p6{-RE7fsl2KH~S?71Fx!9 zPvqYXD-8p^u93fl(a8&Vq!Q=(-cUKN|FEoaP*szGSLH|bLbTVf8F-|%%iEak%pqjG zSI2{|!k}`x?<_k5s>U!u`E%?wI2(AI)=(BUIsns{(LHz;OtL|xo`bBqp^9Mu=+8f1 z`2=s#$H>M^R#rqI8#N$}(He1%H5F{~*{dtt1RD}=OaW$5+V{{ESq#-|++l<&t2@RDsR>StiZx50T)t_=_w#BH75k&voj8A5Ol$JhS>=I(^pw&E_M!CB8ou=LcZS3&!v{LU>jM_v(!~G`~no`N01B7#@|leqN|2 zsjD#~4^In;+x0b6VpZ@?xwkX|c13b1ZxAKfZ8zQmZ&Vz80zIJgvmV(FgYznP@S^vX z;`*tfgrxQlw^G|yDJ)56frZ%K+f))B-JA)CUfhVt3GBo{d9B|bjP2k|0aGZ}*h-SF znm5U!hK4H!VnqB(siD;lXUIrnND)nzUz!0jF!ls8XM|;sCSPMyr1e>J;*DlYcA?JeV@_wDpLGT_H4*%aCVee)Q0r+w1P_cYzpn|9tnQ zYyYgX2gm=2j}(2c*kKE2)by@`xh^+{9-57hn7aZhNrf!~$Z10=sL_583E9U8F^q&$ zT`o3~bUgo&Gyj^re%3mfPuWLM8vpyM#7Ib8+lK!9 zan2eoP0g=I3N#R$$iVJma2m&mX z>KIQ zGk98uJ_Z5?1A5bMJWSm*8z2y{_cOrnA<*j@suYC`i^v2N0gEMXrLbMAGpQ-8+CG~@ z*=94gsl~3Qgd2{Fys){8mJXMipT4kHg)sjZt@Zi1<0EWwXRFG{PF!yyUkDkjBA0}X z)rjJKk`+F|VWF6#WD&yyzO7Hxe{28CX}U~|s~yQGmmf`}3lgPEy*xSuiRsljdJ~>= zo&og$0{SNe&B*z@6&T*htE|u%&AJn`&Dnh&$v)p5ELluZr|vsja9aggl!IT4{!YI< zQ@Q_8;&}u8rRwosp9L>?8i!y@K*}?08@BbHT<$s@-_4ju-lf;ShpS`cm#<|DAzfwW zY)RIdmA*$o8=mBygEl`w{$dVvTo(RsXavyddizG!r<|N`qou#Dqauvnj72gR00}?^g z>}~<5XL^1nKwG#FkfmhSOYIaIfzTiY1fLjdya7hpg^2tbnezC#qSA>fkn`g&5kx0i>wci zKIe#YqUX1SM@aw5)K&LHO%T)8(G7qwikiAA)bpo2`>*!P`S%E2gesRDzykzXW=LEy z=~FB1D%9{rn4>hggJ>5+Z!(!iw5rH49q~RFUm3h^6hlH&ohw4*eX}CiAV7{hiS!DuYPRviL2@}ITIqo1-)0oQe#k# z-W~2HUFdPF!z{grr4(H8`b@1kyi!yE;Mq7=LL-&NqCXGN*S#qS;}6;dAKOZ>WCd6U!RXr zC2b-hK@*%7is1xFg9GgxjWT+a2nB8zx+M-?YM;A9g@A3>yXPH~_CXc85X1$QAD(S( zu9)2W`T)3FZ`C5M0^t*PYB}lWqCvQCW>L?joIHwd9Grsbj>nz4VxHm&18$bh^O~q) zSkcK)lACDj%EAv(IZM;)bf>0SP6;zbzA8ApkpBl!2fkzF+M%j~s$X5B} zngZ6Ft_Us$g-*fUu40 zSqc1_?2^9yi8Xw+-tKLFDoq(?v?T_nij;utq*;+^@`%-EVazM=pm&__SNJb+j+;2< zr#<8|loF95QkJ9fZQP~xb|in9SyQ-cy-Q!zt{vPH_qx$z1h4g-7l?o9nzQsHY$m}_ z$r)Nmf_jZsmL#cr%ByvfiwaUiF$0^>ztp5M)H9h z^-Pm}25aqbn8IPSTFQ#Ts%9%~SlY-e(cH~A-T&=CZwT*{ZVRuaU8T{4EBaG?rd4`3 zR6bw`_1Fb61I-)`U4`_Irv$B`p&pwNuz-T3C*l+M1)E5BBtzzS5T`QdWQJB;WCxiJ z1wcOM>u2N>b<9eubbc_2PunC)E6!3o#&KRB!(>VYr50)#D`*_dGZVZ5jz7&I)msb1 z1Tht~; zkXi}jD%omyDb=aPoYi7aeTH!==vSm=G?zlCt9fxWm!>K8oUKtHrc!^@^p;f* zhBL}H#!kY1+IFbR89d{hSc?J}xUkDS>qu`N+L~<|Tr*t$>?NPvYFbR1voba#r!hJV zJV%=Dbl9B7ecIfES{aM%L?*AjUiT2VHki;BUsv-n+W$MXra|!AMB8$OILGIHd0*aa zQ~l?zBEE2j%>U>G@Sk7tKb`$@Q4B{7-9aC(GLlY|PN=M9{2ST1eo?k>E;{oQ;_TIy z+BiNP4`|=S?iA;xYCDNv=W%fEhRjb!gyFQ|ok7Lib zhw>#Ne-(=W-t0;cki;PG!9R^J?)h@XAb$ib?itd-Oj!Z4#dQl=;39HnMlaj3#Gg!jttRwe)@){hQbd3B!KzDO0WFF5f;)hl81h>OR+A(?2|54k!V zEGU^R9_HPWG+0-}p)3%Yt>!uD{B!M6 z%iqJaZX>F3{n>94MDX=GUY`NgWFTnSHCQ9{7^M;0uN9v3;l9~f8Wqww7$0RD{bn(^ z#7AB`3Ke#T(*jqnltNu$P}{z=Oic9`cW=JBEGYf@T3wrm*AzcvA{D=6_V%~ElGBC8 z`N8IB&D|NP-nd&d7MwE3wBa)^$hObXvi=lmkM?Br@hodzRf^D%>Icci-HLqJl6l-94p(9E$JnTLIFAl0muFtST zqsilU_ZkyXb>orgmc}KGgEZ>!?9~h%xTWT^t)Cz&XrzcH7>0w*1K?WMA{)e3Z}}?D zX)(mRqrz)25X$M09iJ<5kR1cx9D1sil5u!X1QG578GEb@_NZCU0rfR!c|_j97kQnO z*0TUJbxBMohN$I*0A0Nns2DS-JTHQyIjZ|Iq7D@TO0)!bYKgTsb;&Dk;Moxla| zi(GKry7-}E>A%OD|Aq6|U-i5eoR_RT<=0dHdIItvIVy@H!MPD@sUrcs&tQb6w(Sl+ zsRMT~PkS`dOj3D@@WUxnpf^|=s-A2$txuh(EDwCsw! zuf*ru?9eS+z2X7SNlveQ$#DGRur4-#9Ue&T#Z!wU0Bi+mMn(3_NvP*3ghkK5mxp7+4NRt1M8`OLIGtpQI)f}nL3j|2$#1yKvsbdQk^#B!?d-Ako(mI73BJ-s-!{h zGP;5zY8V4T{YESpil^I)4h{ptAcp`<6^GQrL zwe0pEE^J+@{y>v@Vdb3UKYkE#F`Cl&8)W3QNbOV=u&}u%-ljL$EjSVX5M`%dFXT-o zU5bpofy_xBP1W9z=bkU_BHxKVXR4IheAz&_zyP85G2pl;YgcYFY+zb7ZkHNbuQu(c zD^!qpS!BH&hPXRneXt`y>9esa5%yvY;*dVoD%t0I7$4OLKjXd=YbcSl4z0FZDFUY* z{u840x59Mh%w6uC@`rgNEhVYbVf(((8BYhF{CR_Ao3s7E> zvc?(gU9!{?8Pydg!LjiNe5aau=T0yG9TS z8Rt@#u+T%m^R!|U{C<_})Q6%Qqw{B;z#fKAaz41rNOXOIfWmoq|Iq&vE$cP65d-Qq zyO_hnlm7p%R#2X_5EE7*zFk_gSbcI&arb3M*G9GxrA$s_n(Bk!0`3nql|{CDD=v zxn5}do|PB%yr+zuYwQESj)6(D7NadWA6s#G>sISK2L0P9+N$%vL*A zT7al<)sw_F{C1s{iWk?o>4mt+UD87F<4^yQH;2)bt4ri?FuDkqyk_z31tk5ag!|fO zM_9r<6T#Wf<>ky#j)h=-v)xpYO9Er}^jouUgvz>F3cr&8UHQRRb2y&2((8ND(TnWB+b=SIA)gTM?+k7!%hVeYzic|r(0b{Xt66NQN%+QaRRg4@v|9^EuS12uH<5J>sn&H zW0a5!f!X`U|FBWhf8K=Z-=0m4`T!){z|OJo6XL$;kS`$Ek!=NX2Wvj{8v419+Td;{ z3k@?i&B^&Ooe+a7mYc7I^Kr9eG7ra8rq0{snsU6P@yMUBR&!pUy6347F0Hjpt6X?D zQ6^uD)aJrjA1x6v5(>{GGZHnGBzcu}lEzdL^43JOmAm3(@)>1BM zXGc?FVvsIE!Tve1@SxsIO!HSE8;UUsTAE3C~@J&oOg9j>qf&SJrZ zT>r@M8!%1Cy={v3;wh{^!v=arCWh$A=cer1fjrku)INSb4S=;;Wco($-NGK-w0c_U zQ^u>zAiipSo=>^OKId5?qx9z06zSHAadXi(iu9;XP?t9%hyIqFH*8KJ$}lB`lBP8L zJ_WB}VtRRV?ES5RmgFT5`uRvo(J9zOEHjeTBwSkY<#0G7E5buf?d)#pa2v)w^3ny( zd|>elF;^x8aL#=Z-Xk?e%G^#vzuvC)bP-Kx-2?PGJJYptwi+2n#wIk!CLa zwHH;zN5NawOQkNeH|euX?!B;{xh`%9g!R3&z<)bV|9u&~f8Q07A>e?tXf zOC=B8mZvR!*S6Bg{}m%WJ;#Ogp9UTn5FCkf4U$|gZwNIB1QxkAQYT2=p{#6*%O(tbX_Ov4 zz|#77ZM43cq)jMRmFAP4TVSgpt_MH-Rj@H6>HF|2b^#1)zu2v2pz2eneokz?yr+3o zG^AMZn`aJBVo=^9%tso0IuiK^&xhrsa*z4)<8th?MQgPODISqWL!n(xE!vq;A5f^U zwvE3z@Kdj@1%cKrVp^aa`Ny{O*%6K9tp!reXU9LpCfI_%$1wRduQIafr=Iro%dRbl zj8dycUs4scF#^4%lUMAUH0{{}o&7wv$9X4)&yzIPonawCM zANq^EKVP`BPni|x=l69!ZQZnT{c$Vf!7QS`bMYB}3+!g!s5L8d;pcT4*{QBKYx32% z3fdN-vXc283%=*0h!$@*X^msaD7b*7r-8JlK1OJ$mUjO?Y1sa63K0J9xWp&g6s`+uYnHi;dLaLjq@8$HHInCOF({ zdiPH0E`t7M{~%S7n?`DJ89y8@`>xBJ5PkmU{62Y@>W)LkM)Q~AUK0opa~_GRifLo> z%?7@2#L%9A0B6cP9{Sch^4Q^8;e;eB;d91jC#cgVhO`_k_=F3Q+Z&#=?2^=faeOL< zf3T!{B6Fi2>+n0v^&sW;szJ`vcHd;byNtcm;ia}=4)QhO1wV3R7V-O;Aul@2661C~ zBrSB?%qv`q<*src>}M9EZ4I&8Uxat1_te{-P!KMMcUJ7k^NAF z51ZJKum#<(TU+206F%5)o@>`mAk_BUAe6FSZCgf)waqw3Wv!%|+vkJwt`)2go&U7n za+|CN1FI+1zc&39>p5@kVdctsymTpX=FiQIUvG-nyx||F2vRo*IGKp#B_w|G0)hBl zp*bqxdZnsFUv& zNQoH~NbnjKrI@ay#H5}F- zwctRUa<srTN1KR%k z_($Y%o0xOqKVbET1H^L8y}XSRhvjwpX+K=bk8^*lD)ju`jE~fTZxh7}JfBWJBR)Y` z+6Wh=J)~$m*AgS`@H6?FB$nwdx;3lfxUf0)dGPg=Vxz0CDQvnAdLFqNpmn*p)1z1E zbnvqK&8xzZaA`t>C947WI2*k83ML9`2Wk!{?c5cbOp^nUr=-qlA3xLVUyu1(>J12g zO!5EXd$x+H*C8Imj}At}bT_!G1nQkn?yOhmU+dl5J5iUFcpN?>hYB8YwSJkhHLpi= zd2IgovGAqg(cYJ%QqK1_WP{z4suOk;xS#mv-T2y<`OC8MvPlwQ|NOz>u<81VqPM`d z$GW*|jh`uq0o7SI|BpNmm+8+eFhNsyPv#eA6f{m=knuU>hOvZaWpZgtvmFdwFS3rs zvT(GZIw0r?cwh|iM=LzHt#+lp;&OOwCmV_ii(H*{zS=4soEww;PWJmwWeiO zYP+%y;i@K}mb?@Y9?cH-xDA3%)cPe4x+O2zd?ScR6CsA##JM*wm71o%WSBw*6jRz) z2=!Ffk^(a@i6^or@zwK1%g^Tj>a5CDFL>yx@NLzzXRe*97=7+R(H*1O)ABxw1HUUZ zdpXHw+0|MiqdqyZhudr*+b{6W^`$_^)AZ!JJ?P`=-wpLXj^X*7LzUVnEK(j)-Wy_53&B@ZNNMdi-+V!ElyxYZ24 z3Up;E_=%;8Yg@WcjV@^J#6-4paHV)nGA6Mzyhonu+0chMGx-`XWqPkAZNj?&pB0+U z#`4g9Q7ezf+2m!H{Y>Td`k-1bY*eNXFvlrt+?Cp*g6>Oro&V6 z1TwtNWY4yGeW!4HmT_I08)%wLckNa!Bz9n&jh-gGj>EE9N^?fgMfASw*9>4kSWS1H z2s!OTx*Gp{q-7gkby5Gp*T$GpC?Mbv)L1i6cOad~PcZCz46 ze4*SIl->3Izp?k8VNI@EyXX{9Ca7QoR7yleq=V91P^Y4Tp!6P!NC)XP1Vlu-iWKPx z1nIq(ASEch_a=y;2M7>CNV4DHbbV*-y}tD=(S6RJbG<(ZkoJyeJY(GB9_0zYwWu6t z0pg%5#olY$NU<=)N?wD>rE@9xWG@Sycg>TY@q!t)Sc{#Y7@uj^+Mp0xH3wOYGlAao zXyBC(y%pDJG-xP?Q?clwTlA|?g{m!#fFgKfZ99``%p@ z`3Uq=pLee6wTGEB^hX6MjjN467)=)5B&;^d(~uGIW9Y2=H1w6359&sw5ZnBvE=sf? z(7^{eU8_X&)^!=201~bYwKrktR)I%END^>^aB7 z`3m8WD0kPI?Ggk(sR~?u+#ON()EYw^umxOEMqBNYu-Fq9-GVttmFTtH)CBQjqN3k< z`zD$R`?6&jd(oA`=+%JSi2Py2l>zw#xzr)ewnT_e81AzHo*!sR;|^5!&5f$Hhm31) zpj~fO4G02YlFglj*L|XxCbL?24iYALK3%Hb*2sC7IV_8+(5betM&}nG=xNCKRhx6nUDnm9BT_Tn7OVL~ z;kxfJDa)bGyX~OISeZ{HfK;7Qhl|F(JGZSyt_*m# z5Fb<&(vp_g<}O`qv#HNHB9kLrXhzgpE(*g8p-LVZ-Dee>F*hK)&_q-XXV@VOUzl-M zU9-HbvZ03)cm8PTEL>I1MBM$rRjU3Nu#=Yp@oyPwU2&z zZQ6aUoW0I`%_rcr9H>WrvwrHx=E>h$VZ4vba{@em&>5S}PcJa5T8(9iohi5IlB;4l zP0Lchw*A)b)#>=E(@Zm`#rUhA553!YWt(93Y`$#q5>^#{cQxZir5$sx^WumIy^t0Q zVWkS38uP7u{AH=dS#xx?lH(n__-0$PU86%dw)ZmO_qS)vo~?9>@x@@#L#!rYmyTPH z)j}vrc{+OhlIRmooHpESa*ts~@JJd?6SlN-1zY!=MBmCxOgVW1WBN1n7PeBQY)2F{ zH6YOH&JcVyw;YEnN0?g*N*4LK7SmzYq+<}$iWpO6hSV4ZhHxDkFiYSi^uq` zU~4lu6xA8vv3>KucBs^~LqM^V3aqMbknTau?h4S9n3Xqm>edHuc{?W24|T*M1Z{;< z_Zo;W22VSUx4)@$;^m(@f%MQ*@iQfF&-Sz=?p36+k=$ABg&$LdDg_EvJL-GC#fjeX zNuvr%#6{MYMLNnv5Jn_9eAuexqmEIoaKwKtShKQ`uK^fER9R>kK3i?0N+N8~i#4s~ z#P&&D&Q5d{EOi1>Ot0t@p%o%e`{`NRm*$Ap$?*p={GE0l=TavI?;R?cz3bXgnOSMT zgSmGI-ohspCs^GQ$*A1s?!*CaWy@$fy5P_}aLal`cWK8KvnRXF78lkNJU~$Bi?ydK z%AlE`d1~)7*5@z+3*)Lq)MC@m^_Xu15%g=I#Q=Bt7m0XIUo9hUQm?)eukRgX8~p-- z8k^3FAgH4EeAQxh^@k{!KRPShy&AOVf=D>b+*$UhQ%LMKQJ$5iG!n0#s#qRW8dRcC z8ZY2{Ske$p0P3H)eL0QRjRM0>pAMJWj|9gVki0pw4$owIM^SOlPOj~T#GFC%gi>0) zvZom0Lbn?UKQ;B{Y+p;_P~dtsssi;`jWj^1JGS9ofMb;JgSo^J{;GbnHt>dPJ2X!G z{?Y9OL@dqQZ#+2AHqpbHgHC2i=~X)ee5Rn{0Rq-(cAP}e>4SmR^Sd%+jzD#Up2F}? z?G4tx;HC*TzPYDR-wmx@~*N{j+kQ(W+_a_YPd@AwQSk@Bi=q z=?+~%EzqEce`c5WG;ysML;?}?_k2LaUCNq6(X9p=f@H(XZqP`*bPKptFyt@=U&D&|YJ^D700x2p{=XT%_t2 zPV-cnN?iad;-$zaw3#ula7>qZejPGm64J3#BoQ!RWOv-UcWdHPa>3)?bko!=euZ57gJOb8hXMXLnK&~+Pe`aDQPkPC7?FB8Q30HwUH5?5yu)HSOALLmS&o6~M!9KX%Qi}<$HK;1pA5pf6G>@GiZYPRE`KCfQBa+xi z79a9X+u5-U>i}7|r5yOgt^3T2WO?5%c1v;9tLnA(a`|#SM1Q-lxqQvMFB04Cv7 z@$%LF^j>e}@Re(_UDV_Y9q_&Lo2=R9OV6->tN3r*0)q-rttAF{o##r>+Dcn=vT*Zf)`7Mjk z>*uu(V5*`AKNnWk@{U)Bb%Ok4a2jBHjrb`sIx=TmCr|U2mFa+*(`~RNYp+#l$8Ool zcXo!m!#2oyfH7%BEo-A5$k%dP6B5B21?Im}U;hV10+otC7mlWeqyjx&4mQ|DG=k$d z3#h(=yQg+W<9AMASfs<{wpj!odsgvrbtShX!cl*{?rid}QA#vQEY$=hVEQf;;|bBg z5Ctjaz>_`7>+vn2Pq2jO<8cwvh%&s%qvn%+lJ^g1MKQn2O>AufP_Y{c$GZLL62B%8 zXwiGZY%o=bV}|u!!KsajEDKO=4iav6#KyD>U6~h`=XrgexD@kfr4usq0UGTnqY&wT zUQg!P@V5~Q<9A@uYKL4l6gPanrve*la<@7lfkF|O%z)wXEL>==xy9VWEbr;$@Rnc9 z3~%`jXmVY}svBN9U@wa)W=4Fh@YzdlH8D8lKpV3m;h& zi(+Kk3hw-yPFwpebea#{UgA={PQG9|7>Zbq2!C;#o}fo{S2D4nps=*4bf*zEr8S?*`~p6l51U4Eag@tI=a)rOBx9==Z>Qyix6AC8 zlSt7n@_ZS=h#1zl@r+GG4Xs-=N#xZnsR0T57&fBBSXf`uTAMfbkGlQI4y# z?@u+UkoZ&LGp4V3QXJo|3{3ZWUn`MiVCFNy5;8Se>{5CI0rSo@v$kr4uGpu3d*70{ zlhIO-zGEA?_RW)UV|Z(P$m7pn;)nn6^(CwAxmj>c`LS#0a$Q^|o@zLnZ^tx7#xYTR zD5-O)!P}ab@PziZ(uupA53JG*sa_Nw&FiFk@jSoHmWIaNJM!+^wP^9w)K&3j*$b~* z(N>x)mP0ys6HmS!>acIxsy=OT1)2DMO>o&Fg#9m)J88fHy24f-R-7s_mH(tVoSSKx zSLwP}j|!|c{;>d#%cKO`)DsIC!#KX-htDY}+d<;dL$S5&D@9>`XescBm9DJvIni9f zaeWu*x3zXG_8f6`(r*;u$5E(8yhMRpN`7sjlH;@dS?06C;M-4*iqu}ko_Xl-w%R$K zQGTTv^>yqu>sC89#e%)uO4mj}7Bp}eqzP|}WMA>zBjq23)r^5+R)@>H3{ur$4k>h0 zp3Ri9(x4j9{4I|D`AfSoB!dmCFM@s#I*q1;fi_6|U(lCeARIo1=nB+v?|1{Vhw4B; zVgyaSaqKH1m?*vW{ps^i0XOh_gTLo~RIru(#9R;;C#V)DFes+|=@N%RBkNFpJIR%^ z79Jqo=>pCzPQm&k*b;Y=Cp=fA)x({EXkry(tj1J2aL17GD8y|`Wvh_afvDE##@ zime*qn@bh647*#Mr^=@)LtSN@^<2GyrFz0F?f6^hWl|(G%`ssSx4dK);-a0gB+bcb1d ztg+R}G_reY*Fi30a`y&#vgp8CFRX|TzWdWNfNt0!_J!86ed!tbq9NM;0u)|=b0O?; zwO)@2ujC`3d}qNo%=^7EW+{VpJC~j`ThLKR`Yhjb^WP_(|2d8Ke?0dN$185d1?AB` z`Z>oJ$XGN4x6smH;!08*%p1gti>`@s(#-R{Qo4R9!|1^R4UM13Yyql?T3?)PHTUv# z6OXqH(6sF`Cbb;uyPk*Ruqj6LoHBWw7W&}yM$K1VPWUmCa>C#vbC!XoL)#6PItA>T zipTTE2QD!KRBkS=E+6?kD0;XR7bxC^+8j5fu?`%Prh4te7$70;2bz~C@r%E4fNNgMKkO3 zFmE*lEn4~7Aikdd(EPPmhjg~pcKk>?x?n8i%tO~1g^K}6r+XZxW!>v0$7^Z;@Gx-C6Vj-p9t9T&$_gn5guJ@%Cl=nmx1;&a_L<7$Gmvc3iP#d?_Ma(#D}~=!p5%%dwM`xBONqm z3Yo`-!}FQP$_S;ZkaI4wOCq1|QvMIx`tL#Z*+jWa!8scGrDOuK888SCeXd7KUCRVc z60`SMgwI2}E}(jk!Mx$dh<8bU(qZM#3TIRG_2zpdz_HolvBXl8E5~l&wr~gLS`Cl) zivmr$xKt6P%3h;zlRh&m>RntGf3WRM0uj=lm4pUEnw?yOheVp@BUmrKK({Sq>*m{Y)R3Wtt7!w44ES;l~UivAmr4GKOCLHBTQA#IP+Qv=G zlXYbF%+0G)?!Y~*Xl)Au*jbIOI&H3Wtr`JOVu(qY0I36b9ISY#(s9@1^TC%EZL5OXWmN{vhRY-yJ#5M zg1_*yZsiL+lx)=&=`;{zMWq_YRa*@w;?fRdswBR(c2BR`i<)h&rjD`kk%_Y&tqhT@ z{90uSh4?F-GtU$8!$!)hvxHArO{Mm57_xTBtOhY#f(MP(+~2fgA0>p#Ieglxc$}Yf zJwYFdWIb*wF!l*Ei+QxOjX;>rJlwvfHyF6?-XsdU>p5;R3JDW03`YL-z6q4;G4-{5 za3N;u{4i$ka&7 zXP=a3GzCbCa8D&XPq|)bTC;uTZZzqu?3Ka0#_bOSdFmu4AhO|!FJd7w-dkR?sdCK* z70AQ8SMt7=j@fU7C6j2NhQeorS$ce(YJ?CN@yF6xfB{~rLdf!jD|1#R)qEq86RWM9 z5hLz!n+mZop;1;ySgHu3s3l0ZXOd;EI_hKU_>{Q-jYcHiEmm|K$MTXuZ?F}#)|xAN zoMe#!{xd&uH{eTn8J^^1D{LudF z!abX5{PzNidEftf5@^55!iVjDe9}i`D)kaDw(z20{IBtPKP-`!43HO$R`AcPD!Iq+ z`{o+ug2J=pC!anxUgK~lZ$}WoJ3mZ3gsb*pdmk{*?(q`%u_rT)X8%P$@!vTZ!`eMr zIe)H7K9*l<4*1&+*CTKEJkJcBl`T5@D|7ySR2j+r`AV?a=+YBTre zxGd4j`nzkNsP=^&uqR11N&Wr$$B;{3MEJ93C|}e^8US>DugpQvAE*rHU)Sn^}rke?(ah=tFzX)p0AQIAgxi50z3g`@IydhW;OQL&lS`($in4y5)gH z$y)g#=wAmL25mjQ-eE|H%`) z^JyL$En;`Ki`{>NYFg`2+#9wX1u>wQSOZS$o^IXO18cKo0SdGr2inO zzWMz6Z&(L8LrQX^gES^g^vU$cLv*o$pB(GSm*+2cr~Gr7lC_(poPXwV>Dkb=Uvuu? z&8+WDA6nIhXNrj90)64f^b>U_f$JCMjsMGWQnOD)aMnCqWd>ioA!D?N*3YMuTeha3w!X<@BeZsh6|j41!s0c zGJpA%lehm-Oz!^&jjI1)v)|&gk0=2?`}p3GQ?kEQvhcTce)wkyuYRG!z}~r>TG%Sp5E+=_%@ctoA$RO#eUJQ;0I4D09lEeyx?{ z@7pT)c=`h1jS&9UzvP8}c&w)sm)bP)`nmOg3+(!XO4N-4@D$T_?7zoZ#{ae+BWexY4=M|I)z<|8jT9QHvU~CO7mSNx~qH{1EYf0t)`y=W?C^tU@^B zFI7PN7d%r0;F(PJ?OyT!_Va$q6 zmhV^nkyCFxcut)>%=$o6uYo&l3HdD{$gudIX|$?E=XeC?7~a-Q*%r36>e}#62GDzz z+>8U$uSB8z$QL&CKO4#YKWo>|)+UUtFHthUpL6|MQST35V1MG(^UTPOw>9dck%~E5 z4q4^RbfCoWNo>Ou(sP8I4z!{_gf>(l`_SkKf;+ zqUEb%Gh^s?fm*{)`F>`W-;UM*`KuZb&Uu<};*2!CG`*4!g4}He9E-6*qM(9)Mc;B~ zA+rkP>)Iir>>4x>tpGgzduB_^;BTH`GC9qa}9iZ}LV-NApFk|)YW4Shd&$QR3g zdK!OmiV0U21ED6$ZeU*{!snc)q2I4hR6BK_u(#_3z72iB?xANtUFS(szdij+uI?Xd z0zjM(K+BtAt9oyk`JDtajWV6({puh&(ARVGsS!Zh?xpPeaNx(MsQ?4kzi(?8Z?H3U z^#8inFKK~32y%p9`E47RR<+BLhD)l(fEMa@^-yAfmRacj(NE+#wLOh{rh$lXFEF`y zn%!IM?^}JzmK-pj@&m{osr!#v#D5V40|ewgAL$o3NC^yC8oPb;w|Ja2s(qj$VUNrqRc^HpcsZ1>L0`x%64-?aLf?t5Y_kXBbWz#hiO>YF!-u4uwmaa{m@z ziIT;Jos;Qm$FyVW`VyubI%TlqSP87F=NHWGuJ`3o99zO2OpDh}1^62b!5o-aR7IhBg;GrcS=Xy!Q-}9>+L7?MkV z6hLd zhgbLp=$AV41TQRMZU6es>%%xCncSe3)K!D~QvCFpkSVqd8pS^ zj@Tgc?^K^3nz*@4{;nrk`%e9w!vAA2x>1Lz;JbSqn}>dO&-{3ct5j5QS{lBCDlya` zd3*ZOX-P|GQ0k?n40{vZ<^E8(CG)5BVEg|E;wDm-PK;wJbnX-L;_kNi*|9Wb+sb3RNia)b1}R z7=^0;^QeQ^I^awA$_iW%9Po=SNKH+p4qxp)n3^kO%jIh5>VS2^Vf?UL>wsmIYfgMWlfM52t zTr2!c*LJ1j``T0)*dXk$kC`5!gHYehLD8e7>9SWNcPXjqLJm_4KUSnF$_o?5ru1>DBWn$*^&JV)i8y_mL|l z^Ug=y?C7o2SDe~Gi+mw+C*R)3!@X(mr<-}CJ?($>v8|~)<(BdEAL?+2Gq96Ccf)^+ ziT6!?e5IoaGUT0*qlVc_?pkqwcyF;Rulf`uO-w7uHBTcZ%QVU9#yZznYHBUr)g`*~ z(wDPYL@s0t4lv#d;5~im1-(GFB40B$H2ltigivsnO2s)dQ#GSGgjZa|)iz5~MC?gd z?77ztvKY3=?zel&z@{BPceH0vGwS}wqK|$7cL3cwa{T<+#-bpVg@uEPzWOlSYyjTt zxVGZvl&2flHp+@hu#w;H7j>S$ zD~=;czRwlC@FHzdg>uyR>q{dc>-kr*4w(s84s?_Cy%z+b$o80P&K~UvV{F&Wx?Td? zH+3-TuwT)hLeDlT{uQn2QTynOMU`g9`Bz`G&9nGk&@;2Ng`KZFkzaQ7Kp*;t?<#{E z(Vt7mW;e$iZ61E^ddBT6es$?;Y{Kqjk0gM%Q2IdX1sQ4s9#4FxvhaHJ5aki%^bKJ5 zT%$B@-)Os|tE-#(O6Y(e&cnHnTQy_L++zRO~pQclUl5ONd&dOfC6Ux$)ukYpd^9Y#i6*zd-8s26KIqNV+ zStc=GH|w~{qnLhQm4NF3*7@!Wju5p+nJi|)TuSO;_@V8v%`X^|^Iy5C!0x5%qENLF z^4KgAdRp&8^qSgPKA@G>GVC0$6ms9dXd7n9JZX=QYJOI9Ofd?250|W?+j_{9Q|sHa zB5zrPUCa$`MZRgIqS!r;v{R;S$F!~h8=1wAnm(kbO`zOIbL3#!E@!y@r=s_EWsHcG zQhTBdk3l@%xFo$^ZF{~;9ey4Fb>|;Mm=GLgfy0cUH2m%hH@2wIy2Bkp=!a96PY7$KTv`&g_Sy zUVXe}A~)s(92s+|m^jUtf9=76b~bRYH0|*sJc;fu_uFlN1y?o9itRwpJEjHh-jpP-!RP+uVzyh!sv; z%IFTw=|1@_^AmR&;EPF(0io^K6ET)P*DfCjf@r;`xNT@Ty2;3>ZdOnaZMJ!)N z*yyGiS32*-C>4{;SQR#QtKRL56;@Vl)69_f=JJ0k@}=nZj~y(E;Justr{aSI;!O?x ztlZ)%wL$nz)li8P;wyS!T59uz3PhiKOFhQ&kZJ6JaG_x!?HOM#2Dqfat(6pcU~IX& zp0$B#rkcj*iX7g7o<#N7)*K*|em|var*xo=(0vp5{^_);rE1#TWBG#`nnqbkbN*^E zmiDP11P3UP!UrKedB^3~A;JIF`qU{<0# zn<}UtIP@qXngcjPsRh~Es)Dh4;H(k5m^2&a9+?j0LDZ}*pVO+=E(R|@K3EVbc{r%P zzkflXJ)1W&NTD>HLRH&8QwJDk2c8Q6mSMdEI1uP?h{XZ-9tW`Z)M6HEc!4(!WYyN< zRS05`5VF}YS2hKVT@M|w+?cR+v0qqZ)ePm&0KZzKQd+bUUb%0o6Mv7$0bkFaEvTLi zfaKSqA;GOGfX=w%_!!_G1K8ou2Ma&jPXl_10gh4BFv{Y~7ajndJ8WOw*9U%&ob@SG zSx3C?hn0G4Mz2t)=C1yMIhbD8-b)q|WGK}EzG#$4hw4oQPuUL&hFN0zIUMf%6sig; zQ=SK7{CIQZObodXrw5@L*i0-&^S8U72eNYUXk2bANnj`&mAV$VtX)F3!}<7%LX#-? z61w6kR>b2q&s_Nkmmj%^T<_{=*<3qlgi_IrvvOL~i^38m6GvGk}Czg8?@K@q3Nbk}W{ z&&nN4i>N$8<<7;#3UOGL>dcK;DLDuwz65-HK{XZp1&v~9@&SW^0;@ydgq666O4(ERTL~rR%O4W0BM=_gWu5u(^kbl%l{-|N2nHLu%B20A$BOn|jfkGA}rs2ENDgTnSR zH`jw1GB|~)A-8%=tfqFBd0d?#SCSe4h!3xxia9TNZORb)+wrVY)0E8qr2t&=Xd}0xNUhm<9>wF#Sq*BcS0fI1KWgNdLDPsEL3f-1UQ}80 z=55&rUiA`yR_Q_Kig6yuH<`1Ou5QF#0q?SF;(;XdYCaT$lRLVXD$%lv2UFT%qYT6QH-d~9h|omy1<#YeEfq$P9K6QcB0D@ z=8ZZl{#1_iZ0+aMJ;@Lta?P~34*v@vn#Ihm+D3JDArbN2DvA4%@sOcKzM%6Mh;)jY z%|S6(dCJ3MHdORwIBb)!9#)RvdO>%t*Q14Pd;?(R)<1@?Xt@Wpd3)Vqo6k}_zsc;cp|OT1Rtc9!ih}OR`3r zD~psDfia_BZpnW$Rsfoto*atUqvZbz$z1HWm-pfu^xTp>iVIpDdtjJ_=Z%iKBvPGp z7#JLJavAPE zSvlORXSCQfTX*i(5qf@v6~OO@eeRM52rCf@Z_lx$St2zudTdc@!P#zd`S_Nq%qUaL z;-g7f_ou#@vhE}CKkq@EBFF$=)Z~82!01L6fbl^XG6z;sxN8C&x;f!yag0s+ga6$o zKYE!{Qxdr4ilrp1`nI-rrRnT@j8foS6s!6>bf#~;n)2uEiCJ33tqChs`%lLj(=ev* z{Dls1?%}{|_1LVA{F46k4K?tHfm#&zC=qD70V4HD1H`>UUB z6Fc>)>O0+TLuC8A^{){TvzZy3EuUUrGTs_MHg~h%V%&V=FxCK=3dMKl>1CwZqs~<* z=9KitxHa=cb)9-sx^XsF65yYM&P4IB`9*18Idbgm!(-JYj^EltnNT%aof&8)Xk?zl zty^ZR``l+vf6bxA>ZbwZFV;vpkdUsjku+n_YfmpGvv|_ zzPa!FB9*)^KEqYbzZQG4ouChcr4{uF~oZKzSAEQ{yNorm@erN+j z!KK-;)kOc8E7AEf+b{iQ670rmSj1fNsjLlUPMl^^%P#b& z_eNhUE`#u<4L^km8@55&6h9q`AD+22rf%TfF$aDrlnQzDE*o??JiEBdeI#8B-YDo-56PGaTsw)>VYCSaQ6K}!x*3v3gn0m?0L#f}CsCyb&g zWBwJ+|F_YyuL0-Z43w}b0DbN=Fr@F|EB*xVwfZ>Cz;+^xAhkV=Pj3Bsa%vbnS=~Iw z?bv)l-D~&T$*I7x;tSmhGErSOg&||Gd^fzVm{pZkaee<`RQwq;!gRw2UlV1BMSV*>=pgWG2qVQ~`FOS+maNufFa_1c~K>l@^m?u{zrnN>wg5Or&4f z@+re?@RLQmjor|hf_yZSF4UvE6@K_I+d|tSvc}nl^H#0H=60}jza!YpeK8- zwx%BRmhI^B_<5AdiM0=nbXtaoOOZoof110IL!U7}rf3S#b{Cm#9%Y**EcZ<9rw1yO zgX`6L=$Iuh{*9`8%Hh?i7rR`fR`fFsBPh;t8TRkEt>T!ByP$M=%VQPF#p~talH!xZ zsJxWSC%&0yj#SOsxr#|CMpvHsR5f3tYFuereZ=!TAV-Fn`-k4iU}Y-WDobMTMs2A_ z>!`#!sMcvXUJZ{^HA*SkD_Oq>o2*lLl!v`n`A**zD{Cb$(`38%U2SX{^ zTHLG^VpKp1w<73*n1oze+fC4Uf!I#jOSj7}3@uj3<6{#CrXO{gr z-0Nwu_}6-#;xro8B{d_$Tql`yKeE8K3MaM|hw;u%zH9O!LyAHb@RXgm9-Ib2 zbK2ak%eOQNcP(d)cSFv(JLgD|h+X{nv_OqAc@WG}kA%^!uDNo~y5eJ%`+4DWdwbs? zt|J*S=8Q2O=6lbF?XiR){Z`S#?}6{cY%<=;(>#G4XF}i!EBZH;@vEg)XzZx8aYxbqBBj9o%&see{p9AFtTC*@SpEZwL`z2ig<;qg`o+^q;QYdwtV@ z?dQ$6E1BXhIj6e8dxOjaa&h+kt+~+vse7A8aDZWP2thFMBCO5wFcY6HsYkAZ>=4(t zNu#eLD?3C-#KXY5ug(gaziZtC5uM~&*0xf)9h3Hk17>^#pn2EIlk58>GGJ1oda#LB zA{HzN6#nfIBP)e>zP#gX^2j&HxHffmaUra}KOI~vsw7JYh{WDI^r2Wurc5C+J3FtI zOG$hor?t+an^(|z3IPi0T6LF;hQ>kjG>$AZcTlKSL%(FN=i@;+EO^fr5Mx~Jx^raZ znLkEJ)0a|(=AD~wo6V(U?ItkW1lvqWd}A)e#ZA8A`;=fx48q;EsFo7k0>HR&wlaEf z?u@tFpsR7MGerAck~D5)m@9i@K#OmwJt90TRBSZ4$PQQoaLsg)I98r18Xej20+j$Y z&Z%gaci?wcxm^UUb1;_{Cjvzs{$nbiI{bb*!rjdyqbpexOKt5eh-Qq3+e}(mou`E7 zYI|_9S`V$Fb14z8k8Lu*LsN^sL@fz~rQ8a5>qrgSUD@fdfpNEF-t4j9WIfjypq_n) zK`QP3U3nHy2ch;c2x+hAenpnL;bI$pnJtOfB+s4CcSO_dM%pa%v%5(n#E8NnHYvvH zuQuOz2Ukucg~C=i$kZsx`Fik^@FxtFv4`QE;;0t;FU^`7bn62G=yOTK8WFu zX@#m}^oVvOB={p$O5vofo(bQ~Cm-R2?Mc2eT)%u=qGeWCSr;)tqf8{*cu*C4(D#J2~>be0r*@ss(s^= z#wA4`nsh>HwGJYol)xiE^bXOt@b|J^?6+7a=E|y$cz^rc@>tzE8 zqA#QCYKDL$`mL5#=s>G|F+fs$bwpyrT2)`og*hiUu_E56SM6dTM2$}PqLe1Y!xngN z}Bm3k;9h{}M&J^TRsk936g!Sln>Y}k&dBMXE& zukMo!e!@d%VGZdbgw21tgS`DP3n+HG z_U1$kx83U^$F5pZm`t79LqI6Z`E?hfSVgWSRb$YuEldcN+$u~qvq0!aKWcS@K5_B+ zxE3VZ8|Eni9X|;PcCA@?Fjt{%Xc?|w9nM98n!3#j&HS$p?P2QoQ;WIS zS@B579(R~@-lB~4a5r#jO_`Y8`RsgATi+X1PRHnfTJH1J6rlimIBuC;8cE`Fhqdh! zWd=ZU>bp(H!uG7ZwqB+3FxytowHDEK3E0*0(a9_}wQfgWALhyawE3}iDQGFw6tFzj8ijppS*%9Y&hUA__MmdJ2B$7k~o%=cvfp)C;+Krt|$F}24EkG#6 z?HKBJy$*JRjK1caObi#-NwVjXb`f3CW2&gHIXZoC?E&~<8RA-o)QnF)NPQ$-NU(H^ z&J$@84t_L(nmgw0ieV5ktoPYS54O~;d&({eAV*6L8cQQ8tiTa2=~8zM-{sPLZLp~4?2>VZhz6hN&$A_lTqgi@ zH(;qPk?!7=K0V8~ArDkR+|c-LFnIdsH1$E82_CkUM$HU&S_Yy!K@cyTi2dR_ROA@M z){N}g>0}Mj8*_j-w<5h-S(zo3rEyMP#?><1#i)4V%J5Tyb6e%@<6C;pD-Y}&?wLjA zPd)W>o{jIT(+?A z7VSF>&8Or+dkk=(n(p(2Wwwhj*I-jgOk}Gj#_BN5G6zkIthT;VZ^v@F zOBU-TG*I+FJ>O+`_4N9rD_r6VlZi9tacg7~> z+{nDsb>&2eJ-7PZ`;l*mLoL)(*gWygX@ST9_e#KPMEa>zaQVjvwY-1fVZbfu+&m!G z8U>i>>fz23_b^D9#{ssHA$;e^=G*+X!kV z00lV@Ce7J#f6U#5F5*FMQ&O{W5WI>uL!Nh>?7zan{}!rkvXV1|rKiXl!qkgb)k}bU z+Ji~=)k>zB#-#DeYn51(YZz-8!A-(-R=}GU_8+*n9x=?qa&&E0-(%`O6vej zm7IygBOaH=kCAqS_ z*%94#k`a+G8!@V4$??=9OY7g(sexv68Md6qMV*Q1X)BG!k{M)5`kctxttH%^& zws%e!0()L#a%(dpENIi6anpP|LHx`7t(jaqZxdn>5P8HV^yO$5V+3c(&>DtVn%rrb zz>1FXC5^$Rx|%a&i78QIHa{QxP6CzY>a}y{5LHL535PxXMpH-x z&S*k;EE;^MF$%s>1tB`&6YL*pZ1E+S5lp_@F%1OM*4Wlh^K;2qa#}hcNY?{tX@(n_ z?-^paEjD}}($Wrq&->l97>iJT5O(HGCM@2o;Y;Dd73LvjJ_m%h;UZwC1vvSqZUDcbW3c2y?h2cjz>F`Z@PDaQE5@WB~JWg zsOPQU%4C`VIC|GQR~J3^j;=QSH^5;tYWc2}_9rEw0F@xy3&iZKEz=Y$RN zhHSSjd7o;0al9kEF9Lhh{k?XEO!bg1l)l#+0^Zx%*|H;Pk=}d&ceNh*dFV8Mnw(E> z1L_Qbd^&)b%n!pazG>#r)ML&_bT?Q~bLy1c8Xh)p+zJ8?J^!K*&;(wsD`&~Dl*&kS z`|I3AF0uBdv_;wK<@HFf65?)tkn^Cy^~tU64wFcGlQ#Xh!|-0@55V0ow?)v90kQF{j0bKvvj%3Qk&6mJLms}f1Ja$LKO&H< z<#Hg7K$?}29-HzXPxQP@X~+L#7Qm|i@RwpdvbakIGBOB6azCte(Nvt6=dJjs?d&~+ zgBtJb07j4!VXwINMcGyDTOU9aIHRV}VFKQB-L2m8UXCO;boG=5K(gt%&ho9-_Q76h zs_J0Xty*@M@=l{C-dDjOw2Jy`5lKjGlz|rMz$9Y!U-g}HLu$=W@|= z`;KcSg4+1RvaXr^d=d&OPcE{^-7m8E@aKI78vu`XBod6%T*cbzVR^UbC_HNSDDS(L3F|{oQhCi*#gHHJ!;ZuDFv+s=Gnao5X)wL@!i9KhD z<^UO#FM(b@Jv*Rnm_c0c-l1_fP2a8T2ct^teYilt3S1 zH!cE1gIB;9J?rW|gGgG<1%EQxVvOptX0^(zvs8R1$+xurxz*}!z1~^g^au!DA8!Fw&9tmGwT+4T zvP#XXj%_?RZzYDJ}{{1=fgrYE!lu2{KJ5)(EDayJfU_$8oPHi@`R!gWU9`hZ^-OR zMt;jRO6o!7XoA{^c|L(Iu$t%&k%*RDs1@yMxSa2~a-?v)DAl+Z#ulz1fPgE^KmLIQ zTdkPR#N3ziHg2PDvVs(Q6Jx8Wl+{!FkJ^k^#5GW;bAkyU?@7@uMJpx7%K%0Hy)u zIYO5;HgMRG;GBA?nu!g%jIbx()^x%p3Lgz&pk560{IZs+90VEP7@OhS5S<9NgS9;= zT))HIw0Z9OOegOvZ1vRCz9Zrq&Ft0yhy@d^yi{1T*+v)6YSLgO~< zi`VY;u@>*smlZ@*c~qS4VL1Fj65G_ypzBN*45wRZm#rj;P=hXh?SpBp_HqkGx`<=q zK`r5NYSr8jy&(yJ^ehU3WDIsV0j;0b(p&AAnHk=uvJc^8EVfL*B~;f~+f&1<;_E4{ zkn=8j(1prK85I#Y8PO8$v9*!7=<{hNg!yGO(xENXvdRiDK(nC4vq@tptKdCWg^$g} z$`Yb?!VT&cDr0}T%-Xk(os4Rf{t49ts;MuM%5YlNRiBXHD}ZqAje)hM^tA=7Nb5JSe(@BKtgr`dTqe6-a$Dt2_zEgym!{; zz5x_|uM%Q!z{Y-58yv)$6suPUuPfejXhRkPIa`3n5iJ|{7}|Z%!~0fa`RRgkelAsc z5KhD98m>tLWUe!knGd{yS-cFsS)7=5efCA1_rT&M&2N2B&*gGD%X3lOE~|ic)-8Jz zj2YKrWubT0MUPsmMwaHY7HA#=iCX*ONACVKVO;Z0tsT%p|*A*}6IW?5r+SaMZFC9$?29Q-!Mv zHeO5qXb%wGAppynsbGFt-DVeIBg>`>n`soX{Q45!lAEM><6V-Ti?eqgIou+^hra^5 z+E&Vr889s`+#hDj3YKa!Uc^t*1ZZyCv9!ksT_@*#O&ZcjO|#0<=YU>_K<7bio+ z00lM!qga+2 zVZ<5n@~A*%R-5aET3D6F62c>4&$DJgesXB}ZTnAm{>g#PkmE|>f5es5C5Pdy^i|I# zGf(xZjyP^y??Tq|8ouEM=K_I{HmsiHb!TN|MaXC-74X9HCiC6w?)6rYKv{Y6jW*tm zLe)PP7Cv`8#kVLv_+~nNnQv=OfDspe!;{ zYrhgUXrEV(6qXcFH5}bsSPuYCoV4@Az};V2-3XA_-BFzd@Q2RoMpTDbAUU>l(3=7p zyK*IM=W5Pt5z3InP{fWpi@RRwn_jeCrOHeA$*h1D8E1jbH47=}pZ{hm5!t!_HYopB>_$P!ey~&Uzu0^0u&BGOeOy39MZg3B0Tl%a=}?f85*$J)X^@Tq zLAr)eKmn0%5QL$-!68Jt8A5WTV+f@g82IhcCp_owo+R{bHU)had|0Dn;04LIQ;<*`d9Vwf+yCPi# zid*azU*f}_CaC49i(JBWj%<4PgL6A>*Nzo^D<#-|Eyq86$M+1ameqbc&5=KjP#Zg{ z+XXF$ZPd>5Z<)cc3@($N=lD3Qk5LM8{dKDo;;gRn4@?8j>K)N{xgD@$hn(Mfn`3Km zZQPD|x+_NykSm%WHg4PW>kqtEYJ4N@z8_vO3D>>UAREUlPeLcm$^gMAK$3*t7u_cL zo&enr%{c8^-m;BCHUmW!CWwWeH@%Vl%Q4tp3>SkVvb-`i$_n3Ci5v9?mr;B#Ewjc> zod~$~_M;-v6*uL3d;K$#F0;50S?xB5G`f`4M4=ETEJTWFDzuqL9Sv8Qc}my6Jf2`9o$B@2D=yRrR;>}mx()`-0Vm6(9F+W{>$FlvvVAXX*)8Vb*_RL z0~6Ff&(S`m8Vdxw)~sp%MiED7zRXOU>tp*yw%$?P-3ai0@Tm)QL4I^G7rTXy?gM?# zEuG3is>1Eq?B-aD9CE-W783PQ^9IbGtk(4Wy=y`icz+0O z&9fyB(<8A7Ld9aWKdAUYR_2HQp=Fb6>1Ta=%*)UgIkkf03BX9(htExa**U)N140a_ zNqNCkkCQj{DxI>cVPEQ-rfz7w3Ke=Gd~VxZJp1oKp7Wf#w6 zN#3^D5W#Y=gA0ldT;KO8>1Tb^|Eu2i<3W2Kf;KsB*o-2h_@lB-;hah&v#(@Y=HVTn z9nUNSn&gOxaVkDLi7pPS<>*lHzc%na zY&PxWt45m#CyO74yG+~L%Kx1$_Bvz(TL9+8OJ~J0FGjWzC}Gbsf&sdZPu0(*UAad1 zaxOSvFC}+&AQIECy`58}yq)b=%!4qbsbEiawuJuf*ysk36!vdY;*K$}`8LoeHsJyn%B}o65d#|`=gqMF)o$~9`r9ddV^cc$tLXdKR^-GYY^!{d++GO?uub3Md zp%lBbtio6oEe{9N1j>67Pa-C7b?8DIalged|HW8Yjf&nogU_R27hl9=bhRqo)d6$@VV~#OCd7B@V>{iB%q~0 za@F_mBn~`$#A6%?S6BYej4jHON5_J*mo?A{_f~4cc|r(A+%K>ts40j>$t3OJU#=0p zKa^hUX*&{#(IU8&W1E%hK9=SC$=kQ_(8~gzR|@_-`@4f^hJ;nXn$AzmJ@mi`7HZ1F z5e&9Y;s45!#+6>PfT;NI80h~9TcZCR1N}b`%jSP4i2sMj{2{pf|I2j>B?Q^KVlKdH zuxmCrDXN^#QLNLr1;4*WZka@PYusNfKfkUM9`Ju;M|H7)7JH8@h^@>@t~0&Tf>}yZ zGVjhun_B~@ta1UomOK|sqRPH>U7A+HI>i&gxdPv8FArbi7#JsR1X_)$kW)uY zWP;}X{!g5jU(Z%7T(MbZ`qq!^zS!W@faVL-c091VyGoYuvUG`zTo^Zl80p|e7Y|bh zymv1pk38H4U|M}Lwbn94st z6^QR?*wg!Nf8<-NOq~dFQ9*6gOUNs)ZG!e z4D_s*^W8gL3Y(U)?e;4-w+YyCRIHD#bOqy%jE;t=-cnl7@}IZNy~{tjYa-x2D|=Vr zf5X1-1PRHvQ6a!Vs+ADdbQbz+F{``b_)6o+3zre7UldJF=v$H(wX+`Ixx|K4X_t2lWO=FRr; z-g7JJqkuV@AMYc9p*He~rt%#pXdF?`bvt5tt8G8=*G!h~IU($a-ta>)RUeLq1lL}> z9&YJXq689R?S{w{9@xHry27g#>{z?e$!XfdHTx~rp4RE>WCiw3 z^;By*Lvw#7@+nS)1DZ_nYPA~n5fO2S)Od?-Z$~f(qz7@?ER4RhSa@)mUiuA~JBsS= zUfZI!e#>|#fPytkcCNCM3g>+1f_?t>%g8wnfPgX-KzIr%QLv3s0IVqGVF*~7}iB*gx)XDO!CcMfM1 ztL!e}v0U}{N3gs`=r!hjc%6?3Ai*p2KE17@@}FKMC5vVX;`9=D%pfMOuBam5~T?nPh4+MsBNiG zs15yA3kjEOlF{ZLH`QL8$StlFm_B*cQ}lsey*J@{9ewxljm>>0>J9C~eLC3inkbgm zvSPDu+ci6C@3!ec(MxUB+Tg8y-CC1czS>Ke@qDyZ^?-3-m3_OOYmC&Ut7S(u-SFDb zBGN>5S}G_NI;y(u>gKaG7z(ayGt{`vqW~kGIC>U;@NNF0AgokHEqrTektsnBCMvWBs=)AC72?UhUPSUG+SM4^{~N>euyJu-`bn5*0yHHvYJV+y07y z$lk|^9SfoQ;dDCc`%g+tLvkDTmg3fC^O=y6>4G^0@ut{j-?@=;$pEAK?Iy7q%Oh%N zzs=aO2x$34zm;bC`z@mEIE=rXoV!#>AkEIp%IzbzXv6?Hbt^NA<7qfHWJyb;dTYKb z!6bNVNTxQ%B>`or?LQ%Cs@={&ofj8*PAE!%rd+}I#{$2va)$Y=`*t~U86OQRn!?u32^~B7FbRr%pLW4%5r6Hvqhg)}TRqVgH9Id~)tANmr7O=1Ch!a5# z9_1Gt*cLUy(e1W#&c^WbyvX^q+ntDRg@7ccg$*U4 zp`1a#8yEOM9)53?D|D^Gx03B}tmJmT>zQ2fA%|F^Wh8$|HtaCQVsyk*Gr??*)?hI$ zlB=6wq+OQ9;cXD#6U|62WJH861002Zc-L&5uI%>P3I2r*&rW`IRLQ=KHSyl& z^Zd{5l`FI#j#lUHN@sq6jvOp(Ap>?{e&rjp}Y5vb+rez58MG38~zvXF0C+(4OlM8wjQ z-O=_f#Mkmm{D~{vp$qO$w9C(R%_iRIO8q=%;wloqV$!tJQI@;Ol3z;?B)wLNULJGS zO>e6CmNl&_ZN4tEW3-FbbOawF;d1xUY)`)Cn;wg7qBb=fPN`h393@RQvb#PyA7nmzY%m7Q>stpMvIkLot#IpccvM`)#>MxS6|18dTQ zSv$7`9if?8zf}_g(!7f8vW0dBm)T2#ZA^@3PLB6Tpo(wvjf!fp7DJXzo<>9nw4Q3- zYU>S=kMm*67w$L3UL`{)+h8{fZ?X51 zwRA+AsAi95NnPZkG%ZYi!c7p%-*h`W2%;dkcv3wXYip%yddGUgt@3cr)$riOtK>ba zr$Y&t�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
  • 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�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

    OSUZ9ITcfiB1B+*9{ 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� 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 Za5D Date: Tue, 22 Aug 2023 09:42:59 +0300 Subject: [PATCH 22/97] improve --extend --- pr_agent/agent/pr_agent.py | 2 - pr_agent/algo/utils.py | 3 +- pr_agent/cli.py | 20 +- pr_agent/servers/help.py | 2 +- pr_agent/settings/configuration.toml | 10 +- pr_agent/tools/pr_code_suggestions.py | 107 ++++++++- .../tools/pr_extended_code_suggestions.py | 215 ------------------ 7 files changed, 122 insertions(+), 237 deletions(-) delete mode 100644 pr_agent/tools/pr_extended_code_suggestions.py diff --git a/pr_agent/agent/pr_agent.py b/pr_agent/agent/pr_agent.py index f722695c..70121f3c 100644 --- a/pr_agent/agent/pr_agent.py +++ b/pr_agent/agent/pr_agent.py @@ -11,7 +11,6 @@ from pr_agent.tools.pr_description import PRDescription from pr_agent.tools.pr_information_from_user import PRInformationFromUser from pr_agent.tools.pr_questions import PRQuestions from pr_agent.tools.pr_reviewer import PRReviewer -from pr_agent.tools.pr_extended_code_suggestions import PRExtendedCodeSuggestions from pr_agent.tools.pr_update_changelog import PRUpdateChangelog from pr_agent.tools.pr_config import PRConfig @@ -26,7 +25,6 @@ command2class = { "describe_pr": PRDescription, "improve": PRCodeSuggestions, "improve_code": PRCodeSuggestions, - "extended_improve": PRExtendedCodeSuggestions, "ask": PRQuestions, "ask_question": PRQuestions, "update_changelog": PRUpdateChangelog, diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py index 87e206cf..2139203f 100644 --- a/pr_agent/algo/utils.py +++ b/pr_agent/algo/utils.py @@ -247,7 +247,8 @@ def update_settings_from_args(args: List[str]) -> List[str]: arg = arg.strip('-').strip() vals = arg.split('=', 1) if len(vals) != 2: - logging.error(f'Invalid argument format: {arg}') + if len(vals) > 2: # --extended is a valid argument + logging.error(f'Invalid argument format: {arg}') other_args.append(arg) continue key, value = _fix_key_value(*vals) diff --git a/pr_agent/cli.py b/pr_agent/cli.py index 0f871041..01c1a7ec 100644 --- a/pr_agent/cli.py +++ b/pr_agent/cli.py @@ -19,13 +19,21 @@ For example: - cli.py --pr_url=... reflect Supported commands: -review / review_pr - Add a review that includes a summary of the PR and specific suggestions for improvement. -ask / ask_question [question] - Ask a question about the PR. -describe / describe_pr - Modify the PR title and description based on the PR's contents. -improve / improve_code - Suggest improvements to the code in the PR as pull request comments ready to commit. -reflect - Ask the PR author questions about the PR. -update_changelog - Update the changelog based on the PR's contents. +-review / review_pr - Add a review that includes a summary of the PR and specific suggestions for improvement. +-ask / ask_question [question] - Ask a question about the PR. + +-describe / describe_pr - Modify the PR title and description based on the PR's contents. + +-improve / improve_code - Suggest improvements to the code in the PR as pull request comments ready to commit. +Extended mode ('improve --extended') employs several calls, and provides a more thorough feedback + +-reflect - Ask the PR author questions about the PR. + +-update_changelog - Update the changelog based on the PR's contents. + + +Configuration: To edit any configuration parameter from 'configuration.toml', just add -config_path=. For example: 'python cli.py --pr_url=... review --pr_reviewer.extra_instructions="focus on the file: ..."' """) diff --git a/pr_agent/servers/help.py b/pr_agent/servers/help.py index 1ee7fc4d..0f3f3caa 100644 --- a/pr_agent/servers/help.py +++ b/pr_agent/servers/help.py @@ -1,7 +1,7 @@ commands_text = "> **/review [-i]**: Request a review of your Pull Request. For an incremental review, which only " \ "considers changes since the last review, include the '-i' option.\n" \ "> **/describe**: Modify the PR title and description based on the contents of the PR.\n" \ - "> **/improve**: Suggest improvements to the code in the PR. \n" \ + "> **/improve [--extended]**: Suggest improvements to the code in the PR. Extended mode employs several calls, and provides a more thorough feedback. \n" \ "> **/ask \\**: Pose a question about the PR.\n" \ "> **/update_changelog**: Update the changelog based on the PR's contents.\n\n" \ ">To edit any configuration parameter from **configuration.toml**, add --config_path=new_value\n" \ diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index 00c9b453..b1d19f97 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -31,14 +31,12 @@ extra_instructions = "" [pr_code_suggestions] # /improve # num_code_suggestions=4 extra_instructions = "" - -[pr_extendeted_code_suggestions] # /extended_improve # +rank_suggestions = false +# params for '/improve --extended' mode num_code_suggestions_per_chunk=8 -extra_instructions = "" +rank_extended_suggestions = true max_number_of_calls = 5 -rank_suggestions = true -final_clip_factor = 0.5 - +final_clip_factor = 0.9 [pr_update_changelog] # /update_changelog # push_changelog_changes=false diff --git a/pr_agent/tools/pr_code_suggestions.py b/pr_agent/tools/pr_code_suggestions.py index ebb88b21..4dc2f400 100644 --- a/pr_agent/tools/pr_code_suggestions.py +++ b/pr_agent/tools/pr_code_suggestions.py @@ -2,11 +2,13 @@ import copy import json import logging import textwrap +from typing import List +import yaml from jinja2 import Environment, StrictUndefined from pr_agent.algo.ai_handler import AiHandler -from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models +from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models, get_pr_multi_diffs from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.utils import try_fix_json from pr_agent.config_loader import get_settings @@ -22,6 +24,13 @@ class PRCodeSuggestions: self.git_provider.get_languages(), self.git_provider.get_files() ) + # extended mode + self.is_extended = any(["extended" in arg for arg in args]) + if self.is_extended: + num_code_suggestions = get_settings().pr_code_suggestions.num_code_suggestions_per_chunk + else: + num_code_suggestions = get_settings().pr_code_suggestions.num_code_suggestions + self.ai_handler = AiHandler() self.patches_diff = None self.prediction = None @@ -32,7 +41,7 @@ class PRCodeSuggestions: "description": self.git_provider.get_pr_description(), "language": self.main_language, "diff": "", # empty diff for initial calculation - "num_code_suggestions": get_settings().pr_code_suggestions.num_code_suggestions, + "num_code_suggestions": num_code_suggestions, "extra_instructions": get_settings().pr_code_suggestions.extra_instructions, "commit_messages_str": self.git_provider.get_commit_messages(), } @@ -42,14 +51,22 @@ class PRCodeSuggestions: get_settings().pr_code_suggestions_prompt.user) async def run(self): - # assert type(self.git_provider) != BitbucketProvider, "Bitbucket is not supported for now" - logging.info('Generating code suggestions for PR...') if get_settings().config.publish_output: self.git_provider.publish_comment("Preparing review...", is_temporary=True) - await retry_with_fallback_models(self._prepare_prediction) + logging.info('Preparing PR review...') - data = self._prepare_pr_code_suggestions() + if not self.is_extended: + await retry_with_fallback_models(self._prepare_prediction) + data = self._prepare_pr_code_suggestions() + else: + data = await retry_with_fallback_models(self._prepare_prediction_extended) + + if (not self.is_extended and get_settings().pr_code_suggestions.rank_suggestions) or \ + (self.is_extended and get_settings().pr_code_suggestions.rank_extended_suggestions): + logging.info('Ranking Suggestions...') + data['Code suggestions'] = await self.rank_suggestions(data['Code suggestions']) + if get_settings().config.publish_output: logging.info('Pushing PR review...') self.git_provider.remove_initial_comment() @@ -145,3 +162,81 @@ class PRCodeSuggestions: return new_code_snippet + async def _prepare_prediction_extended(self, model: str) -> dict: + logging.info('Getting PR diff...') + patches_diff_list = get_pr_multi_diffs(self.git_provider, self.token_handler, model, + max_calls=get_settings().pr_code_suggestions.max_number_of_calls) + + logging.info('Getting multi AI predictions...') + prediction_list = [] + for i, patches_diff in enumerate(patches_diff_list): + logging.info(f"Processing chunk {i + 1} of {len(patches_diff_list)}") + self.patches_diff = patches_diff + prediction = await self._get_prediction(model) + prediction_list.append(prediction) + self.prediction_list = prediction_list + + data = {} + for prediction in prediction_list: + self.prediction = prediction + data_per_chunk = self._prepare_pr_code_suggestions() + if "Code suggestions" in data: + data["Code suggestions"].extend(data_per_chunk["Code suggestions"]) + else: + data.update(data_per_chunk) + self.data = data + return data + + async def rank_suggestions(self, data: List) -> List: + """ + Call a model to rank (sort) code suggestions based on their importance order. + + Args: + data (List): A list of code suggestions to be ranked. + + Returns: + List: The ranked list of code suggestions. + """ + + suggestion_list = [] + # remove invalid suggestions + for i, suggestion in enumerate(data): + if suggestion['existing code'] != suggestion['improved code']: + suggestion_list.append(suggestion) + + data_sorted = [[]] * len(suggestion_list) + + try: + suggestion_str = "" + for i, suggestion in enumerate(suggestion_list): + suggestion_str += f"suggestion {i + 1}: " + str(suggestion) + '\n\n' + + variables = {'suggestion_list': suggestion_list, 'suggestion_str': suggestion_str} + model = get_settings().config.model + environment = Environment(undefined=StrictUndefined) + system_prompt = environment.from_string(get_settings().pr_sort_code_suggestions_prompt.system).render( + variables) + user_prompt = environment.from_string(get_settings().pr_sort_code_suggestions_prompt.user).render(variables) + if get_settings().config.verbosity_level >= 2: + logging.info(f"\nSystem prompt:\n{system_prompt}") + logging.info(f"\nUser prompt:\n{user_prompt}") + response, finish_reason = await self.ai_handler.chat_completion(model=model, system=system_prompt, + user=user_prompt) + + sort_order = yaml.safe_load(response) + for s in sort_order['Sort Order']: + suggestion_number = s['suggestion number'] + importance_order = s['importance order'] + data_sorted[importance_order - 1] = suggestion_list[suggestion_number - 1] + + if get_settings().pr_extendeted_code_suggestions.final_clip_factor != 1: + new_len = int(0.5 + len(data_sorted) * get_settings().pr_extendeted_code_suggestions.final_clip_factor) + data_sorted = data_sorted[:new_len] + except Exception as e: + if get_settings().config.verbosity_level >= 1: + logging.info(f"Could not sort suggestions, error: {e}") + data_sorted = suggestion_list + + return data_sorted + + diff --git a/pr_agent/tools/pr_extended_code_suggestions.py b/pr_agent/tools/pr_extended_code_suggestions.py deleted file mode 100644 index 17f7b570..00000000 --- a/pr_agent/tools/pr_extended_code_suggestions.py +++ /dev/null @@ -1,215 +0,0 @@ -from typing import List -import copy -import json -import logging -import textwrap -from typing import Dict, Any - -import yaml -from jinja2 import Environment, StrictUndefined - -from pr_agent.algo.ai_handler import AiHandler -from pr_agent.algo.pr_processing import get_pr_multi_diffs, retry_with_fallback_models -from pr_agent.algo.token_handler import TokenHandler -from pr_agent.algo.utils import try_fix_json -from pr_agent.config_loader import get_settings -from pr_agent.git_providers import get_git_provider -from pr_agent.git_providers.git_provider import get_main_pr_language - - -class PRExtendedCodeSuggestions: - def __init__(self, pr_url: str, cli_mode=False, args: list = None): - - self.git_provider = get_git_provider()(pr_url) - self.main_language = get_main_pr_language( - self.git_provider.get_languages(), self.git_provider.get_files() - ) - - self.ai_handler = AiHandler() - self.patches_diff = None - self.prediction = None - self.cli_mode = cli_mode - self.vars = { - "title": self.git_provider.pr.title, - "branch": self.git_provider.get_pr_branch(), - "description": self.git_provider.get_pr_description(), - "language": self.main_language, - "diff": "", # empty diff for initial calculation - "num_code_suggestions": get_settings().pr_extendeted_code_suggestions.num_code_suggestions_per_chunk, - "extra_instructions": get_settings().pr_extendeted_code_suggestions.extra_instructions, - "commit_messages_str": self.git_provider.get_commit_messages(), - } - self.token_handler = TokenHandler(self.git_provider.pr, - self.vars, - get_settings().pr_code_suggestions_prompt.system, - get_settings().pr_code_suggestions_prompt.user) - - async def run(self): - logging.info('Generating code suggestions for PR...') - if get_settings().config.publish_output: - self.git_provider.publish_comment("Preparing review...", is_temporary=True) - data = await retry_with_fallback_models(self._prepare_prediction) - - if get_settings().pr_extendeted_code_suggestions.rank_suggestions: - logging.info('Ranking Suggestions...') - data['Code suggestions'] = await self.rank_suggestions(data['Code suggestions']) - - logging.info('Preparing PR review...') - if get_settings().config.publish_output: - logging.info('Pushing PR review...') - self.git_provider.remove_initial_comment() - logging.info('Pushing inline code comments...') - self.push_inline_code_suggestions(data) - - async def _prepare_prediction(self, model: str) -> dict: - logging.info('Getting PR diff...') - patches_diff_list = get_pr_multi_diffs(self.git_provider, self.token_handler, model, - max_calls=get_settings().pr_extendeted_code_suggestions.max_number_of_calls) - - logging.info('Getting multi AI predictions...') - prediction_list = [] - for i, patches_diff in enumerate(patches_diff_list): - logging.info(f"Processing chunk {i + 1} of {len(patches_diff_list)}") - self.patches_diff = patches_diff - prediction = await self._get_prediction(model) - prediction_list.append(prediction) - self.prediction_list = prediction_list - - data = {} - for prediction in prediction_list: - self.prediction = prediction - data_per_chunk = self._prepare_pr_code_suggestions() - if "Code suggestions" in data: - data["Code suggestions"].extend(data_per_chunk["Code suggestions"]) - else: - data.update(data_per_chunk) - self.data = data - return data - - async def _get_prediction(self, model: str): - variables = copy.deepcopy(self.vars) - variables["diff"] = self.patches_diff # update diff - environment = Environment(undefined=StrictUndefined) - system_prompt = environment.from_string(get_settings().pr_code_suggestions_prompt.system).render(variables) - user_prompt = environment.from_string(get_settings().pr_code_suggestions_prompt.user).render(variables) - if get_settings().config.verbosity_level >= 2: - logging.info(f"\nSystem prompt:\n{system_prompt}") - logging.info(f"\nUser prompt:\n{user_prompt}") - response, finish_reason = await self.ai_handler.chat_completion(model=model, temperature=0.2, - system=system_prompt, user=user_prompt) - - return response - - def _prepare_pr_code_suggestions(self) -> str: - review = self.prediction.strip() - try: - data = json.loads(review) - except json.decoder.JSONDecodeError: - if get_settings().config.verbosity_level >= 1: - logging.info(f"Could not parse json response: {review}") - data = try_fix_json(review, code_suggestions=True) - return data - - def push_inline_code_suggestions(self, data): - code_suggestions = [] - - if not data['Code suggestions']: - return self.git_provider.publish_comment('No suggestions found to improve this PR.') - - for d in data['Code suggestions']: - try: - if get_settings().config.verbosity_level >= 1: - logging.info(f"suggestion: {d}") - relevant_file = d['relevant file'].strip() - relevant_lines_str = d['relevant lines'].strip() - if ',' in relevant_lines_str: # handling 'relevant lines': '181, 190' or '178-184, 188-194' - relevant_lines_str = relevant_lines_str.split(',')[0] - relevant_lines_start = int(relevant_lines_str.split('-')[0]) # absolute position - relevant_lines_end = int(relevant_lines_str.split('-')[-1]) - content = d['suggestion content'] - new_code_snippet = d['improved code'] - - if new_code_snippet: - new_code_snippet = self.dedent_code(relevant_file, relevant_lines_start, new_code_snippet) - - body = f"**Suggestion:** {content}\n```suggestion\n" + new_code_snippet + "\n```" - code_suggestions.append({'body': body, 'relevant_file': relevant_file, - 'relevant_lines_start': relevant_lines_start, - 'relevant_lines_end': relevant_lines_end}) - except Exception: - if get_settings().config.verbosity_level >= 1: - logging.info(f"Could not parse suggestion: {d}") - - self.git_provider.publish_code_suggestions(code_suggestions) - - def dedent_code(self, relevant_file, relevant_lines_start, new_code_snippet): - try: # dedent code snippet - self.diff_files = self.git_provider.diff_files if self.git_provider.diff_files \ - else self.git_provider.get_diff_files() - original_initial_line = None - for file in self.diff_files: - if file.filename.strip() == relevant_file: - original_initial_line = file.head_file.splitlines()[relevant_lines_start - 1] - break - if original_initial_line: - suggested_initial_line = new_code_snippet.splitlines()[0] - original_initial_spaces = len(original_initial_line) - len(original_initial_line.lstrip()) - suggested_initial_spaces = len(suggested_initial_line) - len(suggested_initial_line.lstrip()) - delta_spaces = original_initial_spaces - suggested_initial_spaces - if delta_spaces > 0: - new_code_snippet = textwrap.indent(new_code_snippet, delta_spaces * " ").rstrip('\n') - except Exception as e: - if get_settings().config.verbosity_level >= 1: - logging.info(f"Could not dedent code snippet for file {relevant_file}, error: {e}") - - return new_code_snippet - - async def rank_suggestions(self, data: List) -> List: - """ - Call a model to rank (sort) code suggestions based on their importance order. - - Args: - data (List): A list of code suggestions to be ranked. - - Returns: - List: The ranked list of code suggestions. - """ - - suggestion_list = [] - # remove invalid suggestions - for i, suggestion in enumerate(data): - if suggestion['existing code'] != suggestion['improved code']: - suggestion_list.append(suggestion) - - data_sorted = [[]] * len(suggestion_list) - - try: - suggestion_str = "" - for i, suggestion in enumerate(suggestion_list): - suggestion_str += f"suggestion {i + 1}: " + str(suggestion) + '\n\n' - - variables = {'suggestion_list': suggestion_list, 'suggestion_str': suggestion_str} - model = get_settings().config.model - environment = Environment(undefined=StrictUndefined) - system_prompt = environment.from_string(get_settings().pr_sort_code_suggestions_prompt.system).render(variables) - user_prompt = environment.from_string(get_settings().pr_sort_code_suggestions_prompt.user).render(variables) - if get_settings().config.verbosity_level >= 2: - logging.info(f"\nSystem prompt:\n{system_prompt}") - logging.info(f"\nUser prompt:\n{user_prompt}") - response, finish_reason = await self.ai_handler.chat_completion(model=model, system=system_prompt, user=user_prompt) - - sort_order = yaml.safe_load(response) - for s in sort_order['Sort Order']: - suggestion_number = s['suggestion number'] - importance_order = s['importance order'] - data_sorted[importance_order - 1] = suggestion_list[suggestion_number - 1] - - if get_settings().pr_extendeted_code_suggestions.final_clip_factor != 1: - new_len = int(0.5 + len(data_sorted) * get_settings().pr_extendeted_code_suggestions.final_clip_factor) - data_sorted = data_sorted[:new_len] - except Exception as e: - if get_settings().config.verbosity_level >= 1: - logging.info(f"Could not sort suggestions, error: {e}") - data_sorted = suggestion_list - - return data_sorted From 2b22f712fb9105c9a2b220c6d61db5e55615e41a Mon Sep 17 00:00:00 2001 From: zmeir Date: Tue, 22 Aug 2023 09:55:56 +0300 Subject: [PATCH 23/97] Renamed keep_user_description --> add_original_user_description --- pr_agent/settings/configuration.toml | 2 +- pr_agent/tools/pr_description.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index a881c500..d3098f4f 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -24,7 +24,7 @@ extra_instructions = "" [pr_description] # /describe # publish_description_as_comment=false -keep_user_description=false +add_original_user_description=false extra_instructions = "" [pr_questions] # /ask # diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index 13898e8f..98fbe0ce 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -147,7 +147,7 @@ class PRDescription: # Load the AI prediction data into a dictionary data = load_yaml(self.prediction.strip()) - if get_settings().pr_description.keep_user_description and self.user_description: + if get_settings().pr_description.add_original_user_description and self.user_description: data["User Description"] = self.user_description # Initialization From 09ef809080cd72051734d3fe94313efc71b272c1 Mon Sep 17 00:00:00 2001 From: zmeir Date: Tue, 22 Aug 2023 10:04:21 +0300 Subject: [PATCH 24/97] Added comments explaining the logic behind `get_user_description` --- pr_agent/git_providers/git_provider.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pr_agent/git_providers/git_provider.py b/pr_agent/git_providers/git_provider.py index 9db0a5bd..0837155c 100644 --- a/pr_agent/git_providers/git_provider.py +++ b/pr_agent/git_providers/git_provider.py @@ -96,10 +96,14 @@ class GitProvider(ABC): def get_user_description(self) -> str: description = (self.get_pr_description_full() or "").strip() + # if the existing description wasn't generated by the pr-agent, just return it as-is if not description.startswith("## PR Type"): return description + # if the existing description was generated by the pr-agent, but it doesn't contain the user description, + # return nothing (empty string) because it means there is no user description if "## User Description:" not in description: return "" + # otherwise, extract the original user description from the existing pr-agent description and return it return description.split("## User Description:", 1)[1].strip() @abstractmethod From 580af44e7dd26e0565e318c9130146f2eb551aa0 Mon Sep 17 00:00:00 2001 From: idavidov Date: Tue, 22 Aug 2023 10:24:20 +0300 Subject: [PATCH 25/97] Could we consider adding permissions to the GitHub Actions section? I've noticed that this has been a point of confusion for some users, as evidenced by questions in our Discord channel and GitHub issues. Some folks may even be discouraged to the point of not seeking help. I believe adding permissions could significantly improve the user experience. What are your thoughts? --- INSTALL.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/INSTALL.md b/INSTALL.md index 73848ade..4fed88a3 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -54,6 +54,10 @@ on: jobs: pr_agent_job: runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + contents: write name: Run pr agent on every pull request, respond to user comments steps: - name: PR Agent action step @@ -72,6 +76,10 @@ on: jobs: pr_agent_job: runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + contents: write name: Run pr agent on every pull request, respond to user comments steps: - name: PR Agent action step From 82fb611a265527de1761ee4b48da1e860cafbe36 Mon Sep 17 00:00:00 2001 From: zmeir Date: Tue, 22 Aug 2023 10:32:58 +0300 Subject: [PATCH 26/97] Add options to keep original user title --- pr_agent/settings/configuration.toml | 1 + pr_agent/tools/pr_description.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index d3098f4f..73b32878 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -25,6 +25,7 @@ extra_instructions = "" [pr_description] # /describe # publish_description_as_comment=false add_original_user_description=false +keep_original_user_title=false extra_instructions = "" [pr_questions] # /ask # diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index 98fbe0ce..440675fd 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -166,8 +166,14 @@ class PRDescription: elif type(data['PR Type']) == str: pr_types = data['PR Type'].split(',') - # Assign the value of the 'PR Title' key to 'title' variable and remove it from the dictionary - title = data.pop('PR Title') + # Remove the 'PR Title' key from the dictionary + ai_title = data.pop('PR Title') + if get_settings().pr_description.keep_original_user_title: + # Assign the original PR title to the 'title' variable + title = self.vars["title"] + else: + # Assign the value of the 'PR Title' key to 'title' variable + title = ai_title # Iterate over the remaining dictionary items and append the key and value to 'pr_body' in a markdown format, # except for the items containing the word 'walkthrough' From f4f040bf8d8443224f083d4cb544c206de9767fc Mon Sep 17 00:00:00 2001 From: mrT23 Date: Tue, 22 Aug 2023 16:11:51 +0300 Subject: [PATCH 27/97] publish each suggestion separably --- pr_agent/algo/git_patch_processing.py | 24 ++++++++++++------- pr_agent/algo/pr_processing.py | 10 ++++---- .../settings/pr_code_suggestions_prompts.toml | 24 ++++++++++--------- pr_agent/tools/pr_code_suggestions.py | 12 ++++++---- 4 files changed, 42 insertions(+), 28 deletions(-) diff --git a/pr_agent/algo/git_patch_processing.py b/pr_agent/algo/git_patch_processing.py index 230df41e..1a2bd22b 100644 --- a/pr_agent/algo/git_patch_processing.py +++ b/pr_agent/algo/git_patch_processing.py @@ -1,5 +1,4 @@ from __future__ import annotations - import logging import re @@ -157,7 +156,7 @@ def convert_to_hunks_with_lines_numbers(patch: str, file) -> str: example output: ## src/file.ts ---new hunk-- +__new hunk__ 881 line1 882 line2 883 line3 @@ -166,7 +165,7 @@ def convert_to_hunks_with_lines_numbers(patch: str, file) -> str: 889 line6 890 line7 ... ---old hunk-- +__old hunk__ line1 line2 - line3 @@ -177,7 +176,6 @@ def convert_to_hunks_with_lines_numbers(patch: str, file) -> str: """ patch_with_lines_str = f"\n\n## {file.filename}\n" - import re patch_lines = patch.splitlines() RE_HUNK_HEADER = re.compile( r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)") @@ -185,23 +183,30 @@ def convert_to_hunks_with_lines_numbers(patch: str, file) -> str: old_content_lines = [] match = None start1, size1, start2, size2 = -1, -1, -1, -1 + prev_header_line = [] + header_line =[] for line in patch_lines: if 'no newline at end of file' in line.lower(): continue if line.startswith('@@'): + header_line = line match = RE_HUNK_HEADER.match(line) if match and new_content_lines: # found a new hunk, split the previous lines if new_content_lines: - patch_with_lines_str += '\n--new hunk--\n' + if prev_header_line: + patch_with_lines_str += f'\n{prev_header_line}\n' + patch_with_lines_str += '__new hunk__\n' for i, line_new in enumerate(new_content_lines): patch_with_lines_str += f"{start2 + i} {line_new}\n" if old_content_lines: - patch_with_lines_str += '--old hunk--\n' + patch_with_lines_str += '__old hunk__\n' for line_old in old_content_lines: patch_with_lines_str += f"{line_old}\n" new_content_lines = [] old_content_lines = [] + if match: + prev_header_line = header_line try: start1, size1, start2, size2 = map(int, match.groups()[:4]) except: # '@@ -0,0 +1 @@' case @@ -219,12 +224,13 @@ def convert_to_hunks_with_lines_numbers(patch: str, file) -> str: # finishing last hunk if match and new_content_lines: if new_content_lines: - patch_with_lines_str += '\n--new hunk--\n' + patch_with_lines_str += f'\n{header_line}\n' + patch_with_lines_str += '\n__new hunk__\n' for i, line_new in enumerate(new_content_lines): patch_with_lines_str += f"{start2 + i} {line_new}\n" if old_content_lines: - patch_with_lines_str += '\n--old hunk--\n' + patch_with_lines_str += '\n__old hunk__\n' for line_old in old_content_lines: patch_with_lines_str += f"{line_old}\n" - return patch_with_lines_str.strip() + return patch_with_lines_str.rstrip() diff --git a/pr_agent/algo/pr_processing.py b/pr_agent/algo/pr_processing.py index 1003f456..29709d29 100644 --- a/pr_agent/algo/pr_processing.py +++ b/pr_agent/algo/pr_processing.py @@ -24,7 +24,7 @@ OUTPUT_BUFFER_TOKENS_HARD_THRESHOLD = 600 PATCH_EXTRA_LINES = 3 def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler, model: str, - add_line_numbers_to_hunks: bool = False, disable_extra_lines: bool = False) -> str: + add_line_numbers_to_hunks: bool = True, disable_extra_lines: bool = True) -> str: """ Returns a string with the diff of the pull request, applying diff minimization techniques if needed. @@ -103,9 +103,9 @@ def pr_generate_extended_diff(pr_languages: list, # extend each patch with extra lines of context extended_patch = extend_patch(original_file_content_str, patch, num_lines=PATCH_EXTRA_LINES) - full_extended_patch = f"## {file.filename}\n\n{extended_patch}\n" + full_extended_patch = f"\n\n## {file.filename}\n\n{extended_patch}\n" - if add_line_numbers_to_hunks and PATCH_EXTRA_LINES > 0: + if add_line_numbers_to_hunks: full_extended_patch = convert_to_hunks_with_lines_numbers(extended_patch, file) patch_tokens = token_handler.count_tokens(full_extended_patch) @@ -322,7 +322,9 @@ def clip_tokens(text: str, max_tokens: int) -> str: Returns: str: The clipped string. """ - # We'll estimate the number of tokens by hueristically assuming 2.5 tokens per word + if not text: + return text + try: encoder = get_token_encoder() num_input_tokens = len(encoder.encode(text)) diff --git a/pr_agent/settings/pr_code_suggestions_prompts.toml b/pr_agent/settings/pr_code_suggestions_prompts.toml index be90c840..e32ffd72 100644 --- a/pr_agent/settings/pr_code_suggestions_prompts.toml +++ b/pr_agent/settings/pr_code_suggestions_prompts.toml @@ -6,22 +6,23 @@ Example PR Diff input: ' ## src/file1.py ---new hunk-- +@@ -12,3 +12,5 @@ def func1(): +__new hunk__ 12 code line that already existed in the file... 13 code line that already existed in the file.... 14 +new code line added in the PR 15 code line that already existed in the file... 16 code line that already existed in the file... - ---old hunk-- +__old hunk__ code line that already existed in the file... -code line that was removed in the PR code line that already existed in the file... ---new hunk-- +@@ ... @@ def func2(): +__new hunk__ ... ---old hunk-- +__old hunk__ ... @@ -31,11 +32,12 @@ Example PR Diff input: Specific instructions: - Focus on important suggestions like fixing code problems, issues and bugs. As a second priority, provide suggestions for meaningful code improvements, like performance, vulnerability, modularity, and best practices. -- Suggestions should refer only to code from the '--new hunk--' sections, and focus on new lines of code (lines starting with '+'). +- Suggestions should refer only to code from the '__new hunk__' sections, and focus on new lines of code (lines starting with '+'). - Provide the exact line number range (inclusive) for each issue. - Assume there is additional relevant code, that is not included in the diff. - Provide up to {{ num_code_suggestions }} code suggestions. -- Avoid making suggestions that have already been implemented in the PR code. For example, if you propose adding a docstring, type hint, or anything else, make sure it isn't already in the '--new hunk--' code. +- Avoid making suggestions that have already been implemented in the PR code. For example, if you propose adding a docstring, type hint, or anything else, make sure it isn't already in the '__new hunk__' code. +- Don't suggest to add docstring or type hints. {%- if extra_instructions %} @@ -58,19 +60,19 @@ You must use the following JSON schema to format your answer: }, "suggestion content": { "type": "string", - "description": "a concrete suggestion for meaningfully improving the new PR code (lines from the '--new hunk--' sections, starting with '+')." + "description": "a concrete suggestion for meaningfully improving the new PR code (lines from the '__new hunk__' sections, starting with '+')." }, "existing code": { "type": "string", - "description": "a code snippet showing the relevant code lines from a '--new hunk--' section. It must be continuous, correctly formatted and indented, and without line numbers." + "description": "a code snippet showing the relevant code lines from a '__new hunk__' section. It must be continuous, correctly formatted and indented, and without line numbers." }, "relevant lines": { "type": "string", - "description": "the relevant lines from a '--new hunk--' section, in the format of 'start_line-end_line'. For example: '10-15'. They should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above." + "description": "the relevant lines from a '__new hunk__' section, in the format of 'start_line-end_line'. For example: '10-15'. They should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above." }, "improved code": { "type": "string", - "description": "a new code snippet that can be used to replace the relevant lines in '--new hunk--' code. Replacement suggestions should be complete, correctly formatted and indented, and without line numbers." + "description": "a new code snippet that can be used to replace the relevant lines in '__new hunk__' code. Replacement suggestions should be complete, correctly formatted and indented, and without line numbers." } } } diff --git a/pr_agent/tools/pr_code_suggestions.py b/pr_agent/tools/pr_code_suggestions.py index 4dc2f400..cc787f5e 100644 --- a/pr_agent/tools/pr_code_suggestions.py +++ b/pr_agent/tools/pr_code_suggestions.py @@ -70,7 +70,7 @@ class PRCodeSuggestions: if get_settings().config.publish_output: logging.info('Pushing PR review...') self.git_provider.remove_initial_comment() - logging.info('Pushing inline code comments...') + logging.info('Pushing inline code suggestions...') self.push_inline_code_suggestions(data) async def _prepare_prediction(self, model: str): @@ -138,7 +138,11 @@ class PRCodeSuggestions: if get_settings().config.verbosity_level >= 2: logging.info(f"Could not parse suggestion: {d}") - self.git_provider.publish_code_suggestions(code_suggestions) + is_successful = self.git_provider.publish_code_suggestions(code_suggestions) + if not is_successful: + logging.info("Failed to publish code suggestions, trying to publish each suggestion separately") + for code_suggestion in code_suggestions: + self.git_provider.publish_code_suggestions([code_suggestion]) def dedent_code(self, relevant_file, relevant_lines_start, new_code_snippet): try: # dedent code snippet @@ -229,8 +233,8 @@ class PRCodeSuggestions: importance_order = s['importance order'] data_sorted[importance_order - 1] = suggestion_list[suggestion_number - 1] - if get_settings().pr_extendeted_code_suggestions.final_clip_factor != 1: - new_len = int(0.5 + len(data_sorted) * get_settings().pr_extendeted_code_suggestions.final_clip_factor) + if get_settings().pr_code_suggestions.final_clip_factor != 1: + new_len = int(0.5 + len(data_sorted) * get_settings().pr_code_suggestions.final_clip_factor) data_sorted = data_sorted[:new_len] except Exception as e: if get_settings().config.verbosity_level >= 1: From 36e5e5a17e559a340df961517f506d62fb41bd97 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Tue, 22 Aug 2023 16:30:18 +0300 Subject: [PATCH 28/97] update --- pr_agent/settings/pr_code_suggestions_prompts.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pr_agent/settings/pr_code_suggestions_prompts.toml b/pr_agent/settings/pr_code_suggestions_prompts.toml index e32ffd72..a65b546f 100644 --- a/pr_agent/settings/pr_code_suggestions_prompts.toml +++ b/pr_agent/settings/pr_code_suggestions_prompts.toml @@ -36,7 +36,7 @@ Specific instructions: - Provide the exact line number range (inclusive) for each issue. - Assume there is additional relevant code, that is not included in the diff. - Provide up to {{ num_code_suggestions }} code suggestions. -- Avoid making suggestions that have already been implemented in the PR code. For example, if you propose adding a docstring, type hint, or anything else, make sure it isn't already in the '__new hunk__' code. +- Avoid making suggestions that have already been implemented in the PR code. For example, if you want to add logs or change a variable to const, or anything else, make sure it isn't already in the '__new hunk__' code. - Don't suggest to add docstring or type hints. {%- if extra_instructions %} From 9157fa670e707ea6733d10821818a07132915c2c Mon Sep 17 00:00:00 2001 From: mrT23 Date: Tue, 22 Aug 2023 16:32:22 +0300 Subject: [PATCH 29/97] -> bool --- pr_agent/git_providers/bitbucket_provider.py | 2 +- pr_agent/git_providers/git_provider.py | 2 +- pr_agent/git_providers/github_provider.py | 2 +- pr_agent/git_providers/gitlab_provider.py | 2 +- pr_agent/git_providers/local_git_provider.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index 3596f4bf..85ead79d 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -36,7 +36,7 @@ class BitbucketProvider: except Exception: return "" - def publish_code_suggestions(self, code_suggestions: list): + def publish_code_suggestions(self, code_suggestions: list) -> bool: """ Publishes code suggestions as comments on the PR. """ diff --git a/pr_agent/git_providers/git_provider.py b/pr_agent/git_providers/git_provider.py index 4d711a14..c3be8bc3 100644 --- a/pr_agent/git_providers/git_provider.py +++ b/pr_agent/git_providers/git_provider.py @@ -54,7 +54,7 @@ class GitProvider(ABC): pass @abstractmethod - def publish_code_suggestions(self, code_suggestions: list): + def publish_code_suggestions(self, code_suggestions: list) -> bool: pass @abstractmethod diff --git a/pr_agent/git_providers/github_provider.py b/pr_agent/git_providers/github_provider.py index be0fa645..f400b92d 100644 --- a/pr_agent/git_providers/github_provider.py +++ b/pr_agent/git_providers/github_provider.py @@ -166,7 +166,7 @@ class GithubProvider(GitProvider): def publish_inline_comments(self, comments: list[dict]): self.pr.create_review(commit=self.last_commit_id, comments=comments) - def publish_code_suggestions(self, code_suggestions: list): + def publish_code_suggestions(self, code_suggestions: list) -> bool: """ Publishes code suggestions as comments on the PR. """ diff --git a/pr_agent/git_providers/gitlab_provider.py b/pr_agent/git_providers/gitlab_provider.py index ec6f236d..f1fa0119 100644 --- a/pr_agent/git_providers/gitlab_provider.py +++ b/pr_agent/git_providers/gitlab_provider.py @@ -195,7 +195,7 @@ class GitLabProvider(GitProvider): f'No relevant diff found for {relevant_file} {relevant_line_in_file}. Falling back to last diff.') return self.last_diff # fallback to last_diff if no relevant diff is found - def publish_code_suggestions(self, code_suggestions: list): + def publish_code_suggestions(self, code_suggestions: list) -> bool: for suggestion in code_suggestions: try: body = suggestion['body'] diff --git a/pr_agent/git_providers/local_git_provider.py b/pr_agent/git_providers/local_git_provider.py index a4f21969..cf8f38b2 100644 --- a/pr_agent/git_providers/local_git_provider.py +++ b/pr_agent/git_providers/local_git_provider.py @@ -130,7 +130,7 @@ class LocalGitProvider(GitProvider): relevant_lines_start: int, relevant_lines_end: int): raise NotImplementedError('Publishing code suggestions is not implemented for the local git provider') - def publish_code_suggestions(self, code_suggestions: list): + def publish_code_suggestions(self, code_suggestions: list) -> bool: raise NotImplementedError('Publishing code suggestions is not implemented for the local git provider') def publish_labels(self, labels): From 782c1708832830868625b38b8b9c9dc9eafe7b9f Mon Sep 17 00:00:00 2001 From: zmeir Date: Sun, 20 Aug 2023 09:23:21 +0300 Subject: [PATCH 30/97] Support custom deployments for github_app.py and add more options for automatic review actions --- pr_agent/servers/github_app.py | 101 ++++++++++++++++++++------- pr_agent/settings/configuration.toml | 13 ++++ 2 files changed, 90 insertions(+), 24 deletions(-) diff --git a/pr_agent/servers/github_app.py b/pr_agent/servers/github_app.py index 498bb81f..8bd0fd4d 100644 --- a/pr_agent/servers/github_app.py +++ b/pr_agent/servers/github_app.py @@ -1,6 +1,8 @@ import copy import logging import sys +import os +import time from typing import Any, Dict import uvicorn @@ -14,7 +16,7 @@ from pr_agent.config_loader import get_settings, global_settings from pr_agent.git_providers import get_git_provider from pr_agent.servers.utils import verify_signature -logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) +logging.basicConfig(stream=sys.stdout, level=logging.INFO) router = APIRouter() @@ -34,7 +36,8 @@ async def handle_github_webhooks(request: Request, response: Response): context["installation_id"] = installation_id context["settings"] = copy.deepcopy(global_settings) - return await handle_request(body) + response = await handle_request(body, event=request.headers.get("X-GitHub-Event", None)) + return response or {} @router.post("/api/v1/marketplace_webhooks") @@ -48,70 +51,120 @@ async def get_body(request): except Exception as e: logging.error("Error parsing request body", e) raise HTTPException(status_code=400, detail="Error parsing request body") from e - body_bytes = await request.body() - signature_header = request.headers.get('x-hub-signature-256', None) - webhook_secret = getattr(get_settings().github, 'webhook_secret', None) - if webhook_secret: - verify_signature(body_bytes, webhook_secret, signature_header) + if get_settings().github_app.verify_signature: + body_bytes = await request.body() + signature_header = request.headers.get('x-hub-signature-256', None) + webhook_secret = getattr(get_settings().github, 'webhook_secret', None) + if webhook_secret: + verify_signature(body_bytes, webhook_secret, signature_header) return body +_duplicate_requests_cache = {} -async def handle_request(body: Dict[str, Any]): +async def handle_request(body: Dict[str, Any], event: str): """ Handle incoming GitHub webhook requests. Args: body: The request body. + event: The GitHub event type. """ - action = body.get("action") + action = body.get("action", None) if not action: return {} agent = PRAgent() + bot_user = get_settings().github_app.bot_user + logging.info(f"action: '{action}'") + logging.info(f"event: '{event}'") + if get_settings().github_app.duplicate_requests_cache and _is_duplicate_request(body): + return {} + + # handle all sorts of comment events (e.g. issue_comment) if action == 'created': if "comment" not in body: return {} - comment_body = body.get("comment", {}).get("body") - sender = body.get("sender", {}).get("login") - if sender and 'bot' in sender: + comment_body = body.get("comment", {}).get("body", None) + sender = body.get("sender", {}).get("login", None) + if sender and bot_user in sender: + logging.info(f"Ignoring comment from {bot_user} user") return {} - if "issue" not in body or "pull_request" not in body["issue"]: + logging.info(f"Processing comment from {sender} user") + if "issue" in body and "pull_request" in body["issue"] and "url" in body["issue"]["pull_request"]: + api_url = body["issue"]["pull_request"]["url"] + elif "comment" in body and "pull_request_url" in body["comment"]: + api_url = body["comment"]["pull_request_url"] + else: return {} - pull_request = body["issue"]["pull_request"] - api_url = pull_request.get("url") + logging.info(f"Handling comment because of event={event} and action={action}") comment_id = body.get("comment", {}).get("id") provider = get_git_provider()(pr_url=api_url) await agent.handle_request(api_url, comment_body, notify=lambda: provider.add_eyes_reaction(comment_id)) - - elif action == "opened" or 'reopened' in action: - pull_request = body.get("pull_request") + # handle pull_request event: + # automatically review opened/reopened/ready_for_review PRs as long as they're not in draft, + # as well as direct review requests from the bot + elif event == 'pull_request': + pull_request = body.get("pull_request", None) if not pull_request: return {} - api_url = pull_request.get("url") - if not api_url: + api_url = pull_request.get("url", None) + if api_url is None: return {} - await agent.handle_request(api_url, "/auto_review") + if pull_request.get("draft", True) or pull_request.get("state", None) != "open" or pull_request.get("user", {}).get("login", "") == bot_user: + return {} + if action in get_settings().github_app.handle_pr_actions: + if action == "review_requested": + if body.get("requested_reviewer", {}).get("login", "") != bot_user: + return {} + if pull_request.get("created_at", None) == pull_request.get("updated_at", None): + # avoid double reviews when opening a PR for the first time + return {} + logging.info(f"Performing review because of event={event} and action={action}") + for command in get_settings().github_app.pr_commands: + logging.info(f"Performing command: {command}") + await agent.handle_request(api_url, command) + logging.info("event or action does not require handling") return {} +def _is_duplicate_request(body: Dict[str, Any]) -> bool: + """ + In some deployments its possible to get duplicate requests if the handling is long, + This function checks if the request is duplicate and if so - ignores it. + """ + request_hash = hash(str(body)) + logging.info(f"request_hash: {request_hash}") + request_time = time.monotonic() + ttl = get_settings().github_app.duplicate_requests_cache_ttl # in seconds + to_delete = [key for key, key_time in _duplicate_requests_cache.items() if request_time - key_time > ttl] + for key in to_delete: + del _duplicate_requests_cache[key] + is_duplicate = request_hash in _duplicate_requests_cache + _duplicate_requests_cache[request_hash] = request_time + if is_duplicate: + logging.info(f"Ignoring duplicate request {request_hash}") + return is_duplicate + + @router.get("/") async def root(): return {"status": "ok"} def start(): - # Override the deployment type to app - get_settings().set("GITHUB.DEPLOYMENT_TYPE", "app") + if get_settings().github_app.override_deployment_type: + # Override the deployment type to app + get_settings().set("GITHUB.DEPLOYMENT_TYPE", "app") get_settings().set("CONFIG.PUBLISH_OUTPUT_PROGRESS", False) middleware = [Middleware(RawContextMiddleware)] app = FastAPI(middleware=middleware) app.include_router(router) - uvicorn.run(app, host="0.0.0.0", port=3000) + uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "3000"))) if __name__ == '__main__': diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index ce920efd..5658d6e2 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -43,6 +43,19 @@ extra_instructions = "" deployment_type = "user" ratelimit_retries = 5 +[github_app] +# these toggles allows running the github app from custom deployments +bot_user = "github-actions[bot]" +verify_signature = true +override_deployment_type = true +# in some deployments it's possible to get duplicate requests if the handling is long, +# these settings are used to avoid handling duplicate requests. +duplicate_requests_cache = false +duplicate_requests_cache_ttl = 60 # in seconds +# settings for "pull_request" event +handle_pr_actions = ['opened', 'reopened', 'ready_for_review', 'review_requested'] +pr_commands = ["/describe", "/auto_review"] + [gitlab] # URL to the gitlab service url = "https://gitlab.com" From 62fe1de12d4dd3b6fb3e48693e7e98ab617f7e07 Mon Sep 17 00:00:00 2001 From: Zohar Meir <33152084+zmeir@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:28:06 +0300 Subject: [PATCH 31/97] Remove redundant toggle --- pr_agent/servers/github_app.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pr_agent/servers/github_app.py b/pr_agent/servers/github_app.py index 8bd0fd4d..a80daa03 100644 --- a/pr_agent/servers/github_app.py +++ b/pr_agent/servers/github_app.py @@ -51,12 +51,11 @@ async def get_body(request): except Exception as e: logging.error("Error parsing request body", e) raise HTTPException(status_code=400, detail="Error parsing request body") from e - if get_settings().github_app.verify_signature: + webhook_secret = getattr(get_settings().github, 'webhook_secret', None) + if webhook_secret: body_bytes = await request.body() signature_header = request.headers.get('x-hub-signature-256', None) - webhook_secret = getattr(get_settings().github, 'webhook_secret', None) - if webhook_secret: - verify_signature(body_bytes, webhook_secret, signature_header) + verify_signature(body_bytes, webhook_secret, signature_header) return body From a9a7a55f0246861b5a1dc1e52d0931d3c45a52fb Mon Sep 17 00:00:00 2001 From: Zohar Meir <33152084+zmeir@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:28:43 +0300 Subject: [PATCH 32/97] Remove redundant toggle --- pr_agent/settings/configuration.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index 5658d6e2..68db675c 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -46,7 +46,6 @@ ratelimit_retries = 5 [github_app] # these toggles allows running the github app from custom deployments bot_user = "github-actions[bot]" -verify_signature = true override_deployment_type = true # in some deployments it's possible to get duplicate requests if the handling is long, # these settings are used to avoid handling duplicate requests. From 3d771e28ce72a7cd4476d62e2956a0e39267eec1 Mon Sep 17 00:00:00 2001 From: Zohar Meir <33152084+zmeir@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:33:25 +0300 Subject: [PATCH 33/97] Remove redundant None default in dict.get --- pr_agent/servers/github_app.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pr_agent/servers/github_app.py b/pr_agent/servers/github_app.py index a80daa03..7a7208d0 100644 --- a/pr_agent/servers/github_app.py +++ b/pr_agent/servers/github_app.py @@ -70,7 +70,7 @@ async def handle_request(body: Dict[str, Any], event: str): body: The request body. event: The GitHub event type. """ - action = body.get("action", None) + action = body.get("action") if not action: return {} agent = PRAgent() @@ -85,8 +85,8 @@ async def handle_request(body: Dict[str, Any], event: str): if action == 'created': if "comment" not in body: return {} - comment_body = body.get("comment", {}).get("body", None) - sender = body.get("sender", {}).get("login", None) + comment_body = body.get("comment", {}).get("body") + sender = body.get("sender", {}).get("login") if sender and bot_user in sender: logging.info(f"Ignoring comment from {bot_user} user") return {} @@ -106,19 +106,19 @@ async def handle_request(body: Dict[str, Any], event: str): # automatically review opened/reopened/ready_for_review PRs as long as they're not in draft, # as well as direct review requests from the bot elif event == 'pull_request': - pull_request = body.get("pull_request", None) + pull_request = body.get("pull_request") if not pull_request: return {} - api_url = pull_request.get("url", None) - if api_url is None: + api_url = pull_request.get("url") + if not api_url: return {} - if pull_request.get("draft", True) or pull_request.get("state", None) != "open" or pull_request.get("user", {}).get("login", "") == bot_user: + if pull_request.get("draft", True) or pull_request.get("state") != "open" or pull_request.get("user", {}).get("login", "") == bot_user: return {} if action in get_settings().github_app.handle_pr_actions: if action == "review_requested": if body.get("requested_reviewer", {}).get("login", "") != bot_user: return {} - if pull_request.get("created_at", None) == pull_request.get("updated_at", None): + if pull_request.get("created_at") == pull_request.get("updated_at"): # avoid double reviews when opening a PR for the first time return {} logging.info(f"Performing review because of event={event} and action={action}") From 04be1573d59c35e607dabde48cc6b0db97a997d0 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Tue, 22 Aug 2023 20:10:36 +0300 Subject: [PATCH 34/97] improved review --- pr_agent/algo/pr_processing.py | 2 +- pr_agent/algo/utils.py | 14 ++-- pr_agent/settings/configuration.toml | 4 +- .../settings/pr_code_suggestions_prompts.toml | 2 +- pr_agent/settings/pr_reviewer_prompts.toml | 76 +++++++++++++------ 5 files changed, 67 insertions(+), 31 deletions(-) diff --git a/pr_agent/algo/pr_processing.py b/pr_agent/algo/pr_processing.py index 29709d29..1c34e603 100644 --- a/pr_agent/algo/pr_processing.py +++ b/pr_agent/algo/pr_processing.py @@ -24,7 +24,7 @@ OUTPUT_BUFFER_TOKENS_HARD_THRESHOLD = 600 PATCH_EXTRA_LINES = 3 def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler, model: str, - add_line_numbers_to_hunks: bool = True, disable_extra_lines: bool = True) -> str: + add_line_numbers_to_hunks: bool = False, disable_extra_lines: bool = False) -> str: """ Returns a string with the diff of the pull request, applying diff minimization techniques if needed. diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py index 2139203f..de9e902f 100644 --- a/pr_agent/algo/utils.py +++ b/pr_agent/algo/utils.py @@ -32,33 +32,37 @@ def convert_to_markdown(output_data: dict) -> str: emojis = { "Main theme": "🎯", + "PR summary": "📝", "Type of PR": "📌", "Score": "🏅", "Relevant tests added": "🧪", "Unrelated changes": "⚠️", "Focused PR": "✨", "Security concerns": "🔒", - "General PR suggestions": "💡", + "General suggestions": "💡", "Insights from user's answers": "📝", "Code feedback": "🤖", } for key, value in output_data.items(): - if not value: + if value is None: continue if isinstance(value, dict): markdown_text += f"## {key}\n\n" markdown_text += convert_to_markdown(value) elif isinstance(value, list): - if key.lower() == 'code feedback': - markdown_text += "\n" # just looks nicer with additional line breaks emoji = emojis.get(key, "") - markdown_text += f"- {emoji} **{key}:**\n\n" + if key.lower() == 'code feedback': + markdown_text += f"\n\n- **

    { emoji } Code feedback:**\n\n" + else: + markdown_text += f"- {emoji} **{key}:**\n\n" for item in value: if isinstance(item, dict) and key.lower() == 'code feedback': markdown_text += parse_code_suggestion(item) elif item: markdown_text += f" - {item}\n" + if key.lower() == 'code feedback': + markdown_text += "
    \n\n" elif value != 'n/a': emoji = emojis.get(key, "") markdown_text += f"- {emoji} **{key}:** {value}\n" diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index 8286eca2..a718a771 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -12,11 +12,11 @@ max_description_tokens = 500 max_commits_tokens = 500 [pr_reviewer] # /review # -require_focused_review=true +require_focused_review=false require_score_review=false require_tests_review=true require_security_review=true -num_code_suggestions=3 +num_code_suggestions=4 inline_code_comments = false ask_and_reflect=false automatic_review=true diff --git a/pr_agent/settings/pr_code_suggestions_prompts.toml b/pr_agent/settings/pr_code_suggestions_prompts.toml index a65b546f..4e4b57e5 100644 --- a/pr_agent/settings/pr_code_suggestions_prompts.toml +++ b/pr_agent/settings/pr_code_suggestions_prompts.toml @@ -36,7 +36,7 @@ Specific instructions: - Provide the exact line number range (inclusive) for each issue. - Assume there is additional relevant code, that is not included in the diff. - Provide up to {{ num_code_suggestions }} code suggestions. -- Avoid making suggestions that have already been implemented in the PR code. For example, if you want to add logs or change a variable to const, or anything else, make sure it isn't already in the '__new hunk__' code. +- Avoid making suggestions that have already been implemented in the PR code. For example, if you want to add logs, or change a variable to const, or anything else, make sure it isn't already in the '__new hunk__' code. - Don't suggest to add docstring or type hints. {%- if extra_instructions %} diff --git a/pr_agent/settings/pr_reviewer_prompts.toml b/pr_agent/settings/pr_reviewer_prompts.toml index b65d62e4..7c21f433 100644 --- a/pr_agent/settings/pr_reviewer_prompts.toml +++ b/pr_agent/settings/pr_reviewer_prompts.toml @@ -1,13 +1,36 @@ [pr_review_prompt] -system="""You are CodiumAI-PR-Reviewer, a language model designed to review git pull requests. -Your task is to provide constructive and concise feedback for the PR, and also provide meaningful code suggestions to improve the new PR code (the '+' lines). +system="""You are PR-Reviewer, a language model designed to review git pull requests. +Your task is to provide constructive and concise feedback for the PR, and also provide meaningful code suggestions. + +Example PR Diff input: +' +## src/file1.py + +@@ -12,5 +12,5 @@ def func1(): +code line that already existed in the file... +code line that already existed in the file.... +-code line that was removed in the PR ++new code line added in the PR + code line that already existed in the file... + code line that already existed in the file... + +@@ ... @@ def func2(): +... + + +## src/file2.py +... +' + +Thre review should focus on new code added in the PR (lines starting with '+'), and not on code that already existed in the file (lines starting with '-', or without prefix). + {%- if num_code_suggestions > 0 %} - Provide up to {{ num_code_suggestions }} code suggestions. -- Try to focus on the most important suggestions, like fixing code problems, issues and bugs. As a second priority, provide suggestions for meaningful code improvements, like performance, vulnerability, modularity, and best practices. -- Suggestions should focus on improving the new added code lines. -- Make sure not to provide suggestions repeating modifications already implemented in the new PR code (the '+' lines). +- Focus on important suggestions like fixing code problems, issues and bugs. As a second priority, provide suggestions for meaningful code improvements, like performance, vulnerability, modularity, and best practices. +- Avoid making suggestions that have already been implemented in the PR code. For example, if you want to add logs, or change a variable to const, or anything else, make sure it isn't already in the PR code. +- Don't suggest to add docstring or type hints. +- Suggestions should focus on improving the new code added in the PR (lines starting with '+') {%- endif %} -- If needed, each YAML output should be in block scalar format ('|-') {%- if extra_instructions %} @@ -21,6 +44,9 @@ PR Analysis: Main theme: type: string description: a short explanation of the PR + PR summary: + type: string + description: summary of the PR in 2-3 sentences. Type of PR: type: string enum: @@ -33,7 +59,7 @@ PR Analysis: {%- if require_score %} Score: type: int - description: >- + description: |- Rate this PR on a scale of 0-100 (inclusive), where 0 means the worst possible PR code, and 100 means PR code of the highest quality, without any bugs or performance issues, that is ready to be merged immediately and @@ -47,13 +73,13 @@ PR Analysis: {%- if question_str %} Insights from user's answer: type: string - description: >- + description: |- shortly summarize the insights you gained from the user's answers to the questions {%- endif %} {%- if require_focused %} Focused PR: type: string - description: >- + description: |- Is this a focused PR, in the sense that all the PR code diff changes are united under a single focused theme ? If the theme is too broad, or the PR code diff changes are too scattered, then the PR is not focused. Explain @@ -62,12 +88,11 @@ PR Analysis: PR Feedback: General suggestions: type: string - description: >- + description: |- General suggestions and feedback for the contributors and maintainers of this PR. May include important suggestions for the overall structure, primary purpose, best practices, critical bugs, and other aspects of the - PR. Don't address PR title and description, or lack of tests. Explain your - suggestions. + PR. Don't address PR title and description, or lack of tests. Explain your suggestions. {%- if num_code_suggestions > 0 %} Code feedback: type: array @@ -79,7 +104,7 @@ PR Feedback: description: the relevant file full path suggestion: type: string - description: | + description: |- a concrete suggestion for meaningfully improving the new PR code. Also describe how, specifically, the suggestion can be applied to new PR code. Add tags with importance measure that matches each suggestion @@ -87,9 +112,9 @@ PR Feedback: adding docstrings, renaming PR title and description, or linter like. relevant line: type: string - description: | + description: |- a single code line taken from the relevant file, to which the suggestion applies. - The line should be a '+' line. + The code line should start with a '+'. Make sure to output the line exactly as it appears in the relevant file {%- endif %} {%- if require_security %} @@ -104,22 +129,29 @@ PR Feedback: Example output: ```yaml PR Analysis: - Main theme: xxx - Type of PR: Bug fix + Main theme: |- + xxx + PR summary: |- + xxx + Type of PR: |- + Bug fix {%- if require_score %} Score: 89 {%- endif %} - Relevant tests added: No + Relevant tests added: |- + No {%- if require_focused %} Focused PR: no, because ... {%- endif %} PR Feedback: - General PR suggestions: ... + General PR suggestions: |- + ... {%- if num_code_suggestions > 0 %} Code feedback: - relevant file: |- directory/xxx.py - suggestion: xxx [important] + suggestion: |- + xxx [important] relevant line: |- xxx ... @@ -129,7 +161,7 @@ PR Feedback: {%- endif %} ``` -Make sure to output a valid YAML. Use multi-line block scalar ('|') if needed. +Each YAML output MUST be after a newline, indented, with block scalar indicator ('|-'). Don't repeat the prompt in the answer, and avoid outputting the 'type' and 'description' fields. """ @@ -161,7 +193,7 @@ The PR Git Diff: ``` {{diff}} ``` -Note that lines in the diff body are prefixed with a symbol that represents the type of change: '-' for deletions, '+' for additions, and ' ' (a space) for unchanged lines. +Note that lines in the diff body are prefixed with a symbol that represents the type of change: '-' for deletions, '+' for additions. Focus on the '+' lines. Response (should be a valid YAML, and nothing else): ```yaml From 412c86593d2e298b4ae1960129838d6665af4cb8 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Tue, 22 Aug 2023 20:21:52 +0300 Subject: [PATCH 35/97] fixed tests --- pr_agent/algo/utils.py | 2 +- tests/unittest/test_convert_to_markdown.py | 34 ++++------------------ 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py index de9e902f..4d09b6e7 100644 --- a/pr_agent/algo/utils.py +++ b/pr_agent/algo/utils.py @@ -45,7 +45,7 @@ def convert_to_markdown(output_data: dict) -> str: } for key, value in output_data.items(): - if value is None: + if value is None or value == '' or value == {}: continue if isinstance(value, dict): markdown_text += f"## {key}\n\n" diff --git a/tests/unittest/test_convert_to_markdown.py b/tests/unittest/test_convert_to_markdown.py index 4463513f..bb6f2268 100644 --- a/tests/unittest/test_convert_to_markdown.py +++ b/tests/unittest/test_convert_to_markdown.py @@ -67,33 +67,11 @@ class TestConvertToMarkdown: ] } expected_output = """\ -- 🎯 **Main theme:** Test -- 📌 **Type of PR:** Test type -- 🧪 **Relevant tests added:** no -- ✨ **Focused PR:** Yes -- 💡 **General PR suggestions:** general suggestion... - -- 🤖 **Code feedback:** - - - **Code example:** - - **Before:** - ``` - Code before - ``` - - **After:** - ``` - Code after - ``` - - - **Code example:** - - **Before:** - ``` - Code before 2 - ``` - - **After:** - ``` - Code after 2 - ``` +- 🎯 **Main theme:** Test\n\ +- 📌 **Type of PR:** Test type\n\ +- 🧪 **Relevant tests added:** no\n\ +- ✨ **Focused PR:** Yes\n\ +- **General PR suggestions:** general suggestion...\n\n\n- **
    🤖 Code feedback:**\n\n - **Code example:**\n - **Before:**\n ```\n Code before\n ```\n - **After:**\n ```\n Code after\n ```\n\n - **Code example:**\n - **Before:**\n ```\n Code before 2\n ```\n - **After:**\n ```\n Code after 2\n ```\n\n
    \ """ assert convert_to_markdown(input_data).strip() == expected_output.strip() @@ -113,5 +91,5 @@ class TestConvertToMarkdown: 'General PR suggestions': {}, 'Code suggestions': {} } - expected_output = "" + expected_output = '' assert convert_to_markdown(input_data).strip() == expected_output.strip() From f17b4fcc9eec44220c26e4fa13aa1c7c57356590 Mon Sep 17 00:00:00 2001 From: zmeir Date: Tue, 22 Aug 2023 21:14:03 +0300 Subject: [PATCH 36/97] Made the automatic describe command the least destructive --- pr_agent/settings/configuration.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index b763ef2e..c0cadf58 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -61,7 +61,10 @@ duplicate_requests_cache = false duplicate_requests_cache_ttl = 60 # in seconds # settings for "pull_request" event handle_pr_actions = ['opened', 'reopened', 'ready_for_review', 'review_requested'] -pr_commands = ["/describe", "/auto_review"] +pr_commands = [ + "/describe --pr_description.add_original_user_description=true --pr_description.keep_original_user_title=true", + "/auto_review", +] [gitlab] # URL to the gitlab service From d31b66b65603f04d7e9858fa73292d2cec000f19 Mon Sep 17 00:00:00 2001 From: Phill Zarfos Date: Tue, 22 Aug 2023 17:15:11 -0400 Subject: [PATCH 37/97] initial implementation of CodeCommit --- INSTALL.md | 68 ++++ README.md | 42 +- pr_agent/git_providers/__init__.py | 2 + pr_agent/git_providers/codecommit_client.py | 203 ++++++++++ pr_agent/git_providers/codecommit_provider.py | 363 ++++++++++++++++++ pyproject.toml | 3 +- requirements.txt | 1 + tests/unittest/test_codecommit_client.py | 136 +++++++ tests/unittest/test_codecommit_provider.py | 119 ++++++ 9 files changed, 915 insertions(+), 22 deletions(-) create mode 100644 pr_agent/git_providers/codecommit_client.py create mode 100644 pr_agent/git_providers/codecommit_provider.py create mode 100644 tests/unittest/test_codecommit_client.py create mode 100644 tests/unittest/test_codecommit_provider.py diff --git a/INSTALL.md b/INSTALL.md index 4fed88a3..303b3217 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -255,3 +255,71 @@ docker push codiumai/pr-agent:github_app # Push to your Docker repository 5. Configure the lambda function to have a Function URL. 6. Go back to steps 8-9 of [Method 5](#method-5-run-as-a-github-app) with the function url as your Webhook URL. The Webhook URL would look like `https:///api/v1/github_webhooks` + +--- + +#### AWS CodeCommit Setup + +Not all features have been added to CodeCommit yet. As of right now, CodeCommit has been implemented to run the pr-agent CLI on the command line, using AWS credentials stored in environment variables. (More features will be added in the future.) The following is a set of instructions to have pr-agent do a review of your CodeCommit pull request from the command line: + +1. Create an IAM user that you will use to read CodeCommit pull requests and post comments + * Note: That user should have CLI access only, not Console access +2. Add IAM permissions to that user, to allow access to CodeCommit (see IAM Role example below) +3. Generate an Access Key for your IAM user +4. Set the Access Key and Secret using environment variables (see Access Key example below) +5. Set the `git_provider` value to `codecommit` in the `pr_agent/settings/configuration.toml` settings file +6. Set the `PYTHONPATH` to include your `pr-agent` project directory + * Option A: Add `PYTHONPATH="/PATH/TO/PROJECTS/pr-agent` to your `.env` file + * Option B: Set `PYTHONPATH` and run the CLI in one command, for example: + * `PYTHONPATH="/PATH/TO/PROJECTS/pr-agent python pr_agent/cli.py [--ARGS]` + +#### AWS CodeCommit IAM Role Example + +Example IAM permissions to that user to allow access to CodeCommit: + +* Note: The following is a working example of IAM permissions that has read access to the repositories and write access to allow posting comments +* Note: If you only want pr-agent to review your pull requests, you can tighten the IAM permissions further, however this IAM example will work, and allow the pr-agent to post comments to the PR +* Note: You may want to replace the `"Resource": "*"` with your list of repos, to limit access to only those repos + +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "codecommit:BatchDescribe*", + "codecommit:BatchGet*", + "codecommit:Describe*", + "codecommit:EvaluatePullRequestApprovalRules", + "codecommit:Get*", + "codecommit:List*", + "codecommit:PostComment*", + "codecommit:PutCommentReaction" + ], + "Resource": "*" + } + ] +} +``` + +#### AWS CodeCommit Access Key and Secret + +Example setting the Access Key and Secret using environment variables + +```sh +export AWS_ACCESS_KEY_ID="XXXXXXXXXXXXXXXX" +export AWS_SECRET_ACCESS_KEY="XXXXXXXXXXXXXXXX" +export AWS_DEFAULT_REGION="us-east-1" +``` + +#### AWS CodeCommit CLI Example + +After you set up AWS CodeCommit using the instructions above, here is an example CLI run that tells pr-agent to **review** a given pull request. +(Replace your specific PYTHONPATH and PR URL in the example) + +```sh +PYTHONPATH="/PATH/TO/PROJECTS/pr-agent" python pr_agent/cli.py \ + --pr_url https://us-east-1.console.aws.amazon.com/codesuite/codecommit/repositories/MY_REPO_NAME/pull-requests/321 \ + review +``` diff --git a/README.md b/README.md index d2c4e171..1b120241 100644 --- a/README.md +++ b/README.md @@ -75,26 +75,26 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull ## Overview `PR-Agent` offers extensive pull request functionalities across various git providers: -| | | GitHub | Gitlab | Bitbucket | -|-------|---------------------------------------------|:------:|:------:|:---------:| -| TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | ⮑ Inline review | :white_check_mark: | :white_check_mark: | | -| | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: -| | Auto-Description | :white_check_mark: | :white_check_mark: | | -| | Improve Code | :white_check_mark: | :white_check_mark: | | -| | Reflect and Review | :white_check_mark: | | | -| | Update CHANGELOG.md | :white_check_mark: | | | -| | | | | | -| USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | App / webhook | :white_check_mark: | :white_check_mark: | | -| | Tagging bot | :white_check_mark: | | | -| | Actions | :white_check_mark: | | | -| | | | | | -| CORE | PR compression | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | Repo language prioritization | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | Adaptive and token-aware
    file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | Incremental PR Review | :white_check_mark: | | | +| | | GitHub | Gitlab | Bitbucket | CodeCommit | +|-------|---------------------------------------------|:------:|:------:|:---------:|:----------:| +| TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | ⮑ Inline review | :white_check_mark: | :white_check_mark: | | | +| | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Auto-Description | :white_check_mark: | :white_check_mark: | | | +| | Improve Code | :white_check_mark: | :white_check_mark: | | | +| | Reflect and Review | :white_check_mark: | | | | +| | Update CHANGELOG.md | :white_check_mark: | | | | +| | | | | | | +| USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | App / webhook | :white_check_mark: | :white_check_mark: | | | +| | Tagging bot | :white_check_mark: | | | | +| | Actions | :white_check_mark: | | | | +| | | | | | | +| CORE | PR compression | :white_check_mark: | :white_check_mark: | :white_check_mark: | | +| | Repo language prioritization | :white_check_mark: | :white_check_mark: | :white_check_mark: | | +| | Adaptive and token-aware
    file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: | | +| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | | +| | Incremental PR Review | :white_check_mark: | | | | Examples for invoking the different tools via the CLI: - **Review**: python cli.py --pr_url= review @@ -153,7 +153,7 @@ Here are some advantages of PR-Agent: - We emphasize **real-life practical usage**. Each tool (review, improve, ask, ...) has a single GPT-4 call, no more. We feel that this is critical for realistic team usage - obtaining an answer quickly (~30 seconds) and affordably. - Our [PR Compression strategy](./PR_COMPRESSION.md) is a core ability that enables to effectively tackle both short and long PRs. - Our JSON prompting strategy enables to have **modular, customizable tools**. For example, the '/review' tool categories can be controlled via the [configuration](./CONFIGURATION.md) file. Adding additional categories is easy and accessible. -- We support **multiple git providers** (GitHub, Gitlab, Bitbucket), **multiple ways** to use the tool (CLI, GitHub Action, GitHub App, Docker, ...), and **multiple models** (GPT-4, GPT-3.5, Anthropic, Cohere, Llama2). +- We support **multiple git providers** (GitHub, Gitlab, Bitbucket, CodeCommit), **multiple ways** to use the tool (CLI, GitHub Action, GitHub App, Docker, ...), and **multiple models** (GPT-4, GPT-3.5, Anthropic, Cohere, Llama2). - We are open-source, and welcome contributions from the community. diff --git a/pr_agent/git_providers/__init__.py b/pr_agent/git_providers/__init__.py index e7c2aa0f..dddf58c8 100644 --- a/pr_agent/git_providers/__init__.py +++ b/pr_agent/git_providers/__init__.py @@ -1,5 +1,6 @@ from pr_agent.config_loader import get_settings from pr_agent.git_providers.bitbucket_provider import BitbucketProvider +from pr_agent.git_providers.codecommit_provider import CodeCommitProvider from pr_agent.git_providers.github_provider import GithubProvider from pr_agent.git_providers.gitlab_provider import GitLabProvider from pr_agent.git_providers.local_git_provider import LocalGitProvider @@ -8,6 +9,7 @@ _GIT_PROVIDERS = { 'github': GithubProvider, 'gitlab': GitLabProvider, 'bitbucket': BitbucketProvider, + 'codecommit': CodeCommitProvider, 'local' : LocalGitProvider } diff --git a/pr_agent/git_providers/codecommit_client.py b/pr_agent/git_providers/codecommit_client.py new file mode 100644 index 00000000..c1cfa763 --- /dev/null +++ b/pr_agent/git_providers/codecommit_client.py @@ -0,0 +1,203 @@ +import boto3 +import botocore + + +class CodeCommitDifferencesResponse: + """ + CodeCommitDifferencesResponse is the response object returned from our get_differences() function. + It maps the JSON response to member variables of this class. + """ + + def __init__(self, json: dict): + before_blob = json.get("beforeBlob", {}) + after_blob = json.get("afterBlob", {}) + + self.before_blob_id = before_blob.get("blobId", "") + self.before_blob_path = before_blob.get("path", "") + self.after_blob_id = after_blob.get("blobId", "") + self.after_blob_path = after_blob.get("path", "") + self.change_type = json.get("changeType", "") + + +class CodeCommitPullRequestResponse: + """ + CodeCommitPullRequestResponse is the response object returned from our get_pr() function. + It maps the JSON response to member variables of this class. + """ + + def __init__(self, json: dict): + self.title = json.get("title", "") + self.description = json.get("description", "") + + self.targets = [] + for target in json.get("pullRequestTargets", []): + self.targets.append(CodeCommitPullRequestResponse.CodeCommitPullRequestTarget(target)) + + class CodeCommitPullRequestTarget: + """ + CodeCommitPullRequestTarget is a subclass of CodeCommitPullRequestResponse that + holds details about an individual target commit. + """ + + def __init__(self, json: dict): + self.source_commit = json.get("sourceCommit", "") + self.source_branch = json.get("sourceReference", "") + self.destination_commit = json.get("destinationCommit", "") + self.destination_branch = json.get("destinationReference", "") + + +class CodeCommitClient: + """ + CodeCommitClient is a wrapper around the AWS boto3 SDK for the CodeCommit client + """ + + def __init__(self): + self.boto_client = None + + def _connect_boto_client(self): + try: + self.boto_client = boto3.client("codecommit") + except Exception as e: + raise ValueError(f"Failed to connect to AWS CodeCommit: {e}") + + def get_differences(self, repo_name: int, destination_commit: str, source_commit: str): + """ + Get the differences between two commits in CodeCommit. + + Parameters: + - repo_name: Name of the repository + - destination_commit: Commit hash you want to merge into (the "before" hash) (usually on the main or master branch) + - source_commit: Commit hash of the code you are adding (the "after" branch) + + Returns: + - List of CodeCommitDifferencesResponse objects + + Boto3 Documentation: + aws codecommit get-differences + https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_differences.html + """ + if self.boto_client is None: + self._connect_boto_client() + + # The differences response from AWS is paginated, so we need to iterate through the pages to get all the differences. + differences = [] + try: + paginator = self.boto_client.get_paginator("get_differences") + for page in paginator.paginate( + repositoryName=repo_name, + beforeCommitSpecifier=destination_commit, + afterCommitSpecifier=source_commit, + ): + differences.extend(page.get("differences", [])) + except botocore.exceptions.ClientError as e: + raise ValueError(f"Failed to retrieve differences from CodeCommit PR #{self.pr_num}") from e + + output = [] + for json in differences: + output.append(CodeCommitDifferencesResponse(json)) + return output + + def get_file(self, repo_name: str, file_path: str, sha_hash: str, optional: bool = False): + """ + Retrieve a file from CodeCommit. + + Parameters: + - repo_name: Name of the repository + - file_path: Path to the file you are retrieving + - sha_hash: Commit hash of the file you are retrieving + + Returns: + - File contents + + Boto3 Documentation: + aws codecommit get_file + https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_file.html + """ + if not file_path: + return "" + + if self.boto_client is None: + self._connect_boto_client() + + try: + response = self.boto_client.get_file(repositoryName=repo_name, commitSpecifier=sha_hash, filePath=file_path) + except botocore.exceptions.ClientError as e: + # if the file does not exist, but is flagged as optional, then return an empty string + if optional and e.response["Error"]["Code"] == 'FileDoesNotExistException': + return "" + raise ValueError(f"CodeCommit cannot retrieve file '{file_path}' from repository '{repo_name}'") from e + except Exception as e: + raise ValueError(f"CodeCommit cannot retrieve file '{file_path}' from repository '{repo_name}'") from e + if "fileContent" not in response: + raise ValueError(f"File content is empty for file: {file_path}") + + return response.get("fileContent", "") + + def get_pr(self, pr_number: int): + """ + Get a information about a CodeCommit PR. + + Parameters: + - pr_number: The PR number you are requesting + + Returns: + - CodeCommitPullRequestResponse object + + Boto3 Documentation: + aws codecommit get_pull_request + https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_pull_request.html + """ + if self.boto_client is None: + self._connect_boto_client() + + try: + response = self.boto_client.get_pull_request(pullRequestId=str(pr_number)) + except botocore.exceptions.ClientError as e: + if e.response["Error"]["Code"] == 'PullRequestDoesNotExistException': + raise ValueError(f"CodeCommit cannot retrieve PR: PR number does not exist: {pr_number}") from e + raise ValueError(f"CodeCommit cannot retrieve PR: {pr_number}: boto client error") from e + except Exception as e: + raise ValueError(f"CodeCommit cannot retrieve PR: {pr_number}") from e + + if "pullRequest" not in response: + raise ValueError("CodeCommit PR number not found: {pr_number}") + + return CodeCommitPullRequestResponse(response.get("pullRequest", {})) + + def publish_comment(self, repo_name: str, pr_number: int, destination_commit: str, source_commit: str, comment: str): + """ + Publish a comment to a pull request + + Parameters: + - repo_name: name of the repository + - pr_number: number of the pull request + - destination_commit: The commit hash you want to merge into (the "before" hash) (usually on the main or master branch) + - source_commit: The commit hash of the code you are adding (the "after" branch) + - pr_comment: comment + + Returns: + - None + + Boto3 Documentation: + aws codecommit post_comment_for_pull_request + https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/post_comment_for_pull_request.html + """ + if self.boto_client is None: + self._connect_boto_client() + + try: + self.boto_client.post_comment_for_pull_request( + pullRequestId=str(pr_number), + repositoryName=repo_name, + beforeCommitId=destination_commit, + afterCommitId=source_commit, + content=comment, + ) + except botocore.exceptions.ClientError as e: + if e.response["Error"]["Code"] == 'RepositoryDoesNotExistException': + raise ValueError(f"Repository does not exist: {repo_name}") from e + if e.response["Error"]["Code"] == 'PullRequestDoesNotExistException': + raise ValueError(f"PR number does not exist: {pr_number}") from e + raise ValueError(f"Boto3 client error calling post_comment_for_pull_request") from e + except Exception as e: + raise ValueError(f"Error calling post_comment_for_pull_request") from e diff --git a/pr_agent/git_providers/codecommit_provider.py b/pr_agent/git_providers/codecommit_provider.py new file mode 100644 index 00000000..a747e7f2 --- /dev/null +++ b/pr_agent/git_providers/codecommit_provider.py @@ -0,0 +1,363 @@ +import logging +import os +from collections import Counter +from typing import List, Optional, Tuple +from urllib.parse import urlparse + +from ..algo.language_handler import is_valid_file, language_extension_map +from ..algo.pr_processing import clip_tokens +from ..algo.utils import load_large_diff +from ..config_loader import get_settings +from .git_provider import EDIT_TYPE, FilePatchInfo, GitProvider, IncrementalPR +from pr_agent.git_providers.codecommit_client import CodeCommitClient + + +class PullRequestCCMimic: + """ + This class mimics the PullRequest class from the PyGithub library for the CodeCommitProvider. + """ + + def __init__(self, title: str, diff_files: List[FilePatchInfo]): + self.title = title + self.diff_files = diff_files + self.description = None + self.source_commit = None + self.source_branch = None # the branch containing your new code changes + self.destination_commit = None + self.destination_branch = None # the branch you are going to merge into + + +class CodeCommitFile: + """ + This class represents a file in a pull request in CodeCommit. + """ + + def __init__( + self, + a_path: str, + a_blob_id: str, + b_path: str, + b_blob_id: str, + edit_type: EDIT_TYPE, + ): + self.a_path = a_path + self.a_blob_id = a_blob_id + self.b_path = b_path + self.b_blob_id = b_blob_id + self.edit_type: EDIT_TYPE = edit_type + self.filename = b_path if b_path else a_path + + +class CodeCommitProvider(GitProvider): + """ + This class implements the GitProvider interface for AWS CodeCommit repositories. + """ + + def __init__(self, pr_url: Optional[str] = None, incremental: Optional[bool] = False): + self.codecommit_client = CodeCommitClient() + self.aws_client = None + self.repo_name = None + self.pr_num = None + self.pr = None + self.diff_files = None + self.git_files = None + if pr_url: + self.set_pr(pr_url) + + def provider_name(self): + return "CodeCommit" + + def is_supported(self, capability: str) -> bool: + if capability in [ + "get_issue_comments", + "create_inline_comment", + "publish_inline_comments", + "get_labels", + ]: + return False + return True + + def set_pr(self, pr_url: str): + self.repo_name, self.pr_num = self._parse_pr_url(pr_url) + self.pr = self._get_pr() + + def get_files(self) -> list[CodeCommitFile]: + # bring files from CodeCommit only once + if self.git_files: + return self.git_files + + self.git_files = [] + differences = self.codecommit_client.get_differences(self.repo_name, self.pr.destination_commit, self.pr.source_commit) + for item in differences: + self.git_files.append(CodeCommitFile(item.before_blob_path, + item.before_blob_id, + item.after_blob_path, + item.after_blob_id, + CodeCommitProvider._get_edit_type(item.change_type))) + return self.git_files + + def get_diff_files(self) -> list[FilePatchInfo]: + """ + Retrieves the list of files that have been modified, added, deleted, or renamed in a pull request in CodeCommit, + along with their content and patch information. + + Returns: + diff_files (List[FilePatchInfo]): List of FilePatchInfo objects representing the modified, added, deleted, + or renamed files in the merge request. + """ + # bring files from CodeCommit only once + if self.diff_files: + return self.diff_files + + self.diff_files = [] + + files = self.get_files() + for diff_item in files: + patch_filename = "" + if diff_item.a_blob_id is not None: + patch_filename = diff_item.a_path + original_file_content_str = self.codecommit_client.get_file( + self.repo_name, diff_item.a_path, self.pr.destination_commit) + if isinstance(original_file_content_str, (bytes, bytearray)): + original_file_content_str = original_file_content_str.decode("utf-8") + else: + original_file_content_str = "" + + if diff_item.b_blob_id is not None: + patch_filename = diff_item.b_path + new_file_content_str = self.codecommit_client.get_file(self.repo_name, diff_item.b_path, self.pr.source_commit) + if isinstance(new_file_content_str, (bytes, bytearray)): + new_file_content_str = new_file_content_str.decode("utf-8") + else: + new_file_content_str = "" + + patch = load_large_diff(patch_filename, new_file_content_str, original_file_content_str) + + # Store the diffs as a list of FilePatchInfo objects + info = FilePatchInfo( + original_file_content_str, + new_file_content_str, + patch, + diff_item.b_path, + edit_type=diff_item.edit_type, + old_filename=None + if diff_item.a_path == diff_item.b_path + else diff_item.a_path, + ) + # Only add valid files to the diff list + # "bad extensions" are set in the language_extensions.toml file + # a "valid file" is one that is not in the "bad extensions" list + if is_valid_file(info.filename): + self.diff_files.append(info) + + return self.diff_files + + def publish_description(self, pr_title: str, pr_body: str): + return "" # not implemented yet + + def publish_comment(self, pr_comment: str, is_temporary: bool = False): + if is_temporary: + logging.info(pr_comment) + return + + try: + self.codecommit_client.publish_comment( + repo_name=self.repo_name, + pr_number=str(self.pr_num), + destination_commit=self.pr.destination_commit, + source_commit=self.pr.source_commit, + comment=pr_comment, + ) + except Exception as e: + raise ValueError(f"CodeCommit Cannot post comment for PR: {self.pr_num}") from e + + def publish_code_suggestions(self, code_suggestions: list) -> bool: + return [""] # not implemented yet + + def publish_labels(self, labels): + return [""] # not implemented yet + + def get_labels(self): + return [""] # not implemented yet + + def remove_initial_comment(self): + return "" # not implemented yet + + def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str): + raise NotImplementedError("CodeCommit provider does not support publishing inline comments yet") + + def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str): + raise NotImplementedError("CodeCommit provider does not support creating inline comments yet") + + def publish_inline_comments(self, comments: list[dict]): + raise NotImplementedError("CodeCommit provider does not support publishing inline comments yet") + + def get_title(self): + return self.pr.get("title", "") + + def get_languages(self): + """ + Returns a dictionary of languages, containing the percentage of each language used in the PR. + + Returns: + dict: A dictionary where each key is a language name and the corresponding value is the percentage of that language in the PR. + """ + commit_files = self.get_files() + filenames = [ item.filename for item in commit_files ] + extensions = CodeCommitProvider._get_file_extensions(filenames) + + # Calculate the percentage of each file extension in the PR + percentages = CodeCommitProvider._get_language_percentages(extensions) + + # The global language_extension_map is a dictionary of languages, + # where each dictionary item is a BoxList of extensions. + # We want a dictionary of extensions, + # where each dictionary item is a language name. + # We build that language->extension dictionary here in main_extensions_flat. + main_extensions_flat = {} + for language, extensions in language_extension_map.items(): + for ext in extensions: + main_extensions_flat[ext] = language + + # Map the file extension/languages to percentages + languages = {} + for ext, pct in percentages.items(): + languages[main_extensions_flat.get(ext, "")] = pct + + return languages + + def get_pr_branch(self): + return self.pr.source_branch + + def get_pr_description_full(self) -> str: + return self.pr.description + + def get_user_id(self): + return -1 # not implemented yet + + def get_issue_comments(self): + raise NotImplementedError("CodeCommit provider does not support issue comments yet") + + def get_repo_settings(self): + # a local ".pr_agent.toml" settings file is optional + settings_filename = ".pr_agent.toml" + return self.codecommit_client.get_file(self.repo_name, settings_filename, self.pr.source_commit, optional=True) + + def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]: + return True + + def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool: + return True + + @staticmethod + def _parse_pr_url(pr_url: str) -> Tuple[str, int]: + # Example PR URL: + # https://us-east-1.console.aws.amazon.com/codesuite/codecommit/repositories/__MY_REPO__/pull-requests/123456" + parsed_url = urlparse(pr_url) + + if "us-east-1.console.aws.amazon.com" not in parsed_url.netloc: + raise ValueError(f"The provided URL is not a valid CodeCommit URL: {pr_url}") + + path_parts = parsed_url.path.strip("/").split("/") + + if ( + len(path_parts) < 6 + or path_parts[0] != "codesuite" + or path_parts[1] != "codecommit" + or path_parts[2] != "repositories" + or path_parts[4] != "pull-requests" + ): + raise ValueError(f"The provided URL does not appear to be a CodeCommit PR URL: {pr_url}") + + repo_name = path_parts[3] + + try: + pr_number = int(path_parts[5]) + except ValueError as e: + raise ValueError(f"Unable to convert PR number to integer: '{path_parts[5]}'") from e + + return repo_name, pr_number + + def _get_pr(self): + response = self.codecommit_client.get_pr(self.pr_num) + + if len(response.targets) == 0: + raise ValueError(f"No files found in CodeCommit PR: {self.pr_num}") + + # TODO: implement support for multiple commits in one CodeCommit PR + # for now, we are only using the first commit in the PR + if len(response.targets) > 1: + logging.warning( + "Multiple commits in one PR is not supported for CodeCommit yet. Continuing, using the first commit only..." + ) + + # Return our object that mimics PullRequest class from the PyGithub library + # (This strategy was copied from the LocalGitProvider) + mimic = PullRequestCCMimic(response.title, self.diff_files) + mimic.description = response.description + mimic.source_commit = response.targets[0].source_commit + mimic.source_branch = response.targets[0].source_branch + mimic.destination_commit = response.targets[0].destination_commit + mimic.destination_branch = response.targets[0].destination_branch + + return mimic + + def get_commit_messages(self): + return "" # not implemented yet + + @staticmethod + def _get_edit_type(codecommit_change_type): + """ + Convert the CodeCommit change type string to the EDIT_TYPE enum. + The CodeCommit change type string is returned from the get_differences SDK method. + + Returns: + An EDIT_TYPE enum representing the modified, added, deleted, or renamed file in the PR diff. + """ + t = codecommit_change_type.upper() + edit_type = None + if t == "A": + edit_type = EDIT_TYPE.ADDED + elif t == "D": + edit_type = EDIT_TYPE.DELETED + elif t == "M": + edit_type = EDIT_TYPE.MODIFIED + elif t == "R": + edit_type = EDIT_TYPE.RENAMED + return edit_type + + @staticmethod + def _get_file_extensions(filenames): + """ + Return a list of file extensions from a list of filenames. + The returned extensions will include the dot "." prefix, + to accommodate for the dots in the existing language_extension_map settings. + Filenames with no extension will return an empty string for the extension. + """ + extensions = [] + for filename in filenames: + filename, ext = os.path.splitext(filename) + if ext: + extensions.append(ext.lower()) + else: + extensions.append("") + return extensions + + @staticmethod + def _get_language_percentages(extensions): + """ + Return a dictionary containing the programming language name (as the key), + and the percentage that language is used (as the value), + given a list of file extensions. + """ + total_files = len(extensions) + if total_files == 0: + return {} + + # Identify language by file extension and count + lang_count = Counter(extensions) + # Convert counts to percentages + lang_percentage = { + lang: round(count / total_files * 100) for lang, count in lang_count.items() + } + return lang_percentage diff --git a/pyproject.toml b/pyproject.toml index 2e8f2b5c..802cd0d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,8 @@ dependencies = [ "GitPython~=3.1.32", "starlette-context==0.3.6", "litellm~=0.1.351", - "PyYAML==6.0" + "PyYAML==6.0", + "boto3~=1.28.25" ] [project.urls] diff --git a/requirements.txt b/requirements.txt index 470fc6ef..f7af1669 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,3 +15,4 @@ litellm~=0.1.351 PyYAML==6.0 starlette-context==0.3.6 litellm~=0.1.351 +boto3~=1.28.25 diff --git a/tests/unittest/test_codecommit_client.py b/tests/unittest/test_codecommit_client.py new file mode 100644 index 00000000..5d09bdd1 --- /dev/null +++ b/tests/unittest/test_codecommit_client.py @@ -0,0 +1,136 @@ +from unittest.mock import MagicMock +from pr_agent.git_providers.codecommit_client import CodeCommitClient + + +class TestCodeCommitProvider: + def test_get_differences(self): + # Create a mock CodeCommitClient instance and codecommit_client member + api = CodeCommitClient() + api.boto_client = MagicMock() + + # Mock the response from the AWS client for get_differences method + api.boto_client.get_paginator.return_value.paginate.return_value = [ + { + "differences": [ + { + "beforeBlob": { + "path": "file1.py", + "blobId": "291b15c3ab4219e43a5f4f9091e5a97ee9d7400b", + }, + "afterBlob": { + "path": "file1.py", + "blobId": "46ad86582da03cc34c804c24b17976571bca1eba", + }, + "changeType": "M", + }, + { + "beforeBlob": {"path": "", "blobId": ""}, + "afterBlob": { + "path": "file2.py", + "blobId": "2404c7874fcbd684d6779c1420072f088647fd79", + }, + "changeType": "A", + }, + { + "beforeBlob": { + "path": "file3.py", + "blobId": "9af7989045ce40e9478ebb8089dfbadac19a9cde", + }, + "afterBlob": {"path": "", "blobId": ""}, + "changeType": "D", + }, + { + "beforeBlob": { + "path": "file5.py", + "blobId": "738e36eec120ef9d6393a149252698f49156d5b4", + }, + "afterBlob": { + "path": "file6.py", + "blobId": "faecdb85f7ba199df927a783b261378a1baeca85", + }, + "changeType": "R", + }, + ] + } + ] + + diffs = api.get_differences("my_test_repo", "commit1", "commit2") + + assert len(diffs) == 4 + assert diffs[0].before_blob_path == "file1.py" + assert diffs[0].before_blob_id == "291b15c3ab4219e43a5f4f9091e5a97ee9d7400b" + assert diffs[0].after_blob_path == "file1.py" + assert diffs[0].after_blob_id == "46ad86582da03cc34c804c24b17976571bca1eba" + assert diffs[0].change_type == "M" + assert diffs[1].before_blob_path == "" + assert diffs[1].before_blob_id == "" + assert diffs[1].after_blob_path == "file2.py" + assert diffs[1].after_blob_id == "2404c7874fcbd684d6779c1420072f088647fd79" + assert diffs[1].change_type == "A" + assert diffs[2].before_blob_path == "file3.py" + assert diffs[2].before_blob_id == "9af7989045ce40e9478ebb8089dfbadac19a9cde" + assert diffs[2].after_blob_path == "" + assert diffs[2].after_blob_id == "" + assert diffs[2].change_type == "D" + assert diffs[3].before_blob_path == "file5.py" + assert diffs[3].before_blob_id == "738e36eec120ef9d6393a149252698f49156d5b4" + assert diffs[3].after_blob_path == "file6.py" + assert diffs[3].after_blob_id == "faecdb85f7ba199df927a783b261378a1baeca85" + assert diffs[3].change_type == "R" + + def test_get_file(self): + # Create a mock CodeCommitClient instance and codecommit_client member + api = CodeCommitClient() + api.boto_client = MagicMock() + + # Mock the response from the AWS client for get_pull_request method + # def get_file(self, repo_name: str, file_path: str, sha_hash: str): + api.boto_client.get_file.return_value = { + "commitId": "6335d6d4496e8d50af559560997604bb03abc122", + "blobId": "c172209495d7968a8fdad76469564fb708460bc1", + "filePath": "requirements.txt", + "fileSize": 65, + "fileContent": b"boto3==1.28.25\ndynaconf==3.1.12\nfastapi==0.99.0\nPyGithub==1.59.*\n", + } + + repo_name = "my_test_repo" + file_path = "requirements.txt" + sha_hash = "84114a356ece1e5b7637213c8e486fea7c254656" + content = api.get_file(repo_name, file_path, sha_hash) + + assert len(content) == 65 + assert content == b"boto3==1.28.25\ndynaconf==3.1.12\nfastapi==0.99.0\nPyGithub==1.59.*\n" + assert content.decode("utf-8") == "boto3==1.28.25\ndynaconf==3.1.12\nfastapi==0.99.0\nPyGithub==1.59.*\n" + + def test_get_pr(self): + # Create a mock CodeCommitClient instance and codecommit_client member + api = CodeCommitClient() + api.boto_client = MagicMock() + + # Mock the response from the AWS client for get_pull_request method + api.boto_client.get_pull_request.return_value = { + "pullRequest": { + "pullRequestId": "3", + "title": "My PR", + "description": "My PR description", + "pullRequestTargets": [ + { + "sourceCommit": "commit1", + "sourceReference": "branch1", + "destinationCommit": "commit2", + "destinationReference": "branch2", + "repositoryName": "my_test_repo", + } + ], + } + } + + pr = api.get_pr(321) + + assert pr.title == "My PR" + assert pr.description == "My PR description" + assert len(pr.targets) == 1 + assert pr.targets[0].source_commit == "commit1" + assert pr.targets[0].source_branch == "branch1" + assert pr.targets[0].destination_commit == "commit2" + assert pr.targets[0].destination_branch == "branch2" diff --git a/tests/unittest/test_codecommit_provider.py b/tests/unittest/test_codecommit_provider.py new file mode 100644 index 00000000..e35f7250 --- /dev/null +++ b/tests/unittest/test_codecommit_provider.py @@ -0,0 +1,119 @@ +import pytest +from pr_agent.git_providers.codecommit_provider import CodeCommitFile +from pr_agent.git_providers.codecommit_provider import CodeCommitProvider +from pr_agent.git_providers.git_provider import EDIT_TYPE + + +class TestCodeCommitFile: + # Test that a CodeCommitFile object is created successfully with valid parameters. + # Generated by CodiumAI + def test_valid_parameters(self): + a_path = "path/to/file_a" + a_blob_id = "12345" + b_path = "path/to/file_b" + b_blob_id = "67890" + edit_type = EDIT_TYPE.ADDED + + file = CodeCommitFile(a_path, a_blob_id, b_path, b_blob_id, edit_type) + + assert file.a_path == a_path + assert file.a_blob_id == a_blob_id + assert file.b_path == b_path + assert file.b_blob_id == b_blob_id + assert file.edit_type == edit_type + assert file.filename == b_path + + +class TestCodeCommitProvider: + def test_parse_pr_url(self): + url = "https://us-east-1.console.aws.amazon.com/codesuite/codecommit/repositories/my_test_repo/pull-requests/321" + repo_name, pr_number = CodeCommitProvider._parse_pr_url(url) + assert repo_name == "my_test_repo" + assert pr_number == 321 + + # Test that an error is raised when an invalid CodeCommit URL is provided to the set_pr() method of the CodeCommitProvider class. + # Generated by CodiumAI + def test_invalid_codecommit_url(self): + provider = CodeCommitProvider() + with pytest.raises(ValueError): + provider.set_pr("https://example.com/codecommit/repositories/my_test_repo/pull-requests/4321") + + def test_get_file_extensions(self): + filenames = [ + "app.py", + "cli.py", + "composer.json", + "composer.lock", + "hello.py", + "image1.jpg", + "image2.JPG", + "index.js", + "provider.py", + "README", + "test.py", + ] + expected_extensions = [ + ".py", + ".py", + ".json", + ".lock", + ".py", + ".jpg", + ".jpg", + ".js", + ".py", + "", + ".py", + ] + extensions = CodeCommitProvider._get_file_extensions(filenames) + assert extensions == expected_extensions + + def test_get_language_percentages(self): + extensions = [ + ".py", + ".py", + ".json", + ".lock", + ".py", + ".jpg", + ".jpg", + ".js", + ".py", + "", + ".py", + ] + percentages = CodeCommitProvider._get_language_percentages(extensions) + assert percentages[".py"] == 45 + assert percentages[".json"] == 9 + assert percentages[".lock"] == 9 + assert percentages[".jpg"] == 18 + assert percentages[".js"] == 9 + assert percentages[""] == 9 + + # The _get_file_extensions function needs the "." prefix on the extension, + # but the _get_language_percentages function will work with or without the "." prefix + extensions = [ + "txt", + "py", + "py", + ] + percentages = CodeCommitProvider._get_language_percentages(extensions) + assert percentages["py"] == 67 + assert percentages["txt"] == 33 + + # test an empty list + percentages = CodeCommitProvider._get_language_percentages([]) + assert percentages == {} + + def test_get_edit_type(self): + assert CodeCommitProvider._get_edit_type("A") == EDIT_TYPE.ADDED + assert CodeCommitProvider._get_edit_type("D") == EDIT_TYPE.DELETED + assert CodeCommitProvider._get_edit_type("M") == EDIT_TYPE.MODIFIED + assert CodeCommitProvider._get_edit_type("R") == EDIT_TYPE.RENAMED + + assert CodeCommitProvider._get_edit_type("a") == EDIT_TYPE.ADDED + assert CodeCommitProvider._get_edit_type("d") == EDIT_TYPE.DELETED + assert CodeCommitProvider._get_edit_type("m") == EDIT_TYPE.MODIFIED + assert CodeCommitProvider._get_edit_type("r") == EDIT_TYPE.RENAMED + + assert CodeCommitProvider._get_edit_type("X") is None From 3a93dcd6a75cd8213d7e41c49108bc0400285fd6 Mon Sep 17 00:00:00 2001 From: Ori Kotek Date: Wed, 23 Aug 2023 00:37:04 +0300 Subject: [PATCH 38/97] Add build and test on pull request open, reopen --- .github/workflows/build-and-test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 960da61b..114bbb7e 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -2,6 +2,8 @@ name: Build-and-test on: push: + pull_request: + types: [ opened, reopened ] jobs: build-and-test: From 16150e9c84dfe1482fc58e39a72122f52649cc2e Mon Sep 17 00:00:00 2001 From: mrT23 Date: Wed, 23 Aug 2023 09:19:15 +0300 Subject: [PATCH 39/97] update litellm --- pr_agent/algo/ai_handler.py | 2 -- pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pr_agent/algo/ai_handler.py b/pr_agent/algo/ai_handler.py index 4e1f86e3..1a12564b 100644 --- a/pr_agent/algo/ai_handler.py +++ b/pr_agent/algo/ai_handler.py @@ -48,8 +48,6 @@ class AiHandler: litellm.replicate_key = get_settings().replicate.key if get_settings().get("HUGGINGFACE.KEY", None): litellm.huggingface_key = get_settings().huggingface.key - if get_settings().get("HUGGINGFACE.KEY", None): - litellm.huggingface_key = get_settings().huggingface.key except AttributeError as e: raise ValueError("OpenAI key is required") from e diff --git a/pyproject.toml b/pyproject.toml index 802cd0d8..9a945dca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ dependencies = [ "atlassian-python-api==3.39.0", "GitPython~=3.1.32", "starlette-context==0.3.6", - "litellm~=0.1.351", + "litellm~=0.1.445", "PyYAML==6.0", "boto3~=1.28.25" ] From 52ba2793cd67c4850c64aeda4f0bc0ee104f4eaa Mon Sep 17 00:00:00 2001 From: szecsip Date: Wed, 23 Aug 2023 15:59:49 +0000 Subject: [PATCH 40/97] modify get_main_pr_language to handle azuredevops provided language format --- pr_agent/git_providers/git_provider.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pr_agent/git_providers/git_provider.py b/pr_agent/git_providers/git_provider.py index 2a891938..e8c1c8a8 100644 --- a/pr_agent/git_providers/git_provider.py +++ b/pr_agent/git_providers/git_provider.py @@ -1,3 +1,4 @@ +import logging from abc import ABC, abstractmethod from dataclasses import dataclass @@ -112,6 +113,8 @@ def get_main_pr_language(languages, files) -> str: # validate that the specific commit uses the main language extension_list = [] for file in files: + if isinstance(file, str): + file = FilePatchInfo(base_file=None, head_file=None, patch=None, filename=file) extension_list.append(file.filename.rsplit('.')[-1]) # get the most common extension @@ -133,10 +136,12 @@ def get_main_pr_language(languages, files) -> str: most_common_extension == 'scala' and top_language == 'scala' or \ most_common_extension == 'kt' and top_language == 'kotlin' or \ most_common_extension == 'pl' and top_language == 'perl' or \ - most_common_extension == 'swift' and top_language == 'swift': + most_common_extension == 'swift' and top_language == 'swift' or \ + most_common_extension == top_language: main_language_str = top_language - except Exception: + except Exception as e: + logging.info(e) pass return main_language_str From 01d1cf98f4be1b4cdefb1ebbee1ed0d5e1b17749 Mon Sep 17 00:00:00 2001 From: szecsip Date: Wed, 23 Aug 2023 16:01:10 +0000 Subject: [PATCH 41/97] init Azure DevOps git provider --- .../git_providers/azuredevops_provider.py | 161 +++++++++++++----- 1 file changed, 123 insertions(+), 38 deletions(-) diff --git a/pr_agent/git_providers/azuredevops_provider.py b/pr_agent/git_providers/azuredevops_provider.py index 1af45cd4..7290810c 100644 --- a/pr_agent/git_providers/azuredevops_provider.py +++ b/pr_agent/git_providers/azuredevops_provider.py @@ -1,23 +1,25 @@ +import json import logging from typing import Optional, Tuple from urllib.parse import urlparse import os -import requests - from msrest.authentication import BasicAuthentication from azure.devops.connection import Connection +from azure.devops.v7_0.git.models import Comment, CommentThread, GitVersionDescriptor, GitPullRequest from ..algo.pr_processing import clip_tokens from ..config_loader import get_settings -from .git_provider import FilePatchInfo +from ..algo.utils import load_large_diff +from ..algo.language_handler import is_valid_file +from .git_provider import EDIT_TYPE, FilePatchInfo + class AzureDevopsProvider: def __init__(self, pr_url: Optional[str] = None, incremental: Optional[bool] = False): self.azure_devops_client = self._get_azure_devops_client() - logging.info(self.azure_devops_client) self.workspace_slug = None self.repo_slug = None @@ -40,9 +42,10 @@ class AzureDevopsProvider: def get_repo_settings(self): try: - contents = self.azure_devops_client.get_item_content(repository_id=self.repo_slug, project=self.workspace_slug, download=False, include_content_metadata=False, include_content=True, path=".pr_agent.toml") - logging.info("get repo settings") - logging.info(contents) + contents = self.azure_devops_client.get_item_content(repository_id=self.repo_slug, + project=self.workspace_slug, download=False, + include_content_metadata=False, include_content=True, + path=".pr_agent.toml") return contents except Exception as e: logging.info("get repo settings error") @@ -51,42 +54,121 @@ class AzureDevopsProvider: def get_files(self): files = [] - for i in self.azure_devops_client.get_pull_request_commits(project=self.workspace_slug, repository_id=self.repo_slug, pull_request_id=self.pr_num): - #logging.info(i) - changes_obj = self.azure_devops_client.get_changes(project=self.workspace_slug, repository_id=self.repo_slug, commit_id=i.commit_id) - #logging.info(changes_obj) - #logging.info("***********") + for i in self.azure_devops_client.get_pull_request_commits(project=self.workspace_slug, + repository_id=self.repo_slug, + pull_request_id=self.pr_num): + + changes_obj = self.azure_devops_client.get_changes(project=self.workspace_slug, + repository_id=self.repo_slug, commit_id=i.commit_id) + for c in changes_obj.changes: files.append(c['item']['path']) - #logging.info("###########") - return files + return list(set(files)) def get_diff_files(self) -> list[FilePatchInfo]: - diffs = self.pr.diffstat() - diff_split = ['diff --git%s' % x for x in self.pr.diff().split('diff --git') if x.strip()] - - diff_files = [] - for index, diff in enumerate(diffs): - original_file_content_str = self._get_pr_file_content(diff.old.get_data('links')) - new_file_content_str = self._get_pr_file_content(diff.new.get_data('links')) - diff_files.append(FilePatchInfo(original_file_content_str, new_file_content_str, - diff_split[index], diff.new.path)) - return diff_files + try: + base_sha = self.pr.last_merge_target_commit + head_sha = self.pr.last_merge_source_commit + + commits = self.azure_devops_client.get_pull_request_commits(project=self.workspace_slug, + repository_id=self.repo_slug, + pull_request_id=self.pr_num) + + diff_files = [] + diffs = [] + diff_types = {} + + for c in commits: + changes_obj = self.azure_devops_client.get_changes(project=self.workspace_slug, + repository_id=self.repo_slug, commit_id=c.commit_id) + for i in changes_obj.changes: + logging.info(i) + diffs.append(i['item']['path']) + diff_types[i['item']['path']] = i['changeType'] + + diffs = list(set(diffs)) + + for file in diffs: + if not is_valid_file(file): + continue + + version = GitVersionDescriptor(version=head_sha.commit_id, version_type='commit') + new_file_content_str = self.azure_devops_client.get_item(repository_id=self.repo_slug, + path=file, + project=self.workspace_slug, + version_descriptor=version, + download=False, + include_content=True) + + new_file_content_str = new_file_content_str.content + + edit_type = EDIT_TYPE.MODIFIED + if diff_types[file] == 'add': + edit_type = EDIT_TYPE.ADDED + elif diff_types[file] == 'delete': + edit_type = EDIT_TYPE.DELETED + elif diff_types[file] == 'rename': + edit_type = EDIT_TYPE.RENAMED + + version = GitVersionDescriptor(version=base_sha.commit_id, version_type='commit') + original_file_content_str = self.azure_devops_client.get_item(repository_id=self.repo_slug, + path=file, + project=self.workspace_slug, + version_descriptor=version, + download=False, + include_content=True) + original_file_content_str = original_file_content_str.content + + patch = load_large_diff(file, new_file_content_str, original_file_content_str) + + diff_files.append(FilePatchInfo(original_file_content_str, new_file_content_str, + patch=patch, + filename=file, + edit_type=edit_type)) + + self.diff_files = diff_files + return diff_files + except Exception as e: + print(f"Error: {str(e)}") + return [] def publish_comment(self, pr_comment: str, is_temporary: bool = False): - comment = self.pr.comment(pr_comment) + comment = Comment(content=pr_comment) + thread = CommentThread(comments=[comment]) + thread_response = self.azure_devops_client.create_thread(comment_thread=thread, project=self.workspace_slug, + repository_id=self.repo_slug, + pull_request_id=self.pr_num) if is_temporary: - self.temp_comments.append(comment['id']) + self.temp_comments.append({'thread_id': thread_response.id, 'comment_id': comment.id}) + + def publish_description(self, pr_title: str, pr_body: str): + try: + updated_pr = GitPullRequest() + updated_pr.title = pr_title + updated_pr.description = pr_body + self.azure_devops_client.update_pull_request(project=self.workspace_slug, + repository_id=self.repo_slug, + pull_request_id=self.pr_num, + git_pull_request_to_update=updated_pr) + except Exception as e: + logging.exception(f"Could not update pull request {self.pr_num} description: {e}") def remove_initial_comment(self): try: for comment in self.temp_comments: - self.pr.delete(f'comments/{comment}') + new_comment_thread = CommentThread(comments=[Comment(content='bumm')]) + # self.azure_devops_client.delete_comment(project=self.workspace_slug, repository_id=self.repo_slug, thread_id=comment['thread_id'], comment_id=comment['comment_id'], pull_request_id=self.pr_num) + + res = self.azure_devops_client.update_thread(project=self.workspace_slug, repository_id=self.repo_slug, + thread_id=comment['thread_id'], + pull_request_id=self.pr_num, + comment_thread=new_comment_thread) + logging.info(res) except Exception as e: logging.exception(f"Failed to remove temp comments, error: {e}") def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str): - pass + raise NotImplementedError("Azure DevOps provider does not support publishing inline comment yet") def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str): raise NotImplementedError("Azure DevOps provider does not support creating inline comments yet") @@ -99,7 +181,9 @@ class AzureDevopsProvider: def get_languages(self): languages = [] - files = self.azure_devops_client.get_items(project=self.workspace_slug, repository_id=self.repo_slug, recursion_level="Full", include_content_metadata=True, include_links=False, download=False) + files = self.azure_devops_client.get_items(project=self.workspace_slug, repository_id=self.repo_slug, + recursion_level="Full", include_content_metadata=True, + include_links=False, download=False) for f in files: if f.git_object_type == 'blob': file_name, file_extension = os.path.splitext(f.path) @@ -113,12 +197,14 @@ class AzureDevopsProvider: total_extensions = sum(extension_counts.values()) extension_percentages = {ext: (count / total_extensions) * 100 for ext, count in extension_counts.items()} - logging.info(extension_percentages) return extension_percentages def get_pr_branch(self): - return self.pr.source_branch + pr_info = self.azure_devops_client.get_pull_request_by_id(project=self.workspace_slug, + pull_request_id=self.pr_num) + source_branch = pr_info.source_ref_name.split('/')[-1] + return source_branch def get_pr_description(self): max_tokens = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None) @@ -141,13 +227,12 @@ class AzureDevopsProvider: @staticmethod def _parse_pr_url(pr_url: str) -> Tuple[str, int]: parsed_url = urlparse(pr_url) - + if 'azure.com' not in parsed_url.netloc: raise ValueError("The provided URL is not a valid Azure DevOps URL") path_parts = parsed_url.path.strip('/').split('/') - logging.info(path_parts) - + if len(path_parts) < 6 or path_parts[4] != 'pullrequest': raise ValueError("The provided URL does not appear to be a Azure DevOps PR URL") @@ -176,13 +261,13 @@ class AzureDevopsProvider: def _get_repo(self): if self.repo is None: - self.repo = self.azure_devops_client.get_repository(project=self.workspace_slug, repository_id=self.repo_slug) - #logging.info(self.repo) + self.repo = self.azure_devops_client.get_repository(project=self.workspace_slug, + repository_id=self.repo_slug) return self.repo def _get_pr(self): - logging.info(self.azure_devops_client.get_pull_request_by_id(pull_request_id=self.pr_num, project=self.workspace_slug)) - return self.azure_devops_client.get_pull_request_by_id(pull_request_id=self.pr_num, project=self.workspace_slug) + self.pr = self.azure_devops_client.get_pull_request_by_id(pull_request_id=self.pr_num, project=self.workspace_slug) + return self.pr def _get_pr_file_content(self, remote_link: str): return "" From 123741faf3fc148828798c04c56ff5eb39d5b59b Mon Sep 17 00:00:00 2001 From: Ori Kotek Date: Thu, 24 Aug 2023 12:10:13 +0300 Subject: [PATCH 42/97] Bitbucket server, WIP --- pr_agent/servers/bitbucket_app.py | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 pr_agent/servers/bitbucket_app.py diff --git a/pr_agent/servers/bitbucket_app.py b/pr_agent/servers/bitbucket_app.py new file mode 100644 index 00000000..3b21ca7a --- /dev/null +++ b/pr_agent/servers/bitbucket_app.py @@ -0,0 +1,51 @@ +import json +import os + +import uvicorn +from fastapi import APIRouter, FastAPI, Request, Response +from starlette.middleware import Middleware +from starlette.responses import JSONResponse +from starlette_context.middleware import RawContextMiddleware + +from pr_agent.config_loader import get_settings + +router = APIRouter() + + +@router.get("/") +async def handle_manifest(request: Request, response: Response): + manifest = open("atlassian-connect.json", "rt").read() + manifest_obj = json.loads(manifest) + return JSONResponse(manifest_obj) + +@router.post("/webhook") +async def handle_github_webhooks(request: Request, response: Response): + data = await request.json() + print(data) + +@router.get("/webhook") +async def handle_github_webhooks(request: Request, response: Response): + return "Webhook server online!" + +@router.post("/installed") +async def handle_installed_webhooks(request: Request, response: Response): + data = await request.json() + print(data) + +@router.post("/uninstalled") +async def handle_uninstalled_webhooks(request: Request, response: Response): + data = await request.json() + print(data) + + +def start(): + get_settings().set("CONFIG.PUBLISH_OUTPUT_PROGRESS", False) + middleware = [Middleware(RawContextMiddleware)] + app = FastAPI(middleware=middleware) + app.include_router(router) + + uvicorn.run(app, host="0.0.0.0", port=int(os.getenv("PORT", "3000"))) + + +if __name__ == '__main__': + start() From 5079daa4ad837cb5336c7f8a6de98e50d43a3c24 Mon Sep 17 00:00:00 2001 From: Ori Kotek Date: Thu, 24 Aug 2023 16:33:51 +0300 Subject: [PATCH 43/97] Bitbucket server, WIP --- pr_agent/git_providers/bitbucket_provider.py | 7 +- pr_agent/secret_providers/__init__.py | 16 ++++ .../google_cloud_storage_secret_provider.py | 35 +++++++++ pr_agent/secret_providers/secret_provider.py | 12 +++ pr_agent/servers/bitbucket_app.py | 74 ++++++++++++++++++- pr_agent/settings/configuration.toml | 1 + pyproject.toml | 4 +- requirements.txt | 2 + 8 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 pr_agent/secret_providers/__init__.py create mode 100644 pr_agent/secret_providers/google_cloud_storage_secret_provider.py create mode 100644 pr_agent/secret_providers/secret_provider.py diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index bee0e351..6da864e4 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -5,6 +5,7 @@ from urllib.parse import urlparse import requests from atlassian.bitbucket import Cloud +from starlette_context import context from ..config_loader import get_settings from .git_provider import FilePatchInfo, GitProvider @@ -13,7 +14,11 @@ from .git_provider import FilePatchInfo, GitProvider class BitbucketProvider(GitProvider): def __init__(self, pr_url: Optional[str] = None, incremental: Optional[bool] = False): s = requests.Session() - s.headers['Authorization'] = f'Bearer {get_settings().get("BITBUCKET.BEARER_TOKEN", None)}' + try: + bearer = context.get("bitbucket_bearer_token", None) + s.headers['Authorization'] = f'Bearer {bearer}' + except Exception: + s.headers['Authorization'] = f'Bearer {get_settings().get("BITBUCKET.BEARER_TOKEN", None)}' s.headers['Content-Type'] = 'application/json' self.headers = s.headers self.bitbucket_client = Cloud(session=s) diff --git a/pr_agent/secret_providers/__init__.py b/pr_agent/secret_providers/__init__.py new file mode 100644 index 00000000..1cc3ea7b --- /dev/null +++ b/pr_agent/secret_providers/__init__.py @@ -0,0 +1,16 @@ +from pr_agent.config_loader import get_settings + + +def get_secret_provider(): + try: + provider_id = get_settings().config.secret_provider + except AttributeError as e: + raise ValueError("secret_provider is a required attribute in the configuration file") from e + try: + if provider_id == 'google_cloud_storage': + from pr_agent.secret_providers.google_cloud_storage_secret_provider import GoogleCloudStorageSecretProvider + return GoogleCloudStorageSecretProvider() + else: + raise ValueError(f"Unknown secret provider: {provider_id}") + except Exception as e: + raise ValueError(f"Failed to initialize secret provider {provider_id}") from e diff --git a/pr_agent/secret_providers/google_cloud_storage_secret_provider.py b/pr_agent/secret_providers/google_cloud_storage_secret_provider.py new file mode 100644 index 00000000..18db5c4b --- /dev/null +++ b/pr_agent/secret_providers/google_cloud_storage_secret_provider.py @@ -0,0 +1,35 @@ +import ujson + +from google.cloud import storage + +from pr_agent.config_loader import get_settings +from pr_agent.git_providers.gitlab_provider import logger +from pr_agent.secret_providers.secret_provider import SecretProvider + + +class GoogleCloudStorageSecretProvider(SecretProvider): + def __init__(self): + try: + self.client = storage.Client.from_service_account_info(ujson.loads(get_settings().google_cloud_storage. + service_account)) + self.bucket_name = get_settings().google_cloud_storage.bucket_name + self.bucket = self.client.bucket(self.bucket_name) + except Exception as e: + logger.error(f"Failed to initialize Google Cloud Storage Secret Provider: {e}") + raise e + + def get_secret(self, secret_name: str) -> str: + try: + blob = self.bucket.blob(secret_name) + return blob.download_as_string() + except Exception as e: + logger.error(f"Failed to get secret {secret_name} from Google Cloud Storage: {e}") + return "" + + def store_secret(self, secret_name: str, secret_value: str): + try: + blob = self.bucket.blob(secret_name) + blob.upload_from_string(secret_value) + except Exception as e: + logger.error(f"Failed to store secret {secret_name} in Google Cloud Storage: {e}") + raise e diff --git a/pr_agent/secret_providers/secret_provider.py b/pr_agent/secret_providers/secret_provider.py new file mode 100644 index 00000000..df1e7780 --- /dev/null +++ b/pr_agent/secret_providers/secret_provider.py @@ -0,0 +1,12 @@ +from abc import ABC, abstractmethod + + +class SecretProvider(ABC): + + @abstractmethod + def get_secret(self, secret_name: str) -> str: + pass + + @abstractmethod + def store_secret(self, secret_name: str, secret_value: str): + pass diff --git a/pr_agent/servers/bitbucket_app.py b/pr_agent/servers/bitbucket_app.py index 3b21ca7a..039fc5c8 100644 --- a/pr_agent/servers/bitbucket_app.py +++ b/pr_agent/servers/bitbucket_app.py @@ -1,16 +1,52 @@ +import hashlib import json +import logging import os +import time +import jwt +import requests import uvicorn from fastapi import APIRouter, FastAPI, Request, Response from starlette.middleware import Middleware from starlette.responses import JSONResponse +from starlette_context import context from starlette_context.middleware import RawContextMiddleware +from pr_agent.agent.pr_agent import PRAgent from pr_agent.config_loader import get_settings +from pr_agent.secret_providers import get_secret_provider router = APIRouter() +secret_provider = get_secret_provider() +async def get_bearer_token(shared_secret: str, client_key: str): + try: + now = int(time.time()) + url = "https://bitbucket.org/site/oauth2/access_token" + canonical_url = "GET&/site/oauth2/access_token&" + qsh = hashlib.sha256(canonical_url.encode("utf-8")).hexdigest() + app_key = get_settings().bitbucket.app_key + + payload = { + "iss": app_key, + "iat": now, + "exp": now + 240, + "qsh": qsh, + "sub": client_key, + } + token = jwt.encode(payload, shared_secret, algorithm="HS256") + payload = 'grant_type=urn%3Abitbucket%3Aoauth2%3Ajwt' + headers = { + 'Authorization': f'JWT {token}', + 'Content-Type': 'application/x-www-form-urlencoded' + } + response = requests.request("POST", url, headers=headers, data=payload) + bearer_token = response.json()["access_token"] + return bearer_token + except Exception as e: + logging.error(f"Failed to get bearer token: {e}") + raise e @router.get("/") async def handle_manifest(request: Request, response: Response): @@ -20,8 +56,24 @@ async def handle_manifest(request: Request, response: Response): @router.post("/webhook") async def handle_github_webhooks(request: Request, response: Response): - data = await request.json() - print(data) + try: + print(request.headers) + data = await request.json() + print(data) + owner = data["data"]["repository"]["owner"]["username"] + secrets = json.loads(secret_provider.get_secret(owner)) + shared_secret = secrets["shared_secret"] + client_key = secrets["client_key"] + bearer_token = await get_bearer_token(shared_secret, client_key) + context['bitbucket_bearer_token'] = bearer_token + event = data["event"] + agent = PRAgent() + if event == "pullrequest:created": + pr_url = data["data"]["pullrequest"]["links"]["html"]["href"] + await agent.handle_request(pr_url, "review") + except Exception as e: + logging.error(f"Failed to handle webhook: {e}") + return JSONResponse({"error": "Unable to handle webhook"}, status_code=500) @router.get("/webhook") async def handle_github_webhooks(request: Request, response: Response): @@ -29,8 +81,21 @@ async def handle_github_webhooks(request: Request, response: Response): @router.post("/installed") async def handle_installed_webhooks(request: Request, response: Response): - data = await request.json() - print(data) + try: + print(request.headers) + data = await request.json() + print(data) + shared_secret = data["sharedSecret"] + client_key = data["clientKey"] + username = data["principal"]["username"] + secrets = { + "shared_secret": shared_secret, + "client_key": client_key + } + secret_provider.store_secret(username, json.dumps(secrets)) + except Exception as e: + logging.error(f"Failed to register user: {e}") + return JSONResponse({"error": "Unable to register user"}, status_code=500) @router.post("/uninstalled") async def handle_uninstalled_webhooks(request: Request, response: Response): @@ -40,6 +105,7 @@ async def handle_uninstalled_webhooks(request: Request, response: Response): def start(): get_settings().set("CONFIG.PUBLISH_OUTPUT_PROGRESS", False) + get_settings().set("CONFIG.GIT_PROVIDER", "bitbucket") middleware = [Middleware(RawContextMiddleware)] app = FastAPI(middleware=middleware) app.include_router(router) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index c2350526..f8abd555 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -11,6 +11,7 @@ ai_timeout=180 max_description_tokens = 500 max_commits_tokens = 500 litellm_debugger=false +secret_provider="google_cloud_storage" [pr_reviewer] # /review # require_focused_review=false diff --git a/pyproject.toml b/pyproject.toml index 9a945dca..8d429668 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,9 @@ dependencies = [ "starlette-context==0.3.6", "litellm~=0.1.445", "PyYAML==6.0", - "boto3~=1.28.25" + "boto3~=1.28.25", + "google-cloud-storage==2.10.0", + "ujson==5.8.0" ] [project.urls] diff --git a/requirements.txt b/requirements.txt index 0bbb6f28..fe92a74b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,3 +15,5 @@ PyYAML==6.0 starlette-context==0.3.6 litellm~=0.1.445 boto3~=1.28.25 +google-cloud-storage==2.10.0 +ujson==5.8.0 From 5d529a71ada04095395248da310462a3f7f89f9f Mon Sep 17 00:00:00 2001 From: szecsip Date: Thu, 24 Aug 2023 15:20:00 +0000 Subject: [PATCH 44/97] some minor changes in Azure DevOps git provider --- .../git_providers/azuredevops_provider.py | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/pr_agent/git_providers/azuredevops_provider.py b/pr_agent/git_providers/azuredevops_provider.py index 7290810c..3669bae6 100644 --- a/pr_agent/git_providers/azuredevops_provider.py +++ b/pr_agent/git_providers/azuredevops_provider.py @@ -7,7 +7,8 @@ import os from msrest.authentication import BasicAuthentication from azure.devops.connection import Connection -from azure.devops.v7_0.git.models import Comment, CommentThread, GitVersionDescriptor, GitPullRequest + +from azure.devops.v7_1.git.models import Comment, CommentThread, GitVersionDescriptor, GitPullRequest from ..algo.pr_processing import clip_tokens from ..config_loader import get_settings @@ -32,7 +33,7 @@ class AzureDevopsProvider: self.set_pr(pr_url) def is_supported(self, capability: str) -> bool: - if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments', 'get_labels']: + if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments', 'get_labels', 'remove_initial_comment']: return False return True @@ -48,8 +49,7 @@ class AzureDevopsProvider: path=".pr_agent.toml") return contents except Exception as e: - logging.info("get repo settings error") - logging.info(e) + logging.exception("get repo settings error") return "" def get_files(self): @@ -82,7 +82,6 @@ class AzureDevopsProvider: changes_obj = self.azure_devops_client.get_changes(project=self.workspace_slug, repository_id=self.repo_slug, commit_id=c.commit_id) for i in changes_obj.changes: - logging.info(i) diffs.append(i['item']['path']) diff_types[i['item']['path']] = i['changeType'] @@ -154,18 +153,7 @@ class AzureDevopsProvider: logging.exception(f"Could not update pull request {self.pr_num} description: {e}") def remove_initial_comment(self): - try: - for comment in self.temp_comments: - new_comment_thread = CommentThread(comments=[Comment(content='bumm')]) - # self.azure_devops_client.delete_comment(project=self.workspace_slug, repository_id=self.repo_slug, thread_id=comment['thread_id'], comment_id=comment['comment_id'], pull_request_id=self.pr_num) - - res = self.azure_devops_client.update_thread(project=self.workspace_slug, repository_id=self.repo_slug, - thread_id=comment['thread_id'], - pull_request_id=self.pr_num, - comment_thread=new_comment_thread) - logging.info(res) - except Exception as e: - logging.exception(f"Failed to remove temp comments, error: {e}") + return "" # not implemented yet def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str): raise NotImplementedError("Azure DevOps provider does not support publishing inline comment yet") @@ -224,6 +212,9 @@ class AzureDevopsProvider: def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool: return True + def get_issue_comments(self): + raise NotImplementedError("Azure DevOps provider does not support issue comments yet") + @staticmethod def _parse_pr_url(pr_url: str) -> Tuple[str, int]: parsed_url = urlparse(pr_url) @@ -269,8 +260,5 @@ class AzureDevopsProvider: self.pr = self.azure_devops_client.get_pull_request_by_id(pull_request_id=self.pr_num, project=self.workspace_slug) return self.pr - def _get_pr_file_content(self, remote_link: str): - return "" - def get_commit_messages(self): return "" # not implemented yet From c163d47a631d8ba5210c23947e4c8850a7271a33 Mon Sep 17 00:00:00 2001 From: szecsip Date: Thu, 24 Aug 2023 15:22:14 +0000 Subject: [PATCH 45/97] fix imports --- pr_agent/git_providers/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pr_agent/git_providers/__init__.py b/pr_agent/git_providers/__init__.py index f65553b0..061ff048 100644 --- a/pr_agent/git_providers/__init__.py +++ b/pr_agent/git_providers/__init__.py @@ -1,5 +1,6 @@ from pr_agent.config_loader import get_settings from pr_agent.git_providers.bitbucket_provider import BitbucketProvider +from pr_agent.git_providers.codecommit_provider import CodeCommitProvider from pr_agent.git_providers.github_provider import GithubProvider from pr_agent.git_providers.gitlab_provider import GitLabProvider from pr_agent.git_providers.local_git_provider import LocalGitProvider @@ -9,8 +10,9 @@ _GIT_PROVIDERS = { 'github': GithubProvider, 'gitlab': GitLabProvider, 'bitbucket': BitbucketProvider, - 'local': LocalGitProvider, - 'azure': AzureDevopsProvider + 'codecommit': CodeCommitProvider, + 'azure': AzureDevopsProvider, + 'local': LocalGitProvider } def get_git_provider(): From 355abfc39ac83fb23247141b905f3935b0b9e3d9 Mon Sep 17 00:00:00 2001 From: Ori Kotek Date: Thu, 24 Aug 2023 18:35:41 +0300 Subject: [PATCH 46/97] Bitbucket server, WIP --- docker/Dockerfile | 4 + pr_agent/git_providers/bitbucket_provider.py | 146 ++++++++++++------- pr_agent/servers/atlassian-connect.json | 33 +++++ pr_agent/servers/bitbucket_app.py | 60 +++++--- pr_agent/servers/github_app.py | 2 +- pr_agent/tools/pr_description.py | 8 +- 6 files changed, 174 insertions(+), 79 deletions(-) create mode 100644 pr_agent/servers/atlassian-connect.json diff --git a/docker/Dockerfile b/docker/Dockerfile index 61ab74cf..8d28a9ed 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -9,6 +9,10 @@ FROM base as github_app ADD pr_agent pr_agent CMD ["python", "pr_agent/servers/github_app.py"] +FROM base as bitbucket_app +ADD pr_agent pr_agent +CMD ["python", "pr_agent/servers/bitbucket_app.py"] + FROM base as github_polling ADD pr_agent pr_agent CMD ["python", "pr_agent/servers/github_polling.py"] diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index 6da864e4..0cd860fa 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -12,14 +12,18 @@ from .git_provider import FilePatchInfo, GitProvider class BitbucketProvider(GitProvider): - def __init__(self, pr_url: Optional[str] = None, incremental: Optional[bool] = False): + def __init__( + self, pr_url: Optional[str] = None, incremental: Optional[bool] = False + ): s = requests.Session() try: bearer = context.get("bitbucket_bearer_token", None) - s.headers['Authorization'] = f'Bearer {bearer}' + s.headers["Authorization"] = f"Bearer {bearer}" except Exception: - s.headers['Authorization'] = f'Bearer {get_settings().get("BITBUCKET.BEARER_TOKEN", None)}' - s.headers['Content-Type'] = 'application/json' + s.headers[ + "Authorization" + ] = f'Bearer {get_settings().get("BITBUCKET.BEARER_TOKEN", None)}' + s.headers["Content-Type"] = "application/json" self.headers = s.headers self.bitbucket_client = Cloud(session=s) self.workspace_slug = None @@ -31,37 +35,44 @@ class BitbucketProvider(GitProvider): self.incremental = incremental if pr_url: self.set_pr(pr_url) - self.bitbucket_comment_api_url = self.pr._BitbucketBase__data['links']['comments']['href'] + self.bitbucket_comment_api_url = self.pr._BitbucketBase__data["links"][ + "comments" + ]["href"] def get_repo_settings(self): try: - contents = self.repo_obj.get_contents(".pr_agent.toml", ref=self.pr.head.sha).decoded_content + contents = self.repo_obj.get_contents( + ".pr_agent.toml", ref=self.pr.head.sha + ).decoded_content return contents except Exception: return "" - + def publish_code_suggestions(self, code_suggestions: list) -> bool: """ Publishes code suggestions as comments on the PR. """ post_parameters_list = [] for suggestion in code_suggestions: - body = suggestion['body'] - relevant_file = suggestion['relevant_file'] - relevant_lines_start = suggestion['relevant_lines_start'] - relevant_lines_end = suggestion['relevant_lines_end'] + body = suggestion["body"] + relevant_file = suggestion["relevant_file"] + relevant_lines_start = suggestion["relevant_lines_start"] + relevant_lines_end = suggestion["relevant_lines_end"] if not relevant_lines_start or relevant_lines_start == -1: if get_settings().config.verbosity_level >= 2: logging.exception( - f"Failed to publish code suggestion, relevant_lines_start is {relevant_lines_start}") + f"Failed to publish code suggestion, relevant_lines_start is {relevant_lines_start}" + ) continue if relevant_lines_end < relevant_lines_start: if get_settings().config.verbosity_level >= 2: - logging.exception(f"Failed to publish code suggestion, " - f"relevant_lines_end is {relevant_lines_end} and " - f"relevant_lines_start is {relevant_lines_start}") + logging.exception( + f"Failed to publish code suggestion, " + f"relevant_lines_end is {relevant_lines_end} and " + f"relevant_lines_start is {relevant_lines_start}" + ) continue if relevant_lines_end > relevant_lines_start: @@ -80,8 +91,7 @@ class BitbucketProvider(GitProvider): "side": "RIGHT", } post_parameters_list.append(post_parameters) - - + try: self.publish_inline_comments(post_parameters_list) return True @@ -91,7 +101,12 @@ class BitbucketProvider(GitProvider): return False def is_supported(self, capability: str) -> bool: - if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments', 'get_labels']: + if capability in [ + "get_issue_comments", + "create_inline_comment", + "publish_inline_comments", + "get_labels", + ]: return False return True @@ -104,57 +119,65 @@ class BitbucketProvider(GitProvider): def get_diff_files(self) -> list[FilePatchInfo]: diffs = self.pr.diffstat() - diff_split = ['diff --git%s' % x for x in self.pr.diff().split('diff --git') if x.strip()] - + diff_split = [ + "diff --git%s" % x for x in self.pr.diff().split("diff --git") if x.strip() + ] + diff_files = [] for index, diff in enumerate(diffs): - original_file_content_str = self._get_pr_file_content(diff.old.get_data('links')) - new_file_content_str = self._get_pr_file_content(diff.new.get_data('links')) - diff_files.append(FilePatchInfo(original_file_content_str, new_file_content_str, - diff_split[index], diff.new.path)) + original_file_content_str = self._get_pr_file_content( + diff.old.get_data("links") + ) + new_file_content_str = self._get_pr_file_content(diff.new.get_data("links")) + diff_files.append( + FilePatchInfo( + original_file_content_str, + new_file_content_str, + diff_split[index], + diff.new.path, + ) + ) return diff_files def publish_comment(self, pr_comment: str, is_temporary: bool = False): comment = self.pr.comment(pr_comment) if is_temporary: - self.temp_comments.append(comment['id']) + self.temp_comments.append(comment["id"]) def remove_initial_comment(self): try: for comment in self.temp_comments: - self.pr.delete(f'comments/{comment}') + self.pr.delete(f"comments/{comment}") except Exception as e: logging.exception(f"Failed to remove temp comments, error: {e}") - def publish_inline_comment(self, comment: str, from_line: int, to_line: int, file: str): - payload = json.dumps( { - "content": { - "raw": comment, - }, - "inline": { - "to": from_line, - "path": file - }, - }) + def publish_inline_comment( + self, comment: str, from_line: int, to_line: int, file: str + ): + payload = json.dumps( + { + "content": { + "raw": comment, + }, + "inline": {"to": from_line, "path": file}, + } + ) response = requests.request( - "POST", - self.bitbucket_comment_api_url, - data=payload, - headers=self.headers + "POST", self.bitbucket_comment_api_url, data=payload, headers=self.headers ) return response - - def publish_inline_comments(self, comments: list[dict]): for comment in comments: - self.publish_inline_comment(comment['body'], comment['start_line'], comment['line'], comment['path']) + self.publish_inline_comment( + comment["body"], comment["start_line"], comment["line"], comment["path"] + ) def get_title(self): return self.pr.title def get_languages(self): - languages = {self._get_repo().get_data('language'): 0} + languages = {self._get_repo().get_data("language"): 0} return languages def get_pr_branch(self): @@ -167,7 +190,9 @@ class BitbucketProvider(GitProvider): return 0 def get_issue_comments(self): - raise NotImplementedError("Bitbucket provider does not support issue comments yet") + raise NotImplementedError( + "Bitbucket provider does not support issue comments yet" + ) def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]: return True @@ -178,14 +203,16 @@ class BitbucketProvider(GitProvider): @staticmethod def _parse_pr_url(pr_url: str) -> Tuple[str, int]: parsed_url = urlparse(pr_url) - - if 'bitbucket.org' not in parsed_url.netloc: + + if "bitbucket.org" not in parsed_url.netloc: raise ValueError("The provided URL is not a valid Bitbucket URL") - path_parts = parsed_url.path.strip('/').split('/') - - if len(path_parts) < 4 or path_parts[2] != 'pull-requests': - raise ValueError("The provided URL does not appear to be a Bitbucket PR URL") + path_parts = parsed_url.path.strip("/").split("/") + + if len(path_parts) < 4 or path_parts[2] != "pull-requests": + raise ValueError( + "The provided URL does not appear to be a Bitbucket PR URL" + ) workspace_slug = path_parts[0] repo_slug = path_parts[1] @@ -198,7 +225,9 @@ class BitbucketProvider(GitProvider): def _get_repo(self): if self.repo is None: - self.repo = self.bitbucket_client.workspaces.get(self.workspace_slug).repositories.get(self.repo_slug) + self.repo = self.bitbucket_client.workspaces.get( + self.workspace_slug + ).repositories.get(self.repo_slug) return self.repo def _get_pr(self): @@ -209,3 +238,16 @@ class BitbucketProvider(GitProvider): def get_commit_messages(self): return "" # not implemented yet + + def publish_description(self, pr_title: str, pr_body: str): + pass + def create_inline_comment( + self, body: str, relevant_file: str, relevant_line_in_file: str + ): + pass + + def publish_labels(self, labels): + pass + + def get_labels(self): + pass diff --git a/pr_agent/servers/atlassian-connect.json b/pr_agent/servers/atlassian-connect.json new file mode 100644 index 00000000..f976cf80 --- /dev/null +++ b/pr_agent/servers/atlassian-connect.json @@ -0,0 +1,33 @@ +{ + "name": "CodiumAI PR-Agent", + "description": "CodiumAI PR-Agent", + "key": "app_key", + "vendor": { + "name": "CodiumAI", + "url": "https://codium.ai" + }, + "authentication": { + "type": "jwt" + }, + "baseUrl": "https://53e2-212-199-118-78.ngrok-free.app", + "lifecycle": { + "installed": "/installed", + "uninstalled": "/uninstalled" + }, + "scopes": [ + "account", + "repository", + "pullrequest" + ], + "contexts": [ + "account" + ], + "modules": { + "webhooks": [ + { + "event": "*", + "url": "/webhook" + } + ] + } +} \ No newline at end of file diff --git a/pr_agent/servers/bitbucket_app.py b/pr_agent/servers/bitbucket_app.py index 039fc5c8..69209b94 100644 --- a/pr_agent/servers/bitbucket_app.py +++ b/pr_agent/servers/bitbucket_app.py @@ -1,22 +1,26 @@ +import copy import hashlib import json import logging import os +import sys import time import jwt import requests import uvicorn from fastapi import APIRouter, FastAPI, Request, Response +from starlette.background import BackgroundTasks from starlette.middleware import Middleware from starlette.responses import JSONResponse from starlette_context import context from starlette_context.middleware import RawContextMiddleware from pr_agent.agent.pr_agent import PRAgent -from pr_agent.config_loader import get_settings +from pr_agent.config_loader import get_settings, global_settings from pr_agent.secret_providers import get_secret_provider +logging.basicConfig(stream=sys.stdout, level=logging.INFO) router = APIRouter() secret_provider = get_secret_provider() @@ -51,29 +55,44 @@ async def get_bearer_token(shared_secret: str, client_key: str): @router.get("/") async def handle_manifest(request: Request, response: Response): manifest = open("atlassian-connect.json", "rt").read() + try: + manifest = manifest.replace("app_key", get_settings().bitbucket.app_key) + except: + logging.error("Failed to replace api_key in Bitbucket manifest, trying to continue") manifest_obj = json.loads(manifest) return JSONResponse(manifest_obj) @router.post("/webhook") -async def handle_github_webhooks(request: Request, response: Response): - try: - print(request.headers) - data = await request.json() - print(data) - owner = data["data"]["repository"]["owner"]["username"] - secrets = json.loads(secret_provider.get_secret(owner)) - shared_secret = secrets["shared_secret"] - client_key = secrets["client_key"] - bearer_token = await get_bearer_token(shared_secret, client_key) - context['bitbucket_bearer_token'] = bearer_token - event = data["event"] - agent = PRAgent() - if event == "pullrequest:created": - pr_url = data["data"]["pullrequest"]["links"]["html"]["href"] - await agent.handle_request(pr_url, "review") - except Exception as e: - logging.error(f"Failed to handle webhook: {e}") - return JSONResponse({"error": "Unable to handle webhook"}, status_code=500) +async def handle_github_webhooks(background_tasks: BackgroundTasks, request: Request): + print(request.headers) + jwt_header = request.headers.get("authorization", None) + if jwt_header: + input_jwt = jwt_header.split(" ")[1] + data = await request.json() + print(data) + async def inner(): + try: + owner = data["data"]["repository"]["owner"]["username"] + secrets = json.loads(secret_provider.get_secret(owner)) + shared_secret = secrets["shared_secret"] + client_key = secrets["client_key"] + jwt.decode(input_jwt, shared_secret, audience=client_key, algorithms=["HS256"]) + bearer_token = await get_bearer_token(shared_secret, client_key) + context['bitbucket_bearer_token'] = bearer_token + context["settings"] = copy.deepcopy(global_settings) + event = data["event"] + agent = PRAgent() + if event == "pullrequest:created": + pr_url = data["data"]["pullrequest"]["links"]["html"]["href"] + await agent.handle_request(pr_url, "review") + elif event == "pullrequest:comment_created": + pr_url = data["data"]["pullrequest"]["links"]["html"]["href"] + comment_body = data["data"]["comment"]["content"]["raw"] + await agent.handle_request(pr_url, comment_body) + except Exception as e: + logging.error(f"Failed to handle webhook: {e}") + background_tasks.add_task(inner) + return "OK" @router.get("/webhook") async def handle_github_webhooks(request: Request, response: Response): @@ -106,6 +125,7 @@ async def handle_uninstalled_webhooks(request: Request, response: Response): def start(): get_settings().set("CONFIG.PUBLISH_OUTPUT_PROGRESS", False) get_settings().set("CONFIG.GIT_PROVIDER", "bitbucket") + get_settings().set("PR_DESCRIPTION.PUBLISH_DESCRIPTION_AS_COMMENT", True) middleware = [Middleware(RawContextMiddleware)] app = FastAPI(middleware=middleware) app.include_router(router) diff --git a/pr_agent/servers/github_app.py b/pr_agent/servers/github_app.py index 7a7208d0..1d1c901c 100644 --- a/pr_agent/servers/github_app.py +++ b/pr_agent/servers/github_app.py @@ -167,4 +167,4 @@ def start(): if __name__ == '__main__': - start() \ No newline at end of file + start() diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index 440675fd..acd272bc 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -153,12 +153,6 @@ class PRDescription: # Initialization pr_types = [] - # Iterate over the dictionary items and append the key and value to 'markdown_text' in a markdown format - markdown_text = "" - for key, value in data.items(): - markdown_text += f"## {key}\n\n" - markdown_text += f"{value}\n\n" - # If the 'PR Type' key is present in the dictionary, split its value by comma and assign it to 'pr_types' if 'PR Type' in data: if type(data['PR Type']) == list: @@ -194,6 +188,8 @@ class PRDescription: if idx < len(data) - 1: pr_body += "\n___\n" + markdown_text = f"## Title\n\n{title}\n\n___\n{pr_body}" + if get_settings().config.verbosity_level >= 2: logging.info(f"title:\n{title}\n{pr_body}") From 12167bc3a1feae8156368fd87627830d7edc7657 Mon Sep 17 00:00:00 2001 From: szecsip Date: Thu, 24 Aug 2023 16:34:20 +0000 Subject: [PATCH 47/97] fix imports --- pr_agent/git_providers/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pr_agent/git_providers/__init__.py b/pr_agent/git_providers/__init__.py index 061ff048..e5aca2fb 100644 --- a/pr_agent/git_providers/__init__.py +++ b/pr_agent/git_providers/__init__.py @@ -1,6 +1,5 @@ from pr_agent.config_loader import get_settings from pr_agent.git_providers.bitbucket_provider import BitbucketProvider -from pr_agent.git_providers.codecommit_provider import CodeCommitProvider from pr_agent.git_providers.github_provider import GithubProvider from pr_agent.git_providers.gitlab_provider import GitLabProvider from pr_agent.git_providers.local_git_provider import LocalGitProvider @@ -10,9 +9,8 @@ _GIT_PROVIDERS = { 'github': GithubProvider, 'gitlab': GitLabProvider, 'bitbucket': BitbucketProvider, - 'codecommit': CodeCommitProvider, 'azure': AzureDevopsProvider, - 'local': LocalGitProvider + 'local' : LocalGitProvider } def get_git_provider(): From ceaff2a269701a404d8f73942f630ab403d65761 Mon Sep 17 00:00:00 2001 From: szecsip Date: Thu, 24 Aug 2023 16:35:34 +0000 Subject: [PATCH 48/97] fix exception printing --- pr_agent/git_providers/git_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pr_agent/git_providers/git_provider.py b/pr_agent/git_providers/git_provider.py index e8c1c8a8..3329631e 100644 --- a/pr_agent/git_providers/git_provider.py +++ b/pr_agent/git_providers/git_provider.py @@ -141,7 +141,7 @@ def get_main_pr_language(languages, files) -> str: main_language_str = top_language except Exception as e: - logging.info(e) + logging.exception(e) pass return main_language_str From 9e878d0d9abbfdda1db9e6156963177e82c7a4ef Mon Sep 17 00:00:00 2001 From: Ori Kotek Date: Sun, 27 Aug 2023 10:11:46 +0300 Subject: [PATCH 49/97] Bitbucket server --- pr_agent/servers/atlassian-connect.json | 2 +- pr_agent/servers/bitbucket_app.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pr_agent/servers/atlassian-connect.json b/pr_agent/servers/atlassian-connect.json index f976cf80..1ff50865 100644 --- a/pr_agent/servers/atlassian-connect.json +++ b/pr_agent/servers/atlassian-connect.json @@ -9,7 +9,7 @@ "authentication": { "type": "jwt" }, - "baseUrl": "https://53e2-212-199-118-78.ngrok-free.app", + "baseUrl": "base_url", "lifecycle": { "installed": "/installed", "uninstalled": "/uninstalled" diff --git a/pr_agent/servers/bitbucket_app.py b/pr_agent/servers/bitbucket_app.py index 69209b94..cc6491d4 100644 --- a/pr_agent/servers/bitbucket_app.py +++ b/pr_agent/servers/bitbucket_app.py @@ -54,9 +54,11 @@ async def get_bearer_token(shared_secret: str, client_key: str): @router.get("/") async def handle_manifest(request: Request, response: Response): - manifest = open("atlassian-connect.json", "rt").read() + cur_dir = os.path.dirname(os.path.abspath(__file__)) + manifest = open(os.path.join(cur_dir, "atlassian-connect.json"), "rt").read() try: manifest = manifest.replace("app_key", get_settings().bitbucket.app_key) + manifest = manifest.replace("base_url", get_settings().bitbucket.base_url) except: logging.error("Failed to replace api_key in Bitbucket manifest, trying to continue") manifest_obj = json.loads(manifest) From 9286e617532478c4d6d8296bfe3db3bf0f54cf92 Mon Sep 17 00:00:00 2001 From: Ori Kotek Date: Sun, 27 Aug 2023 15:36:39 +0300 Subject: [PATCH 50/97] Consolidate redundant dependency list --- pyproject.toml | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8d429668..811cd2bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,27 +27,7 @@ classifiers = [ "Programming Language :: Python :: 3", ] -dependencies = [ - "dynaconf==3.1.12", - "fastapi==0.99.0", - "PyGithub==1.59.*", - "retry==0.9.2", - "openai==0.27.8", - "Jinja2==3.1.2", - "tiktoken==0.4.0", - "uvicorn==0.22.0", - "python-gitlab==3.15.0", - "pytest~=7.4.0", - "aiohttp~=3.8.4", - "atlassian-python-api==3.39.0", - "GitPython~=3.1.32", - "starlette-context==0.3.6", - "litellm~=0.1.445", - "PyYAML==6.0", - "boto3~=1.28.25", - "google-cloud-storage==2.10.0", - "ujson==5.8.0" -] +dependencies = {file = ["requirements.txt"]} [project.urls] "Homepage" = "https://github.com/Codium-ai/pr-agent" From 82ac9d447b2772658730a9308334e4909c34e816 Mon Sep 17 00:00:00 2001 From: Ori Kotek Date: Sun, 27 Aug 2023 15:39:45 +0300 Subject: [PATCH 51/97] Consolidate redundant dependency list --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 811cd2bf..0e1289f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,9 @@ classifiers = [ "Operating System :: Independent", "Programming Language :: Python :: 3", ] +dynamic = ["dependencies"] +[tool.setuptools.dynamic] dependencies = {file = ["requirements.txt"]} [project.urls] From a0f53d23afcdcaa44372a964238a47c4fab9d504 Mon Sep 17 00:00:00 2001 From: Ori Kotek Date: Sun, 27 Aug 2023 15:58:14 +0300 Subject: [PATCH 52/97] Consolidate redundant dependency list --- docker/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 8d28a9ed..cda90849 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,7 @@ FROM python:3.10 as base WORKDIR /app +RUN pip install pip setuptools --upgrade ADD pyproject.toml . RUN pip install . && rm pyproject.toml ENV PYTHONPATH=/app From 85bc307186204d60b605f99b275f6c5616a66940 Mon Sep 17 00:00:00 2001 From: Ori Kotek Date: Sun, 27 Aug 2023 16:00:38 +0300 Subject: [PATCH 53/97] Consolidate redundant dependency list --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index cda90849..4336cacc 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,9 +1,9 @@ FROM python:3.10 as base WORKDIR /app -RUN pip install pip setuptools --upgrade ADD pyproject.toml . -RUN pip install . && rm pyproject.toml +ADD requirements.txt . +RUN pip install . && rm pyproject.toml requirements.txt ENV PYTHONPATH=/app FROM base as github_app From e776cebc339fd6287623fac95590c24e4f65c54c Mon Sep 17 00:00:00 2001 From: mrT23 Date: Mon, 28 Aug 2023 08:31:56 +0300 Subject: [PATCH 54/97] update README.md --- INSTALL.md | 39 ++++++++++++++++++++++++++------------- README.md | 16 +++++++++------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 55c59492..5115e882 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,6 +1,20 @@ ## Installation +To get started with PR-Agent quickly, you first need to acquire two tokens: + +1. An OpenAI key from [here](https://platform.openai.com/), with access to GPT-4. +2. A GitHub personal access token (classic) with the repo scope. + +There are several ways to use PR-Agent: + +- [Method 1: Use Docker image (no installation required)](INSTALL.md#method-1-use-docker-image-no-installation-required) +- [Method 2: Run as a GitHub Action](INSTALL.md#method-2-run-as-a-github-action) +- [Method 3: Run from source](INSTALL.md#method-3-run-from-source) +- [Method 4: Run as a polling server](INSTALL.md#method-4-run-as-a-polling-server) +- [Method 5: Run as a GitHub App](INSTALL.md#method-5-run-as-a-github-app) +- [Method 6: Deploy as a Lambda Function](INSTALL.md#method-6---deploy-as-a-lambda-function) +- [Method 7: AWS CodeCommit](INSTALL.md#method-7---aws-codecommit-setup) --- #### Method 1: Use Docker image (no installation required) @@ -143,14 +157,6 @@ python pr_agent/cli.py --pr_url describe python pr_agent/cli.py --pr_url improve ``` -5. **Debugging LLM API Calls** -If you're testing your codium/pr-agent server, and need to see if calls were made successfully + the exact call logs, you can use the [LiteLLM Debugger tool](https://docs.litellm.ai/docs/debugging/hosted_debugging). - -You can do this by setting `litellm_debugger=true` in configuration.toml. Your Logs will be viewable in real-time @ `admin.litellm.ai/`. Set your email in the `.secrets.toml` under 'user_email'. - - - - --- #### Method 4: Run as a polling server @@ -247,7 +253,7 @@ docker push codiumai/pr-agent:github_app # Push to your Docker repository --- -#### Deploy as a Lambda Function +#### Method 6 - Deploy as a Lambda Function 1. Follow steps 1-5 of [Method 5](#method-5-run-as-a-github-app). 2. Build a docker image that can be used as a lambda function @@ -266,7 +272,7 @@ docker push codiumai/pr-agent:github_app # Push to your Docker repository --- -#### AWS CodeCommit Setup +#### Method 7 - AWS CodeCommit Setup Not all features have been added to CodeCommit yet. As of right now, CodeCommit has been implemented to run the pr-agent CLI on the command line, using AWS credentials stored in environment variables. (More features will be added in the future.) The following is a set of instructions to have pr-agent do a review of your CodeCommit pull request from the command line: @@ -281,7 +287,7 @@ Not all features have been added to CodeCommit yet. As of right now, CodeCommit * Option B: Set `PYTHONPATH` and run the CLI in one command, for example: * `PYTHONPATH="/PATH/TO/PROJECTS/pr-agent python pr_agent/cli.py [--ARGS]` -#### AWS CodeCommit IAM Role Example +##### AWS CodeCommit IAM Role Example Example IAM permissions to that user to allow access to CodeCommit: @@ -311,7 +317,7 @@ Example IAM permissions to that user to allow access to CodeCommit: } ``` -#### AWS CodeCommit Access Key and Secret +##### AWS CodeCommit Access Key and Secret Example setting the Access Key and Secret using environment variables @@ -321,7 +327,7 @@ export AWS_SECRET_ACCESS_KEY="XXXXXXXXXXXXXXXX" export AWS_DEFAULT_REGION="us-east-1" ``` -#### AWS CodeCommit CLI Example +##### AWS CodeCommit CLI Example After you set up AWS CodeCommit using the instructions above, here is an example CLI run that tells pr-agent to **review** a given pull request. (Replace your specific PYTHONPATH and PR URL in the example) @@ -331,3 +337,10 @@ PYTHONPATH="/PATH/TO/PROJECTS/pr-agent" python pr_agent/cli.py \ --pr_url https://us-east-1.console.aws.amazon.com/codesuite/codecommit/repositories/MY_REPO_NAME/pull-requests/321 \ review ``` + +#### Appendix - **Debugging LLM API Calls** +If you're testing your codium/pr-agent server, and need to see if calls were made successfully + the exact call logs, you can use the [LiteLLM Debugger tool](https://docs.litellm.ai/docs/debugging/hosted_debugging). + +You can do this by setting `litellm_debugger=true` in configuration.toml. Your Logs will be viewable in real-time @ `admin.litellm.ai/`. Set your email in the `.secrets.toml` under 'user_email'. + + \ No newline at end of file diff --git a/README.md b/README.md index 1b120241..f04fc71c 100644 --- a/README.md +++ b/README.md @@ -15,17 +15,17 @@ Making pull requests less painful with an AI agent
    -CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull requests faster and more efficiently. It automatically analyzes the pull request and can provide several types of feedback: +CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull requests faster and more efficiently. It automatically analyzes the pull request and can provide several types of PR feedback: -**Auto-Description**: Automatically generating PR description - title, type, summary, code walkthrough and PR labels. +**[Auto-Description](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1687561986)**: Automatically generating [PR description](https://github.com/Codium-ai/pr-agent/pull/229#issue-1860711415) - title, type, summary, code walkthrough and labels. \ -**PR Review**: Adjustable feedback about the PR main theme, type, relevant tests, security issues, focus, score, and various suggestions for the PR content. +**[Auto Review](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021901)**: [Adjustable feedback](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695022908) about the PR main theme, type, relevant tests, security issues, score, and various suggestions for the PR content. \ -**Question Answering**: Answering free-text questions about the PR. +**[Question Answering](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695020538)**: Answering [free-text questions](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021332) about the PR. \ -**Code Suggestions**: Committable code suggestions for improving the PR. +**[Code Suggestions](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695024952)**: [Committable code suggestions](https://github.com/Codium-ai/pr-agent/pull/229#discussion_r1306919276) for improving the PR. \ -**Update Changelog**: Automatically updating the CHANGELOG.md file with the PR changes. +**[Update Changelog](https://github.com/Codium-ai/pr-agent/pull/168#issuecomment-1662425518)**: Automatically updating the CHANGELOG.md file with the [PR changes](https://github.com/Codium-ai/pr-agent/pull/168#discussion_r1282077645).

    Example results:

    @@ -82,6 +82,7 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull | | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | Auto-Description | :white_check_mark: | :white_check_mark: | | | | | Improve Code | :white_check_mark: | :white_check_mark: | | | +| | ⮑ Extended | :white_check_mark: | :white_check_mark: | | | | | Reflect and Review | :white_check_mark: | | | | | | Update CHANGELOG.md | :white_check_mark: | | | | | | | | | | | @@ -160,8 +161,9 @@ Here are some advantages of PR-Agent: ## Roadmap - [x] Support additional models, as a replacement for OpenAI (see [here](https://github.com/Codium-ai/pr-agent/pull/172)) -- [ ] Develop additional logic for handling large PRs +- [x] Develop additional logic for handling large PRs (see [here](https://github.com/Codium-ai/pr-agent/pull/229)) - [ ] Add additional context to the prompt. For example, repo (or relevant files) summarization, with tools such a [ctags](https://github.com/universal-ctags/ctags) +- [ ] PR-Agent for issues, and just for pull requests - [ ] Adding more tools. Possible directions: - [x] PR description - [x] Inline code suggestions From 3051dc50fb0f6fe4d61801220da9590a6d398084 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Mon, 28 Aug 2023 08:41:02 +0300 Subject: [PATCH 55/97] update README.md --- INSTALL.md | 16 ++++++++-------- README.md | 23 ++++++++++++----------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 5115e882..88ad92bb 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -17,7 +17,7 @@ There are several ways to use PR-Agent: - [Method 7: AWS CodeCommit](INSTALL.md#method-7---aws-codecommit-setup) --- -#### Method 1: Use Docker image (no installation required) +### Method 1: Use Docker image (no installation required) To request a review for a PR, or ask a question about a PR, you can run directly from the Docker image. Here's how: @@ -55,7 +55,7 @@ Possible questions you can ask include: --- -#### Method 2: Run as a GitHub Action +### Method 2: Run as a GitHub Action You can use our pre-built Github Action Docker image to run PR-Agent as a Github Action. @@ -125,7 +125,7 @@ When you open your next PR, you should see a comment from `github-actions` bot w --- -#### Method 3: Run from source +### Method 3: Run from source 1. Clone this repository: @@ -159,7 +159,7 @@ python pr_agent/cli.py --pr_url improve --- -#### Method 4: Run as a polling server +### Method 4: Run as a polling server Request reviews by tagging your Github user on a PR Follow steps 1-3 of method 2. @@ -171,7 +171,7 @@ python pr_agent/servers/github_polling.py --- -#### Method 5: Run as a GitHub App +### Method 5: Run as a GitHub App Allowing you to automate the review process on your private or public repositories. 1. Create a GitHub App from the [Github Developer Portal](https://docs.github.com/en/developers/apps/creating-a-github-app). @@ -253,7 +253,7 @@ docker push codiumai/pr-agent:github_app # Push to your Docker repository --- -#### Method 6 - Deploy as a Lambda Function +### Method 6 - Deploy as a Lambda Function 1. Follow steps 1-5 of [Method 5](#method-5-run-as-a-github-app). 2. Build a docker image that can be used as a lambda function @@ -272,7 +272,7 @@ docker push codiumai/pr-agent:github_app # Push to your Docker repository --- -#### Method 7 - AWS CodeCommit Setup +### Method 7 - AWS CodeCommit Setup Not all features have been added to CodeCommit yet. As of right now, CodeCommit has been implemented to run the pr-agent CLI on the command line, using AWS credentials stored in environment variables. (More features will be added in the future.) The following is a set of instructions to have pr-agent do a review of your CodeCommit pull request from the command line: @@ -338,7 +338,7 @@ PYTHONPATH="/PATH/TO/PROJECTS/pr-agent" python pr_agent/cli.py \ review ``` -#### Appendix - **Debugging LLM API Calls** +### Appendix - **Debugging LLM API Calls** If you're testing your codium/pr-agent server, and need to see if calls were made successfully + the exact call logs, you can use the [LiteLLM Debugger tool](https://docs.litellm.ai/docs/debugging/hosted_debugging). You can do this by setting `litellm_debugger=true` in configuration.toml. Your Logs will be viewable in real-time @ `admin.litellm.ai/`. Set your email in the `.secrets.toml` under 'user_email'. diff --git a/README.md b/README.md index f04fc71c..47dca106 100644 --- a/README.md +++ b/README.md @@ -17,43 +17,43 @@ Making pull requests less painful with an AI agent CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull requests faster and more efficiently. It automatically analyzes the pull request and can provide several types of PR feedback: -**[Auto-Description](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1687561986)**: Automatically generating [PR description](https://github.com/Codium-ai/pr-agent/pull/229#issue-1860711415) - title, type, summary, code walkthrough and labels. +**Auto-Description**: Automatically generating [PR description](https://github.com/Codium-ai/pr-agent/pull/229#issue-1860711415) - title, type, summary, code walkthrough and labels. \ -**[Auto Review](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021901)**: [Adjustable feedback](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695022908) about the PR main theme, type, relevant tests, security issues, score, and various suggestions for the PR content. +**Auto Review**: [Adjustable feedback](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695022908) about the PR main theme, type, relevant tests, security issues, score, and various suggestions for the PR content. \ -**[Question Answering](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695020538)**: Answering [free-text questions](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021332) about the PR. +**Question Answering**: Answering [free-text questions](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021332) about the PR. \ -**[Code Suggestions](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695024952)**: [Committable code suggestions](https://github.com/Codium-ai/pr-agent/pull/229#discussion_r1306919276) for improving the PR. +**Code Suggestions**: [Committable code suggestions](https://github.com/Codium-ai/pr-agent/pull/229#discussion_r1306919276) for improving the PR. \ -**[Update Changelog](https://github.com/Codium-ai/pr-agent/pull/168#issuecomment-1662425518)**: Automatically updating the CHANGELOG.md file with the [PR changes](https://github.com/Codium-ai/pr-agent/pull/168#discussion_r1282077645). +**Update Changelog**: Automatically updating the CHANGELOG.md file with the [PR changes](https://github.com/Codium-ai/pr-agent/pull/168#discussion_r1282077645).

    Example results:

    -

    /describe:

    +

    -

    /review:

    +

    /review:

    -

    /reflect_and_review:

    +

    /reflect_and_review:

    -

    /ask:

    +

    /ask:

    -

    /improve:

    +

    /improve:

    @@ -135,7 +135,8 @@ There are several ways to use PR-Agent: - Request reviews by tagging your GitHub user on a PR - [Method 5: Run as a GitHub App](INSTALL.md#method-5-run-as-a-github-app) - Allowing you to automate the review process on your private or public repositories - +- [Method 6: Deploy as a Lambda Function](INSTALL.md#method-6---deploy-as-a-lambda-function) +- [Method 7: AWS CodeCommit](INSTALL.md#method-7---aws-codecommit-setup) ## How it works From 2dc2a45e4be9b19fe3c03eb6843173e3bf202c2a Mon Sep 17 00:00:00 2001 From: mrT23 Date: Mon, 28 Aug 2023 09:48:43 +0300 Subject: [PATCH 56/97] yaml --- pr_agent/algo/utils.py | 2 +- .../settings/pr_code_suggestions_prompts.toml | 129 ++++++++++-------- pr_agent/tools/pr_code_suggestions.py | 20 +-- 3 files changed, 81 insertions(+), 70 deletions(-) diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py index 4d09b6e7..1259a46e 100644 --- a/pr_agent/algo/utils.py +++ b/pr_agent/algo/utils.py @@ -276,7 +276,7 @@ def _fix_key_value(key: str, value: str): def load_yaml(review_text: str) -> dict: review_text = review_text.removeprefix('```yaml').rstrip('`') try: - data = yaml.load(review_text, Loader=yaml.SafeLoader) + data = yaml.safe_load(review_text) except Exception as e: logging.error(f"Failed to parse AI prediction: {e}") data = try_fix_yaml(review_text) diff --git a/pr_agent/settings/pr_code_suggestions_prompts.toml b/pr_agent/settings/pr_code_suggestions_prompts.toml index 4e4b57e5..f60b9cc2 100644 --- a/pr_agent/settings/pr_code_suggestions_prompts.toml +++ b/pr_agent/settings/pr_code_suggestions_prompts.toml @@ -1,8 +1,8 @@ [pr_code_suggestions_prompt] -system="""You are a language model called PR-Code-Reviewer. -Your task is to provide meaningful actionable code suggestions, to improve the new code presented in a PR. +system="""You are a language model called PR-Code-Reviewer, that specializes in suggesting code improvements for Pull Request (PR). +Your task is to provide meaningful and actionable code suggestions, to improve the new code presented in a PR. -Example PR Diff input: +Example for a PR Diff input: ' ## src/file1.py @@ -10,8 +10,8 @@ Example PR Diff input: __new hunk__ 12 code line that already existed in the file... 13 code line that already existed in the file.... -14 +new code line added in the PR -15 code line that already existed in the file... +14 +new code line1 added in the PR +15 +new code line2 added in the PR 16 code line that already existed in the file... __old hunk__ code line that already existed in the file... @@ -31,13 +31,17 @@ __old hunk__ ' Specific instructions: -- Focus on important suggestions like fixing code problems, issues and bugs. As a second priority, provide suggestions for meaningful code improvements, like performance, vulnerability, modularity, and best practices. -- Suggestions should refer only to code from the '__new hunk__' sections, and focus on new lines of code (lines starting with '+'). -- Provide the exact line number range (inclusive) for each issue. -- Assume there is additional relevant code, that is not included in the diff. - Provide up to {{ num_code_suggestions }} code suggestions. -- Avoid making suggestions that have already been implemented in the PR code. For example, if you want to add logs, or change a variable to const, or anything else, make sure it isn't already in the '__new hunk__' code. -- Don't suggest to add docstring or type hints. +- Prioritize suggestions that address major problems, issues and bugs in the code. + As a second priority, suggestions should focus on best practices, code readability, maintainability, enhancments, performance, and other aspects. + Don't suggest to add docstring or type hints. + Try to provide diverse and insightful suggestions. +- Suggestions should refer only to code from the '__new hunk__' sections, and focus on new lines of code (lines starting with '+'). + Avoid making suggestions that have already been implemented in the PR code. For example, if you want to add logs, or change a variable to const, or anything else, make sure it isn't already in the '__new hunk__' code. + For each suggestion, make sure to take into consideration also the context, meaning the lines before and after the relevant code. +- Provide the exact line numbers range (inclusive) for each issue. +- Assume there is additional relevant code, that is not included in the diff. + {%- if extra_instructions %} @@ -45,63 +49,76 @@ Extra instructions from the user: {{ extra_instructions }} {%- endif %} -You must use the following JSON schema to format your answer: -```json -{ - "Code suggestions": { - "type": "array", - "minItems": 1, - "maxItems": {{ num_code_suggestions }}, - "uniqueItems": "true", - "items": { - "relevant file": { - "type": "string", - "description": "the relevant file full path" - }, - "suggestion content": { - "type": "string", - "description": "a concrete suggestion for meaningfully improving the new PR code (lines from the '__new hunk__' sections, starting with '+')." - }, - "existing code": { - "type": "string", - "description": "a code snippet showing the relevant code lines from a '__new hunk__' section. It must be continuous, correctly formatted and indented, and without line numbers." - }, - "relevant lines": { - "type": "string", - "description": "the relevant lines from a '__new hunk__' section, in the format of 'start_line-end_line'. For example: '10-15'. They should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above." - }, - "improved code": { - "type": "string", - "description": "a new code snippet that can be used to replace the relevant lines in '__new hunk__' code. Replacement suggestions should be complete, correctly formatted and indented, and without line numbers." - } - } - } -} +You must use the following YAML schema to format your answer: +```yaml +Code suggestions: + type: array + minItems: 1 + maxItems: {{ num_code_suggestions }} + uniqueItems: true + items: + relevant file: + type: string + description: the relevant file full path + suggestion content: + type: string + description: |- + a concrete suggestion for meaningfully improving the new PR code. + existing code: + type: string + description: |- + a code snippet showing the relevant code lines from a '__new hunk__' section. + It must be continuous, correctly formatted and indented, and without line numbers. + relevant lines: + type: string + description: |- + the relevant lines from a '__new hunk__' section, in the format of 'start_line-end_line'. + For example: '10-15'. They should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above. + improved code: + type: string + description: |- + a new code snippet that can be used to replace the relevant lines in '__new hunk__' code. + Replacement suggestions should be complete, correctly formatted and indented, and without line numbers. ``` -Don't output line numbers in the 'improved code' snippets. +Example output: +```yaml +Code suggestions: + - relevant file: |- + src/file1.py + suggestion content: |- + Add a docstring to func1() + existing code: |- + def func1(): + relevant lines: '12-12' + improved code: |- + ... +``` + + +Each YAML output MUST be after a newline, indented, with block scalar indicator ('|-'). Don't repeat the prompt in the answer, and avoid outputting the 'type' and 'description' fields. """ user="""PR Info: -Title: '{{title}}' -Branch: '{{branch}}' -Description: '{{description}}' -{%- if language %} -Main language: {{language}} -{%- endif %} -{%- if commit_messages_str %} -Commit messages: -{{commit_messages_str}} +Title: '{{title}}' + +Branch: '{{branch}}' + +Description: '{{description}}' + +{%- if language %} + +Main language: {{language}} {%- endif %} The PR Diff: ``` -{{diff}} +{{- diff|trim }} ``` -Response (should be a valid JSON, and nothing else): -```json +Response (should be a valid YAML, and nothing else): +```yaml """ diff --git a/pr_agent/tools/pr_code_suggestions.py b/pr_agent/tools/pr_code_suggestions.py index cc787f5e..d9fb3051 100644 --- a/pr_agent/tools/pr_code_suggestions.py +++ b/pr_agent/tools/pr_code_suggestions.py @@ -1,16 +1,13 @@ import copy -import json import logging import textwrap -from typing import List - -import yaml +from typing import List, Dict from jinja2 import Environment, StrictUndefined from pr_agent.algo.ai_handler import AiHandler from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models, get_pr_multi_diffs from pr_agent.algo.token_handler import TokenHandler -from pr_agent.algo.utils import try_fix_json +from pr_agent.algo.utils import load_yaml from pr_agent.config_loader import get_settings from pr_agent.git_providers import BitbucketProvider, get_git_provider from pr_agent.git_providers.git_provider import get_main_pr_language @@ -98,14 +95,11 @@ class PRCodeSuggestions: return response - def _prepare_pr_code_suggestions(self) -> str: + def _prepare_pr_code_suggestions(self) -> Dict: review = self.prediction.strip() - try: - data = json.loads(review) - except json.decoder.JSONDecodeError: - if get_settings().config.verbosity_level >= 2: - logging.info(f"Could not parse json response: {review}") - data = try_fix_json(review, code_suggestions=True) + data = load_yaml(review) + if isinstance(data, list): + data = {'Code suggestions': data} return data def push_inline_code_suggestions(self, data): @@ -227,7 +221,7 @@ class PRCodeSuggestions: response, finish_reason = await self.ai_handler.chat_completion(model=model, system=system_prompt, user=user_prompt) - sort_order = yaml.safe_load(response) + sort_order = load_yaml(response) for s in sort_order['Sort Order']: suggestion_number = s['suggestion number'] importance_order = s['importance order'] From 314d13e25ff99ac93416056ee61b975abcb8baa2 Mon Sep 17 00:00:00 2001 From: zmeir Date: Mon, 28 Aug 2023 16:13:26 +0300 Subject: [PATCH 57/97] Fixed incorrect usage for Azure OpenAI API --- pr_agent/algo/ai_handler.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pr_agent/algo/ai_handler.py b/pr_agent/algo/ai_handler.py index 1a12564b..fcc5f04c 100644 --- a/pr_agent/algo/ai_handler.py +++ b/pr_agent/algo/ai_handler.py @@ -87,8 +87,6 @@ class AiHandler: f"Generating completion with {model}" f"{(' from deployment ' + deployment_id) if deployment_id else ''}" ) - if self.azure: - model = self.azure + "/" + model response = await acompletion( model=model, deployment_id=deployment_id, @@ -97,6 +95,7 @@ class AiHandler: {"role": "user", "content": user} ], temperature=temperature, + azure=self.azure, force_timeout=get_settings().config.ai_timeout ) except (APIError, Timeout, TryAgain) as e: From d3c7dcc40712f5b28f13205aa8ff5220c511dfaf Mon Sep 17 00:00:00 2001 From: mrT23 Date: Mon, 28 Aug 2023 20:21:29 +0300 Subject: [PATCH 58/97] AZURE_DEVOPS_AVAILABLE --- pr_agent/git_providers/azuredevops_provider.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pr_agent/git_providers/azuredevops_provider.py b/pr_agent/git_providers/azuredevops_provider.py index 3669bae6..71ae0947 100644 --- a/pr_agent/git_providers/azuredevops_provider.py +++ b/pr_agent/git_providers/azuredevops_provider.py @@ -5,10 +5,13 @@ from urllib.parse import urlparse import os -from msrest.authentication import BasicAuthentication -from azure.devops.connection import Connection - -from azure.devops.v7_1.git.models import Comment, CommentThread, GitVersionDescriptor, GitPullRequest +AZURE_DEVOPS_AVAILABLE = True +try: + from msrest.authentication import BasicAuthentication + from azure.devops.connection import Connection + from azure.devops.v7_1.git.models import Comment, CommentThread, GitVersionDescriptor, GitPullRequest +except ImportError: + AZURE_DEVOPS_AVAILABLE = False from ..algo.pr_processing import clip_tokens from ..config_loader import get_settings @@ -19,6 +22,8 @@ from .git_provider import EDIT_TYPE, FilePatchInfo class AzureDevopsProvider: def __init__(self, pr_url: Optional[str] = None, incremental: Optional[bool] = False): + if not AZURE_DEVOPS_AVAILABLE: + raise ImportError("Azure DevOps provider is not available. Please install the required dependencies.") self.azure_devops_client = self._get_azure_devops_client() From ce54a7b79ece3a2fcaee17db6dc81eb6c667f168 Mon Sep 17 00:00:00 2001 From: Itamar Friedman <108689937+coditamar@users.noreply.github.com> Date: Mon, 28 Aug 2023 22:08:08 +0300 Subject: [PATCH 59/97] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 47dca106..7bb4b47d 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,9 @@ Examples for invoking the different tools via the CLI: "" is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). In the [configuration](./CONFIGURATION.md) file you can select your git provider (GitHub, Gitlab, Bitbucket), and further configure the different tools. +Options that are available in the configuration file can be specificied at run time when calling actions. Two examples: +- /review --pr_reviewer.extra_instructions="focus on the file: ..." +- /describe --pr_description.add_original_user_description=false -pr_description.extra_instructions="make sure to mention: ..." ## Try it now @@ -177,7 +180,7 @@ Here are some advantages of PR-Agent: ## Similar Projects -- [CodiumAI - Meaningful tests for busy devs](https://github.com/Codium-ai/codiumai-vscode-release) +- [CodiumAI - Meaningful tests for busy devs](https://github.com/Codium-ai/codiumai-vscode-release) (although various capabilities are much more advanced in the CodiumAI IDE plugins) - [Aider - GPT powered coding in your terminal](https://github.com/paul-gauthier/aider) - [openai-pr-reviewer](https://github.com/coderabbitai/openai-pr-reviewer) - [CodeReview BOT](https://github.com/anc95/ChatGPT-CodeReview) From edaeb99b43f01b496523145b58a86f5167da0bb5 Mon Sep 17 00:00:00 2001 From: Itamar Friedman <108689937+coditamar@users.noreply.github.com> Date: Mon, 28 Aug 2023 22:29:08 +0300 Subject: [PATCH 60/97] Update README.md type Co-authored-by: Tim Perkins --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7bb4b47d..65f1c8d6 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ Examples for invoking the different tools via the CLI: "" is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). In the [configuration](./CONFIGURATION.md) file you can select your git provider (GitHub, Gitlab, Bitbucket), and further configure the different tools. -Options that are available in the configuration file can be specificied at run time when calling actions. Two examples: +Options that are available in the configuration file can be specified at run time when calling actions. Two examples: - /review --pr_reviewer.extra_instructions="focus on the file: ..." - /describe --pr_description.add_original_user_description=false -pr_description.extra_instructions="make sure to mention: ..." From f26264daf1d16703b4e549edb612c950b677632b Mon Sep 17 00:00:00 2001 From: Phill Zarfos Date: Tue, 29 Aug 2023 17:59:52 -0400 Subject: [PATCH 61/97] added describe command to CodeCommit --- INSTALL.md | 4 +- README.md | 2 +- pr_agent/git_providers/codecommit_client.py | 61 +++++++++--- pr_agent/git_providers/codecommit_provider.py | 99 +++++++++++++++++-- tests/unittest/test_codecommit_provider.py | 53 ++++++++++ 5 files changed, 199 insertions(+), 20 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 88ad92bb..ba2547b1 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -309,7 +309,9 @@ Example IAM permissions to that user to allow access to CodeCommit: "codecommit:Get*", "codecommit:List*", "codecommit:PostComment*", - "codecommit:PutCommentReaction" + "codecommit:PutCommentReaction", + "codecommit:UpdatePullRequestDescription", + "codecommit:UpdatePullRequestTitle" ], "Resource": "*" } diff --git a/README.md b/README.md index 47dca106..faab0af1 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull | TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | ⮑ Inline review | :white_check_mark: | :white_check_mark: | | | | | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | Auto-Description | :white_check_mark: | :white_check_mark: | | | +| | Auto-Description | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | | Improve Code | :white_check_mark: | :white_check_mark: | | | | | ⮑ Extended | :white_check_mark: | :white_check_mark: | | | | | Reflect and Review | :white_check_mark: | | | | diff --git a/pr_agent/git_providers/codecommit_client.py b/pr_agent/git_providers/codecommit_client.py index c1cfa763..6200340d 100644 --- a/pr_agent/git_providers/codecommit_client.py +++ b/pr_agent/git_providers/codecommit_client.py @@ -64,7 +64,7 @@ class CodeCommitClient: """ Get the differences between two commits in CodeCommit. - Parameters: + Args: - repo_name: Name of the repository - destination_commit: Commit hash you want to merge into (the "before" hash) (usually on the main or master branch) - source_commit: Commit hash of the code you are adding (the "after" branch) @@ -73,8 +73,8 @@ class CodeCommitClient: - List of CodeCommitDifferencesResponse objects Boto3 Documentation: - aws codecommit get-differences - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_differences.html + - aws codecommit get-differences + - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_differences.html """ if self.boto_client is None: self._connect_boto_client() @@ -101,7 +101,7 @@ class CodeCommitClient: """ Retrieve a file from CodeCommit. - Parameters: + Args: - repo_name: Name of the repository - file_path: Path to the file you are retrieving - sha_hash: Commit hash of the file you are retrieving @@ -110,8 +110,8 @@ class CodeCommitClient: - File contents Boto3 Documentation: - aws codecommit get_file - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_file.html + - aws codecommit get_file + - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_file.html """ if not file_path: return "" @@ -137,15 +137,15 @@ class CodeCommitClient: """ Get a information about a CodeCommit PR. - Parameters: + Args: - pr_number: The PR number you are requesting Returns: - CodeCommitPullRequestResponse object Boto3 Documentation: - aws codecommit get_pull_request - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_pull_request.html + - aws codecommit get_pull_request + - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/get_pull_request.html """ if self.boto_client is None: self._connect_boto_client() @@ -164,11 +164,48 @@ class CodeCommitClient: return CodeCommitPullRequestResponse(response.get("pullRequest", {})) + def publish_description(self, pr_number: int, pr_title: str, pr_body: str): + """ + Set the title and description on a pull request + + Args: + - pr_number: the AWS CodeCommit pull request number + - pr_title: title of the pull request + - pr_body: body of the pull request + + Returns: + - None + + Boto3 Documentation: + - aws codecommit update_pull_request_title + - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/update_pull_request_title.html + - aws codecommit update_pull_request_description + - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/update_pull_request_description.html + """ + if self.boto_client is None: + self._connect_boto_client() + + try: + self.boto_client.update_pull_request_title(pullRequestId=str(pr_number), title=pr_title) + self.boto_client.update_pull_request_description(pullRequestId=str(pr_number), description=pr_body) + except botocore.exceptions.ClientError as e: + if e.response["Error"]["Code"] == 'PullRequestDoesNotExistException': + raise ValueError(f"PR number does not exist: {pr_number}") from e + if e.response["Error"]["Code"] == 'InvalidTitleException': + raise ValueError(f"Invalid title for PR number: {pr_number}") from e + if e.response["Error"]["Code"] == 'InvalidDescriptionException': + raise ValueError(f"Invalid description for PR number: {pr_number}") from e + if e.response["Error"]["Code"] == 'PullRequestAlreadyClosedException': + raise ValueError(f"PR is already closed: PR number: {pr_number}") from e + raise ValueError(f"Boto3 client error calling publish_description") from e + except Exception as e: + raise ValueError(f"Error calling publish_description") from e + def publish_comment(self, repo_name: str, pr_number: int, destination_commit: str, source_commit: str, comment: str): """ Publish a comment to a pull request - Parameters: + Args: - repo_name: name of the repository - pr_number: number of the pull request - destination_commit: The commit hash you want to merge into (the "before" hash) (usually on the main or master branch) @@ -179,8 +216,8 @@ class CodeCommitClient: - None Boto3 Documentation: - aws codecommit post_comment_for_pull_request - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/post_comment_for_pull_request.html + - aws codecommit post_comment_for_pull_request + - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/post_comment_for_pull_request.html """ if self.boto_client is None: self._connect_boto_client() diff --git a/pr_agent/git_providers/codecommit_provider.py b/pr_agent/git_providers/codecommit_provider.py index a747e7f2..d43409c3 100644 --- a/pr_agent/git_providers/codecommit_provider.py +++ b/pr_agent/git_providers/codecommit_provider.py @@ -1,5 +1,6 @@ import logging import os +import re from collections import Counter from typing import List, Optional, Tuple from urllib.parse import urlparse @@ -153,17 +154,27 @@ class CodeCommitProvider(GitProvider): return self.diff_files def publish_description(self, pr_title: str, pr_body: str): - return "" # not implemented yet + try: + self.codecommit_client.publish_description( + pr_number=self.pr_num, + pr_title=pr_title, + pr_body=CodeCommitProvider._add_additional_newlines(pr_body), + ) + except Exception as e: + raise ValueError(f"CodeCommit Cannot publish description for PR: {self.pr_num}") from e def publish_comment(self, pr_comment: str, is_temporary: bool = False): if is_temporary: logging.info(pr_comment) return + pr_comment = CodeCommitProvider._remove_markdown_html(pr_comment) + pr_comment = CodeCommitProvider._add_additional_newlines(pr_comment) + try: self.codecommit_client.publish_comment( repo_name=self.repo_name, - pr_number=str(self.pr_num), + pr_number=self.pr_num, destination_commit=self.pr.destination_commit, source_commit=self.pr.source_commit, comment=pr_comment, @@ -200,7 +211,7 @@ class CodeCommitProvider(GitProvider): Returns a dictionary of languages, containing the percentage of each language used in the PR. Returns: - dict: A dictionary where each key is a language name and the corresponding value is the percentage of that language in the PR. + - dict: A dictionary where each key is a language name and the corresponding value is the percentage of that language in the PR. """ commit_files = self.get_files() filenames = [ item.filename for item in commit_files ] @@ -251,11 +262,20 @@ class CodeCommitProvider(GitProvider): @staticmethod def _parse_pr_url(pr_url: str) -> Tuple[str, int]: + """ + Parse the CodeCommit PR URL and return the repository name and PR number. + + Args: + - pr_url: the full AWS CodeCommit pull request URL + + Returns: + - Tuple[str, int]: A tuple containing the repository name and PR number. + """ # Example PR URL: # https://us-east-1.console.aws.amazon.com/codesuite/codecommit/repositories/__MY_REPO__/pull-requests/123456" parsed_url = urlparse(pr_url) - if "us-east-1.console.aws.amazon.com" not in parsed_url.netloc: + if not CodeCommitProvider._is_valid_codecommit_hostname(parsed_url.netloc): raise ValueError(f"The provided URL is not a valid CodeCommit URL: {pr_url}") path_parts = parsed_url.path.strip("/").split("/") @@ -278,6 +298,22 @@ class CodeCommitProvider(GitProvider): return repo_name, pr_number + @staticmethod + def _is_valid_codecommit_hostname(hostname: str) -> bool: + """ + Check if the provided hostname is a valid AWS CodeCommit hostname. + + This is not an exhaustive check of AWS region names, + but instead uses a regex to check for matching AWS region patterns. + + Args: + - hostname: the hostname to check + + Returns: + - bool: True if the hostname is valid, False otherwise. + """ + return re.match(r"^[a-z]{2}-(gov-)?[a-z]+-\d\.console\.aws\.amazon\.com$", hostname) is not None + def _get_pr(self): response = self.codecommit_client.get_pr(self.pr_num) @@ -306,13 +342,52 @@ class CodeCommitProvider(GitProvider): return "" # not implemented yet @staticmethod - def _get_edit_type(codecommit_change_type): + def _add_additional_newlines(body: str) -> str: + """ + Replace single newlines in a PR body with double newlines. + + CodeCommit Markdown does not seem to render as well as GitHub Markdown, + so we add additional newlines to the PR body to make it more readable in CodeCommit. + + Args: + - body: the PR body + + Returns: + - str: the PR body with the double newlines added + """ + return re.sub(r'(? str: + """ + Remove the HTML tags from a PR comment. + + CodeCommit Markdown does not seem to render as well as GitHub Markdown, + so we remove the HTML tags from the PR comment to make it more readable in CodeCommit. + + Args: + - comment: the PR comment + + Returns: + - str: the PR comment with the HTML tags removed + """ + comment = comment.replace("

    ", "") + comment = comment.replace("
    ", "") + comment = comment.replace("", "") + comment = comment.replace("", "") + return comment + + @staticmethod + def _get_edit_type(codecommit_change_type: str): """ Convert the CodeCommit change type string to the EDIT_TYPE enum. The CodeCommit change type string is returned from the get_differences SDK method. + Args: + - codecommit_change_type: the CodeCommit change type string + Returns: - An EDIT_TYPE enum representing the modified, added, deleted, or renamed file in the PR diff. + - An EDIT_TYPE enum representing the modified, added, deleted, or renamed file in the PR diff. """ t = codecommit_change_type.upper() edit_type = None @@ -333,6 +408,12 @@ class CodeCommitProvider(GitProvider): The returned extensions will include the dot "." prefix, to accommodate for the dots in the existing language_extension_map settings. Filenames with no extension will return an empty string for the extension. + + Args: + - filenames: a list of filenames + + Returns: + - list: A list of file extensions, including the dot "." prefix. """ extensions = [] for filename in filenames: @@ -349,6 +430,12 @@ class CodeCommitProvider(GitProvider): Return a dictionary containing the programming language name (as the key), and the percentage that language is used (as the value), given a list of file extensions. + + Args: + - extensions: a list of file extensions + + Returns: + - dict: A dictionary where each key is a language name and the corresponding value is the percentage of that language in the PR. """ total_files = len(extensions) if total_files == 0: diff --git a/tests/unittest/test_codecommit_provider.py b/tests/unittest/test_codecommit_provider.py index e35f7250..9de7c45c 100644 --- a/tests/unittest/test_codecommit_provider.py +++ b/tests/unittest/test_codecommit_provider.py @@ -26,11 +26,48 @@ class TestCodeCommitFile: class TestCodeCommitProvider: def test_parse_pr_url(self): + # Test that the _parse_pr_url() function can extract the repo name and PR number from a CodeCommit URL url = "https://us-east-1.console.aws.amazon.com/codesuite/codecommit/repositories/my_test_repo/pull-requests/321" repo_name, pr_number = CodeCommitProvider._parse_pr_url(url) assert repo_name == "my_test_repo" assert pr_number == 321 + def test_is_valid_codecommit_hostname(self): + # Test the various AWS regions + assert CodeCommitProvider._is_valid_codecommit_hostname("af-south-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("ap-east-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("ap-northeast-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("ap-northeast-2.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("ap-northeast-3.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("ap-south-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("ap-south-2.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("ap-southeast-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("ap-southeast-2.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("ap-southeast-3.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("ap-southeast-4.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("ca-central-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("eu-central-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("eu-central-2.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("eu-north-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("eu-south-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("eu-south-2.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("eu-west-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("eu-west-2.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("eu-west-3.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("il-central-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("me-central-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("me-south-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("sa-east-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("us-east-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("us-east-2.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("us-gov-east-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("us-gov-west-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("us-west-1.console.aws.amazon.com") + assert CodeCommitProvider._is_valid_codecommit_hostname("us-west-2.console.aws.amazon.com") + # Test non-AWS regions + assert not CodeCommitProvider._is_valid_codecommit_hostname("no-such-region.console.aws.amazon.com") + assert not CodeCommitProvider._is_valid_codecommit_hostname("console.aws.amazon.com") + # Test that an error is raised when an invalid CodeCommit URL is provided to the set_pr() method of the CodeCommitProvider class. # Generated by CodiumAI def test_invalid_codecommit_url(self): @@ -106,6 +143,7 @@ class TestCodeCommitProvider: assert percentages == {} def test_get_edit_type(self): + # Test that the _get_edit_type() function can convert a CodeCommit letter to an EDIT_TYPE enum assert CodeCommitProvider._get_edit_type("A") == EDIT_TYPE.ADDED assert CodeCommitProvider._get_edit_type("D") == EDIT_TYPE.DELETED assert CodeCommitProvider._get_edit_type("M") == EDIT_TYPE.MODIFIED @@ -117,3 +155,18 @@ class TestCodeCommitProvider: assert CodeCommitProvider._get_edit_type("r") == EDIT_TYPE.RENAMED assert CodeCommitProvider._get_edit_type("X") is None + + def test_add_additional_newlines(self): + # a short string to test adding double newlines + input = "abc\ndef\n\n___\nghi\njkl\nmno\n\npqr\n" + expect = "abc\n\ndef\n\n___\n\nghi\n\njkl\n\nmno\n\npqr\n\n" + assert CodeCommitProvider._add_additional_newlines(input) == expect + # a test example from a real PR + input = "## PR Type:\nEnhancement\n\n___\n## PR Description:\nThis PR introduces a new feature to the script, allowing users to filter servers by name.\n\n___\n## PR Main Files Walkthrough:\n`foo`: The foo script has been updated to include a new command line option `-f` or `--filter`.\n`bar`: The bar script has been updated to list stopped servers.\n" + expect = "## PR Type:\n\nEnhancement\n\n___\n\n## PR Description:\n\nThis PR introduces a new feature to the script, allowing users to filter servers by name.\n\n___\n\n## PR Main Files Walkthrough:\n\n`foo`: The foo script has been updated to include a new command line option `-f` or `--filter`.\n\n`bar`: The bar script has been updated to list stopped servers.\n\n" + assert CodeCommitProvider._add_additional_newlines(input) == expect + + def test_remove_markdown_html(self): + input = "## PR Feedback\n
    Code feedback:\nfile foo\n\n" + expect = "## PR Feedback\nCode feedback:\nfile foo\n\n" + assert CodeCommitProvider._remove_markdown_html(input) == expect \ No newline at end of file From d64b1f80da65c5dfc07a75c593cb116fa7dadc21 Mon Sep 17 00:00:00 2001 From: Ori Kotek Date: Wed, 30 Aug 2023 12:12:09 +0300 Subject: [PATCH 62/97] Allow overriding GitHub app default action by using repo local file --- pr_agent/servers/github_app.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pr_agent/servers/github_app.py b/pr_agent/servers/github_app.py index 1d1c901c..10584e54 100644 --- a/pr_agent/servers/github_app.py +++ b/pr_agent/servers/github_app.py @@ -12,6 +12,7 @@ from starlette_context import context from starlette_context.middleware import RawContextMiddleware from pr_agent.agent.pr_agent import PRAgent +from pr_agent.algo.utils import update_settings_from_args from pr_agent.config_loader import get_settings, global_settings from pr_agent.git_providers import get_git_provider from pr_agent.servers.utils import verify_signature @@ -123,8 +124,13 @@ async def handle_request(body: Dict[str, Any], event: str): return {} logging.info(f"Performing review because of event={event} and action={action}") for command in get_settings().github_app.pr_commands: - logging.info(f"Performing command: {command}") - await agent.handle_request(api_url, command) + split_command = command.split(" ") + command = split_command[0] + args = split_command[1:] + other_args = update_settings_from_args(args) + new_command = ' '.join([command] + other_args) + logging.info(f"Performing command: {new_command}") + await agent.handle_request(api_url, new_command) logging.info("event or action does not require handling") return {} From 307b3b4bf7560ee676636d9dc810329e1c794f14 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Wed, 30 Aug 2023 19:42:46 +0300 Subject: [PATCH 63/97] GitHub App instructions --- CONFIGURATION.md | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 9bbfd910..79f86313 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -21,20 +21,41 @@ verbosity_level=2 ``` This is useful for debugging or experimenting with the different tools. -### Working from pre-built repo (GitHub Action/GitHub App/Docker) -When running PR-Agent from a pre-built repo, the default configuration file will be loaded. +### Working from GitHub App (pre-built repo) +When running PR-Agent from GitHub App, the default configuration file (`configuration.toml`) will be loaded. -To edit the configuration, you have two options: -1. Place a local configuration file in the root of your local repo. The local file will be used instead of the default one. -2. For online usage, just add `--config_path=` to you command, to edit a specific configuration value. +#### GitHub app default tools +The GitHub app configuration is defined in the `[github_app]` section of the configuration file. +The main parameter is `pr_commands`, which is a list of tools to run when a new PR is opened: +``` +[github_app] +pr_commands = [ + "/describe --pr_description.add_original_user_description=true --pr_description.keep_original_user_title=true", + "/auto_review", +] +``` +This means that when a new PR is opened, PR-Agent will run the `describe` and `auto_review` tools. +For the describe tool, the `add_original_user_description` and `keep_original_user_title` parameters will be set to `true`. + +However, you can override the default actions parameters by uploading a local configuration called `.pr_agent.toml`, to the root of your repo. +For example, if your local `.pr_agent.toml` file contains: +``` +[pr_description] +add_original_user_description = false +keep_original_user_title = false +``` +Then when a new PR is opened, PR-Agent will run the `describe` tool with the above parameters. + +#### Online usage +For online usage (calling tools by comments on a PR), just add `--config_path=` to any command, to edit a specific configuration value. For example if you want to edit `pr_reviewer` configurations, you can run: ``` /review --pr_reviewer.extra_instructions="..." --pr_reviewer.require_score_review=false ... ``` - Any configuration value in `configuration.toml` file can be similarly edited. -### General configuration parameters + +### General configuration walkthrough #### Changing a model See [here](pr_agent/algo/__init__.py) for the list of available models. From ad6dd38fe37a7b700eea4ebc5ec8c831f0c61429 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Wed, 30 Aug 2023 19:46:33 +0300 Subject: [PATCH 64/97] GitHub App instructions --- CONFIGURATION.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 79f86313..07cc5701 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -25,7 +25,7 @@ This is useful for debugging or experimenting with the different tools. When running PR-Agent from GitHub App, the default configuration file (`configuration.toml`) will be loaded. #### GitHub app default tools -The GitHub app configuration is defined in the `[github_app]` section of the configuration file. +The GitHub app specific configurations are defined in the `[github_app]` section of the configuration file. The main parameter is `pr_commands`, which is a list of tools to run when a new PR is opened: ``` [github_app] @@ -37,7 +37,7 @@ pr_commands = [ This means that when a new PR is opened, PR-Agent will run the `describe` and `auto_review` tools. For the describe tool, the `add_original_user_description` and `keep_original_user_title` parameters will be set to `true`. -However, you can override the default actions parameters by uploading a local configuration called `.pr_agent.toml`, to the root of your repo. +However, you can override the default tool parameters by uploading a local configuration called `.pr_agent.toml`, to the root of your repo. For example, if your local `.pr_agent.toml` file contains: ``` [pr_description] @@ -46,6 +46,8 @@ keep_original_user_title = false ``` Then when a new PR is opened, PR-Agent will run the `describe` tool with the above parameters. +Note that a local `.pr_agent.toml` file enables you to edit and customize the default parameters of any tool, not just the ones that are run by default. + #### Online usage For online usage (calling tools by comments on a PR), just add `--config_path=` to any command, to edit a specific configuration value. For example if you want to edit `pr_reviewer` configurations, you can run: From eec62c14dcf0917160a6c5902c6e8deb5b2a7fb9 Mon Sep 17 00:00:00 2001 From: Krish Dholakia Date: Wed, 30 Aug 2023 09:49:01 -0700 Subject: [PATCH 65/97] bump litellm version - prevent default logging --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fe92a74b..2441b1c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ atlassian-python-api==3.39.0 GitPython~=3.1.32 PyYAML==6.0 starlette-context==0.3.6 -litellm~=0.1.445 +litellm~=0.1.504 boto3~=1.28.25 google-cloud-storage==2.10.0 ujson==5.8.0 From 3c27432f50ba074171896761ec1e4e6b7c35f3f5 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Wed, 30 Aug 2023 19:53:28 +0300 Subject: [PATCH 66/97] GitHub App instructions --- CONFIGURATION.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 07cc5701..66ec4f06 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -25,8 +25,8 @@ This is useful for debugging or experimenting with the different tools. When running PR-Agent from GitHub App, the default configuration file (`configuration.toml`) will be loaded. #### GitHub app default tools -The GitHub app specific configurations are defined in the `[github_app]` section of the configuration file. -The main parameter is `pr_commands`, which is a list of tools to run when a new PR is opened: +The `[github_app]` section of the configuration file defines GitHub app specific configurations. +The important parameter is `pr_commands`, which is a list of tools that will be run automatically when a new PR is opened: ``` [github_app] pr_commands = [ @@ -37,7 +37,7 @@ pr_commands = [ This means that when a new PR is opened, PR-Agent will run the `describe` and `auto_review` tools. For the describe tool, the `add_original_user_description` and `keep_original_user_title` parameters will be set to `true`. -However, you can override the default tool parameters by uploading a local configuration called `.pr_agent.toml`, to the root of your repo. +However, you can override the default tool parameters by uploading a local configuration file called `.pr_agent.toml` to the root of your repo. For example, if your local `.pr_agent.toml` file contains: ``` [pr_description] @@ -46,10 +46,10 @@ keep_original_user_title = false ``` Then when a new PR is opened, PR-Agent will run the `describe` tool with the above parameters. -Note that a local `.pr_agent.toml` file enables you to edit and customize the default parameters of any tool, not just the ones that are run by default. +Note that a local `.pr_agent.toml` file enables you to edit and customize the default parameters of any tool, not just the ones that are run automatically. #### Online usage -For online usage (calling tools by comments on a PR), just add `--config_path=` to any command, to edit a specific configuration value. +For online usage (calling tools by comments on a PR like `/ask ...`), just add `--config_path=` to any command, to edit a specific configuration value. For example if you want to edit `pr_reviewer` configurations, you can run: ``` /review --pr_reviewer.extra_instructions="..." --pr_reviewer.require_score_review=false ... From 92e23ff260f6f6dcd59bf26ab41f4781e8556388 Mon Sep 17 00:00:00 2001 From: zmeir Date: Wed, 30 Aug 2023 23:05:41 +0300 Subject: [PATCH 67/97] Fix #254 --- pr_agent/git_providers/git_provider.py | 4 ++-- pr_agent/tools/pr_description.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pr_agent/git_providers/git_provider.py b/pr_agent/git_providers/git_provider.py index 9dced773..4651d47d 100644 --- a/pr_agent/git_providers/git_provider.py +++ b/pr_agent/git_providers/git_provider.py @@ -86,11 +86,11 @@ class GitProvider(ABC): def get_pr_description_full(self) -> str: pass - def get_pr_description(self) -> str: + def get_pr_description(self, *, full: bool = True) -> str: from pr_agent.config_loader import get_settings from pr_agent.algo.pr_processing import clip_tokens max_tokens = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None) - description = self.get_pr_description_full() + description = self.get_pr_description_full() if full else self.get_user_description() if max_tokens: return clip_tokens(description, max_tokens) return description diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index acd272bc..c45917f4 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -36,7 +36,7 @@ class PRDescription: self.vars = { "title": self.git_provider.pr.title, "branch": self.git_provider.get_pr_branch(), - "description": self.git_provider.get_pr_description(), + "description": self.git_provider.get_pr_description(full=False), "language": self.main_pr_language, "diff": "", # empty diff for initial calculation "extra_instructions": get_settings().pr_description.extra_instructions, From c6c97ac98aaa4dc58f2227f3048f60290e609a61 Mon Sep 17 00:00:00 2001 From: zmeir Date: Wed, 30 Aug 2023 23:33:38 +0300 Subject: [PATCH 68/97] Try to change the improve command prompt to prevent split lines range --- .../settings/pr_code_suggestions_prompts.toml | 18 ++++++++++++------ pr_agent/tools/pr_code_suggestions.py | 7 ++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/pr_agent/settings/pr_code_suggestions_prompts.toml b/pr_agent/settings/pr_code_suggestions_prompts.toml index f60b9cc2..6867a883 100644 --- a/pr_agent/settings/pr_code_suggestions_prompts.toml +++ b/pr_agent/settings/pr_code_suggestions_prompts.toml @@ -68,12 +68,17 @@ Code suggestions: type: string description: |- a code snippet showing the relevant code lines from a '__new hunk__' section. - It must be continuous, correctly formatted and indented, and without line numbers. - relevant lines: - type: string + It must be contiguous, correctly formatted and indented, and without line numbers. + relevant lines start: + type: integer description: |- - the relevant lines from a '__new hunk__' section, in the format of 'start_line-end_line'. - For example: '10-15'. They should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above. + The relevant line number from a '__new hunk__' section where the suggestion starts (inclusive). + Should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above. + relevant lines end: + type: integer + description: |- + The relevant line number from a '__new hunk__' section where the suggestion ends (inclusive). + Should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above. improved code: type: string description: |- @@ -90,7 +95,8 @@ Code suggestions: Add a docstring to func1() existing code: |- def func1(): - relevant lines: '12-12' + relevant lines start: 12 + relevant lines end: 12 improved code: |- ... ``` diff --git a/pr_agent/tools/pr_code_suggestions.py b/pr_agent/tools/pr_code_suggestions.py index d9fb3051..ba45598e 100644 --- a/pr_agent/tools/pr_code_suggestions.py +++ b/pr_agent/tools/pr_code_suggestions.py @@ -113,11 +113,8 @@ class PRCodeSuggestions: if get_settings().config.verbosity_level >= 2: logging.info(f"suggestion: {d}") relevant_file = d['relevant file'].strip() - relevant_lines_str = d['relevant lines'].strip() - if ',' in relevant_lines_str: # handling 'relevant lines': '181, 190' or '178-184, 188-194' - relevant_lines_str = relevant_lines_str.split(',')[0] - relevant_lines_start = int(relevant_lines_str.split('-')[0]) # absolute position - relevant_lines_end = int(relevant_lines_str.split('-')[-1]) + relevant_lines_start = int(d['relevant lines start']) # absolute position + relevant_lines_end = int(d['relevant lines end']) content = d['suggestion content'] new_code_snippet = d['improved code'] From 48233fde237ca3c6a8513e81a0ce4d7085b41410 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Thu, 31 Aug 2023 08:02:14 +0300 Subject: [PATCH 69/97] Editing the prompts --- CONFIGURATION.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 66ec4f06..67a2be2d 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -48,6 +48,24 @@ Then when a new PR is opened, PR-Agent will run the `describe` tool with the abo Note that a local `.pr_agent.toml` file enables you to edit and customize the default parameters of any tool, not just the ones that are run automatically. +#### Editing the prompts +The prompts for the different tools of PR-Agent are defined in the `pr_agent/settings` folder. + +In practice, the prompts are loaded and stored as a standard setting object, +so editing them is similar to editing any other configuration value - just place the relevant setting in `.pr_agent.toml`file, and override the default value. + +For example, if you want to edit the prompts of the [describe](./pr_agent/settings/pr_description_prompts.toml) tool, you can add the following to your `.pr_agent.toml` file: +``` +[pr_description_prompt] +system=""" +... +""" +user=""" +... +""" +``` +Note that the new prompt will need to generate an output compatible with the relevant [post-process function](./pr_agent/tools/pr_description.py). + #### Online usage For online usage (calling tools by comments on a PR like `/ask ...`), just add `--config_path=` to any command, to edit a specific configuration value. For example if you want to edit `pr_reviewer` configurations, you can run: From e0ca594a6992d4e173821d46f0a9070c7dfe276f Mon Sep 17 00:00:00 2001 From: mrT23 Date: Thu, 31 Aug 2023 08:06:43 +0300 Subject: [PATCH 70/97] Editing the prompts --- CONFIGURATION.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 67a2be2d..26b72dc9 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -49,10 +49,10 @@ Then when a new PR is opened, PR-Agent will run the `describe` tool with the abo Note that a local `.pr_agent.toml` file enables you to edit and customize the default parameters of any tool, not just the ones that are run automatically. #### Editing the prompts -The prompts for the different tools of PR-Agent are defined in the `pr_agent/settings` folder. +The prompts for the various PR-Agent tools are defined in the `pr_agent/settings` folder. -In practice, the prompts are loaded and stored as a standard setting object, -so editing them is similar to editing any other configuration value - just place the relevant setting in `.pr_agent.toml`file, and override the default value. +In practice, the prompts are loaded and stored as a standard setting object. Hence, +editing them is similar to editing any other configuration value - just place the relevant key in `.pr_agent.toml`file, and override the default value. For example, if you want to edit the prompts of the [describe](./pr_agent/settings/pr_description_prompts.toml) tool, you can add the following to your `.pr_agent.toml` file: ``` From 376c4523ddb55273e0524b840817462bc1a7918c Mon Sep 17 00:00:00 2001 From: mrT23 Date: Thu, 31 Aug 2023 08:08:09 +0300 Subject: [PATCH 71/97] Editing the prompts --- CONFIGURATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 26b72dc9..cce29c77 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -64,7 +64,7 @@ user=""" ... """ ``` -Note that the new prompt will need to generate an output compatible with the relevant [post-process function](./pr_agent/tools/pr_description.py). +Note that the new prompt will need to generate an output compatible with the relevant [post-process function](./pr_agent/tools/pr_description.py#L137). #### Online usage For online usage (calling tools by comments on a PR like `/ask ...`), just add `--config_path=` to any command, to edit a specific configuration value. From ce9014073c0b3b2229770145ecd62b08e992ab80 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Thu, 31 Aug 2023 08:19:33 +0300 Subject: [PATCH 72/97] Editing the prompts --- CONFIGURATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index cce29c77..c54d047d 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -35,7 +35,7 @@ pr_commands = [ ] ``` This means that when a new PR is opened, PR-Agent will run the `describe` and `auto_review` tools. -For the describe tool, the `add_original_user_description` and `keep_original_user_title` parameters will be set to `true`. +For the describe tool, the `add_original_user_description` and `keep_original_user_title` parameters will be set to true. However, you can override the default tool parameters by uploading a local configuration file called `.pr_agent.toml` to the root of your repo. For example, if your local `.pr_agent.toml` file contains: From 5cbcef276c1a5b535e33f08c36d257034669be2d Mon Sep 17 00:00:00 2001 From: idavidov Date: Thu, 31 Aug 2023 09:19:02 +0300 Subject: [PATCH 73/97] cross link in INSTALL for GitAPP configuration overwrite --- INSTALL.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index ba2547b1..1b0f5bc4 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -251,7 +251,10 @@ docker push codiumai/pr-agent:github_app # Push to your Docker repository 9. Install the app by navigating to the "Install App" tab and selecting your desired repositories. ---- +> **Note:** When running PR-Agent from GitHub App, the default configuration file (configuration.toml) will be loaded.
    +> However, you can override the default tool parameters by uploading a local configuration file
    +> For more information please check out [CONFIGURATION.md](CONFIGURATION.md#working-from-github-app-pre-built-repo) + ### Method 6 - Deploy as a Lambda Function From 8823d8c0e962d4465e5a7b46671559336b948705 Mon Sep 17 00:00:00 2001 From: idavidov Date: Thu, 31 Aug 2023 09:21:40 +0300 Subject: [PATCH 74/97] small format change --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 1b0f5bc4..e778e266 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -254,7 +254,7 @@ docker push codiumai/pr-agent:github_app # Push to your Docker repository > **Note:** When running PR-Agent from GitHub App, the default configuration file (configuration.toml) will be loaded.
    > However, you can override the default tool parameters by uploading a local configuration file
    > For more information please check out [CONFIGURATION.md](CONFIGURATION.md#working-from-github-app-pre-built-repo) - +--- ### Method 6 - Deploy as a Lambda Function From 8263bf5f9cf4a3c8bb6ec098ee446afefc84e0cb Mon Sep 17 00:00:00 2001 From: Ori Kotek Date: Thu, 31 Aug 2023 09:26:16 +0300 Subject: [PATCH 75/97] small refactor of azure devops --- Dockerfile.github_action | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile.github_action b/Dockerfile.github_action index d6763f0a..dd1ae750 100644 --- a/Dockerfile.github_action +++ b/Dockerfile.github_action @@ -2,7 +2,8 @@ FROM python:3.10 as base WORKDIR /app ADD pyproject.toml . -RUN pip install . && rm pyproject.toml +ADD requirements.txt . +RUN pip install . && rm pyproject.toml requirements.txt ENV PYTHONPATH=/app ADD pr_agent pr_agent ADD github_action/entrypoint.sh / From 06d00032df3e58adbe831ae0f535587d57e3f567 Mon Sep 17 00:00:00 2001 From: szecsip Date: Thu, 31 Aug 2023 11:47:51 +0000 Subject: [PATCH 76/97] update docs for Azure DevOps --- CONFIGURATION.md | 18 ++++++++++++++++++ README.md | 44 ++++++++++++++++++++++---------------------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 66ec4f06..f9d3d010 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -73,8 +73,26 @@ key = ... Also review the [AiHandler](pr_agent/algo/ai_handler.py) file for instruction how to set keys for other models. +#### Changing a GIT provider +See [here](pr_agent/git_providers/__init__.py) for the list of GIT providers. + +To use GitHub, for example, set: +``` +[config] +git_provider="github" +``` + #### Extra instructions +##### General All PR-Agent tools have a parameter called `extra_instructions`, that enables to add free-text extra instructions. Example usage: ``` /update_changelog --pr_update_changelog.extra_instructions="Make sure to update also the version ..." +``` + +##### Azure DevOps provider +To use Azure DevOps provider set: +``` +[config] +git_provider="azure" +use_repo_settings_file=false ``` \ No newline at end of file diff --git a/README.md b/README.md index 095d2c04..85705c6c 100644 --- a/README.md +++ b/README.md @@ -75,27 +75,27 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull ## Overview `PR-Agent` offers extensive pull request functionalities across various git providers: -| | | GitHub | Gitlab | Bitbucket | CodeCommit | -|-------|---------------------------------------------|:------:|:------:|:---------:|:----------:| -| TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | ⮑ Inline review | :white_check_mark: | :white_check_mark: | | | -| | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | Auto-Description | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -| | Improve Code | :white_check_mark: | :white_check_mark: | | | -| | ⮑ Extended | :white_check_mark: | :white_check_mark: | | | -| | Reflect and Review | :white_check_mark: | | | | -| | Update CHANGELOG.md | :white_check_mark: | | | | -| | | | | | | -| USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | App / webhook | :white_check_mark: | :white_check_mark: | | | -| | Tagging bot | :white_check_mark: | | | | -| | Actions | :white_check_mark: | | | | -| | | | | | | -| CORE | PR compression | :white_check_mark: | :white_check_mark: | :white_check_mark: | | -| | Repo language prioritization | :white_check_mark: | :white_check_mark: | :white_check_mark: | | -| | Adaptive and token-aware
    file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: | | -| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | | -| | Incremental PR Review | :white_check_mark: | | | | +| | | GitHub | Gitlab | Bitbucket | CodeCommit | Azure DevOps | +|-------|---------------------------------------------|:------:|:------:|:---------:|:----------:|:----------:| +| TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | ⮑ Inline review | :white_check_mark: | :white_check_mark: | | | | +| | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: +| | Auto-Description | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: | +| | Improve Code | :white_check_mark: | :white_check_mark: | | | | +| | ⮑ Extended | :white_check_mark: | :white_check_mark: | | | | +| | Reflect and Review | :white_check_mark: | | | | :white_check_mark: | +| | Update CHANGELOG.md | :white_check_mark: | | | | | +| | | | | | | | +| USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | App / webhook | :white_check_mark: | :white_check_mark: | | | | +| | Tagging bot | :white_check_mark: | | | | | +| | Actions | :white_check_mark: | | | | | +| | | | | | | | +| CORE | PR compression | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +| | Repo language prioritization | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +| | Adaptive and token-aware
    file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +| | Incremental PR Review | :white_check_mark: | | | | | Examples for invoking the different tools via the CLI: - **Review**: python cli.py --pr_url= review @@ -184,4 +184,4 @@ Here are some advantages of PR-Agent: - [Aider - GPT powered coding in your terminal](https://github.com/paul-gauthier/aider) - [openai-pr-reviewer](https://github.com/coderabbitai/openai-pr-reviewer) - [CodeReview BOT](https://github.com/anc95/ChatGPT-CodeReview) -- [AI-Maintainer](https://github.com/merwanehamadi/AI-Maintainer) +- [AI-Maintainer](https://github.com/merwanehamadi/AI-Maintainer) \ No newline at end of file From 24900305d6f6e0042cc9ceb10964bca8cc47d779 Mon Sep 17 00:00:00 2001 From: szecsip Date: Thu, 31 Aug 2023 11:50:41 +0000 Subject: [PATCH 77/97] update docs for Azure DevOps --- CONFIGURATION.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index f9d3d010..cedd8514 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -90,9 +90,16 @@ All PR-Agent tools have a parameter called `extra_instructions`, that enables to ``` ##### Azure DevOps provider -To use Azure DevOps provider set: +To use Azure DevOps provider use the following settings in configuration.toml: ``` [config] git_provider="azure" use_repo_settings_file=false +``` + +And use the following settings (you have to replace the values) in .secrets.toml: +``` +[azure_devops] +org = "https://dev.azure.com/YOUR_ORGANIZATION/" +pat = "YOUR_PAT_TOKEN" ``` \ No newline at end of file From be19b64542995deb7a6fbe4c0eabf07b7908aa18 Mon Sep 17 00:00:00 2001 From: szecsip Date: Thu, 31 Aug 2023 11:53:54 +0000 Subject: [PATCH 78/97] add dependencies for Azure DevOps provider --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 2441b1c1..99efa846 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,5 @@ litellm~=0.1.504 boto3~=1.28.25 google-cloud-storage==2.10.0 ujson==5.8.0 +azure-devops==7.1.0b3 +msrest==0.7.1 \ No newline at end of file From f7c698ff54aecafc8e639bfe051ccfa44b17f063 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Fri, 1 Sep 2023 19:40:38 +0300 Subject: [PATCH 79/97] update --- README.md | 81 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 85705c6c..c236c8fa 100644 --- a/README.md +++ b/README.md @@ -35,33 +35,55 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull

    -

    /review:

    -
    -

    - -

    -
    -

    /reflect_and_review:

    -
    -

    - -

    -
    -

    /ask:

    -
    -

    - -

    -
    -

    /improve:

    -
    -

    - -

    -
    + +[//]: # (

    /review:

    ) +[//]: # (
    ) + +[//]: # (

    ) + +[//]: # () + +[//]: # (

    ) + +[//]: # (
    ) +[//]: # (

    /reflect_and_review:

    ) + +[//]: # (
    ) + +[//]: # (

    ) + +[//]: # () + +[//]: # (

    ) + +[//]: # (
    ) + +[//]: # (

    /ask:

    ) + +[//]: # (
    ) + +[//]: # (

    ) + +[//]: # () + +[//]: # (

    ) + +[//]: # (
    ) + +[//]: # (

    /improve:

    ) + +[//]: # (
    ) + +[//]: # (

    ) + +[//]: # () + +[//]: # (

    ) + +[//]: # (
    )
    - +## Table of Contents - [Overview](#overview) - [Try it now](#try-it-now) - [Installation](#installation) @@ -78,7 +100,6 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull | | | GitHub | Gitlab | Bitbucket | CodeCommit | Azure DevOps | |-------|---------------------------------------------|:------:|:------:|:---------:|:----------:|:----------:| | TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | ⮑ Inline review | :white_check_mark: | :white_check_mark: | | | | | | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Auto-Description | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: | | | Improve Code | :white_check_mark: | :white_check_mark: | | | | @@ -91,10 +112,10 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull | | Tagging bot | :white_check_mark: | | | | | | | Actions | :white_check_mark: | | | | | | | | | | | | | -| CORE | PR compression | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -| | Repo language prioritization | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -| | Adaptive and token-aware
    file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +| CORE | PR compression | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Repo language prioritization | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Adaptive and token-aware
    file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | Incremental PR Review | :white_check_mark: | | | | | Examples for invoking the different tools via the CLI: From 777c773a9098f9c18ac35bb9373abd727873a235 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Fri, 1 Sep 2023 19:50:10 +0300 Subject: [PATCH 80/97] update --- CONFIGURATION.md | 23 +++++++++++++++++++---- README.md | 22 +++++++--------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 320ddedf..162a51d3 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -2,18 +2,33 @@ The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)** +Options that are available in the configuration file can be specified at run time when calling actions. Two examples: +``` +- /review --pr_reviewer.extra_instructions="focus on the file: ..." +- /describe --pr_description.add_original_user_description=false -pr_description.extra_instructions="make sure to mention: ..." +``` + ### Working from CLI When running from source (CLI), your local configuration file will be initially used. -Example for invoking the 'review' tools via the CLI: +Examples for invoking the different tools via the CLI: ``` -python cli.py --pr-url= review +- **Review**: python cli.py --pr_url= review +- **Describe**: python cli.py --pr_url= describe +- **Improve**: python cli.py --pr_url= improve +- **Ask**: python cli.py --pr_url= ask "Write me a poem about this PR" +- **Reflect**: python cli.py --pr_url= reflect +- **Update Changelog**: python cli.py --pr_url= update_changelog ``` -In addition to general configurations, the 'review' tool will use parameters from the `[pr_reviewer]` section (every tool has a dedicated section in the configuration file). -Note that you can print results locally, without publishing them, by setting in `configuration.toml`: +"" is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). +Notes: + +(1) In addition to general configurations, each tool has its own configurations. For example, the 'review' tool will use parameters from the `[pr_reviewer]` section. + +(2) You can print results locally, without publishing them, by setting in `configuration.toml`: ``` [config] publish_output=true diff --git a/README.md b/README.md index c236c8fa..cbf13970 100644 --- a/README.md +++ b/README.md @@ -118,29 +118,21 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull | | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | Incremental PR Review | :white_check_mark: | | | | | -Examples for invoking the different tools via the CLI: -- **Review**: python cli.py --pr_url= review -- **Describe**: python cli.py --pr_url= describe -- **Improve**: python cli.py --pr_url= improve -- **Ask**: python cli.py --pr_url= ask "Write me a poem about this PR" -- **Reflect**: python cli.py --pr_url= reflect -- **Update Changelog**: python cli.py --pr_url= update_changelog - -"" is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). - -In the [configuration](./CONFIGURATION.md) file you can select your git provider (GitHub, Gitlab, Bitbucket), and further configure the different tools. -Options that are available in the configuration file can be specified at run time when calling actions. Two examples: -- /review --pr_reviewer.extra_instructions="focus on the file: ..." -- /describe --pr_description.add_original_user_description=false -pr_description.extra_instructions="make sure to mention: ..." +Review the [configuration](./CONFIGURATION.md) section for instruction how to use the different tools, select the relevant git provider (GitHub, Gitlab, Bitbucket,...), and adjust the configuration file to your needs. ## Try it now Try GPT-4 powered PR-Agent on your public GitHub repository for free. Just mention `@CodiumAI-Agent` and add the desired command in any PR comment! The agent will generate a response based on your command. +For example, add a comment: +``` +@CodiumAI-Agent /review +``` +And the agent will respond with a review of your PR ![Review generation process](https://www.codium.ai/images/demo-2.gif) -To set up your own PR-Agent, see the [Installation](#installation) section +To set up your own PR-Agent, see the [Installation](#installation) section below. --- ## Installation From 7a6efbcb5590e21519bb76dc25697744a31791bf Mon Sep 17 00:00:00 2001 From: mrT23 Date: Fri, 1 Sep 2023 19:56:20 +0300 Subject: [PATCH 81/97] update --- CONFIGURATION.md | 11 +++++++---- README.md | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 162a51d3..82326a27 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -2,6 +2,11 @@ The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)** +The `git_provider` field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: +` +"github", "gitlab", "azure", "codecommit","local" +` + Options that are available in the configuration file can be specified at run time when calling actions. Two examples: ``` - /review --pr_reviewer.extra_instructions="focus on the file: ..." @@ -13,20 +18,18 @@ When running from source (CLI), your local configuration file will be initially Examples for invoking the different tools via the CLI: -``` - **Review**: python cli.py --pr_url= review - **Describe**: python cli.py --pr_url= describe - **Improve**: python cli.py --pr_url= improve - **Ask**: python cli.py --pr_url= ask "Write me a poem about this PR" - **Reflect**: python cli.py --pr_url= reflect - **Update Changelog**: python cli.py --pr_url= update_changelog -``` -"" is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). +`` is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). Notes: -(1) In addition to general configurations, each tool has its own configurations. For example, the 'review' tool will use parameters from the `[pr_reviewer]` section. +(1) In addition to general configurations, each tool has its own configurations. For example, the 'review' tool will use parameters from the `[pr_reviewer]` section in the [configuration file](pr_agent/settings/configuration.toml) (2) You can print results locally, without publishing them, by setting in `configuration.toml`: ``` diff --git a/README.md b/README.md index cbf13970..71278a03 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,6 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull - [How it works](#how-it-works) - [Why use PR-Agent](#why-use-pr-agent) - [Roadmap](#roadmap) -- [Similar projects](#similar-projects)
    @@ -133,6 +132,7 @@ And the agent will respond with a review of your PR To set up your own PR-Agent, see the [Installation](#installation) section below. + --- ## Installation From 7f6493009cbc4393176da98cd61faa0c914c2f11 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Fri, 1 Sep 2023 20:05:33 +0300 Subject: [PATCH 82/97] update --- CONFIGURATION.md | 21 ++++++--------------- README.md | 6 +++--- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 82326a27..10a18aa7 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -14,7 +14,7 @@ Options that are available in the configuration file can be specified at run tim ``` ### Working from CLI -When running from source (CLI), your local configuration file will be initially used. +When running from source (CLI), your local configuration file will be used. Examples for invoking the different tools via the CLI: @@ -27,9 +27,9 @@ Examples for invoking the different tools via the CLI: `` is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). -Notes: +**Notes:** -(1) In addition to general configurations, each tool has its own configurations. For example, the 'review' tool will use parameters from the `[pr_reviewer]` section in the [configuration file](pr_agent/settings/configuration.toml) +(1) In addition to general configuration options, each tool has its own configurations. For example, the 'review' tool will use parameters from the `[pr_reviewer]` section in the [configuration file](/pr_agent/settings/configuration.toml#L16) (2) You can print results locally, without publishing them, by setting in `configuration.toml`: ``` @@ -40,11 +40,11 @@ verbosity_level=2 This is useful for debugging or experimenting with the different tools. ### Working from GitHub App (pre-built repo) -When running PR-Agent from GitHub App, the default configuration file (`configuration.toml`) will be loaded. +When running PR-Agent from GitHub App, the default configuration file (`configuration.toml`) will be initially loaded. #### GitHub app default tools -The `[github_app]` section of the configuration file defines GitHub app specific configurations. -The important parameter is `pr_commands`, which is a list of tools that will be run automatically when a new PR is opened: +The `[github_app]` section defines the GitHub app specific configurations. +An important parameter is `pr_commands`, which is a list of tools that will be run automatically when a new PR is opened: ``` [github_app] pr_commands = [ @@ -109,15 +109,6 @@ key = ... Also review the [AiHandler](pr_agent/algo/ai_handler.py) file for instruction how to set keys for other models. -#### Changing a GIT provider -See [here](pr_agent/git_providers/__init__.py) for the list of GIT providers. - -To use GitHub, for example, set: -``` -[config] -git_provider="github" -``` - #### Extra instructions ##### General All PR-Agent tools have a parameter called `extra_instructions`, that enables to add free-text extra instructions. Example usage: diff --git a/README.md b/README.md index 71278a03..ec0d5315 100644 --- a/README.md +++ b/README.md @@ -117,16 +117,16 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull | | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | Incremental PR Review | :white_check_mark: | | | | | -Review the [configuration](./CONFIGURATION.md) section for instruction how to use the different tools, select the relevant git provider (GitHub, Gitlab, Bitbucket,...), and adjust the configuration file to your needs. +Review the [configuration](./CONFIGURATION.md) section for instructions how to use the different tools, select the relevant git provider (GitHub, Gitlab, Bitbucket,...), and adjust the configuration file to your needs. ## Try it now Try GPT-4 powered PR-Agent on your public GitHub repository for free. Just mention `@CodiumAI-Agent` and add the desired command in any PR comment! The agent will generate a response based on your command. -For example, add a comment: +For example, add a comment to any pull request with the following text: ``` @CodiumAI-Agent /review ``` -And the agent will respond with a review of your PR +and the agent will respond with a review of your PR ![Review generation process](https://www.codium.ai/images/demo-2.gif) From 4d6d6c48121c06f677be6dcc0e5b9ddccdaace37 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Fri, 1 Sep 2023 20:09:53 +0300 Subject: [PATCH 83/97] update --- CONFIGURATION.md | 17 ++++++++--------- README.md | 4 ++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 10a18aa7..5124bb63 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -4,7 +4,7 @@ The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via t The `git_provider` field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: ` -"github", "gitlab", "azure", "codecommit","local" +"github", "gitlab", "azure", "codecommit", "local" ` Options that are available in the configuration file can be specified at run time when calling actions. Two examples: @@ -18,12 +18,12 @@ When running from source (CLI), your local configuration file will be used. Examples for invoking the different tools via the CLI: -- **Review**: python cli.py --pr_url= review -- **Describe**: python cli.py --pr_url= describe -- **Improve**: python cli.py --pr_url= improve -- **Ask**: python cli.py --pr_url= ask "Write me a poem about this PR" -- **Reflect**: python cli.py --pr_url= reflect -- **Update Changelog**: python cli.py --pr_url= update_changelog +- **Review**: `python cli.py --pr_url= review` +- **Describe**: `python cli.py --pr_url= describe` +- **Improve**: `python cli.py --pr_url= improve` +- **Ask**: `python cli.py --pr_url= ask "Write me a poem about this PR"` +- **Reflect**: `python cli.py --pr_url= reflect` +- **Update Changelog**: `python cli.py --pr_url= update_changelog` `` is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). @@ -110,13 +110,12 @@ key = ... Also review the [AiHandler](pr_agent/algo/ai_handler.py) file for instruction how to set keys for other models. #### Extra instructions -##### General All PR-Agent tools have a parameter called `extra_instructions`, that enables to add free-text extra instructions. Example usage: ``` /update_changelog --pr_update_changelog.extra_instructions="Make sure to update also the version ..." ``` -##### Azure DevOps provider +#### Azure DevOps provider To use Azure DevOps provider use the following settings in configuration.toml: ``` [config] diff --git a/README.md b/README.md index ec0d5315..d0f8e9db 100644 --- a/README.md +++ b/README.md @@ -117,11 +117,11 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull | | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | Incremental PR Review | :white_check_mark: | | | | | -Review the [configuration](./CONFIGURATION.md) section for instructions how to use the different tools, select the relevant git provider (GitHub, Gitlab, Bitbucket,...), and adjust the configuration file to your needs. +Review the **[configuration](./CONFIGURATION.md)** section for detailed instructions how to use the different tools, select the relevant git provider (GitHub, Gitlab, Bitbucket,...), and adjust the configuration file to your needs. ## Try it now -Try GPT-4 powered PR-Agent on your public GitHub repository for free. Just mention `@CodiumAI-Agent` and add the desired command in any PR comment! The agent will generate a response based on your command. +You can try GPT-4 powered PR-Agent, on your public GitHub repository, instantly. Just mention `@CodiumAI-Agent` and add the desired command in any PR comment. The agent will generate a response based on your command. For example, add a comment to any pull request with the following text: ``` @CodiumAI-Agent /review From 44b790567b03be1832989b3969bfeaed987fa478 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Fri, 1 Sep 2023 19:40:38 +0300 Subject: [PATCH 84/97] update readme --- CONFIGURATION.md | 50 ++++++++++++---------- README.md | 107 ++++++++++++++++++++++++++--------------------- 2 files changed, 89 insertions(+), 68 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 320ddedf..5124bb63 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -2,18 +2,36 @@ The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)** +The `git_provider` field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: +` +"github", "gitlab", "azure", "codecommit", "local" +` + +Options that are available in the configuration file can be specified at run time when calling actions. Two examples: +``` +- /review --pr_reviewer.extra_instructions="focus on the file: ..." +- /describe --pr_description.add_original_user_description=false -pr_description.extra_instructions="make sure to mention: ..." +``` + ### Working from CLI -When running from source (CLI), your local configuration file will be initially used. +When running from source (CLI), your local configuration file will be used. -Example for invoking the 'review' tools via the CLI: +Examples for invoking the different tools via the CLI: -``` -python cli.py --pr-url= review -``` -In addition to general configurations, the 'review' tool will use parameters from the `[pr_reviewer]` section (every tool has a dedicated section in the configuration file). +- **Review**: `python cli.py --pr_url= review` +- **Describe**: `python cli.py --pr_url= describe` +- **Improve**: `python cli.py --pr_url= improve` +- **Ask**: `python cli.py --pr_url= ask "Write me a poem about this PR"` +- **Reflect**: `python cli.py --pr_url= reflect` +- **Update Changelog**: `python cli.py --pr_url= update_changelog` -Note that you can print results locally, without publishing them, by setting in `configuration.toml`: +`` is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). +**Notes:** + +(1) In addition to general configuration options, each tool has its own configurations. For example, the 'review' tool will use parameters from the `[pr_reviewer]` section in the [configuration file](/pr_agent/settings/configuration.toml#L16) + +(2) You can print results locally, without publishing them, by setting in `configuration.toml`: ``` [config] publish_output=true @@ -22,11 +40,11 @@ verbosity_level=2 This is useful for debugging or experimenting with the different tools. ### Working from GitHub App (pre-built repo) -When running PR-Agent from GitHub App, the default configuration file (`configuration.toml`) will be loaded. +When running PR-Agent from GitHub App, the default configuration file (`configuration.toml`) will be initially loaded. #### GitHub app default tools -The `[github_app]` section of the configuration file defines GitHub app specific configurations. -The important parameter is `pr_commands`, which is a list of tools that will be run automatically when a new PR is opened: +The `[github_app]` section defines the GitHub app specific configurations. +An important parameter is `pr_commands`, which is a list of tools that will be run automatically when a new PR is opened: ``` [github_app] pr_commands = [ @@ -91,23 +109,13 @@ key = ... Also review the [AiHandler](pr_agent/algo/ai_handler.py) file for instruction how to set keys for other models. -#### Changing a GIT provider -See [here](pr_agent/git_providers/__init__.py) for the list of GIT providers. - -To use GitHub, for example, set: -``` -[config] -git_provider="github" -``` - #### Extra instructions -##### General All PR-Agent tools have a parameter called `extra_instructions`, that enables to add free-text extra instructions. Example usage: ``` /update_changelog --pr_update_changelog.extra_instructions="Make sure to update also the version ..." ``` -##### Azure DevOps provider +#### Azure DevOps provider To use Azure DevOps provider use the following settings in configuration.toml: ``` [config] diff --git a/README.md b/README.md index 85705c6c..d0f8e9db 100644 --- a/README.md +++ b/README.md @@ -35,33 +35,55 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull

    -

    /review:

    -
    -

    - -

    -
    -

    /reflect_and_review:

    -
    -

    - -

    -
    -

    /ask:

    -
    -

    - -

    -
    -

    /improve:

    -
    -

    - -

    -
    + +[//]: # (

    /review:

    ) +[//]: # (
    ) + +[//]: # (

    ) + +[//]: # () + +[//]: # (

    ) + +[//]: # (
    ) +[//]: # (

    /reflect_and_review:

    ) + +[//]: # (
    ) + +[//]: # (

    ) + +[//]: # () + +[//]: # (

    ) + +[//]: # (
    ) + +[//]: # (

    /ask:

    ) + +[//]: # (
    ) + +[//]: # (

    ) + +[//]: # () + +[//]: # (

    ) + +[//]: # (
    ) + +[//]: # (

    /improve:

    ) + +[//]: # (
    ) + +[//]: # (

    ) + +[//]: # () + +[//]: # (

    ) + +[//]: # (
    )
    - +## Table of Contents - [Overview](#overview) - [Try it now](#try-it-now) - [Installation](#installation) @@ -69,7 +91,6 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull - [How it works](#how-it-works) - [Why use PR-Agent](#why-use-pr-agent) - [Roadmap](#roadmap) -- [Similar projects](#similar-projects)
    @@ -78,7 +99,6 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull | | | GitHub | Gitlab | Bitbucket | CodeCommit | Azure DevOps | |-------|---------------------------------------------|:------:|:------:|:---------:|:----------:|:----------:| | TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| | ⮑ Inline review | :white_check_mark: | :white_check_mark: | | | | | | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Auto-Description | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: | | | Improve Code | :white_check_mark: | :white_check_mark: | | | | @@ -91,34 +111,27 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull | | Tagging bot | :white_check_mark: | | | | | | | Actions | :white_check_mark: | | | | | | | | | | | | | -| CORE | PR compression | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -| | Repo language prioritization | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -| | Adaptive and token-aware
    file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | -| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | | :white_check_mark: | +| CORE | PR compression | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Repo language prioritization | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Adaptive and token-aware
    file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | Incremental PR Review | :white_check_mark: | | | | | -Examples for invoking the different tools via the CLI: -- **Review**: python cli.py --pr_url= review -- **Describe**: python cli.py --pr_url= describe -- **Improve**: python cli.py --pr_url= improve -- **Ask**: python cli.py --pr_url= ask "Write me a poem about this PR" -- **Reflect**: python cli.py --pr_url= reflect -- **Update Changelog**: python cli.py --pr_url= update_changelog - -"" is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). - -In the [configuration](./CONFIGURATION.md) file you can select your git provider (GitHub, Gitlab, Bitbucket), and further configure the different tools. -Options that are available in the configuration file can be specified at run time when calling actions. Two examples: -- /review --pr_reviewer.extra_instructions="focus on the file: ..." -- /describe --pr_description.add_original_user_description=false -pr_description.extra_instructions="make sure to mention: ..." +Review the **[configuration](./CONFIGURATION.md)** section for detailed instructions how to use the different tools, select the relevant git provider (GitHub, Gitlab, Bitbucket,...), and adjust the configuration file to your needs. ## Try it now -Try GPT-4 powered PR-Agent on your public GitHub repository for free. Just mention `@CodiumAI-Agent` and add the desired command in any PR comment! The agent will generate a response based on your command. +You can try GPT-4 powered PR-Agent, on your public GitHub repository, instantly. Just mention `@CodiumAI-Agent` and add the desired command in any PR comment. The agent will generate a response based on your command. +For example, add a comment to any pull request with the following text: +``` +@CodiumAI-Agent /review +``` +and the agent will respond with a review of your PR ![Review generation process](https://www.codium.ai/images/demo-2.gif) -To set up your own PR-Agent, see the [Installation](#installation) section + +To set up your own PR-Agent, see the [Installation](#installation) section below. --- From 20c32375e197d45984d2223e6ed00a573b9a0535 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Fri, 1 Sep 2023 20:14:14 +0300 Subject: [PATCH 85/97] auto --- CONFIGURATION.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 5124bb63..7790f9f5 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -44,7 +44,7 @@ When running PR-Agent from GitHub App, the default configuration file (`configur #### GitHub app default tools The `[github_app]` section defines the GitHub app specific configurations. -An important parameter is `pr_commands`, which is a list of tools that will be run automatically when a new PR is opened: +An important parameter is `pr_commands`, which is a list of tools that will be **run automatically when a new PR is opened**: ``` [github_app] pr_commands = [ @@ -55,7 +55,7 @@ pr_commands = [ This means that when a new PR is opened, PR-Agent will run the `describe` and `auto_review` tools. For the describe tool, the `add_original_user_description` and `keep_original_user_title` parameters will be set to true. -However, you can override the default tool parameters by uploading a local configuration file called `.pr_agent.toml` to the root of your repo. +However, you can override the default tool parameters by uploading a local configuration file, called `.pr_agent.toml`, to the root of your repo. For example, if your local `.pr_agent.toml` file contains: ``` [pr_description] From 690c8194790af018ad6fd0ff74ec283160376202 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Fri, 1 Sep 2023 20:22:10 +0300 Subject: [PATCH 86/97] update CONFIGURATION.md --- CONFIGURATION.md | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index b92d6e0b..e7ab2d65 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -2,18 +2,36 @@ The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)** +The `git_provider` field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: +` +"github", "gitlab", "azure", "codecommit", "local" +` + +Options that are available in the configuration file can be specified at run time when calling actions. Two examples: +``` +- /review --pr_reviewer.extra_instructions="focus on the file: ..." +- /describe --pr_description.add_original_user_description=false -pr_description.extra_instructions="make sure to mention: ..." +``` + ### Working from CLI -When running from source (CLI), your local configuration file will be initially used. +When running from source (CLI), your local configuration file will be used. -Example for invoking the 'review' tools via the CLI: +Examples for invoking the different tools via the CLI: -``` -python cli.py --pr-url= review -``` -In addition to general configurations, the 'review' tool will use parameters from the `[pr_reviewer]` section (every tool has a dedicated section in the configuration file). +- **Review**: `python cli.py --pr_url= review` +- **Describe**: `python cli.py --pr_url= describe` +- **Improve**: `python cli.py --pr_url= improve` +- **Ask**: `python cli.py --pr_url= ask "Write me a poem about this PR"` +- **Reflect**: `python cli.py --pr_url= reflect` +- **Update Changelog**: `python cli.py --pr_url= update_changelog` -Note that you can print results locally, without publishing them, by setting in `configuration.toml`: +`` is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). +**Notes:** + +(1) In addition to general configuration options, each tool has its own configurations. For example, the 'review' tool will use parameters from the `[pr_reviewer]` section in the [configuration file](/pr_agent/settings/configuration.toml#L16) + +(2) You can print results locally, without publishing them, by setting in `configuration.toml`: ``` [config] publish_output=true @@ -22,7 +40,7 @@ verbosity_level=2 This is useful for debugging or experimenting with the different tools. ### Working from GitHub App (pre-built repo) -When running PR-Agent from GitHub App, the default configuration file (`configuration.toml`) will be loaded. +When running PR-Agent from GitHub App, the default configuration file (`configuration.toml`) will be initially loaded. #### GitHub app default tools The `[github_app]` section defines the GitHub app specific configurations. @@ -91,23 +109,13 @@ key = ... Also review the [AiHandler](pr_agent/algo/ai_handler.py) file for instruction how to set keys for other models. -#### Changing a GIT provider -See [here](pr_agent/git_providers/__init__.py) for the list of GIT providers. - -To use GitHub, for example, set: -``` -[config] -git_provider="github" -``` - #### Extra instructions -##### General All PR-Agent tools have a parameter called `extra_instructions`, that enables to add free-text extra instructions. Example usage: ``` /update_changelog --pr_update_changelog.extra_instructions="Make sure to update also the version ..." ``` -##### Azure DevOps provider +#### Azure DevOps provider To use Azure DevOps provider use the following settings in configuration.toml: ``` [config] From e79bcbed930b62feaf51128807fab62c87a5bd8d Mon Sep 17 00:00:00 2001 From: mrT23 Date: Fri, 1 Sep 2023 20:25:07 +0300 Subject: [PATCH 87/97] update CONFIGURATION.md --- CONFIGURATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index e7ab2d65..872caedc 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -2,7 +2,7 @@ The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)** -The `git_provider` field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: +The [git_provider](pr-agent/blob/main/pr_agent/settings/configuration.toml#L4) field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: ` "github", "gitlab", "azure", "codecommit", "local" ` From d52c11b907cdb5fded834fe3047f840b34e634b6 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Fri, 1 Sep 2023 20:26:36 +0300 Subject: [PATCH 88/97] update CONFIGURATION.md --- CONFIGURATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 872caedc..f3daa1d0 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -2,7 +2,7 @@ The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)** -The [git_provider](pr-agent/blob/main/pr_agent/settings/configuration.toml#L4) field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: +The [git_provider](pr-agent/settings/configuration.toml#L4) field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: ` "github", "gitlab", "azure", "codecommit", "local" ` From 98019fe97f11142072959add69bb6edc7ff97c55 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Fri, 1 Sep 2023 20:27:29 +0300 Subject: [PATCH 89/97] update CONFIGURATION.md --- CONFIGURATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index f3daa1d0..b02c7e2b 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -2,7 +2,7 @@ The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)** -The [git_provider](pr-agent/settings/configuration.toml#L4) field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: +The [git_provider](pr_agent/settings/configuration.toml#L4) field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: ` "github", "gitlab", "azure", "codecommit", "local" ` From 2b8a8ce824f4dfda40ac5c3aaa78bd4486169341 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Sat, 2 Sep 2023 09:19:35 +0300 Subject: [PATCH 90/97] update README.md --- INSTALL.md | 78 ++++++++++++++++++------------------ README.md | 10 ++--- CONFIGURATION.md => Usage.md | 73 ++++++++++++++++++++++++--------- 3 files changed, 99 insertions(+), 62 deletions(-) rename CONFIGURATION.md => Usage.md (62%) diff --git a/INSTALL.md b/INSTALL.md index e778e266..37bee57e 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -9,8 +9,8 @@ To get started with PR-Agent quickly, you first need to acquire two tokens: There are several ways to use PR-Agent: - [Method 1: Use Docker image (no installation required)](INSTALL.md#method-1-use-docker-image-no-installation-required) -- [Method 2: Run as a GitHub Action](INSTALL.md#method-2-run-as-a-github-action) -- [Method 3: Run from source](INSTALL.md#method-3-run-from-source) +- [Method 2: Run from source](INSTALL.md#method-2-run-from-source) +- [Method 3: Run as a GitHub Action](INSTALL.md#method-3-run-as-a-github-action) - [Method 4: Run as a polling server](INSTALL.md#method-4-run-as-a-polling-server) - [Method 5: Run as a GitHub App](INSTALL.md#method-5-run-as-a-github-app) - [Method 6: Deploy as a Lambda Function](INSTALL.md#method-6---deploy-as-a-lambda-function) @@ -55,7 +55,41 @@ Possible questions you can ask include: --- -### Method 2: Run as a GitHub Action +### Method 2: Run from source + +1. Clone this repository: + +``` +git clone https://github.com/Codium-ai/pr-agent.git +``` + +2. Install the requirements in your favorite virtual environment: + +``` +pip install -r requirements.txt +``` + +3. Copy the secrets template file and fill in your OpenAI key and your GitHub user token: + +``` +cp pr_agent/settings/.secrets_template.toml pr_agent/settings/.secrets.toml +chmod 600 pr_agent/settings/.secrets.toml +# Edit .secrets.toml file +``` + +4. Add the pr_agent folder to your PYTHONPATH, then run the cli.py script: + +``` +export PYTHONPATH=[$PYTHONPATH:] +python pr_agent/cli.py --pr_url review +python pr_agent/cli.py --pr_url ask +python pr_agent/cli.py --pr_url describe +python pr_agent/cli.py --pr_url improve +``` + +--- + +### Method 3: Run as a GitHub Action You can use our pre-built Github Action Docker image to run PR-Agent as a Github Action. @@ -114,7 +148,7 @@ The GITHUB_TOKEN secret is automatically created by GitHub. 3. Merge this change to your main branch. When you open your next PR, you should see a comment from `github-actions` bot with a review of your PR, and instructions on how to use the rest of the tools. -4. You may configure PR-Agent by adding environment variables under the env section corresponding to any configurable property in the [configuration](./CONFIGURATION.md) file. Some examples: +4. You may configure PR-Agent by adding environment variables under the env section corresponding to any configurable property in the [configuration](./Usage.md) file. Some examples: ```yaml env: # ... previous environment values @@ -125,40 +159,6 @@ When you open your next PR, you should see a comment from `github-actions` bot w --- -### Method 3: Run from source - -1. Clone this repository: - -``` -git clone https://github.com/Codium-ai/pr-agent.git -``` - -2. Install the requirements in your favorite virtual environment: - -``` -pip install -r requirements.txt -``` - -3. Copy the secrets template file and fill in your OpenAI key and your GitHub user token: - -``` -cp pr_agent/settings/.secrets_template.toml pr_agent/settings/.secrets.toml -chmod 600 pr_agent/settings/.secrets.toml -# Edit .secrets.toml file -``` - -4. Add the pr_agent folder to your PYTHONPATH, then run the cli.py script: - -``` -export PYTHONPATH=[$PYTHONPATH:] -python pr_agent/cli.py --pr_url review -python pr_agent/cli.py --pr_url ask -python pr_agent/cli.py --pr_url describe -python pr_agent/cli.py --pr_url improve -``` - ---- - ### Method 4: Run as a polling server Request reviews by tagging your Github user on a PR @@ -253,7 +253,7 @@ docker push codiumai/pr-agent:github_app # Push to your Docker repository > **Note:** When running PR-Agent from GitHub App, the default configuration file (configuration.toml) will be loaded.
    > However, you can override the default tool parameters by uploading a local configuration file
    -> For more information please check out [CONFIGURATION.md](CONFIGURATION.md#working-from-github-app-pre-built-repo) +> For more information please check out [CONFIGURATION.md](Usage.md#working-from-github-app-pre-built-repo) --- ### Method 6 - Deploy as a Lambda Function diff --git a/README.md b/README.md index d0f8e9db..cdd2688f 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull - [Overview](#overview) - [Try it now](#try-it-now) - [Installation](#installation) -- [Configuration](./CONFIGURATION.md) +- [Usage guide](./Usage.md) - [How it works](#how-it-works) - [Why use PR-Agent](#why-use-pr-agent) - [Roadmap](#roadmap) @@ -117,7 +117,7 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull | | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | Incremental PR Review | :white_check_mark: | | | | | -Review the **[configuration](./CONFIGURATION.md)** section for detailed instructions how to use the different tools, select the relevant git provider (GitHub, Gitlab, Bitbucket,...), and adjust the configuration file to your needs. +Review the **[usage guide](./Usage.md)** section for detailed instructions how to use the different tools, select the relevant git provider (GitHub, Gitlab, Bitbucket,...), and adjust the configuration file to your needs. ## Try it now @@ -145,8 +145,8 @@ To get started with PR-Agent quickly, you first need to acquire two tokens: There are several ways to use PR-Agent: - [Method 1: Use Docker image (no installation required)](INSTALL.md#method-1-use-docker-image-no-installation-required) -- [Method 2: Run as a GitHub Action](INSTALL.md#method-2-run-as-a-github-action) -- [Method 3: Run from source](INSTALL.md#method-3-run-from-source) +- [Method 2: Run from source](INSTALL.md#method-2-run-from-source) +- [Method 3: Run as a GitHub Action](INSTALL.md#method-3-run-as-a-github-action) - [Method 4: Run as a polling server](INSTALL.md#method-4-run-as-a-polling-server) - Request reviews by tagging your GitHub user on a PR - [Method 5: Run as a GitHub App](INSTALL.md#method-5-run-as-a-github-app) @@ -170,7 +170,7 @@ Here are some advantages of PR-Agent: - We emphasize **real-life practical usage**. Each tool (review, improve, ask, ...) has a single GPT-4 call, no more. We feel that this is critical for realistic team usage - obtaining an answer quickly (~30 seconds) and affordably. - Our [PR Compression strategy](./PR_COMPRESSION.md) is a core ability that enables to effectively tackle both short and long PRs. -- Our JSON prompting strategy enables to have **modular, customizable tools**. For example, the '/review' tool categories can be controlled via the [configuration](./CONFIGURATION.md) file. Adding additional categories is easy and accessible. +- Our JSON prompting strategy enables to have **modular, customizable tools**. For example, the '/review' tool categories can be controlled via the [configuration](pr_agent/settings/configuration.toml) file. Adding additional categories is easy and accessible. - We support **multiple git providers** (GitHub, Gitlab, Bitbucket, CodeCommit), **multiple ways** to use the tool (CLI, GitHub Action, GitHub App, Docker, ...), and **multiple models** (GPT-4, GPT-3.5, Anthropic, Cohere, Llama2). - We are open-source, and welcome contributions from the community. diff --git a/CONFIGURATION.md b/Usage.md similarity index 62% rename from CONFIGURATION.md rename to Usage.md index b02c7e2b..baca3db9 100644 --- a/CONFIGURATION.md +++ b/Usage.md @@ -1,20 +1,39 @@ -## Configuration +## Usage guide +### Introduction + +There are 3 basic ways to invoke CodiumAI PR-Agent: +1. Locally running a CLI command +2. Online usage - by [commenting](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021901) on a PR +3. Enabling PR-Agent specific tools to run automatically when a new PR is opened + +See the [installation guide](/INSTALL.md) for instructions on how to install and run your own PR-Agent. + +Specifically, CLI commands can be issued by invoking a pre-built [docker image](/INSTALL.md#running-from-source), or by invoking a [locally cloned repo](INSTALL.md#method-2-run-from-source) + +For online usage, you will need to setup either a [GitHub App](INSTALL.md#method-5-run-as-a-github-app), or a [GitHub Action](INSTALL.md#method-3-run-as-a-github-action). +GitHub App and GitHub Action also enable to run PR-Agent specific tool automatically when a new PR is opened. + + +#### The configuration file The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)** +In addition to general configuration options, each tool has its own configurations. For example, the 'review' tool will use parameters from the `[pr_reviewer]` section in the [configuration file](/pr_agent/settings/configuration.toml#L16) +** git provider:** The [git_provider](pr_agent/settings/configuration.toml#L4) field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: ` "github", "gitlab", "azure", "codecommit", "local" ` +** online usage:** Options that are available in the configuration file can be specified at run time when calling actions. Two examples: ``` - /review --pr_reviewer.extra_instructions="focus on the file: ..." - /describe --pr_description.add_original_user_description=false -pr_description.extra_instructions="make sure to mention: ..." ``` -### Working from CLI -When running from source (CLI), your local configuration file will be used. +### Working from a local repo (CLI) +When running from your local repo (CLI), your local configuration file, which you can edit, will be used. Examples for invoking the different tools via the CLI: @@ -28,8 +47,10 @@ Examples for invoking the different tools via the CLI: `` is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). **Notes:** - -(1) In addition to general configuration options, each tool has its own configurations. For example, the 'review' tool will use parameters from the `[pr_reviewer]` section in the [configuration file](/pr_agent/settings/configuration.toml#L16) +(1) in addition to editing the configuration file, you can also override any configuration by adding it to the command line: +``` +python cli.py --pr_url= review --pr_reviewer.extra_instructions="focus on the file: ..." +``` (2) You can print results locally, without publishing them, by setting in `configuration.toml`: ``` @@ -39,10 +60,32 @@ verbosity_level=2 ``` This is useful for debugging or experimenting with the different tools. -### Working from GitHub App (pre-built repo) -When running PR-Agent from GitHub App, the default configuration file (`configuration.toml`) will be initially loaded. -#### GitHub app default tools +### Online usage + +Online usage means invoking PR-Agent tools by [comments](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021901) on a PR. +Commands for invoking the different tools via comments: + +- **Review**: `/review` +- **Describe**: `/describe` +- **Improve**: `/improve` +- **Ask**: `/ask "Write me a poem about this PR"` +- **Reflect**: `/reflect` +- **Update Changelog**: `/update_changelog` + + +To edit a specific configuration value, just add `--config_path=` to any command. +For example if you want to edit the `review` tool configurations, you can run: +``` +/review --pr_reviewer.extra_instructions="..." --pr_reviewer.require_score_review=false ... +``` +Any configuration value in [configuration file](pr_agent/settings/configuration.toml) file can be similarly edited. + + +### Working with GitHub App +When running PR-Agent from [GitHub App](INSTALL.md#method-5-run-as-a-github-app), the default configuration file of a pre-built repo will be initially loaded. + +#### GitHub app automatic tools The `[github_app]` section defines the GitHub app specific configurations. An important parameter is `pr_commands`, which is a list of tools that will be **run automatically when a new PR is opened**: ``` @@ -62,7 +105,7 @@ For example, if your local `.pr_agent.toml` file contains: add_original_user_description = false keep_original_user_title = false ``` -Then when a new PR is opened, PR-Agent will run the `describe` tool with the above parameters. +When a new PR is opened, PR-Agent will run the `describe` tool with the above parameters. Note that a local `.pr_agent.toml` file enables you to edit and customize the default parameters of any tool, not just the ones that are run automatically. @@ -84,16 +127,10 @@ user=""" ``` Note that the new prompt will need to generate an output compatible with the relevant [post-process function](./pr_agent/tools/pr_description.py#L137). -#### Online usage -For online usage (calling tools by comments on a PR like `/ask ...`), just add `--config_path=` to any command, to edit a specific configuration value. -For example if you want to edit `pr_reviewer` configurations, you can run: -``` -/review --pr_reviewer.extra_instructions="..." --pr_reviewer.require_score_review=false ... -``` -Any configuration value in `configuration.toml` file can be similarly edited. +### Working with GitHub Action +TBD - -### General configuration walkthrough +### Appendix - general configuration walkthrough #### Changing a model See [here](pr_agent/algo/__init__.py) for the list of available models. From 990f69a95d9664e7138ebc54bd1dfd849be3da70 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Sat, 2 Sep 2023 09:25:38 +0300 Subject: [PATCH 91/97] update README.md --- Usage.md | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/Usage.md b/Usage.md index baca3db9..f54ef26f 100644 --- a/Usage.md +++ b/Usage.md @@ -5,9 +5,9 @@ There are 3 basic ways to invoke CodiumAI PR-Agent: 1. Locally running a CLI command 2. Online usage - by [commenting](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021901) on a PR -3. Enabling PR-Agent specific tools to run automatically when a new PR is opened +3. Enabling PR-Agent tools to run automatically when a new PR is opened -See the [installation guide](/INSTALL.md) for instructions on how to install and run your own PR-Agent. +See the [installation guide](/INSTALL.md) for instructions on how to setup your own PR-Agent. Specifically, CLI commands can be issued by invoking a pre-built [docker image](/INSTALL.md#running-from-source), or by invoking a [locally cloned repo](INSTALL.md#method-2-run-from-source) @@ -17,7 +17,7 @@ GitHub App and GitHub Action also enable to run PR-Agent specific tool automatic #### The configuration file The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)** -In addition to general configuration options, each tool has its own configurations. For example, the 'review' tool will use parameters from the `[pr_reviewer]` section in the [configuration file](/pr_agent/settings/configuration.toml#L16) +In addition to general configuration options, each tool has its own configurations. For example, the `review` tool will use parameters from the `[pr_reviewer]` section in the [configuration file](/pr_agent/settings/configuration.toml#L16) ** git provider:** The [git_provider](pr_agent/settings/configuration.toml#L4) field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: @@ -25,12 +25,17 @@ The [git_provider](pr_agent/settings/configuration.toml#L4) field in the configu "github", "gitlab", "azure", "codecommit", "local" ` -** online usage:** -Options that are available in the configuration file can be specified at run time when calling actions. Two examples: -``` -- /review --pr_reviewer.extra_instructions="focus on the file: ..." -- /describe --pr_description.add_original_user_description=false -pr_description.extra_instructions="make sure to mention: ..." -``` +[//]: # (** online usage:**) + +[//]: # (Options that are available in the configuration file can be specified at run time when calling actions. Two examples:) + +[//]: # (```) + +[//]: # (- /review --pr_reviewer.extra_instructions="focus on the file: ...") + +[//]: # (- /describe --pr_description.add_original_user_description=false -pr_description.extra_instructions="make sure to mention: ...") + +[//]: # (```) ### Working from a local repo (CLI) When running from your local repo (CLI), your local configuration file, which you can edit, will be used. @@ -47,7 +52,7 @@ Examples for invoking the different tools via the CLI: `` is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). **Notes:** -(1) in addition to editing the configuration file, you can also override any configuration by adding it to the command line: +(1) in addition to editing the configuration file, you can also override any configuration value by adding it to the command line: ``` python cli.py --pr_url= review --pr_reviewer.extra_instructions="focus on the file: ..." ``` @@ -83,10 +88,10 @@ Any configuration value in [configuration file](pr_agent/settings/configuration. ### Working with GitHub App -When running PR-Agent from [GitHub App](INSTALL.md#method-5-run-as-a-github-app), the default configuration file of a pre-built repo will be initially loaded. +When running PR-Agent from [GitHub App](INSTALL.md#method-5-run-as-a-github-app), the default configurations of a pre-built repo will be initially loaded. #### GitHub app automatic tools -The `[github_app]` section defines the GitHub app specific configurations. +The `[github_app]` section of the [configuration file](pr_agent/settings/configuration.toml) defines GitHub app specific configurations. An important parameter is `pr_commands`, which is a list of tools that will be **run automatically when a new PR is opened**: ``` [github_app] @@ -112,8 +117,8 @@ Note that a local `.pr_agent.toml` file enables you to edit and customize the de #### Editing the prompts The prompts for the various PR-Agent tools are defined in the `pr_agent/settings` folder. -In practice, the prompts are loaded and stored as a standard setting object. Hence, -editing them is similar to editing any other configuration value - just place the relevant key in `.pr_agent.toml`file, and override the default value. +In practice, the prompts are loaded and stored as a standard setting object. +Hence, editing them is similar to editing any other configuration value - just place the relevant key in `.pr_agent.toml`file, and override the default value. For example, if you want to edit the prompts of the [describe](./pr_agent/settings/pr_description_prompts.toml) tool, you can add the following to your `.pr_agent.toml` file: ``` @@ -130,7 +135,7 @@ Note that the new prompt will need to generate an output compatible with the rel ### Working with GitHub Action TBD -### Appendix - general configuration walkthrough +### Appendix - additional configurations walkthrough #### Changing a model See [here](pr_agent/algo/__init__.py) for the list of available models. From 1f7a833a540e60cab531696d992b54e0ecd352b9 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Sat, 2 Sep 2023 09:33:33 +0300 Subject: [PATCH 92/97] update README.md --- Usage.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Usage.md b/Usage.md index f54ef26f..c1277103 100644 --- a/Usage.md +++ b/Usage.md @@ -8,9 +8,7 @@ There are 3 basic ways to invoke CodiumAI PR-Agent: 3. Enabling PR-Agent tools to run automatically when a new PR is opened See the [installation guide](/INSTALL.md) for instructions on how to setup your own PR-Agent. - Specifically, CLI commands can be issued by invoking a pre-built [docker image](/INSTALL.md#running-from-source), or by invoking a [locally cloned repo](INSTALL.md#method-2-run-from-source) - For online usage, you will need to setup either a [GitHub App](INSTALL.md#method-5-run-as-a-github-app), or a [GitHub Action](INSTALL.md#method-3-run-as-a-github-action). GitHub App and GitHub Action also enable to run PR-Agent specific tool automatically when a new PR is opened. @@ -19,7 +17,7 @@ GitHub App and GitHub Action also enable to run PR-Agent specific tool automatic The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)** In addition to general configuration options, each tool has its own configurations. For example, the `review` tool will use parameters from the `[pr_reviewer]` section in the [configuration file](/pr_agent/settings/configuration.toml#L16) -** git provider:** +**git provider:** The [git_provider](pr_agent/settings/configuration.toml#L4) field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: ` "github", "gitlab", "azure", "codecommit", "local" @@ -38,7 +36,7 @@ The [git_provider](pr_agent/settings/configuration.toml#L4) field in the configu [//]: # (```) ### Working from a local repo (CLI) -When running from your local repo (CLI), your local configuration file, which you can edit, will be used. +When running from your local repo (CLI), your local configuration file will be used. Examples for invoking the different tools via the CLI: @@ -52,7 +50,7 @@ Examples for invoking the different tools via the CLI: `` is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). **Notes:** -(1) in addition to editing the configuration file, you can also override any configuration value by adding it to the command line: +(1) in addition to editing your local configuration file, you can also change any configuration value by adding it to the command line: ``` python cli.py --pr_url= review --pr_reviewer.extra_instructions="focus on the file: ..." ``` @@ -74,7 +72,7 @@ Commands for invoking the different tools via comments: - **Review**: `/review` - **Describe**: `/describe` - **Improve**: `/improve` -- **Ask**: `/ask "Write me a poem about this PR"` +- **Ask**: `/ask "..."` - **Reflect**: `/reflect` - **Update Changelog**: `/update_changelog` @@ -82,7 +80,7 @@ Commands for invoking the different tools via comments: To edit a specific configuration value, just add `--config_path=` to any command. For example if you want to edit the `review` tool configurations, you can run: ``` -/review --pr_reviewer.extra_instructions="..." --pr_reviewer.require_score_review=false ... +/review --pr_reviewer.extra_instructions="..." --pr_reviewer.require_score_review=false ``` Any configuration value in [configuration file](pr_agent/settings/configuration.toml) file can be similarly edited. @@ -91,7 +89,7 @@ Any configuration value in [configuration file](pr_agent/settings/configuration. When running PR-Agent from [GitHub App](INSTALL.md#method-5-run-as-a-github-app), the default configurations of a pre-built repo will be initially loaded. #### GitHub app automatic tools -The `[github_app]` section of the [configuration file](pr_agent/settings/configuration.toml) defines GitHub app specific configurations. +The [github_app](pr_agent/settings/configuration.toml#L56) section defines GitHub app specific configurations. An important parameter is `pr_commands`, which is a list of tools that will be **run automatically when a new PR is opened**: ``` [github_app] From 9567199bb2b723b17dad03113fa951e2282d952b Mon Sep 17 00:00:00 2001 From: mrT23 Date: Sat, 2 Sep 2023 09:37:44 +0300 Subject: [PATCH 93/97] update README.md --- Usage.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Usage.md b/Usage.md index c1277103..329bba54 100644 --- a/Usage.md +++ b/Usage.md @@ -8,14 +8,16 @@ There are 3 basic ways to invoke CodiumAI PR-Agent: 3. Enabling PR-Agent tools to run automatically when a new PR is opened See the [installation guide](/INSTALL.md) for instructions on how to setup your own PR-Agent. -Specifically, CLI commands can be issued by invoking a pre-built [docker image](/INSTALL.md#running-from-source), or by invoking a [locally cloned repo](INSTALL.md#method-2-run-from-source) + +Specifically, CLI commands can be issued by invoking a pre-built [docker image](/INSTALL.md#running-from-source), or by invoking a [locally cloned repo](INSTALL.md#method-2-run-from-source). + For online usage, you will need to setup either a [GitHub App](INSTALL.md#method-5-run-as-a-github-app), or a [GitHub Action](INSTALL.md#method-3-run-as-a-github-action). GitHub App and GitHub Action also enable to run PR-Agent specific tool automatically when a new PR is opened. #### The configuration file -The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)** -In addition to general configuration options, each tool has its own configurations. For example, the `review` tool will use parameters from the `[pr_reviewer]` section in the [configuration file](/pr_agent/settings/configuration.toml#L16) +The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](pr_agent/settings/configuration.toml)**. +In addition to general configuration options, each tool has its own configurations. For example, the `review` tool will use parameters from the [pr_reviewer](/pr_agent/settings/configuration.toml#L16) section in the configuration file. **git provider:** The [git_provider](pr_agent/settings/configuration.toml#L4) field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported: @@ -50,6 +52,7 @@ Examples for invoking the different tools via the CLI: `` is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). **Notes:** + (1) in addition to editing your local configuration file, you can also change any configuration value by adding it to the command line: ``` python cli.py --pr_url= review --pr_reviewer.extra_instructions="focus on the file: ..." @@ -86,7 +89,7 @@ Any configuration value in [configuration file](pr_agent/settings/configuration. ### Working with GitHub App -When running PR-Agent from [GitHub App](INSTALL.md#method-5-run-as-a-github-app), the default configurations of a pre-built repo will be initially loaded. +When running PR-Agent from [GitHub App](INSTALL.md#method-5-run-as-a-github-app), the default configurations from a pre-built repo will be initially loaded. #### GitHub app automatic tools The [github_app](pr_agent/settings/configuration.toml#L56) section defines GitHub app specific configurations. From 544bac70109fdaf848abb9dd28fb3ac64d7e4416 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Sat, 2 Sep 2023 09:43:46 +0300 Subject: [PATCH 94/97] update README.md --- Usage.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Usage.md b/Usage.md index 329bba54..12ff23aa 100644 --- a/Usage.md +++ b/Usage.md @@ -1,5 +1,13 @@ ## Usage guide +### Table of Contents +- [Introduction](#introduction) +- [Working from a local repo (CLI)](#working-from-a-local-repo-cli) +- [Online usage](#online-usage) +- [Working with GitHub App](#working-with-github-app) +- [Working with GitHub Action](#working-with-github-action) +- [Appendix - additional configurations walkthrough](#appendix---additional-configurations-walkthrough) + ### Introduction There are 3 basic ways to invoke CodiumAI PR-Agent: From 0203086aac2bdb12bc8bf858cdafdbe37bd004f0 Mon Sep 17 00:00:00 2001 From: Phill Zarfos Date: Sat, 2 Sep 2023 15:39:57 -0400 Subject: [PATCH 95/97] removed duplicate swift statement --- pr_agent/git_providers/git_provider.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pr_agent/git_providers/git_provider.py b/pr_agent/git_providers/git_provider.py index 4651d47d..330590a1 100644 --- a/pr_agent/git_providers/git_provider.py +++ b/pr_agent/git_providers/git_provider.py @@ -161,7 +161,6 @@ def get_main_pr_language(languages, files) -> str: most_common_extension == 'scala' and top_language == 'scala' or \ most_common_extension == 'kt' and top_language == 'kotlin' or \ most_common_extension == 'pl' and top_language == 'perl' or \ - most_common_extension == 'swift' and top_language == 'swift' or \ most_common_extension == top_language: main_language_str = top_language From 916d7c236ed167028b1149f8e2407b8b77fb57a4 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Sun, 3 Sep 2023 11:16:20 +0300 Subject: [PATCH 96/97] update README.md --- INSTALL.md | 8 ++++---- README.md | 30 +++++++++++++++--------------- Usage.md | 14 +++++++------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 37bee57e..74368ac0 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -81,10 +81,10 @@ chmod 600 pr_agent/settings/.secrets.toml ``` export PYTHONPATH=[$PYTHONPATH:] -python pr_agent/cli.py --pr_url review -python pr_agent/cli.py --pr_url ask -python pr_agent/cli.py --pr_url describe -python pr_agent/cli.py --pr_url improve +python pr_agent/cli.py --pr_url /review +python pr_agent/cli.py --pr_url /ask +python pr_agent/cli.py --pr_url /describe +python pr_agent/cli.py --pr_url /improve ``` --- diff --git a/README.md b/README.md index cdd2688f..f946a59c 100644 --- a/README.md +++ b/README.md @@ -17,17 +17,20 @@ Making pull requests less painful with an AI agent CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull requests faster and more efficiently. It automatically analyzes the pull request and can provide several types of PR feedback: -**Auto-Description**: Automatically generating [PR description](https://github.com/Codium-ai/pr-agent/pull/229#issue-1860711415) - title, type, summary, code walkthrough and labels. +**Auto Description (/describe)**: Automatically generating [PR description](https://github.com/Codium-ai/pr-agent/pull/229#issue-1860711415) - title, type, summary, code walkthrough and labels. \ -**Auto Review**: [Adjustable feedback](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695022908) about the PR main theme, type, relevant tests, security issues, score, and various suggestions for the PR content. +**Auto Review (/review)**: [Adjustable feedback](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695022908) about the PR main theme, type, relevant tests, security issues, score, and various suggestions for the PR content. \ -**Question Answering**: Answering [free-text questions](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021332) about the PR. +**Question Answering (/ask ...)**: Answering [free-text questions](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021332) about the PR. \ -**Code Suggestions**: [Committable code suggestions](https://github.com/Codium-ai/pr-agent/pull/229#discussion_r1306919276) for improving the PR. +**Code Suggestions (/improve)**: [Committable code suggestions](https://github.com/Codium-ai/pr-agent/pull/229#discussion_r1306919276) for improving the PR. \ -**Update Changelog**: Automatically updating the CHANGELOG.md file with the [PR changes](https://github.com/Codium-ai/pr-agent/pull/168#discussion_r1282077645). +**Update Changelog (/update_changelog)**: Automatically updating the CHANGELOG.md file with the [PR changes](https://github.com/Codium-ai/pr-agent/pull/168#discussion_r1282077645). -

    Example results:

    + +See the [usage guide](./Usage.md) for instructions how to run the different tools from [CLI](./Usage.md#working-from-a-local-repo-cli), or by [online usage](./Usage.md#online-usage). + +

    Example results:

    /describe:

    @@ -36,16 +39,13 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review pull

    -[//]: # (

    /review:

    ) -[//]: # (
    ) +

    /review:

    +
    +

    + +

    +
    -[//]: # (

    ) - -[//]: # () - -[//]: # (

    ) - -[//]: # (
    ) [//]: # (

    /reflect_and_review:

    ) [//]: # (
    ) diff --git a/Usage.md b/Usage.md index 12ff23aa..336de974 100644 --- a/Usage.md +++ b/Usage.md @@ -50,12 +50,12 @@ When running from your local repo (CLI), your local configuration file will be u Examples for invoking the different tools via the CLI: -- **Review**: `python cli.py --pr_url= review` -- **Describe**: `python cli.py --pr_url= describe` -- **Improve**: `python cli.py --pr_url= improve` -- **Ask**: `python cli.py --pr_url= ask "Write me a poem about this PR"` -- **Reflect**: `python cli.py --pr_url= reflect` -- **Update Changelog**: `python cli.py --pr_url= update_changelog` +- **Review**: `python cli.py --pr_url= /review` +- **Describe**: `python cli.py --pr_url= /describe` +- **Improve**: `python cli.py --pr_url= /improve` +- **Ask**: `python cli.py --pr_url= /ask "Write me a poem about this PR"` +- **Reflect**: `python cli.py --pr_url= /reflect` +- **Update Changelog**: `python cli.py --pr_url= /update_changelog` `` is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50). @@ -63,7 +63,7 @@ Examples for invoking the different tools via the CLI: (1) in addition to editing your local configuration file, you can also change any configuration value by adding it to the command line: ``` -python cli.py --pr_url= review --pr_reviewer.extra_instructions="focus on the file: ..." +python cli.py --pr_url= /review --pr_reviewer.extra_instructions="focus on the file: ..." ``` (2) You can print results locally, without publishing them, by setting in `configuration.toml`: From ae3d7067d353e3a890b163f6a908101dc771a915 Mon Sep 17 00:00:00 2001 From: Phill Zarfos Date: Sun, 3 Sep 2023 09:22:08 -0400 Subject: [PATCH 97/97] implemented 'improve' command for CodeCommit --- README.md | 4 +- pr_agent/git_providers/codecommit_client.py | 54 +++++++++++++++---- pr_agent/git_providers/codecommit_provider.py | 42 ++++++++++++--- tests/unittest/test_codecommit_client.py | 2 +- 4 files changed, 82 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index f946a59c..5bf7d52e 100644 --- a/README.md +++ b/README.md @@ -101,8 +101,8 @@ See the [usage guide](./Usage.md) for instructions how to run the different tool | TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Auto-Description | :white_check_mark: | :white_check_mark: | | :white_check_mark: | :white_check_mark: | -| | Improve Code | :white_check_mark: | :white_check_mark: | | | | -| | ⮑ Extended | :white_check_mark: | :white_check_mark: | | | | +| | Improve Code | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | +| | ⮑ Extended | :white_check_mark: | :white_check_mark: | | :white_check_mark: | | | | Reflect and Review | :white_check_mark: | | | | :white_check_mark: | | | Update CHANGELOG.md | :white_check_mark: | | | | | | | | | | | | | diff --git a/pr_agent/git_providers/codecommit_client.py b/pr_agent/git_providers/codecommit_client.py index 6200340d..1112ee22 100644 --- a/pr_agent/git_providers/codecommit_client.py +++ b/pr_agent/git_providers/codecommit_client.py @@ -90,7 +90,11 @@ class CodeCommitClient: ): differences.extend(page.get("differences", [])) except botocore.exceptions.ClientError as e: - raise ValueError(f"Failed to retrieve differences from CodeCommit PR #{self.pr_num}") from e + if e.response["Error"]["Code"] == 'RepositoryDoesNotExistException': + raise ValueError(f"CodeCommit cannot retrieve differences: Repository does not exist: {repo_name}") from e + raise ValueError(f"CodeCommit cannot retrieve differences for {source_commit}..{destination_commit}") from e + except Exception as e: + raise ValueError(f"CodeCommit cannot retrieve differences for {source_commit}..{destination_commit}") from e output = [] for json in differences: @@ -122,6 +126,8 @@ class CodeCommitClient: try: response = self.boto_client.get_file(repositoryName=repo_name, commitSpecifier=sha_hash, filePath=file_path) except botocore.exceptions.ClientError as e: + if e.response["Error"]["Code"] == 'RepositoryDoesNotExistException': + raise ValueError(f"CodeCommit cannot retrieve PR: Repository does not exist: {repo_name}") from e # if the file does not exist, but is flagged as optional, then return an empty string if optional and e.response["Error"]["Code"] == 'FileDoesNotExistException': return "" @@ -133,11 +139,12 @@ class CodeCommitClient: return response.get("fileContent", "") - def get_pr(self, pr_number: int): + def get_pr(self, repo_name: str, pr_number: int): """ Get a information about a CodeCommit PR. Args: + - repo_name: Name of the repository - pr_number: The PR number you are requesting Returns: @@ -155,6 +162,8 @@ class CodeCommitClient: except botocore.exceptions.ClientError as e: if e.response["Error"]["Code"] == 'PullRequestDoesNotExistException': raise ValueError(f"CodeCommit cannot retrieve PR: PR number does not exist: {pr_number}") from e + if e.response["Error"]["Code"] == 'RepositoryDoesNotExistException': + raise ValueError(f"CodeCommit cannot retrieve PR: Repository does not exist: {repo_name}") from e raise ValueError(f"CodeCommit cannot retrieve PR: {pr_number}: boto client error") from e except Exception as e: raise ValueError(f"CodeCommit cannot retrieve PR: {pr_number}") from e @@ -201,7 +210,7 @@ class CodeCommitClient: except Exception as e: raise ValueError(f"Error calling publish_description") from e - def publish_comment(self, repo_name: str, pr_number: int, destination_commit: str, source_commit: str, comment: str): + def publish_comment(self, repo_name: str, pr_number: int, destination_commit: str, source_commit: str, comment: str, annotation_file: str = None, annotation_line: int = None): """ Publish a comment to a pull request @@ -210,7 +219,13 @@ class CodeCommitClient: - pr_number: number of the pull request - destination_commit: The commit hash you want to merge into (the "before" hash) (usually on the main or master branch) - source_commit: The commit hash of the code you are adding (the "after" branch) - - pr_comment: comment + - comment: The comment you want to publish + - annotation_file: The file you want to annotate (optional) + - annotation_line: The line number you want to annotate (optional) + + Comment annotations for CodeCommit are different than GitHub. + CodeCommit only designates the starting line number for the comment. + It does not support the ending line number to highlight a range of lines. Returns: - None @@ -223,13 +238,30 @@ class CodeCommitClient: self._connect_boto_client() try: - self.boto_client.post_comment_for_pull_request( - pullRequestId=str(pr_number), - repositoryName=repo_name, - beforeCommitId=destination_commit, - afterCommitId=source_commit, - content=comment, - ) + # If the comment has code annotations, + # then set the file path and line number in the location dictionary + if annotation_file and annotation_line: + self.boto_client.post_comment_for_pull_request( + pullRequestId=str(pr_number), + repositoryName=repo_name, + beforeCommitId=destination_commit, + afterCommitId=source_commit, + content=comment, + location={ + "filePath": annotation_file, + "filePosition": annotation_line, + "relativeFileVersion": "AFTER", + }, + ) + else: + # The comment does not have code annotations + self.boto_client.post_comment_for_pull_request( + pullRequestId=str(pr_number), + repositoryName=repo_name, + beforeCommitId=destination_commit, + afterCommitId=source_commit, + content=comment, + ) except botocore.exceptions.ClientError as e: if e.response["Error"]["Code"] == 'RepositoryDoesNotExistException': raise ValueError(f"Repository does not exist: {repo_name}") from e diff --git a/pr_agent/git_providers/codecommit_provider.py b/pr_agent/git_providers/codecommit_provider.py index d43409c3..1f570a1a 100644 --- a/pr_agent/git_providers/codecommit_provider.py +++ b/pr_agent/git_providers/codecommit_provider.py @@ -180,10 +180,37 @@ class CodeCommitProvider(GitProvider): comment=pr_comment, ) except Exception as e: - raise ValueError(f"CodeCommit Cannot post comment for PR: {self.pr_num}") from e + raise ValueError(f"CodeCommit Cannot publish comment for PR: {self.pr_num}") from e def publish_code_suggestions(self, code_suggestions: list) -> bool: - return [""] # not implemented yet + counter = 1 + for suggestion in code_suggestions: + # Verify that each suggestion has the required keys + if not all(key in suggestion for key in ["body", "relevant_file", "relevant_lines_start"]): + logging.warning(f"Skipping code suggestion #{counter}: Each suggestion must have 'body', 'relevant_file', 'relevant_lines_start' keys") + continue + + # Publish the code suggestion to CodeCommit + try: + logging.debug(f"Code Suggestion #{counter} in file: {suggestion['relevant_file']}: {suggestion['relevant_lines_start']}") + self.codecommit_client.publish_comment( + repo_name=self.repo_name, + pr_number=self.pr_num, + destination_commit=self.pr.destination_commit, + source_commit=self.pr.source_commit, + comment=suggestion["body"], + annotation_file=suggestion["relevant_file"], + annotation_line=suggestion["relevant_lines_start"], + ) + except Exception as e: + raise ValueError(f"CodeCommit Cannot publish code suggestions for PR: {self.pr_num}") from e + + counter += 1 + + # The calling function passes in a list of code suggestions, and this function publishes each suggestion one at a time. + # If we were to return False here, the calling function will attempt to publish the same list of code suggestions again, one at a time. + # Since this function publishes the suggestions one at a time anyway, we always return True here to avoid the retry. + return True def publish_labels(self, labels): return [""] # not implemented yet @@ -195,6 +222,7 @@ class CodeCommitProvider(GitProvider): return "" # not implemented yet def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str): + # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/post_comment_for_compared_commit.html raise NotImplementedError("CodeCommit provider does not support publishing inline comments yet") def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str): @@ -255,9 +283,11 @@ class CodeCommitProvider(GitProvider): return self.codecommit_client.get_file(self.repo_name, settings_filename, self.pr.source_commit, optional=True) def add_eyes_reaction(self, issue_comment_id: int) -> Optional[int]: + logging.info("CodeCommit provider does not support eyes reaction yet") return True def remove_reaction(self, issue_comment_id: int, reaction_id: int) -> bool: + logging.info("CodeCommit provider does not support removing reactions yet") return True @staticmethod @@ -315,16 +345,16 @@ class CodeCommitProvider(GitProvider): return re.match(r"^[a-z]{2}-(gov-)?[a-z]+-\d\.console\.aws\.amazon\.com$", hostname) is not None def _get_pr(self): - response = self.codecommit_client.get_pr(self.pr_num) + response = self.codecommit_client.get_pr(self.repo_name, self.pr_num) if len(response.targets) == 0: raise ValueError(f"No files found in CodeCommit PR: {self.pr_num}") - # TODO: implement support for multiple commits in one CodeCommit PR - # for now, we are only using the first commit in the PR + # TODO: implement support for multiple targets in one CodeCommit PR + # for now, we are only using the first target in the PR if len(response.targets) > 1: logging.warning( - "Multiple commits in one PR is not supported for CodeCommit yet. Continuing, using the first commit only..." + "Multiple targets in one PR is not supported for CodeCommit yet. Continuing, using the first target only..." ) # Return our object that mimics PullRequest class from the PyGithub library diff --git a/tests/unittest/test_codecommit_client.py b/tests/unittest/test_codecommit_client.py index 5d09bdd1..0aa1ffa6 100644 --- a/tests/unittest/test_codecommit_client.py +++ b/tests/unittest/test_codecommit_client.py @@ -125,7 +125,7 @@ class TestCodeCommitProvider: } } - pr = api.get_pr(321) + pr = api.get_pr("my_test_repo", 321) assert pr.title == "My PR" assert pr.description == "My PR description"

    /describe: