diff --git a/README.md b/README.md index a09b341f..da5159c4 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ CodiumAI PR-Agent is an open-source tool to help efficiently review and handle p - See the [Usage Guide](https://pr-agent-docs.codium.ai/usage-guide/) for instructions on running the PR-Agent commands via different interfaces, including _CLI_, _online usage_, or by _automatically triggering_ them when a new PR is opened. -- See the [Tools Guide](https://pr-agent-docs.codium.ai/tools/) for a detailed description of the different tools (tools are run via the commands). +- See the [Tools Guide](https://pr-agent-docs.codium.ai/tools/) for a detailed description of the different tools. Supported commands per platform: diff --git a/docs/docs/assets/logo.svg b/docs/docs/assets/logo.svg new file mode 100644 index 00000000..5de22677 --- /dev/null +++ b/docs/docs/assets/logo.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/css/custom.css b/docs/docs/css/custom.css index 0e9b26fe..2f963ccd 100644 --- a/docs/docs/css/custom.css +++ b/docs/docs/css/custom.css @@ -5,94 +5,14 @@ --md-accent-fg-color: #AEA1F1; } .md-nav__title, .md-nav__link { - font-size: 16px; /* Adjust the font size as needed */ + font-size: 16px; } .md-tabs__link { - font-size: 16px; /* Adjust the font size as needed */ + font-size: 16px; } .md-header__title { - font-size: 20px; /* Adjust the font size as needed */ + font-size: 20px; + margin-left: 0px !important; } - -/* -@media (prefers-color-scheme: light) { - body { - --md-primary-fg-color: #00ffee !important; - --md-primary-bg-color: #ff0000 !important; - } - - body, .md-main, .md-content { - background-color: #4312f5 !important; - } -} - -@media (prefers-color-scheme: dark) { - body { - --md-primary-fg-color: #171518 !important; - --md-primary-bg-color: #171518 !important; - } - - body, .md-main, .md-content { - background-color: #171518 !important; - } - - .md-header__title { - color: #ffffff !important; - } - - .md-tabs .md-tabs__link { - color: #ffffff !important; - } - - .md-tabs .md-tabs__link:hover, - .md-tabs .md-tabs__link:focus { - color: #ffffff !important; - } - - .md-header__button { - color: #ffffff !important; - } - - .md-header__button svg { - fill: currentColor !important; - } - - .md-header__button:hover, - .md-header__button:focus { - color: #ffffff !important; - } - - .md-header__button:hover svg, - .md-header__button:focus svg { - fill: currentColor !important; - } - - .md-search__icon svg { - fill: #ffffff !important; - } - - .md-search__input { - color: #ffffff !important; - } - - .md-nav__item--active > .md-nav__link--active, - .md-nav__link--active { - color: #AEA1F1 !important; - } - - .md-nav--secondary .md-nav__title { - background: #171518; - box-shadow: 0 0 0.4rem 0.4rem #171518; - } - - .md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link { - background: #171518; - box-shadow: 0 0 0.4rem 0.4rem #171518; - } - - .md-content a { - color: #AEA1F1 !important; - } -} */ \ No newline at end of file diff --git a/docs/docs/index.md b/docs/docs/index.md index f604fd25..caa52ded 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -6,7 +6,7 @@ CodiumAI PR-Agent is an open-source tool to help efficiently review and handle p - See the [Usage Guide](./usage-guide/index.md) for instructions on running the PR-Agent commands via different interfaces, including _CLI_, _online usage_, or by _automatically triggering_ them when a new PR is opened. -- See the [Tools Guide](./tools/index.md) for a detailed description of the different tools (tools are run via the commands). +- See the [Tools Guide](./tools/index.md) for a detailed description of the different tools. ## PR-Agent Features diff --git a/docs/docs/installation/azure.md b/docs/docs/installation/azure.md index 8c111581..e8c11664 100644 --- a/docs/docs/installation/azure.md +++ b/docs/docs/installation/azure.md @@ -4,7 +4,6 @@ To use Azure DevOps provider use the following settings in configuration.toml: ``` [config] git_provider="azure" -use_repo_settings_file=false ``` Azure DevOps provider supports [PAT token](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows) or [DefaultAzureCredential](https://learn.microsoft.com/en-us/azure/developer/python/sdk/authentication-overview#authentication-in-server-environments) authentication. diff --git a/docs/docs/installation/github.md b/docs/docs/installation/github.md index f4d08447..b405b020 100644 --- a/docs/docs/installation/github.md +++ b/docs/docs/installation/github.md @@ -2,7 +2,7 @@ You can use our pre-built Github Action Docker image to run PR-Agent as a Github Action. -1. Add the following file to your repository under `.github/workflows/pr_agent.yml`: +1) Add the following file to your repository under `.github/workflows/pr_agent.yml`: ```yaml on: @@ -46,7 +46,7 @@ jobs: OPENAI_KEY: ${{ secrets.OPENAI_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` -2. Add the following secret to your repository under `Settings > Secrets and variables > Actions > New repository secret > Add secret`: +2) Add the following secret to your repository under `Settings > Secrets and variables > Actions > New repository secret > Add secret`: ``` Name = OPENAI_KEY @@ -55,10 +55,10 @@ Secret = The GITHUB_TOKEN secret is automatically created by GitHub. -3. Merge this change to your main branch. +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](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) 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](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) file. Some examples: ```yaml env: # ... previous environment values @@ -67,24 +67,12 @@ When you open your next PR, you should see a comment from `github-actions` bot w PR_CODE_SUGGESTIONS.NUM_CODE_SUGGESTIONS: 6 # Increase number of code suggestions ``` ---- -## Run as a polling server -Request reviews by tagging your GitHub user on a PR - -Follow [steps 1-3](#run-as-a-github-action) of the GitHub Action setup. - -Run the following command to start the server: - -``` -python pr_agent/servers/github_polling.py -``` - --- ## 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). +1) Create a GitHub App from the [Github Developer Portal](https://docs.github.com/en/developers/apps/creating-a-github-app). - Set the following permissions: - Pull requests: Read & write @@ -96,60 +84,62 @@ Allowing you to automate the review process on your private or public repositori - Pull request - Push (if you need to enable triggering on PR update) -2. Generate a random secret for your app, and save it for later. For example, you can use: +2) Generate a random secret for your app, and save it for later. For example, you can use: ``` WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))") ``` -3. Acquire the following pieces of information from your app's settings page: +3) Acquire the following pieces of information from your app's settings page: - App private key (click "Generate a private key" and save the file) - App ID -4. Clone this repository: +4) Clone this repository: ``` git clone https://github.com/Codium-ai/pr-agent.git ``` -5. Copy the secrets template file and fill in the following: - ``` - cp pr_agent/settings/.secrets_template.toml pr_agent/settings/.secrets.toml - # Edit .secrets.toml file - ``` +5) Copy the secrets template file and fill in the following: + +``` +cp pr_agent/settings/.secrets_template.toml pr_agent/settings/.secrets.toml +# Edit .secrets.toml file +``` + - Your OpenAI key. - Copy your app's private key to the private_key field. - Copy your app's ID to the app_id field. - Copy your app's webhook secret to the webhook_secret field. - Set deployment_type to 'app' in [configuration.toml](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) -> The .secrets.toml file is not copied to the Docker image by default, and is only used for local development. -> If you want to use the .secrets.toml file in your Docker image, you can add remove it from the .dockerignore file. -> In most production environments, you would inject the secrets file as environment variables or as mounted volumes. -> For example, in order to inject a secrets file as a volume in a Kubernetes environment you can update your pod spec to include the following, -> assuming you have a secret named `pr-agent-settings` with a key named `.secrets.toml`: -``` - volumes: - - name: settings-volume - secret: - secretName: pr-agent-settings -// ... - containers: -// ... - volumeMounts: - - mountPath: /app/pr_agent/settings_prod - name: settings-volume -``` + > The .secrets.toml file is not copied to the Docker image by default, and is only used for local development. + > If you want to use the .secrets.toml file in your Docker image, you can add remove it from the .dockerignore file. + > In most production environments, you would inject the secrets file as environment variables or as mounted volumes. + > For example, in order to inject a secrets file as a volume in a Kubernetes environment you can update your pod spec to include the following, + > assuming you have a secret named `pr-agent-settings` with a key named `.secrets.toml`: + ``` + volumes: + - name: settings-volume + secret: + secretName: pr-agent-settings + // ... + containers: + // ... + volumeMounts: + - mountPath: /app/pr_agent/settings_prod + name: settings-volume + ``` + + > Another option is to set the secrets as environment variables in your deployment environment, for example `OPENAI.KEY` and `GITHUB.USER_TOKEN`. -> Another option is to set the secrets as environment variables in your deployment environment, for example `OPENAI.KEY` and `GITHUB.USER_TOKEN`. +6) Build a Docker image for the app and optionally push it to a Docker repository. We'll use Dockerhub as an example: -6. Build a Docker image for the app and optionally push it to a Docker repository. We'll use Dockerhub as an example: - -``` -docker build . -t codiumai/pr-agent:github_app --target github_app -f docker/Dockerfile -docker push codiumai/pr-agent:github_app # Push to your Docker repository -``` + ``` + docker build . -t codiumai/pr-agent:github_app --target github_app -f docker/Dockerfile + docker push codiumai/pr-agent:github_app # Push to your Docker repository + ``` 7. Host the app using a server, serverless function, or container environment. Alternatively, for development and debugging, you may use tools like smee.io to forward webhooks to your local machine. @@ -169,7 +159,7 @@ docker push codiumai/pr-agent:github_app # Push to your Docker repository ## Deploy as a Lambda Function -1. Follow steps 1-5 of [Method 5](#run-as-a-github-app). +1. Follow steps 1-5 from [here](#run-as-a-github-app). 2. Build a docker image that can be used as a lambda function ```shell docker buildx build --platform=linux/amd64 . -t codiumai/pr-agent:serverless -f docker/Dockerfile.lambda diff --git a/docs/docs/installation/gitlab.md b/docs/docs/installation/gitlab.md index e768e850..cb3b7931 100644 --- a/docs/docs/installation/gitlab.md +++ b/docs/docs/installation/gitlab.md @@ -1,16 +1,20 @@ ## Run a GitLab webhook server 1. From the GitLab workspace or group, create an access token. Enable the "api" scope only. + 2. Generate a random secret for your app, and save it for later. For example, you can use: ``` WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))") ``` -3. Follow the instructions to build the Docker image, setup a secrets file and deploy on your own server from [Method 5](#run-as-a-github-app) steps 4-7. +3. Follow the instructions to build the Docker image, setup a secrets file and deploy on your own server from [here](https://pr-agent-docs.codium.ai/installation/github/#run-as-a-github-app) steps 4-7. + 4. In the secrets file, fill in the following: - Your OpenAI key. - In the [gitlab] section, fill in personal_access_token and shared_secret. The access token can be a personal access token, or a group or project access token. - Set deployment_type to 'gitlab' in [configuration.toml](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) + 5. Create a webhook in GitLab. Set the URL to the URL of your app's server. Set the secret token to the generated secret from step 2. In the "Trigger" section, check the ‘comments’ and ‘merge request events’ boxes. + 6. Test your installation by opening a merge request or commenting or a merge request using one of CodiumAI's commands. \ No newline at end of file diff --git a/docs/docs/installation/gitlab_pro.md b/docs/docs/installation/gitlab_pro.md new file mode 100644 index 00000000..20902b74 --- /dev/null +++ b/docs/docs/installation/gitlab_pro.md @@ -0,0 +1,37 @@ +## Install Hosted PR-Agent Pro for GitLab (Teams & Enterprise) + +### Step 1 + +Acquire a personal, project or group level access token. Enable the “api” scope in order to allow PR-Agent to read pull requests, comment and respond to requests. + + + +Store the token in a safe place, you won’t be able to access it again after it was generated. + +### Step 2 + +Generate a shared secret and link it to the access token. Browse to [https://register.gitlab.pr-agent.codium.ai](https://register.gitlab.pr-agent.codium.ai). +Fill in your generated GitLab token and your company or personal name in the appropriate fields and click "Submit". + +You should see "Success!" displayed above the Submit button, and a shared secret will be generated. Store it in a safe place, you won’t be able to access it again after it was generated. + +### Step 3 + +Install a webhook for your repository or groups, by clicking “webhooks” on the settings menu. Click the “Add new webhook” button. + + + +In the webhook definition form, fill in the following fields: +URL: https://pro.gitlab.pr-agent.codium.ai/webhook + +Secret token: Your CodiumAI key +Trigger: Check the ‘comments’ and ‘merge request events’ boxes. +Enable SSL verification: Check the box. + + + +### Step 4 + +You’re all set! + +Open a new merge request or add a MR comment with one of PR-Agent’s commands such as /review, /describe or /improve. \ No newline at end of file diff --git a/docs/docs/usage-guide/automations_and_usage.md b/docs/docs/usage-guide/automations_and_usage.md index 81f182b5..b39f7e02 100644 --- a/docs/docs/usage-guide/automations_and_usage.md +++ b/docs/docs/usage-guide/automations_and_usage.md @@ -170,7 +170,6 @@ To use Azure DevOps provider use the following settings in configuration.toml: ``` [config] git_provider="azure" -use_repo_settings_file=false ``` Azure DevOps provider supports [PAT token](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows) or [DefaultAzureCredential](https://learn.microsoft.com/en-us/azure/developer/python/sdk/authentication-overview#authentication-in-server-environments) authentication. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index b71bf125..8da53d48 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -7,6 +7,7 @@ nav: - Locally: 'installation/locally.md' - GitHub: 'installation/github.md' - GitLab: 'installation/gitlab.md' + - 💎 GitLab Pro: 'installation/gitlab_pro.md' - BitBucket: 'installation/bitbucket.md' - Azure DevOps: 'installation/azure.md' - Usage Guide: @@ -34,7 +35,7 @@ nav: - Core Abilities: 'core-abilities/index.md' theme: - logo: assets/logo.png + logo: assets/logo.svg favicon: assets/favicon.ico name: material features: @@ -92,6 +93,9 @@ extra: link: https://twitter.com/CodiumAI - icon: fontawesome/brands/instagram link: https://www.instagram.com/codiumai/ + analytics: + provider: google + property: ${{ secrets.GOOGLE_ANALYTICS_ID }} extra_css: - css/custom.css diff --git a/docs/overrides/partials/footer.html b/docs/overrides/partials/footer.html index 96ba7e86..b3235a69 100644 --- a/docs/overrides/partials/footer.html +++ b/docs/overrides/partials/footer.html @@ -3,7 +3,7 @@ -Footer Customization +Footer diff --git a/pr_agent/algo/ai_handlers/litellm_ai_handler.py b/pr_agent/algo/ai_handlers/litellm_ai_handler.py index 51f72960..527a20a8 100644 --- a/pr_agent/algo/ai_handlers/litellm_ai_handler.py +++ b/pr_agent/algo/ai_handlers/litellm_ai_handler.py @@ -73,6 +73,18 @@ class LiteLLMAIHandler(BaseAiHandler): region_name=get_settings().aws.bedrock_region, ) + def prepare_logs(self, response, system, user, resp, finish_reason): + response_log = response.dict().copy() + response_log['system'] = system + response_log['user'] = user + response_log['output'] = resp + response_log['finish_reason'] = finish_reason + if hasattr(self, 'main_pr_language'): + response_log['main_pr_language'] = self.main_pr_language + else: + response_log['main_pr_language'] = 'unknown' + return response_log + @property def deployment_id(self): """ @@ -125,10 +137,13 @@ class LiteLLMAIHandler(BaseAiHandler): else: resp = response["choices"][0]['message']['content'] finish_reason = response["choices"][0]["finish_reason"] - # usage = response.get("usage") get_logger().debug(f"\nAI response:\n{resp}") - get_logger().debug("Full_response", artifact=response) + # log the full response for debugging + response_log = self.prepare_logs(response, system, user, resp, finish_reason) + get_logger().debug("Full_response", artifact=response_log) + + # for CLI debugging if get_settings().config.verbosity_level >= 2: get_logger().info(f"\nAI response:\n{resp}") diff --git a/pr_agent/algo/utils.py b/pr_agent/algo/utils.py index 3a467416..d9365546 100644 --- a/pr_agent/algo/utils.py +++ b/pr_agent/algo/utils.py @@ -60,7 +60,7 @@ def unique_strings(input_list: List[str]) -> List[str]: return unique_list -def convert_to_markdown(output_data: dict, gfm_supported: bool = True) -> str: +def convert_to_markdown(output_data: dict, gfm_supported: bool = True, incremental_review=None) -> str: """ Convert a dictionary of data into markdown format. Args: @@ -81,7 +81,11 @@ def convert_to_markdown(output_data: dict, gfm_supported: bool = True) -> str: "Estimated effort to review [1-5]": "⏱️", } markdown_text = "" - markdown_text += f"## PR Review\n\n" + if not incremental_review: + markdown_text += f"## PR Review\n\n" + else: + markdown_text += f"## Incremental PR Review\n\n" + markdown_text += f"⏮️ Review for commits since previous PR-Agent review {incremental_review}.\n\n" if gfm_supported: markdown_text += "\n\n" # markdown_text += """""" diff --git a/pr_agent/git_providers/azuredevops_provider.py b/pr_agent/git_providers/azuredevops_provider.py index 3cded721..a710c254 100644 --- a/pr_agent/git_providers/azuredevops_provider.py +++ b/pr_agent/git_providers/azuredevops_provider.py @@ -165,7 +165,7 @@ class AzureDevopsProvider(GitProvider): except Exception as e: get_logger().exception(f"Failed to publish labels, error: {e}") - def get_pr_labels(self): + def get_pr_labels(self, update=False): try: labels = self.azure_devops_client.get_pull_request_labels( project=self.workspace_slug, diff --git a/pr_agent/git_providers/bitbucket_provider.py b/pr_agent/git_providers/bitbucket_provider.py index c4873f88..3516516c 100644 --- a/pr_agent/git_providers/bitbucket_provider.py +++ b/pr_agent/git_providers/bitbucket_provider.py @@ -404,5 +404,5 @@ class BitbucketProvider(GitProvider): pass # bitbucket does not support labels - def get_pr_labels(self): + def get_pr_labels(self, update=False): pass diff --git a/pr_agent/git_providers/bitbucket_server_provider.py b/pr_agent/git_providers/bitbucket_server_provider.py index 3b8168ea..0f9e58d4 100644 --- a/pr_agent/git_providers/bitbucket_server_provider.py +++ b/pr_agent/git_providers/bitbucket_server_provider.py @@ -347,7 +347,7 @@ class BitbucketServerProvider(GitProvider): pass # bitbucket does not support labels - def get_pr_labels(self): + def get_pr_labels(self, update=False): pass def _get_pr_comments_url(self): diff --git a/pr_agent/git_providers/codecommit_provider.py b/pr_agent/git_providers/codecommit_provider.py index cbaf75f1..f035351d 100644 --- a/pr_agent/git_providers/codecommit_provider.py +++ b/pr_agent/git_providers/codecommit_provider.py @@ -216,7 +216,7 @@ class CodeCommitProvider(GitProvider): def publish_labels(self, labels): return [""] # not implemented yet - def get_pr_labels(self): + def get_pr_labels(self, update=False): return [""] # not implemented yet def remove_initial_comment(self): diff --git a/pr_agent/git_providers/gerrit_provider.py b/pr_agent/git_providers/gerrit_provider.py index 5a6da1a2..8e33151a 100644 --- a/pr_agent/git_providers/gerrit_provider.py +++ b/pr_agent/git_providers/gerrit_provider.py @@ -208,7 +208,7 @@ class GerritProvider(GitProvider): Comment = namedtuple('Comment', ['body']) return Comments([Comment(c['message']) for c in reversed(comments)]) - def get_pr_labels(self): + def get_pr_labels(self, update=False): raise NotImplementedError( 'Getting labels is not implemented for the gerrit provider') diff --git a/pr_agent/git_providers/git_provider.py b/pr_agent/git_providers/git_provider.py index a2bd7deb..a7d772b7 100644 --- a/pr_agent/git_providers/git_provider.py +++ b/pr_agent/git_providers/git_provider.py @@ -164,7 +164,7 @@ class GitProvider(ABC): pass @abstractmethod - def get_pr_labels(self): + def get_pr_labels(self, update=False): pass def get_repo_labels(self): diff --git a/pr_agent/git_providers/github_provider.py b/pr_agent/git_providers/github_provider.py index e29c077e..de54ec3f 100644 --- a/pr_agent/git_providers/github_provider.py +++ b/pr_agent/git_providers/github_provider.py @@ -38,6 +38,7 @@ class GithubProvider(GitProvider): self.set_pr(pr_url) self.pr_commits = list(self.pr.get_commits()) if self.incremental.is_incremental: + self.unreviewed_files_set = dict() self.get_incremental_commits() self.last_commit_id = self.pr_commits[-1] self.pr_url = self.get_pr_url() # pr_url for github actions can be as api.github.com, so we need to get the url from the pr object @@ -62,14 +63,15 @@ class GithubProvider(GitProvider): if self.previous_review: self.incremental.commits_range = self.get_commit_range() # Get all files changed during the commit range - self.file_set = dict() + for commit in self.incremental.commits_range: if commit.commit.message.startswith(f"Merge branch '{self._get_repo().default_branch}'"): get_logger().info(f"Skipping merge commit {commit.commit.message}") continue - self.file_set.update({file.filename: file for file in commit.files}) + self.unreviewed_files_set.update({file.filename: file for file in commit.files}) else: - raise ValueError("No previous review found") + get_logger().info("No previous review found, will review the entire PR") + self.incremental.is_incremental = False def get_commit_range(self): last_review_time = self.previous_review.created_at @@ -98,8 +100,8 @@ class GithubProvider(GitProvider): return self.comments[index] def get_files(self): - if self.incremental.is_incremental and self.file_set: - return self.file_set.values() + if self.incremental.is_incremental and self.unreviewed_files_set: + return self.unreviewed_files_set.values() try: git_files = context.get("git_files", None) if git_files: @@ -150,10 +152,10 @@ class GithubProvider(GitProvider): new_file_content_str = self._get_pr_file_content(file, self.pr.head.sha) # communication with GitHub patch = file.patch - if self.incremental.is_incremental and self.file_set: + if self.incremental.is_incremental and self.unreviewed_files_set: original_file_content_str = self._get_pr_file_content(file, self.incremental.last_seen_commit_sha) patch = load_large_diff(file.filename, new_file_content_str, original_file_content_str) - self.file_set[file.filename] = patch + self.unreviewed_files_set[file.filename] = patch else: original_file_content_str = self._get_pr_file_content(file, self.pr.base.sha) if not patch: @@ -653,9 +655,16 @@ class GithubProvider(GitProvider): except Exception as e: get_logger().exception(f"Failed to publish labels, error: {e}") - def get_pr_labels(self): + def get_pr_labels(self, update=False): try: - return [label.name for label in self.pr.labels] + if not update: + labels =self.pr.labels + return [label.name for label in labels] + else: # obtain the latest labels. Maybe they changed while the AI was running + headers, labels = self.pr._requester.requestJsonAndCheck( + "GET", f"{self.pr.issue_url}/labels") + return [label['name'] for label in labels] + except Exception as e: get_logger().exception(f"Failed to get labels, error: {e}") return [] diff --git a/pr_agent/git_providers/gitlab_provider.py b/pr_agent/git_providers/gitlab_provider.py index 5021896d..3a2e1c85 100644 --- a/pr_agent/git_providers/gitlab_provider.py +++ b/pr_agent/git_providers/gitlab_provider.py @@ -419,7 +419,7 @@ class GitLabProvider(GitProvider): def publish_inline_comments(self, comments: list[dict]): pass - def get_pr_labels(self): + def get_pr_labels(self, update=False): return self.mr.labels def get_repo_labels(self): diff --git a/pr_agent/git_providers/local_git_provider.py b/pr_agent/git_providers/local_git_provider.py index 83c50791..678a519c 100644 --- a/pr_agent/git_providers/local_git_provider.py +++ b/pr_agent/git_providers/local_git_provider.py @@ -176,5 +176,5 @@ class LocalGitProvider(GitProvider): def get_issue_comments(self): raise NotImplementedError('Getting issue comments is not implemented for the local git provider') - def get_pr_labels(self): + def get_pr_labels(self, update=False): raise NotImplementedError('Getting labels is not implemented for the local git provider') diff --git a/pr_agent/servers/azuredevops_server_webhook.py b/pr_agent/servers/azuredevops_server_webhook.py index a7ba8659..2b926886 100644 --- a/pr_agent/servers/azuredevops_server_webhook.py +++ b/pr_agent/servers/azuredevops_server_webhook.py @@ -6,6 +6,8 @@ import json import os import re import secrets +from urllib.parse import unquote + import uvicorn from fastapi import APIRouter, Depends, FastAPI, HTTPException from fastapi.security import HTTPBasic, HTTPBasicCredentials @@ -81,7 +83,7 @@ async def handle_webhook(background_tasks: BackgroundTasks, request: Request): actions = [] if data["eventType"] == "git.pullrequest.created": # API V1 (latest) - pr_url = data["resource"]["_links"]["web"]["href"].replace("_apis/git/repositories", "_git") + pr_url = unquote(data["resource"]["_links"]["web"]["href"].replace("_apis/git/repositories", "_git")) log_context["event"] = data["eventType"] log_context["api_url"] = pr_url await _perform_commands_azure("pr_commands", PRAgent(), pr_url, log_context) @@ -90,7 +92,7 @@ async def handle_webhook(background_tasks: BackgroundTasks, request: Request): if available_commands_rgx.match(data["resource"]["comment"]["content"]): if(data["resourceVersion"] == "2.0"): repo = data["resource"]["pullRequest"]["repository"]["webUrl"] - pr_url = f'{repo}/pullrequest/{data["resource"]["pullRequest"]["pullRequestId"]}' + pr_url = unquote(f'{repo}/pullrequest/{data["resource"]["pullRequest"]["pullRequestId"]}') actions = [data["resource"]["comment"]["content"]] else: # API V1 not supported as it does not contain the PR URL diff --git a/pr_agent/servers/bitbucket_app.py b/pr_agent/servers/bitbucket_app.py index ccc53525..a0104cf0 100644 --- a/pr_agent/servers/bitbucket_app.py +++ b/pr_agent/servers/bitbucket_app.py @@ -28,7 +28,7 @@ from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions from pr_agent.tools.pr_description import PRDescription from pr_agent.tools.pr_reviewer import PRReviewer -setup_logger(fmt=LoggingFormat.JSON) +setup_logger(fmt=LoggingFormat.JSON, level="DEBUG") router = APIRouter() secret_provider = get_secret_provider() if get_settings().get("CONFIG.SECRET_PROVIDER") else None diff --git a/pr_agent/servers/github_polling.py b/pr_agent/servers/github_polling.py index 46c21e06..a0e875bc 100644 --- a/pr_agent/servers/github_polling.py +++ b/pr_agent/servers/github_polling.py @@ -8,7 +8,7 @@ from pr_agent.config_loader import get_settings from pr_agent.git_providers import get_git_provider from pr_agent.log import LoggingFormat, get_logger, setup_logger -setup_logger(fmt=LoggingFormat.JSON) +setup_logger(fmt=LoggingFormat.JSON, level="DEBUG") NOTIFICATION_URL = "https://api.github.com/notifications" diff --git a/pr_agent/servers/gitlab_webhook.py b/pr_agent/servers/gitlab_webhook.py index 2779f80d..715185a8 100644 --- a/pr_agent/servers/gitlab_webhook.py +++ b/pr_agent/servers/gitlab_webhook.py @@ -17,7 +17,7 @@ from pr_agent.git_providers.utils import apply_repo_settings from pr_agent.log import LoggingFormat, get_logger, setup_logger from pr_agent.secret_providers import get_secret_provider -setup_logger(fmt=LoggingFormat.JSON) +setup_logger(fmt=LoggingFormat.JSON, level="DEBUG") router = APIRouter() secret_provider = get_secret_provider() if get_settings().get("CONFIG.SECRET_PROVIDER") else None @@ -51,6 +51,9 @@ async def _perform_commands_gitlab(commands_conf: str, agent: PRAgent, api_url: @router.post("/webhook") async def gitlab_webhook(background_tasks: BackgroundTasks, request: Request): log_context = {"server_type": "gitlab_app"} + get_logger().debug("Received a GitLab webhook") + + # Check if the request is authorized if request.headers.get("X-Gitlab-Token") and secret_provider: request_token = request.headers.get("X-Gitlab-Token") secret = secret_provider.get_secret(request_token) @@ -66,46 +69,57 @@ async def gitlab_webhook(background_tasks: BackgroundTasks, request: Request): elif get_settings().get("GITLAB.SHARED_SECRET"): secret = get_settings().get("GITLAB.SHARED_SECRET") if not request.headers.get("X-Gitlab-Token") == secret: + get_logger().error(f"Failed to validate secret") return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content=jsonable_encoder({"message": "unauthorized"})) else: + get_logger().error(f"Failed to validate secret") return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content=jsonable_encoder({"message": "unauthorized"})) gitlab_token = get_settings().get("GITLAB.PERSONAL_ACCESS_TOKEN", None) if not gitlab_token: + get_logger().error(f"No gitlab token found") return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content=jsonable_encoder({"message": "unauthorized"})) + data = await request.json() - get_logger().info(json.dumps(data)) + get_logger().info("GitLab data", artifact=data) + if data.get('object_kind') == 'merge_request' and data['object_attributes'].get('action') in ['open', 'reopen']: - get_logger().info(f"A merge request has been opened: {data['object_attributes'].get('title')}") url = data['object_attributes'].get('url') + get_logger().info(f"New merge request: {url}") await _perform_commands_gitlab("pr_commands", PRAgent(), url, log_context) - # handle_request(background_tasks, url, "/review", log_context) - elif data.get('object_kind') == 'note' and data['event_type'] == 'note': + elif data.get('object_kind') == 'note' and data['event_type'] == 'note': # comment on MR if 'merge_request' in data: mr = data['merge_request'] url = mr.get('url') + get_logger().info(f"A comment has been added to a merge request: {url}") body = data.get('object_attributes', {}).get('note') - if data.get('object_attributes', {}).get('type') == 'DiffNote' and '/ask' in body: - line_range_ = data['object_attributes']['position']['line_range'] - - # if line_range_['start']['type'] == 'new': - start_line = line_range_['start']['new_line'] - end_line = line_range_['end']['new_line'] - # else: - # start_line = line_range_['start']['old_line'] - # end_line = line_range_['end']['old_line'] - - question = body.replace('/ask', '').strip() - path = data['object_attributes']['position']['new_path'] - side = 'RIGHT'# if line_range_['start']['type'] == 'new' else 'LEFT' - comment_id = data['object_attributes']["discussion_id"] - get_logger().info(f"Handling line comment") - body = f"/ask_line --line_start={start_line} --line_end={end_line} --side={side} --file_name={path} --comment_id={comment_id} {question}" + if data.get('object_attributes', {}).get('type') == 'DiffNote' and '/ask' in body: # /ask_line + body = handle_ask_line(body, data) handle_request(background_tasks, url, body, log_context) return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder({"message": "success"})) +def handle_ask_line(body, data): + try: + line_range_ = data['object_attributes']['position']['line_range'] + # if line_range_['start']['type'] == 'new': + start_line = line_range_['start']['new_line'] + end_line = line_range_['end']['new_line'] + # else: + # start_line = line_range_['start']['old_line'] + # end_line = line_range_['end']['old_line'] + question = body.replace('/ask', '').strip() + path = data['object_attributes']['position']['new_path'] + side = 'RIGHT' # if line_range_['start']['type'] == 'new' else 'LEFT' + comment_id = data['object_attributes']["discussion_id"] + get_logger().info(f"Handling line comment") + body = f"/ask_line --line_start={start_line} --line_end={end_line} --side={side} --file_name={path} --comment_id={comment_id} {question}" + except Exception as e: + get_logger().error(f"Failed to handle ask line comment: {e}") + return body + + @router.get("/") async def root(): return {"status": "ok"} diff --git a/pr_agent/servers/help.py b/pr_agent/servers/help.py index 89487b4d..835ad8b9 100644 --- a/pr_agent/servers/help.py +++ b/pr_agent/servers/help.py @@ -9,7 +9,7 @@ class HelpMessage: "> - **/add_docs** 💎: Generate docstring for new components introduced in the PR. \n" \ "> - **/generate_labels** 💎: Generate labels for the PR based on the PR's contents. \n" \ "> - **/analyze** 💎: Automatically analyzes the PR, and presents changes walkthrough for each component. \n\n" \ - ">See the [tools guide](https://github.com/Codium-ai/pr-agent/blob/main/docs/TOOLS_GUIDE.md) for more details.\n" \ + ">See the [tools guide](https://pr-agent-docs.codium.ai/tools/) for more details.\n" \ ">To list the possible configuration parameters, add a **/config** comment. \n" return commands_text @@ -22,13 +22,13 @@ class HelpMessage: @staticmethod def get_review_usage_guide(): output ="**Overview:**\n" - output +="The `review` tool scans the PR code changes, and generates a PR review. The tool can be triggered [automatically](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) every time a new PR is opened, or can be invoked manually by commenting on any PR.\n" + output +="The `review` tool scans the PR code changes, and generates a PR review. The tool can be triggered [automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) every time a new PR is opened, or can be invoked manually by commenting on any PR.\n" output +="""\ When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L19) related to the review tool (`pr_reviewer` section), use the following template: ``` /review --pr_reviewer.some_config1=... --pr_reviewer.some_config2=... ``` -With a [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-app), use the following template: +With a [configuration file](https://pr-agent-docs.codium.ai/usage-guide/configuration_options/), use the following template: ``` [pr_reviewer] some_config1=... @@ -62,7 +62,7 @@ Use triple quotes to write multi-line instructions. Use bullet points to make th # automation output += "
      Feedback            
How to enable\\disable automation
\n\n" output += """\ -- When you first install PR-Agent app, the [default mode](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) for the `review` tool is: +- When you first install PR-Agent app, the [default mode](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) for the `review` tool is: ``` pr_commands = ["/review", ...] ``` @@ -93,7 +93,7 @@ The `review` tool can auto-generate two specific types of labels for a PR: output += "
Extra sub-tools
\n\n" output += """\ The `review` tool provides a collection of possible feedbacks about a PR. -It is recommended to review the [possible options](https://github.com/Codium-ai/pr-agent/blob/main/docs/REVIEW.md#enabledisable-features), and choose the ones relevant for your use case. +It is recommended to review the [possible options](https://pr-agent-docs.codium.ai/tools/review/#enabledisable-features), and choose the ones relevant for your use case. Some of the feature that are disabled by default are quite useful, and should be considered for enabling. For example: `require_score_review`, `require_soc2_ticket`, `require_can_be_split_review`, and more. """ @@ -131,7 +131,7 @@ maximal_review_effort = 5 output += "
" - output += f"\n\nSee the [review usage](https://github.com/Codium-ai/pr-agent/blob/main/docs/REVIEW.md) page for a comprehensive guide on using this tool.\n\n" + output += f"\n\nSee the [review usage](https://pr-agent-docs.codium.ai/tools/review/) page for a comprehensive guide on using this tool.\n\n" return output @@ -141,14 +141,14 @@ maximal_review_effort = 5 def get_describe_usage_guide(): output = "**Overview:**\n" output += "The `describe` tool scans the PR code changes, and generates a description for the PR - title, type, summary, walkthrough and labels. " - output += "The tool can be triggered [automatically](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) every time a new PR is opened, or can be invoked manually by commenting on a PR.\n" + output += "The tool can be triggered [automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) every time a new PR is opened, or can be invoked manually by commenting on a PR.\n" output += """\ When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L46) related to the describe tool (`pr_description` section), use the following template: ``` /describe --pr_description.some_config1=... --pr_description.some_config2=... ``` -With a [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-app), use the following template: +With a [configuration file](https://pr-agent-docs.codium.ai/usage-guide/configuration_options/), use the following template: ``` [pr_description] some_config1=... @@ -160,7 +160,7 @@ some_config2=... # automation output += "
Enabling\\disabling automation
\n\n" output += """\ -- When you first install the app, the [default mode](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) for the describe tool is: +- When you first install the app, the [default mode](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) for the describe tool is: ``` pr_commands = ["/describe --pr_description.add_original_user_description=true" "--pr_description.keep_original_user_title=true", ...] @@ -186,7 +186,7 @@ Note that when markers are enabled, if the original PR description does not cont output += """\ The default labels of the `describe` tool are quite generic: [`Bug fix`, `Tests`, `Enhancement`, `Documentation`, `Other`]. -If you specify [custom labels](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#handle-custom-labels-from-the-repos-labels-page-gem) in the repo's labels page or via configuration file, you can get tailored labels for your use cases. +If you specify [custom labels](https://pr-agent-docs.codium.ai/tools/describe/#handle-custom-labels-from-the-repos-labels-page) in the repo's labels page or via configuration file, you can get tailored labels for your use cases. Examples for custom labels: - `Main topic:performance` - pr_agent:The main topic of this PR is performance - `New endpoint` - pr_agent:A new endpoint was added in this PR @@ -240,7 +240,7 @@ Use triple quotes to write multi-line instructions. Use bullet points to make th output += "" - output += f"\n\nSee the [describe usage](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md) page for a comprehensive guide on using this tool.\n\n" + output += f"\n\nSee the [describe usage](https://pr-agent-docs.codium.ai/tools/describe/) page for a comprehensive guide on using this tool.\n\n" return output @@ -265,7 +265,7 @@ Note that the tool does not have "memory" of previous questions, and answers eac output += "" - output += f"\n\nSee the [ask usage](https://github.com/Codium-ai/pr-agent/blob/main/docs/ASK.md) page for a comprehensive guide on using this tool.\n\n" + output += f"\n\nSee the [ask usage](https://pr-agent-docs.codium.ai/tools/ask/) page for a comprehensive guide on using this tool.\n\n" return output @@ -274,7 +274,7 @@ Note that the tool does not have "memory" of previous questions, and answers eac def get_improve_usage_guide(): output = "**Overview:**\n" output += "The `improve` tool scans the PR code changes, and automatically generates suggestions for improving the PR code. " - output += "The tool can be triggered [automatically](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) every time a new PR is opened, or can be invoked manually by commenting on a PR.\n" + output += "The tool can be triggered [automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) every time a new PR is opened, or can be invoked manually by commenting on a PR.\n" output += """\ When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L69) related to the improve tool (`pr_code_suggestions` section), use the following template: @@ -282,7 +282,7 @@ When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/ /improve --pr_code_suggestions.some_config1=... --pr_code_suggestions.some_config2=... ``` -With a [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-app), use the following template: +With a [configuration file](https://pr-agent-docs.codium.ai/usage-guide/configuration_options/), use the following template: ``` [pr_code_suggestions] @@ -296,7 +296,7 @@ some_config2=... # automation output += "
Enabling\\disabling automation
\n\n" output += """\ -When you first install the app, the [default mode](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) for the improve tool is: +When you first install the app, the [default mode](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) for the improve tool is: ``` pr_commands = ["/improve --pr_code_suggestions.summarize=true", ...] @@ -335,7 +335,7 @@ Use triple quotes to write multi-line instructions. Use bullet points to make th output += """\ - While the current AI for code is getting better and better (GPT-4), it's not flawless. Not all the suggestions will be perfect, and a user should not accept all of them automatically. - Suggestions are not meant to be simplistic. Instead, they aim to give deep feedback and raise questions, ideas and thoughts to the user, who can then use his judgment, experience, and understanding of the code base. -- Recommended to use the 'extra_instructions' field to guide the model to suggestions that are more relevant to the specific needs of the project, or use the [custom suggestions :gem:](https://github.com/Codium-ai/pr-agent/blob/main/docs/CUSTOM_SUGGESTIONS.md) tool +- Recommended to use the 'extra_instructions' field to guide the model to suggestions that are more relevant to the specific needs of the project, or use the [custom suggestions :gem:](https://pr-agent-docs.codium.ai/tools/custom_suggestions/) tool - With large PRs, best quality will be obtained by using 'improve --extended' mode. @@ -349,6 +349,6 @@ Use triple quotes to write multi-line instructions. Use bullet points to make th output += "" - output += f"\n\nSee the [improve usage](https://github.com/Codium-ai/pr-agent/blob/main/docs/IMPROVE.md) page for a more comprehensive guide on using this tool.\n\n" + output += f"\n\nSee the [improve usage](https://pr-agent-docs.codium.ai/tools/improve/) page for a more comprehensive guide on using this tool.\n\n" return output \ No newline at end of file diff --git a/pr_agent/tools/pr_add_docs.py b/pr_agent/tools/pr_add_docs.py index d13a829d..a671dd3b 100644 --- a/pr_agent/tools/pr_add_docs.py +++ b/pr_agent/tools/pr_add_docs.py @@ -26,6 +26,8 @@ class PRAddDocs: ) self.ai_handler = ai_handler() + self.ai_handler.main_pr_language = self.main_language + self.patches_diff = None self.prediction = None self.cli_mode = cli_mode diff --git a/pr_agent/tools/pr_code_suggestions.py b/pr_agent/tools/pr_code_suggestions.py index 51dbe130..33628123 100644 --- a/pr_agent/tools/pr_code_suggestions.py +++ b/pr_agent/tools/pr_code_suggestions.py @@ -46,6 +46,7 @@ class PRCodeSuggestions: num_code_suggestions = get_settings().pr_code_suggestions.num_code_suggestions self.ai_handler = ai_handler() + self.ai_handler.main_pr_language = self.main_language self.patches_diff = None self.prediction = None self.cli_mode = cli_mode @@ -393,11 +394,7 @@ class PRCodeSuggestions: for label, suggestions in suggestions_labels.items(): num_suggestions=len(suggestions) - # pr_body += f"""{label}""" pr_body += f"""{label.capitalize()}\n""" - # pr_body += f"""""" - # pr_body += f"""
{len(suggestions)} suggestions""" - # pr_body += f"""""" for i, suggestion in enumerate(suggestions): relevant_file = suggestion['relevant_file'].strip() @@ -444,11 +441,11 @@ class PRCodeSuggestions: {example_code} """ pr_body += f"" - pr_body += f"" + # pr_body += "" - pr_body += """""" + # pr_body += """""" pr_body += """
""" return pr_body except Exception as e: diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py index 33674d00..7500efa7 100644 --- a/pr_agent/tools/pr_description.py +++ b/pr_agent/tools/pr_description.py @@ -41,6 +41,7 @@ class PRDescription: # Initialize the AI handler self.ai_handler = ai_handler() + self.ai_handler.main_pr_language = self.main_pr_language # Initialize the variables dictionary @@ -118,11 +119,15 @@ class PRDescription: if get_settings().config.publish_output: # publish labels if get_settings().pr_description.publish_labels and self.git_provider.is_supported("get_labels"): - original_labels = self.git_provider.get_pr_labels() + original_labels = self.git_provider.get_pr_labels(update=True) get_logger().debug(f"original labels", artifact=original_labels) user_labels = get_user_labels(original_labels) - get_logger().debug(f"published labels:\n{pr_labels + user_labels}") - self.git_provider.publish_labels(pr_labels + user_labels) + new_labels = pr_labels + user_labels + get_logger().debug(f"published labels", artifact=new_labels) + if sorted(new_labels) != sorted(original_labels): + self.git_provider.publish_labels(new_labels) + else: + get_logger().debug(f"Labels are the same, not updating") # publish description if get_settings().pr_description.publish_description_as_comment: diff --git a/pr_agent/tools/pr_generate_labels.py b/pr_agent/tools/pr_generate_labels.py index 1d91d5e0..4111f7c2 100644 --- a/pr_agent/tools/pr_generate_labels.py +++ b/pr_agent/tools/pr_generate_labels.py @@ -35,7 +35,8 @@ class PRGenerateLabels: # Initialize the AI handler self.ai_handler = ai_handler() - + self.ai_handler.main_pr_language = self.main_pr_language + # Initialize the variables dictionary self.vars = { "title": self.git_provider.pr.title, diff --git a/pr_agent/tools/pr_help_message.py b/pr_agent/tools/pr_help_message.py index 357a9112..f7a5ff13 100644 --- a/pr_agent/tools/pr_help_message.py +++ b/pr_agent/tools/pr_help_message.py @@ -21,21 +21,21 @@ class PRHelpMessage: pr_comment = "## PR Agent Walkthrough\n\n" pr_comment += "🤖 Welcome to the PR Agent, an AI-powered tool for automated pull request analysis, feedback, suggestions and more.""" pr_comment += "\n\nHere is a list of tools you can use to interact with the PR Agent:\n" - base_path = "https://github.com/Codium-ai/pr-agent/tree/main/docs" + base_path = "https://pr-agent-docs.codium.ai/tools" tool_names = [] - tool_names.append(f"[DESCRIBE]({base_path}/DESCRIBE.md)") - tool_names.append(f"[REVIEW]({base_path}/REVIEW.md)") - tool_names.append(f"[IMPROVE]({base_path}/IMPROVE.md)") - tool_names.append(f"[ANALYZE]({base_path}/Analyze.md) 💎") - tool_names.append(f"[UPDATE CHANGELOG]({base_path}/UPDATE_CHANGELOG.md)") - tool_names.append(f"[ADD DOCUMENTATION]({base_path}/ADD_DOCUMENTATION.md) 💎") - tool_names.append(f"[ASK]({base_path}/ASK.md)") - tool_names.append(f"[GENERATE CUSTOM LABELS]({base_path}/GENERATE_CUSTOM_LABELS.md)") - tool_names.append(f"[TEST]({base_path}/TEST.md) 💎") - tool_names.append(f"[CI FEEDBACK]({base_path}/CI_FEEDBACK.md) 💎") - tool_names.append(f"[CUSTOM SUGGESTIONS]({base_path}/CUSTOM_SUGGESTIONS.md) 💎") - tool_names.append(f"[SIMILAR ISSUE]({base_path}/SIMILAR_ISSUE.md)") + tool_names.append(f"[DESCRIBE]({base_path}/describe/)") + tool_names.append(f"[REVIEW]({base_path}/review/)") + tool_names.append(f"[IMPROVE]({base_path}/improve/)") + tool_names.append(f"[ANALYZE]({base_path}/analyze/) 💎") + tool_names.append(f"[UPDATE CHANGELOG]({base_path}/update_changelog/)") + tool_names.append(f"[ADD DOCUMENTATION]({base_path}/documentation/) 💎") + tool_names.append(f"[ASK]({base_path}/ask/)") + tool_names.append(f"[GENERATE CUSTOM LABELS]({base_path}/custom_labels/)") + tool_names.append(f"[TEST]({base_path}/test/) 💎") + tool_names.append(f"[CI FEEDBACK]({base_path}/ci_feedback/) 💎") + tool_names.append(f"[CUSTOM SUGGESTIONS]({base_path}/custom_suggestions/) 💎") + tool_names.append(f"[SIMILAR ISSUE]({base_path}/similar_issues/)") descriptions = [] descriptions.append("Generates PR description - title, type, summary, code walkthrough and labels") @@ -91,7 +91,7 @@ class PRHelpMessage: for i in range(len(tool_names)): pr_comment += f"\n\n\n{tool_names[i]}{commands[i]}{descriptions[i]}" pr_comment += "\n\n" - pr_comment += f"""\n\nNote that each tool be [invoked automatically](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools-for-pr-actions) when a new PR is opened, or called manually by [commenting on a PR](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#online-usage).""" + pr_comment += f"""\n\nNote that each tool be [invoked automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/) when a new PR is opened, or called manually by [commenting on a PR](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#online-usage).""" if get_settings().config.publish_output: self.git_provider.publish_comment(pr_comment) except Exception as e: diff --git a/pr_agent/tools/pr_information_from_user.py b/pr_agent/tools/pr_information_from_user.py index 1523d737..03537181 100644 --- a/pr_agent/tools/pr_information_from_user.py +++ b/pr_agent/tools/pr_information_from_user.py @@ -21,6 +21,8 @@ class PRInformationFromUser: self.git_provider.get_languages(), self.git_provider.get_files() ) self.ai_handler = ai_handler() + self.ai_handler.main_pr_language = self.main_pr_language + self.vars = { "title": self.git_provider.pr.title, "branch": self.git_provider.get_pr_branch(), diff --git a/pr_agent/tools/pr_line_questions.py b/pr_agent/tools/pr_line_questions.py index e6386fd1..e26e06d5 100644 --- a/pr_agent/tools/pr_line_questions.py +++ b/pr_agent/tools/pr_line_questions.py @@ -22,8 +22,11 @@ class PR_LineQuestions: def __init__(self, pr_url: str, args=None, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler): self.question_str = self.parse_args(args) self.git_provider = get_git_provider()(pr_url) - + self.main_pr_language = get_main_pr_language( + self.git_provider.get_languages(), self.git_provider.get_files() + ) self.ai_handler = ai_handler() + self.ai_handler.main_pr_language = self.main_pr_language self.vars = { "title": self.git_provider.pr.title, diff --git a/pr_agent/tools/pr_questions.py b/pr_agent/tools/pr_questions.py index 7d789e2b..3849029d 100644 --- a/pr_agent/tools/pr_questions.py +++ b/pr_agent/tools/pr_questions.py @@ -22,6 +22,8 @@ class PRQuestions: self.git_provider.get_languages(), self.git_provider.get_files() ) self.ai_handler = ai_handler() + self.ai_handler.main_pr_language = self.main_pr_language + self.question_str = question_str self.vars = { "title": self.git_provider.pr.title, diff --git a/pr_agent/tools/pr_reviewer.py b/pr_agent/tools/pr_reviewer.py index 04a13a61..19d1bb94 100644 --- a/pr_agent/tools/pr_reviewer.py +++ b/pr_agent/tools/pr_reviewer.py @@ -46,6 +46,8 @@ class PRReviewer: 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") self.ai_handler = ai_handler() + self.ai_handler.main_pr_language = self.main_language + self.patches_diff = None self.prediction = None @@ -108,6 +110,17 @@ class PRReviewer: relevant_configs = {'pr_reviewer': dict(get_settings().pr_reviewer), 'config': dict(get_settings().config)} get_logger().debug("Relevant configs", artifacts=relevant_configs) + + if self.incremental.is_incremental and hasattr(self.git_provider, "unreviewed_files_set") and not self.git_provider.unreviewed_files_set: + get_logger().info(f"Incremental review is enabled for {self.pr_url} but there are no new files") + previous_review_url = "" + if hasattr(self.git_provider, "previous_review"): + previous_review_url = self.git_provider.previous_review.html_url + if get_settings().config.publish_output: + self.git_provider.publish_comment(f"Incremental Review Skipped\n" + f"No files were changed since the [previous PR Review]({previous_review_url})") + return None + if get_settings().config.publish_output: self.git_provider.publish_comment("Preparing review...", is_temporary=True) @@ -208,21 +221,15 @@ class PRReviewer: pass + incremental_review_markdown_text = None # Add incremental review section if self.incremental.is_incremental: last_commit_url = f"{self.git_provider.get_pr_url()}/commits/" \ f"{self.git_provider.incremental.first_new_commit_sha}" - last_commit_msg = self.incremental.commits_range[0].commit.message if self.incremental.commits_range else "" incremental_review_markdown_text = f"Starting from commit {last_commit_url}" - if last_commit_msg: - replacement = last_commit_msg.splitlines(keepends=False)[0].replace('_', r'\_') - incremental_review_markdown_text += f" \n_({replacement})_" - data = OrderedDict(data) - data.update({'Incremental PR Review': { - "⏮️ Review for commits since previous PR-Agent review": incremental_review_markdown_text}}) - data.move_to_end('Incremental PR Review', last=False) - markdown_text = convert_to_markdown(data, self.git_provider.is_supported("gfm_markdown")) + markdown_text = convert_to_markdown(data, self.git_provider.is_supported("gfm_markdown"), + incremental_review_markdown_text) # Add help text if gfm_markdown is supported if self.git_provider.is_supported("gfm_markdown") and get_settings().pr_reviewer.enable_help_text: @@ -320,6 +327,10 @@ class PRReviewer: if self.is_auto and not self.incremental.first_new_commit_sha: get_logger().info(f"Incremental review is enabled for {self.pr_url} but there are no new commits") return False + + if not hasattr(self.git_provider, "get_incremental_commits"): + get_logger().info(f"Incremental review is not supported for {get_settings().config.git_provider}") + return False # checking if there are enough commits to start the review num_new_commits = len(self.incremental.commits_range) num_commits_threshold = get_settings().pr_reviewer.minimal_commits_for_incremental_review @@ -361,17 +372,20 @@ class PRReviewer: if security_concerns_bool: review_labels.append('Possible security concern') - current_labels = self.git_provider.get_pr_labels() + current_labels = self.git_provider.get_pr_labels(update=True) + get_logger().debug(f"Current labels:\n{current_labels}") if current_labels: current_labels_filtered = [label for label in current_labels if not label.lower().startswith('review effort [1-5]:') and not label.lower().startswith( 'possible security concern')] else: current_labels_filtered = [] - if current_labels or review_labels: - get_logger().debug(f"Current labels:\n{current_labels}") + new_labels = review_labels + current_labels_filtered + if (current_labels or review_labels) and sorted(new_labels) != sorted(current_labels): get_logger().info(f"Setting review labels:\n{review_labels + current_labels_filtered}") - self.git_provider.publish_labels(review_labels + current_labels_filtered) + self.git_provider.publish_labels(new_labels) + else: + get_logger().info(f"Review labels are already set:\n{review_labels + current_labels_filtered}") except Exception as e: get_logger().error(f"Failed to set review labels, error: {e}") diff --git a/pr_agent/tools/pr_update_changelog.py b/pr_agent/tools/pr_update_changelog.py index 4e168d5d..399e0599 100644 --- a/pr_agent/tools/pr_update_changelog.py +++ b/pr_agent/tools/pr_update_changelog.py @@ -26,7 +26,10 @@ class PRUpdateChangelog: ) self.commit_changelog = get_settings().pr_update_changelog.push_changelog_changes self._get_changlog_file() # self.changelog_file_str + self.ai_handler = ai_handler() + self.ai_handler.main_pr_language = self.main_language + self.patches_diff = None self.prediction = None self.cli_mode = cli_mode