Merge commit 'e4f177908b620e46740b03966fda9243473d979e' into hl/pr_review_table

This commit is contained in:
Hussam.lawen
2024-02-08 14:26:29 +02:00
41 changed files with 653 additions and 383 deletions

View File

@ -27,7 +27,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PINECONE.API_KEY: ${{ secrets.PINECONE_API_KEY }} PINECONE.API_KEY: ${{ secrets.PINECONE_API_KEY }}
PINECONE.ENVIRONMENT: ${{ secrets.PINECONE_ENVIRONMENT }} PINECONE.ENVIRONMENT: ${{ secrets.PINECONE_ENVIRONMENT }}
GITHUB_ACTION.AUTO_REVIEW: true GITHUB_ACTION_CONFIG.AUTO_DESCRIBE: true
GITHUB_ACTION.AUTO_IMPROVE: true GITHUB_ACTION_CONFIG.AUTO_REVIEW: true
GITHUB_ACTION_CONFIG.AUTO_IMPROVE: true

View File

@ -1,5 +1,6 @@
[pr_reviewer] [pr_reviewer]
enable_review_labels_effort = true enable_review_labels_effort = true
enable_auto_approval = true
[pr_code_suggestions] [pr_code_suggestions]

View File

@ -14,7 +14,6 @@ There are several ways to use PR-Agent:
**GitHub specific methods** **GitHub specific methods**
- [Run as a GitHub Action](INSTALL.md#run-as-a-github-action) - [Run as a GitHub Action](INSTALL.md#run-as-a-github-action)
- [Run as a polling server](INSTALL.md#run-as-a-polling-server)
- [Run as a GitHub App](INSTALL.md#run-as-a-github-app) - [Run as a GitHub App](INSTALL.md#run-as-a-github-app)
- [Deploy as a Lambda Function](INSTALL.md#deploy-as-a-lambda-function) - [Deploy as a Lambda Function](INSTALL.md#deploy-as-a-lambda-function)
- [AWS CodeCommit](INSTALL.md#aws-codecommit-setup) - [AWS CodeCommit](INSTALL.md#aws-codecommit-setup)
@ -186,19 +185,6 @@ When you open your next PR, you should see a comment from `github-actions` bot w
--- ---
### 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 ### Run as a GitHub App
Allowing you to automate the review process on your private or public repositories. Allowing you to automate the review process on your private or public repositories.

145
README.md
View File

@ -24,7 +24,6 @@ Making pull requests less painful with an AI agent
- [News and Updates](#news-and-updates) - [News and Updates](#news-and-updates)
- [Overview](#overview) - [Overview](#overview)
- [Example results](#example-results) - [Example results](#example-results)
- [Features overview](#features-overview)
- [Try it now](#try-it-now) - [Try it now](#try-it-now)
- [Installation](#installation) - [Installation](#installation)
- [PR-Agent Pro 💎](#pr-agent-pro-) - [PR-Agent Pro 💎](#pr-agent-pro-)
@ -32,33 +31,69 @@ Making pull requests less painful with an AI agent
- [Why use PR-Agent?](#why-use-pr-agent) - [Why use PR-Agent?](#why-use-pr-agent)
## News and Updates ## News and Updates
### Feb 6, 2024
A new feature was added to the `review` tool - [Auto-approve PRs](./docs/REVIEW.md#auto-approval-1). If enabled, this feature enables to automatically approve PRs that meet specific criteria, by commenting on a PR: `/review auto_approve`.
### Feb 2, 2024
We are excited to introduce "PR Actions" 💎:
<kbd>
[<img src="https://www.codium.ai/images/pr_agent/pr_actions.png" width="768"/>](https://www.codium.ai/images/pr_agent/pr-actions.mp4)
</kbd>
(click on the image to see a video demonstration)
### Jan 28, 2024
- 💎 Test - A new tool, [`/test component_name`](https://github.com/Codium-ai/pr-agent/blob/main/docs/TEST.md), was added to PR-Agent Pro. The tool will generate tests for a selected component, based on the PR code changes.
- 💎 Analyze - The [`/analyze`](https://github.com/Codium-ai/pr-agent/blob/main/docs/Analyze.md) tool was updated and simplified. It now presents a summary of the code components that were changed in the PR.
### Jan 21, 2024 ### Jan 21, 2024
- 💎 Custom suggestions - A new tool, `/custom_suggestions`, was added to PR-Agent Pro. The tool will propose only suggestions that follow specific guidelines defined by the user. - 💎 Custom suggestions - A new tool, `/custom_suggestions`, was added to PR-Agent Pro. The tool will propose only suggestions that follow specific guidelines defined by the user.
See [here](https://github.com/Codium-ai/pr-agent/blob/main/docs/CUSTOM_SUGGESTIONS.md) for more details. See [here](https://github.com/Codium-ai/pr-agent/blob/main/docs/CUSTOM_SUGGESTIONS.md) for more details.
### Jan 17, 2024
- 💎 Inline file summary - The `describe` tool has a new option `--pr_description.inline_file_summary`, which allows to add a summary of each file changes to the Diffview page. See [here](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#inline-file-summary-)
- The `improve` tool can now present suggestions in a nice collapsible format, which significantly reduces the PR footprint. See [here](https://github.com/Codium-ai/pr-agent/blob/main/docs/IMPROVE.md#summarized-vs-commitable-code-suggestions) for more details.
- To accompany the improved interface of the `improve` tool, we change the [default automation settings](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L116) of our GithupApp to:
```
pr_commands = [
"/describe --pr_description.add_original_user_description=true --pr_description.keep_original_user_title=true",
"/review --pr_reviewer.num_code_suggestions=0",
"/improve --pr_code_suggestions.summarize=true",
]
```
Meaning that by default, for each PR the `describe`, `review`, and `improve` tools will be triggered automatically, and the `improve` tool will present the suggestions in a single comment.
You can of course overwrite these defaults by adding a `.pr_agent.toml` file to your repo. See [here](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-app).
### Jan 10, 2024
[LanceDB](https://lancedb.com/) is now supported as a locally hosted VectorDB for the `similar_issue` tool. See [here](./docs/SIMILAR_ISSUE.md) for more details.
## Overview ## Overview
<div style="text-align:left;"> <div style="text-align:left;">
CodiumAI PR-Agent is an open-source tool to help efficiently review and handle pull requests. It automatically analyzes the pull request and can provide several types of commands: CodiumAI PR-Agent is an open-source tool to help efficiently review and handle pull requests. It automatically analyzes the pull request and can provide several types of commands:
| | | GitHub | Gitlab | Bitbucket |
|-------|------------------------------------------------------------------------------------------------------------------------------------------|:------:|:------:|:---------:|
| TOOLS | Review | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | ⮑ Incremental | :white_check_mark: | | |
| | ⮑ [SOC2 Compliance](https://github.com/Codium-ai/pr-agent/blob/main/docs/REVIEW.md#soc2-ticket-compliance-) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Describe | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | ⮑ [Inline File Summary](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#inline-file-summary-) 💎 | :white_check_mark: | | |
| | Improve | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | ⮑ Extended | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [Custom Suggestions](https://github.com/Codium-ai/pr-agent/blob/main/docs/CUSTOM_SUGGESTIONS.md) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [Test](https://github.com/Codium-ai/pr-agent/blob/main/docs/TEST.md) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Reflect and Review | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Update CHANGELOG.md | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Find Similar Issue | :white_check_mark: | | |
| | [Add PR Documentation](https://github.com/Codium-ai/pr-agent/blob/main/docs/ADD_DOCUMENTATION.md) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [Custom Labels](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#handle-custom-labels-from-the-repos-labels-page-gem) 💎 | :white_check_mark: | :white_check_mark: | |
| | [Analyze](https://github.com/Codium-ai/pr-agent/blob/main/docs/Analyze.md) 💎 | :white_check_mark: | :white_check_mark: | |
| | | | | |
| USAGE | CLI | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | App / webhook | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Tagging bot | :white_check_mark: | | |
| | Actions | :white_check_mark: | | :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<br />file patch fitting | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Multiple models support | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [Static code analysis](https://github.com/Codium-ai/pr-agent/blob/main/docs/Analyze.md) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [Global configuration](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#global-configuration-file-) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [PR Actions](https://www.codium.ai/images/pr_agent/pr-actions.mp4) 💎 | :white_check_mark: | | |
- 💎 means this feature is available only in [PR-Agent Pro](https://www.codium.ai/pricing/)
- Support for additional git providers is described in [here](./docs/Full_environments.md)
___
**Auto Description ([`/describe`](./docs/DESCRIBE.md))**: Automatically generating PR description - title, type, summary, code walkthrough and labels. **Auto Description ([`/describe`](./docs/DESCRIBE.md))**: Automatically generating PR description - title, type, summary, code walkthrough and labels.
\ \
**Auto Review ([`/review`](./docs/REVIEW.md))**: Adjustable feedback about the PR main theme, type, relevant tests, security issues, score, and various suggestions for the PR content. **Auto Review ([`/review`](./docs/REVIEW.md))**: Adjustable feedback about the PR main theme, type, relevant tests, security issues, score, and various suggestions for the PR content.
@ -78,7 +113,8 @@ CodiumAI PR-Agent is an open-source tool to help efficiently review and handle p
**Analyze 💎 ([`/analyze`](./docs/Analyze.md))**: Automatically analyzes the PR, and presents changes walkthrough for each component. **Analyze 💎 ([`/analyze`](./docs/Analyze.md))**: Automatically analyzes the PR, and presents changes walkthrough for each component.
\ \
**Custom Suggestions 💎 ([`/custom_suggestions`](./docs/CUSTOM_SUGGESTIONS.md))**: Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user. **Custom Suggestions 💎 ([`/custom_suggestions`](./docs/CUSTOM_SUGGESTIONS.md))**: Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user.
\
**Generate Tests 💎 ([`/test component_name`](./docs/TEST.md))**: Automatically generates unit tests for a selected component, based on the PR code changes.
See the [Installation Guide](./INSTALL.md) for instructions on installing and running the tool on different git platforms. See the [Installation Guide](./INSTALL.md) for instructions on installing and running the tool on different git platforms.
@ -155,42 +191,6 @@ See the [Tools Guide](./docs/TOOLS_GUIDE.md) for a detailed description of the d
</div> </div>
<hr> <hr>
## Features 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: |
| | ⮑ Incremental | :white_check_mark: | | |
| | ⮑ [SOC2 Compliance](https://github.com/Codium-ai/pr-agent/blob/main/docs/REVIEW.md#soc2-ticket-compliance-) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Ask | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Describe | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | ⮑ [Inline file summary](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#inline-file-summary-) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Improve | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | ⮑ Extended | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [Custom Suggestions](https://github.com/Codium-ai/pr-agent/blob/main/docs/CUSTOM_SUGGESTIONS.md) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Reflect and Review | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Update CHANGELOG.md | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | Find Similar Issue | :white_check_mark: | | |
| | [Add PR Documentation](https://github.com/Codium-ai/pr-agent/blob/main/docs/ADD_DOCUMENTATION.md) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [Generate Custom Labels](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#handle-custom-labels-from-the-repos-labels-page-gem) 💎 | :white_check_mark: | :white_check_mark: | |
| | [Analyze PR Components](https://github.com/Codium-ai/pr-agent/blob/main/docs/Analyze.md) 💎 | :white_check_mark: | :white_check_mark: | :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<br />file patch fitting | :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: | | |
| | [Static code analysis](https://github.com/Codium-ai/pr-agent/blob/main/docs/Analyze.md) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| | [Global configuration](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#global-configuration-file-) 💎 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
- 💎 means this feature is available only in [PR-Agent Pro](https://www.codium.ai/pricing/)
- Support for additional git providers is described in [here](./docs/Full_enviroments.md)
## Try it now ## Try it now
@ -217,31 +217,34 @@ To use your own version of PR-Agent, you first need to acquire two tokens:
There are several ways to use PR-Agent: 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) **Locally**
- [Method 2: Run from source](INSTALL.md#method-2-run-from-source) - [Use Docker image (no installation required)](./INSTALL.md#use-docker-image-no-installation-required)
- [Method 3: Run as a GitHub Action](INSTALL.md#method-3-run-as-a-github-action) - [Run from source](./INSTALL.md#run-from-source)
- [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 **GitHub specific methods**
- [Method 5: Run as a GitHub App](INSTALL.md#method-5-run-as-a-github-app) - [Run as a GitHub Action](./INSTALL.md#run-as-a-github-action)
- Allowing you to automate the review process on your private or public repositories - [Run as a GitHub App](./INSTALL.md#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) **GitLab specific methods**
- [Method 8: Run a GitLab webhook server](INSTALL.md#method-8---run-a-gitlab-webhook-server) - [Run a GitLab webhook server](./INSTALL.md#run-a-gitlab-webhook-server)
- [Method 9: Run as a Bitbucket Pipeline](INSTALL.md#method-9-run-as-a-bitbucket-pipeline)
**BitBucket specific methods**
- [Run as a Bitbucket Pipeline](./INSTALL.md#run-as-a-bitbucket-pipeline)
## PR-Agent Pro 💎 ## PR-Agent Pro 💎
[PR-Agent Pro](https://www.codium.ai/pricing/) is a hosted version of PR-Agent, provided by CodiumAI. It is available for a monthly fee, and provides the following benefits: [PR-Agent Pro](https://www.codium.ai/pricing/) is a hosted version of PR-Agent, provided by CodiumAI. It is available for a monthly fee, and provides the following benefits:
1. **Fully managed** - We take care of everything for you - hosting, models, regular updates, and more. Installation is as simple as signing up and adding the PR-Agent app to your GitHub\BitBucket repo. 1. **Fully managed** - We take care of everything for you - hosting, models, regular updates, and more. Installation is as simple as signing up and adding the PR-Agent app to your GitHub\BitBucket repo.
2. **Improved privacy** - No data will be stored or used to train models. PR-Agent Pro will employ zero data retention, and will use an OpenAI account with zero data retention. 2. **Improved privacy** - No data will be stored or used to train models. PR-Agent Pro will employ zero data retention, and will use an OpenAI account with zero data retention.
3. **Improved support** - PR-Agent Pro users will receive priority support, and will be able to request new features and capabilities. 3. **Improved support** - PR-Agent Pro users will receive priority support, and will be able to request new features and capabilities.
4. **Extra features** -In addition to the benefits listed above, PR-Agent Pro will emphasize more customization, and the usage of static code analysis, in addition to LLM logic, to improve results. It has the following additional features: 4. **Extra features** -In addition to the benefits listed above, PR-Agent Pro will emphasize more customization, and the usage of static code analysis, in addition to LLM logic, to improve results. It has the following additional tools and features:
- [**SOC2 compliance check**](https://github.com/Codium-ai/pr-agent/blob/main/docs/REVIEW.md#soc2-ticket-compliance-) - [**Analyze PR components**](https://github.com/Codium-ai/pr-agent/blob/main/docs/Analyze.md)
- [**Custom Code Suggestions**](https://github.com/Codium-ai/pr-agent/blob/main/docs/CUSTOM_SUGGESTIONS.md)
- [**Tests**](https://github.com/Codium-ai/pr-agent/blob/main/docs/TEST.md)
- [**PR documentation**](https://github.com/Codium-ai/pr-agent/blob/main/docs/ADD_DOCUMENTATION.md) - [**PR documentation**](https://github.com/Codium-ai/pr-agent/blob/main/docs/ADD_DOCUMENTATION.md)
- [**SOC2 compliance check**](https://github.com/Codium-ai/pr-agent/blob/main/docs/REVIEW.md#soc2-ticket-compliance-)
- [**Custom labels**](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#handle-custom-labels-from-the-repos-labels-page-gem) - [**Custom labels**](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#handle-custom-labels-from-the-repos-labels-page-gem)
- [**Global configuration**](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#global-configuration-file-) - [**Global configuration**](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#global-configuration-file-)
- [**Analyze PR components**](https://github.com/Codium-ai/pr-agent/blob/main/docs/Analyze.md)
- **Custom Code Suggestions** [WIP]
- **Chat on Specific Code Lines** [WIP]
## How it works ## How it works

View File

@ -100,7 +100,7 @@ python -m pr_agent.cli --pr_url=<pr_url> /review --pr_reviewer.extra_instructio
(2) You can print results locally, without publishing them, by setting in `configuration.toml`: (2) You can print results locally, without publishing them, by setting in `configuration.toml`:
``` ```
[config] [config]
publish_output=true publish_output=false
verbosity_level=2 verbosity_level=2
``` ```
This is useful for debugging or experimenting with different tools. This is useful for debugging or experimenting with different tools.
@ -142,28 +142,20 @@ num_code_suggestions=1
Then you will overwrite the default number of code suggestions to 1. Then you will overwrite the default number of code suggestions to 1.
#### GitHub app automatic tools #### GitHub app automatic tools
The [github_app](pr_agent/settings/configuration.toml#L76) section defines GitHub app-specific configurations. The [github_app](pr_agent/settings/configuration.toml#L108) section defines GitHub app specific configurations.
In this section, you can define configurations to control the conditions for which tools will **run automatically**.
##### GitHub app automatic tools for PR actions ##### GitHub app automatic tools for PR actions
The GitHub app can respond to the following actions on a PR: The configuration parameter `pr_commands` defines the list of tools that will be **run automatically** when a new PR is opened.
1. `opened` - Opening a new PR
2. `reopened` - Reopening a closed PR
3. `ready_for_review` - Moving a PR from Draft to Open
4. `review_requested` - Specifically requesting review (in the PR reviewers list) from the `github-actions[bot]` user
The configuration parameter `handle_pr_actions` defines the list of actions for which the GitHub app will trigger the PR-Agent.
The configuration parameter `pr_commands` defines the list of tools that will be **run automatically** when one of the above actions happens (e.g., a new PR is opened):
``` ```
[github_app] [github_app]
handle_pr_actions = ['opened', 'reopened', 'ready_for_review', 'review_requested']
pr_commands = [ pr_commands = [
"/describe --pr_description.add_original_user_description=true --pr_description.keep_original_user_title=true", "/describe --pr_description.add_original_user_description=true --pr_description.keep_original_user_title=true",
"/review", "/review --pr_reviewer.num_code_suggestions=0",
"/improve",
] ]
``` ```
This means that when a new PR is opened/reopened or marked as ready for review, PR-Agent will run the `describe` and `review` tools. This means that when a new PR is opened/reopened or marked as ready for review, PR-Agent will run the `describe`, `review` and `improve` 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, for example, the `add_original_user_description` and `keep_original_user_title` parameters will be set to true.
You can override the default tool parameters by uploading a local configuration file called `.pr_agent.toml` to the root of your repo. 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: For example, if your local `.pr_agent.toml` file contains:
@ -180,7 +172,7 @@ To cancel the automatic run of all the tools, set:
handle_pr_actions = [] handle_pr_actions = []
``` ```
##### GitHub app automatic tools for new code (PR push) ##### GitHub app automatic tools for push actions (commits to an open PR)
In addition to running automatic tools when a PR is opened, the GitHub app can also respond to new code that is pushed to an open PR. In addition to running automatic tools when a PR is opened, the GitHub app can also respond to new code that is pushed to an open PR.
The configuration toggle `handle_push_trigger` can be used to enable this feature. The configuration toggle `handle_push_trigger` can be used to enable this feature.
@ -217,18 +209,19 @@ 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). 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).
### Working with GitHub Action ### Working with GitHub Action
You can configure settings in GitHub action by adding environment variables under the env section in `.github/workflows/pr_agent.yml` file. `GitHub Action` is a different way to trigger PR-Agent tools, and uses a different configuration mechanism than `GitHub App`.
You can configure settings for `GitHub Action` by adding environment variables under the env section in `.github/workflows/pr_agent.yml` file.
Specifically, start by setting the following environment variables: Specifically, start by setting the following environment variables:
```yaml ```yaml
env: env:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }} # Make sure to add your OpenAI key to your repo secrets OPENAI_KEY: ${{ secrets.OPENAI_KEY }} # Make sure to add your OpenAI key to your repo secrets
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Make sure to add your GitHub token to your repo secrets GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Make sure to add your GitHub token to your repo secrets
github_action.auto_review: "true" # enable\disable auto review github_action_config.auto_review: "true" # enable\disable auto review
github_action.auto_describe: "true" # enable\disable auto describe github_action_config.auto_describe: "true" # enable\disable auto describe
github_action.auto_improve: "false" # enable\disable auto improve github_action_config.auto_improve: "true" # enable\disable auto improve
``` ```
`github_action.auto_review`, `github_action.auto_describe` and `github_action.auto_improve` are used to enable/disable automatic tools that run when a new PR is opened. `github_action_config.auto_review`, `github_action_config.auto_describe` and `github_action_config.auto_improve` are used to enable/disable automatic tools that run when a new PR is opened.
If not set, the default option is that only the `review` tool will run automatically when a new PR is opened. If not set, the default configuration is for all three tools to run automatically when a new PR is opened.
Note that you can give additional config parameters by adding environment variables to `.github/workflows/pr_agent.yml`, or by using a `.pr_agent.toml` file in the root of your repo, similar to the GitHub App usage. Note that you can give additional config parameters by adding environment variables to `.github/workflows/pr_agent.yml`, or by using a `.pr_agent.toml` file in the root of your repo, similar to the GitHub App usage.

View File

@ -52,15 +52,23 @@ To edit [configurations](./../pr_agent/settings/configuration.toml#L46) related
### Inline file summary 💎 ### Inline file summary 💎
> This feature is available only in PR-Agent Pro > This feature is available only in PR-Agent Pro
This will enable you to quickly understand the changes in each file, while reviewing the code changes (diff view). This feature will enable you to quickly understand the changes in each file while reviewing the code changes (diff view).
To enable inline file summary, set `pr_description.inline_file_summary` in the configuration file, possible values are:
To add the walkthrough table to the "Files changed" tab, you can click on the checkbox that appears PR Description status message below the main PR Description:
<kbd><img src=https://codium.ai/images/pr_agent/add_table_checkbox.png width="512"></kbd>
If you prefer to have the file summaries appear in the "Files changed" tab on every PR, change the `pr_description.inline_file_summary` parameter in the configuration file, possible values are:
- `'table'`: File changes walkthrough table will be displayed on the top of the "Files changed" tab, in addition to the "Conversation" tab. - `'table'`: File changes walkthrough table will be displayed on the top of the "Files changed" tab, in addition to the "Conversation" tab.
<kbd><img src=https://codium.ai/images/pr_agent/diffview-table.png width="1024"></kbd> <kbd><img src=https://codium.ai/images/pr_agent/diffview-table.png width="768"></kbd>
- `true`: A collapsable file comment with changes title and a changes summary for each file in the PR. - `true`: A collapsable file comment with changes title and a changes summary for each file in the PR.
<kbd><img src=https://codium.ai/images/pr_agent/diffview_changes.png width="1024"></kbd> <kbd><img src=https://codium.ai/images/pr_agent/diffview_changes.png width="768"></kbd>
- `false` (`default`): File changes walkthrough will be added only to the "Conversation" tab. - `false` (`default`): File changes walkthrough will be added only to the "Conversation" tab.
*Note that this feature is currently available only for GitHub. Note that this feature is currently available only for GitHub.
### Handle custom labels from the Repo's labels page :gem: ### Handle custom labels from the Repo's labels page :gem:
@ -81,7 +89,7 @@ The description should be comprehensive and detailed, indicating when to add the
### Markers template ### Markers template
To enable markers, set `pr_description.use_description_markers=true`. To enable markers, set `pr_description.use_description_markers=true`.
markers enable to easily integrate user's content and auto-generated content, with a template-like mechanism. Markers enable to easily integrate user's content and auto-generated content, with a template-like mechanism.
For example, if the PR original description was: For example, if the PR original description was:
``` ```

View File

@ -11,6 +11,7 @@
- [Automation](#automation) - [Automation](#automation)
- [Auto-labels](#auto-labels) - [Auto-labels](#auto-labels)
- [Extra instructions](#extra-instructions) - [Extra instructions](#extra-instructions)
- [Auto-approval](#auto-approval-1)
## Overview ## Overview
The `review` tool scans the PR code changes, and automatically generates a PR review. The `review` tool scans the PR code changes, and automatically generates a PR review.
@ -50,6 +51,9 @@ This sub-tool checks if the PR description properly contains a ticket to a proje
#### Adding PR labels #### Adding PR labels
- `enable_review_labels_security`: if set to true, the tool will publish a 'possible security issue' label if it detects a security issue. Default is true. - `enable_review_labels_security`: if set to true, the tool will publish a 'possible security issue' label if it detects a security issue. Default is true.
- `enable_review_labels_effort`: if set to true, the tool will publish a 'Review effort [1-5]: x' label. Default is true. - `enable_review_labels_effort`: if set to true, the tool will publish a 'Review effort [1-5]: x' label. Default is true.
#### Auto-approval
- `enable_auto_approval`: if set to true, the tool will approve the PR when invoked with the 'auto_approve' command. Default is false. This flag can be changed only from configuration file.
- `maximal_review_effort`: maximal effort level for auto-approval. If the PR's estimated review effort is above this threshold, the auto-approval will not run. Default is 5.
### Incremental Mode ### Incremental Mode
Incremental review only considers changes since the last PR-Agent review. This can be useful when working on the PR in an iterative manner, and you want to focus on the changes since the last review instead of reviewing the entire PR again. Incremental review only considers changes since the last PR-Agent review. This can be useful when working on the PR in an iterative manner, and you want to focus on the changes since the last review instead of reviewing the entire PR again.
@ -97,6 +101,7 @@ ___
3) [Automation](#automation) 3) [Automation](#automation)
4) [Auto-labels](#auto-labels) 4) [Auto-labels](#auto-labels)
5) [Extra instructions](#extra-instructions) 5) [Extra instructions](#extra-instructions)
6) [Auto-approval](#auto-approval)
### General guidelines ### General guidelines
The `review` tool provides a collection of possible feedbacks about a PR. The `review` tool provides a collection of possible feedbacks about a PR.
@ -146,3 +151,27 @@ In the code feedback section, emphasize the following:
``` ```
Use triple quotes to write multi-line instructions. Use bullet points to make the instructions more readable. Use triple quotes to write multi-line instructions. Use bullet points to make the instructions more readable.
### Auto-approval
PR-Agent can approve a PR when a specific comment is invoked.
To ensure safety, the auto-approval feature is disabled by default. To enable auto-approval, you need to actively set in a pre-defined configuration file the following:
```
[pr_reviewer]
enable_auto_approval = true
```
(this specific flag cannot be set with a command line argument, only in the configuration file, committed to the repository)
After enabling, by commenting on a PR:
```
/review auto_approve
```
PR-Agent will automatically approve the PR, and add a comment with the approval.
You can also enable auto-approval only if the PR meets certain requirements, such as that the `estimated_review_effort` label is equal or below a certain threshold, by adjusting the flag:
```
[pr_reviewer]
maximal_review_effort = 5
```

30
docs/TEST.md Normal file
View File

@ -0,0 +1,30 @@
# Test Tool 💎
By combining LLM abilities with static code analysis, the `test` tool generate tests for a selected component, based on the PR code changes.
It can be invoked manually by commenting on any PR:
```
/test component_name
```
where 'component_name' is the name of a specific component in the PR.
To get a list of the components that changed in the PR, use the [`analyze`](https://github.com/Codium-ai/pr-agent/blob/main/docs/Analyze.md) tool.
An example [result](https://github.com/Codium-ai/pr-agent/pull/598#issuecomment-1913679429):
<kbd><img src=https://codium.ai/images/pr_agent/test1.png width="704"></kbd>
___
<kbd><img src=https://codium.ai/images/pr_agent/test2.png width="768"></kbd>
___
<kbd><img src=https://codium.ai/images/pr_agent/test3.png width="768"></kbd>
Language that are currently supported by the tool: Python, Java, C++, JavaScript, TypeScript.
### Configuration options
- `num_tests`: number of tests to generate. Default is 3.
- `testing_framework`: the testing framework to use. If not set, for Python it will use `pytest`, for Java it will use `JUnit`, for C++ it will use `Catch2`, and for JavaScript and TypeScript it will use `jest`.
- `avoid_mocks`: if set to true, the tool will try to avoid using mocks in the generated tests. Note that even if this option is set to true, the tool might still use mocks if it cannot generate a test without them. Default is true.
- `extra_instructions`: Optional extra instructions to the tool. For example: "use the following mock injection scheme: ...".
- `file`: in case there are several components with the same name, you can specify the relevant file.
- `class_name`: in case there are several methods with the same name in the same file, you can specify the relevant class name.
- `enable_help_text`: if set to true, the tool will add a help text to the PR comment. Default is true.

View File

@ -5,8 +5,9 @@
- [ASK](./ASK.md) - [ASK](./ASK.md)
- [SIMILAR_ISSUE](./SIMILAR_ISSUE.md) - [SIMILAR_ISSUE](./SIMILAR_ISSUE.md)
- [UPDATE CHANGELOG](./UPDATE_CHANGELOG.md) - [UPDATE CHANGELOG](./UPDATE_CHANGELOG.md)
- [ADD DOCUMENTATION](./ADD_DOCUMENTATION.md) - [ADD DOCUMENTATION](./ADD_DOCUMENTATION.md) 💎
- [GENERATE CUSTOM LABELS](./GENERATE_CUSTOM_LABELS.md) - [GENERATE CUSTOM LABELS](./GENERATE_CUSTOM_LABELS.md) 💎
- [Analyze](./Analyze.md) - [Analyze](./Analyze.md) 💎
- [Test](./TEST.md) 💎
See the **[installation guide](/INSTALL.md)** for instructions on how to setup PR-Agent. See the **[installation guide](/INSTALL.md)** for instructions on setting up PR-Agent.

View File

@ -45,6 +45,7 @@ commands = list(command2class.keys())
class PRAgent: class PRAgent:
def __init__(self, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler): def __init__(self, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
self.ai_handler = ai_handler # will be initialized in run_action self.ai_handler = ai_handler # will be initialized in run_action
self.forbidden_cli_args = ['enable_auto_approval']
async def handle_request(self, pr_url, request, notify=None) -> bool: async def handle_request(self, pr_url, request, notify=None) -> bool:
# First, apply repo specific settings if exists # First, apply repo specific settings if exists
@ -58,6 +59,13 @@ class PRAgent:
action, *args = list(lexer) action, *args = list(lexer)
else: else:
action, *args = request action, *args = request
if args:
for forbidden_arg in self.forbidden_cli_args:
for arg in args:
if forbidden_arg in arg:
get_logger().error(f"CLI argument '{forbidden_arg}' is forbidden")
return False
args = update_settings_from_args(args) args = update_settings_from_args(args)
action = action.lstrip("/").lower() action = action.lstrip("/").lower()

View File

@ -9,6 +9,7 @@ MAX_TOKENS = {
'gpt-4-0613': 8000, 'gpt-4-0613': 8000,
'gpt-4-32k': 32000, 'gpt-4-32k': 32000,
'gpt-4-1106-preview': 128000, # 128K, but may be limited by config.max_model_tokens 'gpt-4-1106-preview': 128000, # 128K, but may be limited by config.max_model_tokens
'gpt-4-0125-preview': 128000, # 128K, but may be limited by config.max_model_tokens
'claude-instant-1': 100000, 'claude-instant-1': 100000,
'claude-2': 100000, 'claude-2': 100000,
'command-nightly': 4096, 'command-nightly': 4096,

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import re import re
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers.git_provider import EDIT_TYPE from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.log import get_logger from pr_agent.log import get_logger
@ -181,7 +181,7 @@ __old hunk__
... ...
""" """
patch_with_lines_str = f"\n\n## {file.filename}\n" patch_with_lines_str = f"\n\n## file: '{file.filename.strip()}'\n"
patch_lines = patch.splitlines() patch_lines = patch.splitlines()
RE_HUNK_HEADER = re.compile( RE_HUNK_HEADER = re.compile(
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)") r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
@ -202,11 +202,11 @@ __old hunk__
if new_content_lines: if new_content_lines:
if prev_header_line: if prev_header_line:
patch_with_lines_str += f'\n{prev_header_line}\n' patch_with_lines_str += f'\n{prev_header_line}\n'
patch_with_lines_str += '__new hunk__\n' patch_with_lines_str = patch_with_lines_str.rstrip()+'\n__new hunk__\n'
for i, line_new in enumerate(new_content_lines): for i, line_new in enumerate(new_content_lines):
patch_with_lines_str += f"{start2 + i} {line_new}\n" patch_with_lines_str += f"{start2 + i} {line_new}\n"
if old_content_lines: if old_content_lines:
patch_with_lines_str += '__old hunk__\n' patch_with_lines_str = patch_with_lines_str.rstrip()+'\n__old hunk__\n'
for line_old in old_content_lines: for line_old in old_content_lines:
patch_with_lines_str += f"{line_old}\n" patch_with_lines_str += f"{line_old}\n"
new_content_lines = [] new_content_lines = []
@ -236,11 +236,11 @@ __old hunk__
if match and new_content_lines: if match and new_content_lines:
if new_content_lines: if new_content_lines:
patch_with_lines_str += f'\n{header_line}\n' patch_with_lines_str += f'\n{header_line}\n'
patch_with_lines_str += '\n__new hunk__\n' patch_with_lines_str = patch_with_lines_str.rstrip()+ '\n__new hunk__\n'
for i, line_new in enumerate(new_content_lines): for i, line_new in enumerate(new_content_lines):
patch_with_lines_str += f"{start2 + i} {line_new}\n" patch_with_lines_str += f"{start2 + i} {line_new}\n"
if old_content_lines: if old_content_lines:
patch_with_lines_str += '\n__old hunk__\n' patch_with_lines_str = patch_with_lines_str.rstrip() + '\n__old hunk__\n'
for line_old in old_content_lines: for line_old in old_content_lines:
patch_with_lines_str += f"{line_old}\n" patch_with_lines_str += f"{line_old}\n"

View File

@ -1,9 +1,7 @@
from __future__ import annotations from __future__ import annotations
import difflib
import re
import traceback import traceback
from typing import Any, Callable, List, Tuple from typing import Callable, List, Tuple
from github import RateLimitExceededException from github import RateLimitExceededException
@ -11,9 +9,10 @@ from pr_agent.algo.git_patch_processing import convert_to_hunks_with_lines_numbe
from pr_agent.algo.language_handler import sort_files_by_main_languages from pr_agent.algo.language_handler import sort_files_by_main_languages
from pr_agent.algo.file_filter import filter_ignored from pr_agent.algo.file_filter import filter_ignored
from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import get_max_tokens from pr_agent.algo.utils import get_max_tokens, ModelType
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers.git_provider import FilePatchInfo, GitProvider, EDIT_TYPE from pr_agent.git_providers.git_provider import GitProvider
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.log import get_logger from pr_agent.log import get_logger
DELETED_FILES_ = "Deleted files:\n" DELETED_FILES_ = "Deleted files:\n"
@ -209,9 +208,9 @@ def pr_generate_compressed_diff(top_langs: list, token_handler: TokenHandler, mo
if patch: if patch:
if not convert_hunks_to_line_numbers: if not convert_hunks_to_line_numbers:
patch_final = f"## {file.filename}\n\n{patch}\n" patch_final = f"\n\n## file: '{file.filename.strip()}\n\n{patch.strip()}\n'"
else: else:
patch_final = patch patch_final = "\n\n" + patch.strip()
patches.append(patch_final) patches.append(patch_final)
total_tokens += token_handler.count_tokens(patch_final) total_tokens += token_handler.count_tokens(patch_final)
if get_settings().config.verbosity_level >= 2: if get_settings().config.verbosity_level >= 2:
@ -220,8 +219,8 @@ def pr_generate_compressed_diff(top_langs: list, token_handler: TokenHandler, mo
return patches, modified_files_list, deleted_files_list, added_files_list return patches, modified_files_list, deleted_files_list, added_files_list
async def retry_with_fallback_models(f: Callable): async def retry_with_fallback_models(f: Callable, model_type: ModelType = ModelType.REGULAR):
all_models = _get_all_models() all_models = _get_all_models(model_type)
all_deployments = _get_all_deployments(all_models) all_deployments = _get_all_deployments(all_models)
# try each (model, deployment_id) pair until one is successful, otherwise raise exception # try each (model, deployment_id) pair until one is successful, otherwise raise exception
for i, (model, deployment_id) in enumerate(zip(all_models, all_deployments)): for i, (model, deployment_id) in enumerate(zip(all_models, all_deployments)):
@ -243,8 +242,11 @@ async def retry_with_fallback_models(f: Callable):
raise # Re-raise the last exception raise # Re-raise the last exception
def _get_all_models() -> List[str]: def _get_all_models(model_type: ModelType = ModelType.REGULAR) -> List[str]:
model = get_settings().config.model if model_type == ModelType.TURBO:
model = get_settings().config.model_turbo
else:
model = get_settings().config.model
fallback_models = get_settings().config.fallback_models fallback_models = get_settings().config.fallback_models
if not isinstance(fallback_models, list): if not isinstance(fallback_models, list):
fallback_models = [m.strip() for m in fallback_models.split(",")] fallback_models = [m.strip() for m in fallback_models.split(",")]
@ -267,78 +269,6 @@ def _get_all_deployments(all_models: List[str]) -> List[str]:
return all_deployments return all_deployments
def find_line_number_of_relevant_line_in_file(diff_files: List[FilePatchInfo],
relevant_file: str,
relevant_line_in_file: str,
absolute_position: int = None) -> Tuple[int, int]:
position = -1
if absolute_position is None:
absolute_position = -1
re_hunk_header = re.compile(
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
for file in diff_files:
if file.filename and (file.filename.strip() == relevant_file):
patch = file.patch
patch_lines = patch.splitlines()
delta = 0
start1, size1, start2, size2 = 0, 0, 0, 0
if absolute_position != -1: # matching absolute to relative
for i, line in enumerate(patch_lines):
# new hunk
if line.startswith('@@'):
delta = 0
match = re_hunk_header.match(line)
start1, size1, start2, size2 = map(int, match.groups()[:4])
elif not line.startswith('-'):
delta += 1
#
absolute_position_curr = start2 + delta - 1
if absolute_position_curr == absolute_position:
position = i
break
else:
# try to find the line in the patch using difflib, with some margin of error
matches_difflib: list[str | Any] = difflib.get_close_matches(relevant_line_in_file,
patch_lines, n=3, cutoff=0.93)
if len(matches_difflib) == 1 and matches_difflib[0].startswith('+'):
relevant_line_in_file = matches_difflib[0]
for i, line in enumerate(patch_lines):
if line.startswith('@@'):
delta = 0
match = re_hunk_header.match(line)
start1, size1, start2, size2 = map(int, match.groups()[:4])
elif not line.startswith('-'):
delta += 1
if relevant_line_in_file in line and line[0] != '-':
position = i
absolute_position = start2 + delta - 1
break
if position == -1 and relevant_line_in_file[0] == '+':
no_plus_line = relevant_line_in_file[1:].lstrip()
for i, line in enumerate(patch_lines):
if line.startswith('@@'):
delta = 0
match = re_hunk_header.match(line)
start1, size1, start2, size2 = map(int, match.groups()[:4])
elif not line.startswith('-'):
delta += 1
if no_plus_line in line and line[0] != '-':
# The model might add a '+' to the beginning of the relevant_line_in_file even if originally
# it's a context line
position = i
absolute_position = start2 + delta - 1
break
return position, absolute_position
def get_pr_multi_diffs(git_provider: GitProvider, def get_pr_multi_diffs(git_provider: GitProvider,
token_handler: TokenHandler, token_handler: TokenHandler,
model: str, model: str,
@ -375,6 +305,13 @@ def get_pr_multi_diffs(git_provider: GitProvider,
for lang in pr_languages: for lang in pr_languages:
sorted_files.extend(sorted(lang['files'], key=lambda x: x.tokens, reverse=True)) sorted_files.extend(sorted(lang['files'], key=lambda x: x.tokens, reverse=True))
# try first a single run with standard diff string, with patch extension, and no deletions
patches_extended, total_tokens, patches_extended_tokens = pr_generate_extended_diff(
pr_languages, token_handler, add_line_numbers_to_hunks=True)
if total_tokens + OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD < get_max_tokens(model):
return ["\n".join(patches_extended)]
patches = [] patches = []
final_diff_list = [] final_diff_list = []
total_tokens = token_handler.prompt_tokens total_tokens = token_handler.prompt_tokens
@ -398,6 +335,11 @@ def get_pr_multi_diffs(git_provider: GitProvider,
patch = convert_to_hunks_with_lines_numbers(patch, file) patch = convert_to_hunks_with_lines_numbers(patch, file)
new_patch_tokens = token_handler.count_tokens(patch) new_patch_tokens = token_handler.count_tokens(patch)
if patch and (token_handler.prompt_tokens + new_patch_tokens) > get_max_tokens(model) - OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD:
get_logger().warning(f"Patch too large, skipping: {file.filename}")
continue
if patch and (total_tokens + new_patch_tokens > get_max_tokens(model) - OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD): if patch and (total_tokens + new_patch_tokens > get_max_tokens(model) - OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD):
final_diff = "\n".join(patches) final_diff = "\n".join(patches)
final_diff_list.append(final_diff) final_diff_list.append(final_diff)

23
pr_agent/algo/types.py Normal file
View File

@ -0,0 +1,23 @@
from dataclasses import dataclass
from enum import Enum
class EDIT_TYPE(Enum):
ADDED = 1
DELETED = 2
MODIFIED = 3
RENAMED = 4
UNKNOWN = 5
@dataclass
class FilePatchInfo:
base_file: str
head_file: str
patch: str
filename: str
tokens: int = -1
edit_type: EDIT_TYPE = EDIT_TYPE.UNKNOWN
old_filename: str = None
num_plus_lines: int = -1
num_minus_lines: int = -1

View File

@ -5,7 +5,8 @@ import json
import re import re
import textwrap import textwrap
from datetime import datetime from datetime import datetime
from typing import Any, List from enum import Enum
from typing import Any, List, Tuple
import yaml import yaml
from starlette_context import context from starlette_context import context
@ -13,8 +14,12 @@ from starlette_context import context
from pr_agent.algo import MAX_TOKENS from pr_agent.algo import MAX_TOKENS
from pr_agent.algo.token_handler import get_token_encoder from pr_agent.algo.token_handler import get_token_encoder
from pr_agent.config_loader import get_settings, global_settings from pr_agent.config_loader import get_settings, global_settings
from pr_agent.algo.types import FilePatchInfo
from pr_agent.log import get_logger from pr_agent.log import get_logger
class ModelType(str, Enum):
REGULAR = "regular"
TURBO = "turbo"
def get_setting(key: str) -> Any: def get_setting(key: str) -> Any:
try: try:
@ -490,3 +495,75 @@ def replace_code_tags(text):
for i in range(1, len(parts), 2): for i in range(1, len(parts), 2):
parts[i] = '<code>' + parts[i] + '</code>' parts[i] = '<code>' + parts[i] + '</code>'
return ''.join(parts) return ''.join(parts)
def find_line_number_of_relevant_line_in_file(diff_files: List[FilePatchInfo],
relevant_file: str,
relevant_line_in_file: str,
absolute_position: int = None) -> Tuple[int, int]:
position = -1
if absolute_position is None:
absolute_position = -1
re_hunk_header = re.compile(
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
for file in diff_files:
if file.filename and (file.filename.strip() == relevant_file):
patch = file.patch
patch_lines = patch.splitlines()
delta = 0
start1, size1, start2, size2 = 0, 0, 0, 0
if absolute_position != -1: # matching absolute to relative
for i, line in enumerate(patch_lines):
# new hunk
if line.startswith('@@'):
delta = 0
match = re_hunk_header.match(line)
start1, size1, start2, size2 = map(int, match.groups()[:4])
elif not line.startswith('-'):
delta += 1
#
absolute_position_curr = start2 + delta - 1
if absolute_position_curr == absolute_position:
position = i
break
else:
# try to find the line in the patch using difflib, with some margin of error
matches_difflib: list[str | Any] = difflib.get_close_matches(relevant_line_in_file,
patch_lines, n=3, cutoff=0.93)
if len(matches_difflib) == 1 and matches_difflib[0].startswith('+'):
relevant_line_in_file = matches_difflib[0]
for i, line in enumerate(patch_lines):
if line.startswith('@@'):
delta = 0
match = re_hunk_header.match(line)
start1, size1, start2, size2 = map(int, match.groups()[:4])
elif not line.startswith('-'):
delta += 1
if relevant_line_in_file in line and line[0] != '-':
position = i
absolute_position = start2 + delta - 1
break
if position == -1 and relevant_line_in_file[0] == '+':
no_plus_line = relevant_line_in_file[1:].lstrip()
for i, line in enumerate(patch_lines):
if line.startswith('@@'):
delta = 0
match = re_hunk_header.match(line)
start1, size1, start2, size2 = map(int, match.groups()[:4])
elif not line.startswith('-'):
delta += 1
if no_plus_line in line and line[0] != '-':
# The model might add a '+' to the beginning of the relevant_line_in_file even if originally
# it's a context line
position = i
absolute_position = start2 + delta - 1
break
return position, absolute_position

View File

@ -6,7 +6,8 @@ from ..log import get_logger
from ..algo.language_handler import is_valid_file from ..algo.language_handler import is_valid_file
from ..algo.utils import clip_tokens, load_large_diff from ..algo.utils import clip_tokens, load_large_diff
from ..config_loader import get_settings from ..config_loader import get_settings
from .git_provider import EDIT_TYPE, FilePatchInfo, GitProvider from .git_provider import GitProvider
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
AZURE_DEVOPS_AVAILABLE = True AZURE_DEVOPS_AVAILABLE = True

View File

@ -6,10 +6,11 @@ import requests
from atlassian.bitbucket import Cloud from atlassian.bitbucket import Cloud
from starlette_context import context from starlette_context import context
from ..algo.pr_processing import find_line_number_of_relevant_line_in_file from pr_agent.algo.types import FilePatchInfo, EDIT_TYPE
from ..algo.utils import find_line_number_of_relevant_line_in_file
from ..config_loader import get_settings from ..config_loader import get_settings
from ..log import get_logger from ..log import get_logger
from .git_provider import FilePatchInfo, GitProvider, EDIT_TYPE from .git_provider import GitProvider
class BitbucketProvider(GitProvider): class BitbucketProvider(GitProvider):

View File

@ -6,9 +6,9 @@ import requests
from atlassian.bitbucket import Bitbucket from atlassian.bitbucket import Bitbucket
from starlette_context import context from starlette_context import context
from .git_provider import FilePatchInfo, GitProvider, EDIT_TYPE from .git_provider import GitProvider
from ..algo.pr_processing import find_line_number_of_relevant_line_in_file from pr_agent.algo.types import FilePatchInfo
from ..algo.utils import load_large_diff from ..algo.utils import load_large_diff, find_line_number_of_relevant_line_in_file
from ..config_loader import get_settings from ..config_loader import get_settings
from ..log import get_logger from ..log import get_logger

View File

@ -5,9 +5,9 @@ from typing import List, Optional, Tuple
from urllib.parse import urlparse from urllib.parse import urlparse
from pr_agent.git_providers.codecommit_client import CodeCommitClient from pr_agent.git_providers.codecommit_client import CodeCommitClient
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from ..algo.utils import load_large_diff from ..algo.utils import load_large_diff
from .git_provider import EDIT_TYPE, FilePatchInfo, GitProvider from .git_provider import GitProvider
from ..config_loader import get_settings from ..config_loader import get_settings
from ..log import get_logger from ..log import get_logger

View File

@ -13,7 +13,8 @@ import urllib3.util
from git import Repo from git import Repo
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers.git_provider import EDIT_TYPE, FilePatchInfo, GitProvider from pr_agent.git_providers.git_provider import GitProvider
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.git_providers.local_git_provider import PullRequestMimic from pr_agent.git_providers.local_git_provider import PullRequestMimic
from pr_agent.log import get_logger from pr_agent.log import get_logger

View File

@ -1,35 +1,13 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from dataclasses import dataclass
# enum EDIT_TYPE (ADDED, DELETED, MODIFIED, RENAMED) # enum EDIT_TYPE (ADDED, DELETED, MODIFIED, RENAMED)
from enum import Enum
from typing import Optional from typing import Optional
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.algo.types import FilePatchInfo
from pr_agent.log import get_logger from pr_agent.log import get_logger
class EDIT_TYPE(Enum):
ADDED = 1
DELETED = 2
MODIFIED = 3
RENAMED = 4
UNKNOWN = 5
@dataclass
class FilePatchInfo:
base_file: str
head_file: str
patch: str
filename: str
tokens: int = -1
edit_type: EDIT_TYPE = EDIT_TYPE.UNKNOWN
old_filename: str = None
num_plus_lines: int = -1
num_minus_lines: int = -1
class GitProvider(ABC): class GitProvider(ABC):
@abstractmethod @abstractmethod
def is_supported(self, capability: str) -> bool: def is_supported(self, capability: str) -> bool:
@ -193,6 +171,11 @@ class GitProvider(ABC):
def get_latest_commit_url(self) -> str: def get_latest_commit_url(self) -> str:
return "" return ""
def auto_approve(self) -> bool:
return False
def get_main_pr_language(languages, files) -> str: def get_main_pr_language(languages, files) -> str:
""" """
Get the main language of the commit. Return an empty string if cannot determine. Get the main language of the commit. Return an empty string if cannot determine.
@ -261,7 +244,6 @@ def get_main_pr_language(languages, files) -> str:
return main_language_str return main_language_str
class IncrementalPR: class IncrementalPR:
def __init__(self, is_incremental: bool = False): def __init__(self, is_incremental: bool = False):
self.is_incremental = is_incremental self.is_incremental = is_incremental

View File

@ -9,12 +9,12 @@ from retry import retry
from starlette_context import context from starlette_context import context
from ..algo.language_handler import is_valid_file from ..algo.language_handler import is_valid_file
from ..algo.pr_processing import find_line_number_of_relevant_line_in_file from ..algo.utils import load_large_diff, clip_tokens, find_line_number_of_relevant_line_in_file
from ..algo.utils import load_large_diff, clip_tokens
from ..config_loader import get_settings from ..config_loader import get_settings
from ..log import get_logger from ..log import get_logger
from ..servers.utils import RateLimitExceeded from ..servers.utils import RateLimitExceeded
from .git_provider import FilePatchInfo, GitProvider, IncrementalPR, EDIT_TYPE from .git_provider import GitProvider, IncrementalPR
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
class GithubProvider(GitProvider): class GithubProvider(GitProvider):
@ -643,3 +643,13 @@ class GithubProvider(GitProvider):
return pr_id return pr_id
except: except:
return "" return ""
def auto_approve(self) -> bool:
try:
res = self.pr.create_review(event="APPROVE")
if res.state == "APPROVED":
return True
return False
except Exception as e:
get_logger().exception(f"Failed to auto-approve, error: {e}")
return False

View File

@ -7,10 +7,10 @@ import gitlab
from gitlab import GitlabGetError from gitlab import GitlabGetError
from ..algo.language_handler import is_valid_file from ..algo.language_handler import is_valid_file
from ..algo.pr_processing import find_line_number_of_relevant_line_in_file from ..algo.utils import load_large_diff, clip_tokens, find_line_number_of_relevant_line_in_file
from ..algo.utils import load_large_diff, clip_tokens
from ..config_loader import get_settings from ..config_loader import get_settings
from .git_provider import EDIT_TYPE, FilePatchInfo, GitProvider from .git_provider import GitProvider
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from ..log import get_logger from ..log import get_logger

View File

@ -5,7 +5,8 @@ from typing import List
from git import Repo from git import Repo
from pr_agent.config_loader import _find_repository_root, get_settings from pr_agent.config_loader import _find_repository_root, get_settings
from pr_agent.git_providers.git_provider import EDIT_TYPE, FilePatchInfo, GitProvider from pr_agent.git_providers.git_provider import GitProvider
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
from pr_agent.log import get_logger from pr_agent.log import get_logger

View File

@ -2,15 +2,18 @@ from pr_agent.config_loader import get_settings
def get_secret_provider(): def get_secret_provider():
try: if not get_settings().get("CONFIG.SECRET_PROVIDER"):
provider_id = get_settings().config.secret_provider return None
except AttributeError as e:
raise ValueError("secret_provider is a required attribute in the configuration file") from e provider_id = get_settings().config.secret_provider
try: if provider_id == 'google_cloud_storage':
if provider_id == 'google_cloud_storage': try:
from pr_agent.secret_providers.google_cloud_storage_secret_provider import GoogleCloudStorageSecretProvider from pr_agent.secret_providers.google_cloud_storage_secret_provider import GoogleCloudStorageSecretProvider
return GoogleCloudStorageSecretProvider() return GoogleCloudStorageSecretProvider()
else: except Exception as e:
raise ValueError(f"Unknown secret provider: {provider_id}") raise ValueError(f"Failed to initialize google_cloud_storage secret provider {provider_id}") from e
except Exception as e: else:
raise ValueError(f"Failed to initialize secret provider {provider_id}") from e raise ValueError("Unknown SECRET_PROVIDER")

View File

@ -26,7 +26,8 @@ from pr_agent.tools.pr_reviewer import PRReviewer
setup_logger(fmt=LoggingFormat.JSON) setup_logger(fmt=LoggingFormat.JSON)
router = APIRouter() router = APIRouter()
secret_provider = get_secret_provider() secret_provider = get_secret_provider() if get_settings().get("CONFIG.SECRET_PROVIDER") else None
async def get_bearer_token(shared_secret: str, client_key: str): async def get_bearer_token(shared_secret: str, client_key: str):
try: try:

View File

@ -82,14 +82,23 @@ async def run_action():
if action in ["opened", "reopened"]: if action in ["opened", "reopened"]:
pr_url = event_payload.get("pull_request", {}).get("url") pr_url = event_payload.get("pull_request", {}).get("url")
if pr_url: if pr_url:
# legacy - supporting both GITHUB_ACTION and GITHUB_ACTION_CONFIG
auto_review = get_setting_or_env("GITHUB_ACTION.AUTO_REVIEW", None) auto_review = get_setting_or_env("GITHUB_ACTION.AUTO_REVIEW", None)
if auto_review is None:
auto_review = get_setting_or_env("GITHUB_ACTION_CONFIG.AUTO_REVIEW", None)
auto_describe = get_setting_or_env("GITHUB_ACTION.AUTO_DESCRIBE", None)
if auto_describe is None:
auto_describe = get_setting_or_env("GITHUB_ACTION_CONFIG.AUTO_DESCRIBE", None)
auto_improve = get_setting_or_env("GITHUB_ACTION.AUTO_IMPROVE", None)
if auto_improve is None:
auto_improve = get_setting_or_env("GITHUB_ACTION_CONFIG.AUTO_IMPROVE", None)
# invoke by default all three tools
if auto_describe is None or is_true(auto_describe):
await PRDescription(pr_url).run()
if auto_review is None or is_true(auto_review): if auto_review is None or is_true(auto_review):
await PRReviewer(pr_url).run() await PRReviewer(pr_url).run()
auto_describe = get_setting_or_env("GITHUB_ACTION.AUTO_DESCRIBE", None) if auto_improve is None or is_true(auto_improve):
if is_true(auto_describe):
await PRDescription(pr_url).run()
auto_improve = get_setting_or_env("GITHUB_ACTION.AUTO_IMPROVE", None)
if is_true(auto_improve):
await PRCodeSuggestions(pr_url).run() await PRCodeSuggestions(pr_url).run()
# Handle issue comment event # Handle issue comment event

View File

@ -48,7 +48,7 @@ Examples for extra instructions:
``` ```
[pr_reviewer] # /review # [pr_reviewer] # /review #
extra_instructions=""" extra_instructions="""
In the code feedback section, emphasize the following: In the 'general suggestions' section, emphasize the following:
- Does the code logic cover relevant edge cases? - Does the code logic cover relevant edge cases?
- Is the code logic clear and easy to understand? - Is the code logic clear and easy to understand?
- Is the code logic efficient? - Is the code logic efficient?
@ -71,14 +71,14 @@ Edit this field to enable/disable the tool, or to change the used configurations
""" """
output += "\n\n</details></td></tr>\n\n" output += "\n\n</details></td></tr>\n\n"
# code feedback # # code feedback
output += "<tr><td><details> <summary><strong> About the 'Code feedback' section</strong></summary><hr>\n\n" # output += "<tr><td><details> <summary><strong> About the 'Code feedback' section</strong></summary><hr>\n\n"
output+="""\ # output+="""\
The `review` tool provides several type of feedbacks, one of them is code suggestions. # The `review` tool provides several type of feedbacks, one of them is code suggestions.
If you are interested **only** in the code suggestions, it is recommended to use the [`improve`](https://github.com/Codium-ai/pr-agent/blob/main/docs/IMPROVE.md) feature instead, since it dedicated only to code suggestions, and usually gives better results. # If you are interested **only** in the code suggestions, it is recommended to use the [`improve`](https://github.com/Codium-ai/pr-agent/blob/main/docs/IMPROVE.md) feature instead, since it dedicated only to code suggestions, and usually gives better results.
Use the `review` tool if you want to get a more comprehensive feedback, which includes code suggestions as well. # Use the `review` tool if you want to get a more comprehensive feedback, which includes code suggestions as well.
""" # """
output += "\n\n</details></td></tr>\n\n" # output += "\n\n</details></td></tr>\n\n"
# auto-labels # auto-labels
output += "<tr><td><details> <summary><strong> Auto-labels</strong></summary><hr>\n\n" output += "<tr><td><details> <summary><strong> Auto-labels</strong></summary><hr>\n\n"
@ -99,6 +99,31 @@ Some of the feature that are disabled by default are quite useful, and should be
""" """
output += "\n\n</details></td></tr>\n\n" output += "\n\n</details></td></tr>\n\n"
output += "<tr><td><details> <summary><strong> Auto-approve PRs</strong></summary><hr>\n\n"
output += '''\
By invoking:
```
/review auto_approve
```
The tool will automatically approve the PR, and add a comment with the approval.
To ensure safety, the auto-approval feature is disabled by default. To enable auto-approval, you need to actively set in a pre-defined configuration file the following:
```
[pr_reviewer]
enable_auto_approval = true
```
(this specific flag cannot be set with a command line argument, only in the configuration file, committed to the repository)
You can also enable auto-approval only if the PR meets certain requirements, such as that the `estimated_review_effort` is equal or below a certain threshold, by adjusting the flag:
```
[pr_reviewer]
maximal_review_effort = 5
```
'''
output += "\n\n</details></td></tr>\n\n"
# general # general
output += "\n\n<tr><td><details> <summary><strong> More PR-Agent commands</strong></summary><hr> \n\n" output += "\n\n<tr><td><details> <summary><strong> More PR-Agent commands</strong></summary><hr> \n\n"
output += HelpMessage.get_general_bot_help_text() output += HelpMessage.get_general_bot_help_text()
@ -186,6 +211,7 @@ To enable inline file summary, set `pr_description.inline_file_summary` in the c
- `true`: A collapsable file comment with changes title and a changes summary for each file in the PR. - `true`: A collapsable file comment with changes title and a changes summary for each file in the PR.
- `false` (default): File changes walkthrough will be added only to the "Conversation" tab. - `false` (default): File changes walkthrough will be added only to the "Conversation" tab.
""" """
# extra instructions # extra instructions
output += "<tr><td><details> <summary><strong> Utilizing extra instructions</strong></summary><hr>\n\n" output += "<tr><td><details> <summary><strong> Utilizing extra instructions</strong></summary><hr>\n\n"
output += '''\ output += '''\
@ -309,8 +335,9 @@ Use triple quotes to write multi-line instructions. Use bullet points to make th
output += """\ 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. - 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. - 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. - 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
- Best quality will be obtained by using 'improve --extended' mode. - With large PRs, best quality will be obtained by using 'improve --extended' mode.
""" """
output += "\n\n</details></td></tr>\n\n"\ output += "\n\n</details></td></tr>\n\n"\

View File

@ -1,5 +1,6 @@
[config] [config]
model="gpt-4" # "gpt-4-1106-preview" model="gpt-4" # "gpt-4-0125-preview"
model_turbo="gpt-4-0125-preview"
fallback_models=["gpt-3.5-turbo-16k"] fallback_models=["gpt-3.5-turbo-16k"]
git_provider="github" git_provider="github"
publish_output=true publish_output=true
@ -8,11 +9,11 @@ verbosity_level=0 # 0,1,2
use_extra_bad_extensions=false use_extra_bad_extensions=false
use_repo_settings_file=true use_repo_settings_file=true
use_global_settings_file=true use_global_settings_file=true
ai_timeout=180 ai_timeout=90
max_description_tokens = 500 max_description_tokens = 500
max_commits_tokens = 500 max_commits_tokens = 500
max_model_tokens = 32000 # Limits the maximum number of tokens that can be used by any model, regardless of the model's default capabilities. max_model_tokens = 32000 # Limits the maximum number of tokens that can be used by any model, regardless of the model's default capabilities.
patch_extra_lines = 3 patch_extra_lines = 1
secret_provider="google_cloud_storage" secret_provider="google_cloud_storage"
cli_mode=false cli_mode=false
@ -42,12 +43,16 @@ require_all_thresholds_for_incremental_review=false
minimal_commits_for_incremental_review=0 minimal_commits_for_incremental_review=0
minimal_minutes_for_incremental_review=0 minimal_minutes_for_incremental_review=0
enable_help_text=true # Determines whether to include help text in the PR review. Enabled by default. enable_help_text=true # Determines whether to include help text in the PR review. Enabled by default.
# auto approval
enable_auto_approval=false
maximal_review_effort=5
[pr_description] # /describe # [pr_description] # /describe #
publish_labels=true publish_labels=true
publish_description_as_comment=false publish_description_as_comment=false
add_original_user_description=true add_original_user_description=true
keep_original_user_title=false keep_original_user_title=true
use_bullet_points=true use_bullet_points=true
extra_instructions = "" extra_instructions = ""
enable_pr_type=true enable_pr_type=true
@ -68,17 +73,19 @@ enable_help_text=true
[pr_code_suggestions] # /improve # [pr_code_suggestions] # /improve #
max_context_tokens=8000
num_code_suggestions=4 num_code_suggestions=4
summarize = true summarize = true
extra_instructions = "" extra_instructions = ""
rank_suggestions = false rank_suggestions = false
enable_help_text=true enable_help_text=true
# params for '/improve --extended' mode # params for '/improve --extended' mode
auto_extended_mode=false auto_extended_mode=true
num_code_suggestions_per_chunk=8 num_code_suggestions_per_chunk=5
rank_extended_suggestions = true max_number_of_calls = 3
max_number_of_calls = 5 parallel_calls = true
final_clip_factor = 0.9 rank_extended_suggestions = false
final_clip_factor = 0.8
[pr_add_docs] # /add_docs # [pr_add_docs] # /add_docs #
extra_instructions = "" extra_instructions = ""
@ -90,6 +97,15 @@ extra_instructions = ""
[pr_analyze] # /analyze # [pr_analyze] # /analyze #
[pr_test] # /test #
extra_instructions = ""
testing_framework = "" # specify the testing framework you want to use
num_tests=3 # number of tests to generate. max 5.
avoid_mocks=true # if true, the generated tests will prefer to use real objects instead of mocks
file = "" # in case there are several components with the same name, you can specify the relevant file
class_name = "" # in case there are several methods with the same name in the same file, you can specify the relevant class name
enable_help_text=true
[pr_config] # /config # [pr_config] # /config #
[github] [github]
@ -100,7 +116,7 @@ base_url = "https://api.github.com"
publish_inline_comments_fallback_with_verification = true publish_inline_comments_fallback_with_verification = true
try_fix_invalid_inline_comments = true try_fix_invalid_inline_comments = true
[github_action] [github_action_config]
# auto_review = true # set as env var in .github/workflows/pr-agent.yaml # auto_review = true # set as env var in .github/workflows/pr-agent.yaml
# auto_describe = true # set as env var in .github/workflows/pr-agent.yaml # auto_describe = true # set as env var in .github/workflows/pr-agent.yaml
# auto_improve = true # set as env var in .github/workflows/pr-agent.yaml # auto_improve = true # set as env var in .github/workflows/pr-agent.yaml

View File

@ -5,7 +5,7 @@ Your task is to generate {{ docs_for_language }} for code components in the PR D
Example for the PR Diff format: Example for the PR Diff format:
====== ======
## src/file1.py ## file: 'src/file1.py'
@@ -12,3 +12,4 @@ def func1(): @@ -12,3 +12,4 @@ def func1():
__new hunk__ __new hunk__
@ -18,7 +18,6 @@ __old hunk__
-code line that was removed in the PR -code line that was removed in the PR
code line2 that remained unchanged in the PR code line2 that remained unchanged in the PR
@@ ... @@ def func2(): @@ ... @@ def func2():
__new hunk__ __new hunk__
... ...
@ -26,7 +25,7 @@ __old hunk__
... ...
## src/file2.py ## file: 'src/file2.py'
... ...
====== ======

View File

@ -4,19 +4,18 @@ Your task is to provide meaningful and actionable code suggestions, to improve t
Example for the PR Diff format: Example for the PR Diff format:
====== ======
## src/file1.py ## file: 'src/file1.py'
@@ ... @@ def func1(): @@ ... @@ def func1():
__new hunk__ __new hunk__
12 code line1 that remained unchanged in the PR 12 code line1 that remained unchanged in the PR
13 +new code line2 added in the PR 13 +new hunk code line2 added in the PR
14 code line3 that remained unchanged in the PR 14 code line3 that remained unchanged in the PR
__old hunk__ __old hunk__
code line1 that remained unchanged in the PR code line1 that remained unchanged in the PR
-old code line2 that was removed in the PR -old hunk code line2 that was removed in the PR
code line3 that remained unchanged in the PR code line3 that remained unchanged in the PR
@@ ... @@ def func2(): @@ ... @@ def func2():
__new hunk__ __new hunk__
... ...
@ -24,7 +23,7 @@ __old hunk__
... ...
## src/file2.py ## file: 'src/file2.py'
... ...
====== ======
@ -34,7 +33,7 @@ Specific instructions:
- The suggestions should refer only to code from the '__new hunk__' sections, and focus on new lines of code (lines starting with '+'). - The suggestions should refer only to code from the '__new hunk__' sections, and focus on new lines of code (lines starting with '+').
- Prioritize suggestions that address major problems, issues and bugs in the PR code. As a second priority, suggestions should focus on enhancement, best practice, performance, maintainability, and other aspects. - Prioritize suggestions that address major problems, issues and bugs in the PR code. As a second priority, suggestions should focus on enhancement, best practice, performance, maintainability, and other aspects.
- Don't suggest to add docstring, type hints, or comments, or to remove unused imports. - Don't suggest to add docstring, type hints, or comments, or to remove unused imports.
- 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. - Suggestions should not repeat code already present in the '__new hunk__' sections.
- Provide the exact line numbers range (inclusive) for each suggestion. - Provide the exact line numbers range (inclusive) for each suggestion.
- When quoting variables or names from the code, use backticks (`) instead of single quote ('). - When quoting variables or names from the code, use backticks (`) instead of single quote (').
@ -51,6 +50,7 @@ The output must be a YAML object equivalent to type $PRCodeSuggestions, accordin
===== =====
class CodeSuggestion(BaseModel): class CodeSuggestion(BaseModel):
relevant_file: str = Field(description="the relevant file full path") relevant_file: str = Field(description="the relevant file full path")
language: str = Field(description="the code language of the relevant file")
suggestion_content: str = Field(description="an actionable suggestion for meaningfully improving the new code introduced in the PR") suggestion_content: str = Field(description="an actionable suggestion for meaningfully improving the new code introduced in the PR")
{%- if summarize_mode %} {%- if summarize_mode %}
existing_code: str = Field(description="a short code snippet from a '__new hunk__' section to illustrate the relevant existing code. Don't show the line numbers.") existing_code: str = Field(description="a short code snippet from a '__new hunk__' section to illustrate the relevant existing code. Don't show the line numbers.")
@ -72,44 +72,41 @@ class PRCodeSuggestions(BaseModel):
Example output: Example output:
```yaml ```yaml
code_suggestions: code_suggestions:
- relevant_file: |- - relevant_file: |
src/file1.py src/file1.py
suggestion_content: |- language: |
python
suggestion_content: |
Add a docstring to func1() Add a docstring to func1()
{%- if summarize_mode %} {%- if summarize_mode %}
existing_code: |- existing_code: |
def func1(): def func1():
improved_code: |- improved_code: |
... ...
one_sentence_summary: |- one_sentence_summary: |
... ...
relevant_lines_start: 12 relevant_lines_start: 12
relevant_lines_end: 12 relevant_lines_end: 12
{%- else %} {%- else %}
existing_code: |- existing_code: |
def func1(): def func1():
relevant_lines_start: 12 relevant_lines_start: 12
relevant_lines_end: 12 relevant_lines_end: 12
improved_code: |- improved_code: |
... ...
{%- endif %} {%- endif %}
label: |- label: |
... ...
``` ```
Each YAML output MUST be after a newline, indented, with block scalar indicator ('|-'). Each YAML output MUST be after a newline, indented, with block scalar indicator ('|').
""" """
user="""PR Info: user="""PR Info:
Title: '{{title}}' Title: '{{title}}'
{%- if language %}
Main PR language: '{{ language }}'
{%- endif %}
The PR Diff: The PR Diff:
====== ======

View File

@ -39,6 +39,7 @@ class PRType(str, Enum):
Class FileDescription(BaseModel): Class FileDescription(BaseModel):
filename: str = Field(description="the relevant file full path") filename: str = Field(description="the relevant file full path")
language: str = Field(description="the relevant file language")
changes_summary: str = Field(description="concise summary of the changes in the relevant file, in bullet points (1-4 bullet points).") changes_summary: str = Field(description="concise summary of the changes in the relevant file, in bullet points (1-4 bullet points).")
changes_title: str = Field(description="an informative title for the changes in the files, describing its main theme (5-10 words).") changes_title: str = Field(description="an informative title for the changes in the files, describing its main theme (5-10 words).")
label: str = Field(description="a single semantic label that represents a type of code changes that occurred in the File. Possible values (partial list): 'bug fix', 'tests', 'enhancement', 'documentation', 'error handling', 'configuration changes', 'dependencies', 'formatting', 'miscellaneous', ...") label: str = Field(description="a single semantic label that represents a type of code changes that occurred in the File. Possible values (partial list): 'bug fix', 'tests', 'enhancement', 'documentation', 'error handling', 'configuration changes', 'dependencies', 'formatting', 'miscellaneous', ...")
@ -67,6 +68,8 @@ type:
pr_files: pr_files:
- filename: | - filename: |
... ...
language: |
...
changes_summary: | changes_summary: |
... ...
changes_title: | changes_title: |
@ -104,10 +107,7 @@ Previous description:
{%- endif %} {%- endif %}
Branch: '{{branch}}' Branch: '{{branch}}'
{%- if language %}
Main PR language: '{{ language }}'
{%- endif %}
{%- if commit_messages_str %} {%- if commit_messages_str %}
Commit messages: Commit messages:

View File

@ -5,7 +5,7 @@ The review should focus on new code added in the PR diff (lines starting with '+
Example PR Diff: Example PR Diff:
====== ======
## src/file1.py ## file: 'src/file1.py'
@@ -12,5 +12,5 @@ def func1(): @@ -12,5 +12,5 @@ def func1():
code line 1 that remained unchanged in the PR code line 1 that remained unchanged in the PR
@ -14,12 +14,11 @@ code line 2 that remained unchanged in the PR
+code line added in the PR +code line added in the PR
code line 3 that remained unchanged in the PR code line 3 that remained unchanged in the PR
@@ ... @@ def func2(): @@ ... @@ def func2():
... ...
## src/file2.py ## file: 'src/file2.py'
... ...
====== ======
@ -96,6 +95,9 @@ PR Feedback:
relevant file: relevant file:
type: string type: string
description: the relevant file full path description: the relevant file full path
language:
type: string
description: the language of the relevant file
suggestion: suggestion:
type: string type: string
description: |- description: |-
@ -141,6 +143,8 @@ PR Feedback:
Code feedback: Code feedback:
- relevant file: |- - relevant file: |-
directory/xxx.py directory/xxx.py
language: |-
python
suggestion: |- suggestion: |-
xxx [important] xxx [important]
relevant line: |- relevant line: |-
@ -170,10 +174,6 @@ Description:
====== ======
{%- endif %} {%- endif %}
{%- if language %}
Main PR language: '{{ language }}'
{%- endif %}
{%- if commit_messages_str %} {%- if commit_messages_str %}
Commit messages: Commit messages:

View File

@ -1,3 +1,4 @@
import asyncio
import copy import copy
import textwrap import textwrap
from functools import partial from functools import partial
@ -8,7 +9,7 @@ from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.pr_processing import get_pr_diff, get_pr_multi_diffs, retry_with_fallback_models from pr_agent.algo.pr_processing import get_pr_diff, get_pr_multi_diffs, retry_with_fallback_models
from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import load_yaml, replace_code_tags from pr_agent.algo.utils import load_yaml, replace_code_tags, ModelType
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers import get_git_provider from pr_agent.git_providers import get_git_provider
from pr_agent.git_providers.git_provider import get_main_pr_language from pr_agent.git_providers.git_provider import get_main_pr_language
@ -26,6 +27,14 @@ class PRCodeSuggestions:
self.git_provider.get_languages(), self.git_provider.get_files() self.git_provider.get_languages(), self.git_provider.get_files()
) )
# limit context specifically for the improve command, which has hard input to parse:
if get_settings().pr_code_suggestions.max_context_tokens:
MAX_CONTEXT_TOKENS_IMPROVE = get_settings().pr_code_suggestions.max_context_tokens
if get_settings().config.max_model_tokens > MAX_CONTEXT_TOKENS_IMPROVE:
get_logger().info(f"Setting max_model_tokens to {MAX_CONTEXT_TOKENS_IMPROVE} for PR improve")
get_settings().config.max_model_tokens = MAX_CONTEXT_TOKENS_IMPROVE
# extended mode # extended mode
try: try:
self.is_extended = self._get_is_extended(args or []) self.is_extended = self._get_is_extended(args or [])
@ -64,10 +73,10 @@ class PRCodeSuggestions:
get_logger().info('Preparing PR code suggestions...') get_logger().info('Preparing PR code suggestions...')
if not self.is_extended: if not self.is_extended:
await retry_with_fallback_models(self._prepare_prediction) await retry_with_fallback_models(self._prepare_prediction, ModelType.TURBO)
data = self._prepare_pr_code_suggestions() data = self._prepare_pr_code_suggestions()
else: else:
data = await retry_with_fallback_models(self._prepare_prediction_extended) data = await retry_with_fallback_models(self._prepare_prediction_extended, ModelType.TURBO)
if (not data) or (not 'code_suggestions' in data): if (not data) or (not 'code_suggestions' in data):
@ -103,18 +112,18 @@ class PRCodeSuggestions:
async def _prepare_prediction(self, model: str): async def _prepare_prediction(self, model: str):
get_logger().info('Getting PR diff...') get_logger().info('Getting PR diff...')
self.patches_diff = get_pr_diff(self.git_provider, patches_diff = get_pr_diff(self.git_provider,
self.token_handler, self.token_handler,
model, model,
add_line_numbers_to_hunks=True, add_line_numbers_to_hunks=True,
disable_extra_lines=True) disable_extra_lines=True)
get_logger().info('Getting AI prediction...') get_logger().info('Getting AI prediction...')
self.prediction = await self._get_prediction(model) self.prediction = await self._get_prediction(model, patches_diff)
async def _get_prediction(self, model: str): async def _get_prediction(self, model: str, patches_diff: str):
variables = copy.deepcopy(self.vars) variables = copy.deepcopy(self.vars)
variables["diff"] = self.patches_diff # update diff variables["diff"] = patches_diff # update diff
environment = Environment(undefined=StrictUndefined) environment = Environment(undefined=StrictUndefined)
system_prompt = environment.from_string(get_settings().pr_code_suggestions_prompt.system).render(variables) 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) user_prompt = environment.from_string(get_settings().pr_code_suggestions_prompt.user).render(variables)
@ -190,7 +199,8 @@ class PRCodeSuggestions:
original_initial_line = None original_initial_line = None
for file in self.diff_files: for file in self.diff_files:
if file.filename.strip() == relevant_file: if file.filename.strip() == relevant_file:
original_initial_line = file.head_file.splitlines()[relevant_lines_start - 1] if file.head_file: # in bitbucket, head_file is empty. toDo: fix this
original_initial_line = file.head_file.splitlines()[relevant_lines_start - 1]
break break
if original_initial_line: if original_initial_line:
suggested_initial_line = new_code_snippet.splitlines()[0] suggested_initial_line = new_code_snippet.splitlines()[0]
@ -220,14 +230,18 @@ class PRCodeSuggestions:
patches_diff_list = get_pr_multi_diffs(self.git_provider, self.token_handler, model, 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) max_calls=get_settings().pr_code_suggestions.max_number_of_calls)
get_logger().info('Getting multi AI predictions...') # parallelize calls to AI:
prediction_list = [] if get_settings().pr_code_suggestions.parallel_calls:
for i, patches_diff in enumerate(patches_diff_list): get_logger().info('Getting multi AI predictions in parallel...')
get_logger().info(f"Processing chunk {i + 1} of {len(patches_diff_list)}") prediction_list = await asyncio.gather(*[self._get_prediction(model, patches_diff) for patches_diff in patches_diff_list])
self.patches_diff = patches_diff self.prediction_list = prediction_list
prediction = await self._get_prediction(model) else:
prediction_list.append(prediction) get_logger().info('Getting multi AI predictions...')
self.prediction_list = prediction_list prediction_list = []
for i, patches_diff in enumerate(patches_diff_list):
get_logger().info(f"Processing chunk {i + 1} of {len(patches_diff_list)}")
prediction = await self._get_prediction(model, patches_diff)
prediction_list.append(prediction)
data = {} data = {}
for prediction in prediction_list: for prediction in prediction_list:
@ -252,10 +266,15 @@ class PRCodeSuggestions:
""" """
suggestion_list = [] suggestion_list = []
if not data:
return suggestion_list
for suggestion in data: for suggestion in data:
suggestion_list.append(suggestion) suggestion_list.append(suggestion)
data_sorted = [[]] * len(suggestion_list) data_sorted = [[]] * len(suggestion_list)
if len(suggestion_list ) == 1:
return suggestion_list
try: try:
suggestion_str = "" suggestion_str = ""
for i, suggestion in enumerate(suggestion_list): for i, suggestion in enumerate(suggestion_list):
@ -311,7 +330,7 @@ class PRCodeSuggestions:
pr_body += "<table>" pr_body += "<table>"
header = f"Suggestions" header = f"Suggestions"
delta = 77 delta = 75
header += "&nbsp; " * delta header += "&nbsp; " * delta
pr_body += f"""<thead><tr><th></th><th>{header}</th></tr></thead>""" pr_body += f"""<thead><tr><th></th><th>{header}</th></tr></thead>"""
pr_body += """<tbody>""" pr_body += """<tbody>"""

View File

@ -9,7 +9,7 @@ from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
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
from pr_agent.algo.token_handler import TokenHandler from pr_agent.algo.token_handler import TokenHandler
from pr_agent.algo.utils import load_yaml, set_custom_labels, get_user_labels from pr_agent.algo.utils import load_yaml, set_custom_labels, get_user_labels, ModelType
from pr_agent.config_loader import get_settings from pr_agent.config_loader import get_settings
from pr_agent.git_providers import get_git_provider from pr_agent.git_providers import get_git_provider
from pr_agent.git_providers.git_provider import get_main_pr_language from pr_agent.git_providers.git_provider import get_main_pr_language
@ -80,7 +80,7 @@ class PRDescription:
if get_settings().config.publish_output: if get_settings().config.publish_output:
self.git_provider.publish_comment("Preparing PR description...", is_temporary=True) self.git_provider.publish_comment("Preparing PR description...", is_temporary=True)
await retry_with_fallback_models(self._prepare_prediction) await retry_with_fallback_models(self._prepare_prediction, ModelType.TURBO) # turbo model because larger context
get_logger().info(f"Preparing answer {self.pr_id}") get_logger().info(f"Preparing answer {self.pr_id}")
if self.prediction: if self.prediction:
@ -113,22 +113,27 @@ class PRDescription:
if get_settings().config.publish_output: if get_settings().config.publish_output:
get_logger().info(f"Pushing answer {self.pr_id}") get_logger().info(f"Pushing answer {self.pr_id}")
# publish labels
if get_settings().pr_description.publish_labels and self.git_provider.is_supported("get_labels"):
current_labels = self.git_provider.get_pr_labels()
user_labels = get_user_labels(current_labels)
self.git_provider.publish_labels(pr_labels + user_labels)
# publish description
if get_settings().pr_description.publish_description_as_comment: if get_settings().pr_description.publish_description_as_comment:
get_logger().info(f"Publishing answer as comment") get_logger().info(f"Publishing answer as comment")
self.git_provider.publish_comment(full_markdown_description) self.git_provider.publish_comment(full_markdown_description)
else: else:
self.git_provider.publish_description(pr_title, pr_body) self.git_provider.publish_description(pr_title, pr_body)
if get_settings().pr_description.publish_labels and self.git_provider.is_supported("get_labels"):
current_labels = self.git_provider.get_pr_labels()
user_labels = get_user_labels(current_labels)
self.git_provider.publish_labels(pr_labels + user_labels)
# publish final update message
if (get_settings().pr_description.final_update_message and if (get_settings().pr_description.final_update_message and
hasattr(self.git_provider, 'pr_url') and self.git_provider.pr_url): hasattr(self.git_provider, 'pr_url') and self.git_provider.pr_url):
latest_commit_url = self.git_provider.get_latest_commit_url() latest_commit_url = self.git_provider.get_latest_commit_url()
if latest_commit_url: if latest_commit_url:
self.git_provider.publish_comment( self.git_provider.publish_comment(
f"**[PR Description]({self.git_provider.pr_url})** updated to latest commit ({latest_commit_url})") f"**[PR Description]({self.git_provider.get_pr_url()})** updated to latest commit ({latest_commit_url})")
self.git_provider.remove_initial_comment() self.git_provider.remove_initial_comment()
except Exception as e: except Exception as e:
get_logger().error(f"Error generating PR description {self.pr_id}: {e}") get_logger().error(f"Error generating PR description {self.pr_id}: {e}")
@ -358,7 +363,7 @@ class PRDescription:
try: try:
pr_body += "<table>" pr_body += "<table>"
header = f"Relevant files" header = f"Relevant files"
delta = 77 delta = 75
# header += "&nbsp; " * delta # header += "&nbsp; " * delta
pr_body += f"""<thead><tr><th></th><th align="left">{header}</th></tr></thead>""" pr_body += f"""<thead><tr><th></th><th align="left">{header}</th></tr></thead>"""
pr_body += """<tbody>""" pr_body += """<tbody>"""
@ -374,8 +379,7 @@ class PRDescription:
for filename, file_changes_title, file_change_description in list_tuples: for filename, file_changes_title, file_change_description in list_tuples:
filename = filename.replace("'", "`") filename = filename.replace("'", "`")
filename_publish = filename.split("/")[-1] filename_publish = filename.split("/")[-1]
file_changes_title_br = insert_br_after_x_chars(file_changes_title, x=(delta - 5), file_changes_title_br = insert_br_after_x_chars(file_changes_title, x=(delta - 5))
new_line_char="\n\n")
file_changes_title_extended = file_changes_title_br.strip() + "</code>" file_changes_title_extended = file_changes_title_br.strip() + "</code>"
if len(file_changes_title_extended) < (delta - 5): if len(file_changes_title_extended) < (delta - 5):
file_changes_title_extended += "&nbsp; " * ((delta - 5) - len(file_changes_title_extended)) file_changes_title_extended += "&nbsp; " * ((delta - 5) - len(file_changes_title_extended))
@ -407,7 +411,11 @@ class PRDescription:
{filename} {filename}
{file_change_description_br} {file_change_description_br}
</details>
</details>
</td> </td>
<td><a href="{link}">{diff_plus_minus}</a>{delta_nbsp}</td> <td><a href="{link}">{diff_plus_minus}</a>{delta_nbsp}</td>
</tr> </tr>
@ -423,48 +431,74 @@ class PRDescription:
pass pass
return pr_body return pr_body
def insert_br_after_x_chars(text, x=70, new_line_char="<br> "): def insert_br_after_x_chars(text, x=70):
""" """
Insert <br> into a string after a word that increases its length above x characters. Insert <br> into a string after a word that increases its length above x characters.
Use proper HTML tags for code and new lines.
""" """
if len(text) < x: if len(text) < x:
return text return text
lines = text.splitlines() # replace odd instances of ` with <code> and even instances of ` with </code>
text = replace_code_tags(text)
# convert list items to <li>
if text.startswith("- "):
text = "<li>" + text[2:]
text = text.replace("\n- ", '<br><li> ').replace("\n - ", '<br><li> ')
# convert new lines to <br>
text = text.replace("\n", '<br>')
# split text into lines
lines = text.split('<br>')
words = [] words = []
for i,line in enumerate(lines): for i, line in enumerate(lines):
words += line.split(' ') words += line.split(' ')
if i<len(lines)-1: if i < len(lines) - 1:
words[-1] += "\n" words[-1] += "<br>"
def count_chars_without_html(string):
if '<' not in string:
return len(string)
no_html_string = re.sub('<[^>]+>', '', string)
return len(no_html_string)
# words = text.split(' ') new_text = []
new_text = ""
current_length = 0
is_inside_code = False is_inside_code = False
current_length = 0
for word in words: for word in words:
# Check if adding this word exceeds x characters is_saved_word = False
if current_length + len(word) > x: if word == "<code>" or word == "</code>" or word == "<li>" or word == "<br>":
if not is_inside_code: is_saved_word = True
new_text += f"{new_line_char} " # Insert line break
current_length = 0 # Reset counter len_word = count_chars_without_html(word)
if not is_saved_word and (current_length + len_word > x):
if is_inside_code:
new_text.append("</code><br><code>")
else: else:
new_text += f"`{new_line_char} `" new_text.append("<br>")
# check if inside <code> tag current_length = 0 # Reset counter
if word.startswith("`") and not is_inside_code and not word.endswith("`"): new_text.append(word + " ")
is_inside_code = True
if word.endswith("`"):
is_inside_code = False
# Add the word to the new text if not is_saved_word:
if word.endswith("\n"): current_length += len_word + 1 # Add 1 for the space
new_text += word
else:
new_text += word + " "
current_length += len(word) + 1 # Add 1 for the space
if word == "<li>" or word == "<br>":
if word.endswith("\n"):
current_length = 0 current_length = 0
return new_text.strip() # Remove trailing space
if "<code>" in word:
is_inside_code = True
if "</code>" in word:
is_inside_code = False
return ''.join(new_text).strip()
def replace_code_tags(text):
"""
Replace odd instances of ` with <code> and even instances of ` with </code>
"""
parts = text.split('`')
for i in range(1, len(parts), 2):
parts[i] = '<code>' + parts[i] + '</code>'
return ''.join(parts)

View File

@ -36,6 +36,7 @@ class PRReviewer:
ai_handler (BaseAiHandler): The AI handler to be used for the review. Defaults to None. ai_handler (BaseAiHandler): The AI handler to be used for the review. Defaults to None.
args (list, optional): List of arguments passed to the PRReviewer class. Defaults to None. args (list, optional): List of arguments passed to the PRReviewer class. Defaults to None.
""" """
self.args = args
self.parse_args(args) # -i command self.parse_args(args) # -i command
self.git_provider = get_git_provider()(pr_url, incremental=self.incremental) self.git_provider = get_git_provider()(pr_url, incremental=self.incremental)
@ -102,6 +103,11 @@ class PRReviewer:
if self.incremental.is_incremental and not self._can_run_incremental_review(): if self.incremental.is_incremental and not self._can_run_incremental_review():
return None return None
if isinstance(self.args, list) and self.args and self.args[0] == 'auto_approve':
get_logger().info(f'Auto approve flow PR: {self.pr_url} ...')
self.auto_approve_logic()
return None
get_logger().info(f'Reviewing PR: {self.pr_url} ...') get_logger().info(f'Reviewing PR: {self.pr_url} ...')
if get_settings().config.publish_output: if get_settings().config.publish_output:
@ -392,3 +398,30 @@ class PRReviewer:
self.git_provider.publish_labels(review_labels + current_labels_filtered) self.git_provider.publish_labels(review_labels + current_labels_filtered)
except Exception as e: except Exception as e:
get_logger().error(f"Failed to set review labels, error: {e}") get_logger().error(f"Failed to set review labels, error: {e}")
def auto_approve_logic(self):
"""
Auto-approve a pull request if it meets the conditions for auto-approval.
"""
if get_settings().pr_reviewer.enable_auto_approval:
maximal_review_effort = get_settings().pr_reviewer.maximal_review_effort
if maximal_review_effort < 5:
current_labels = self.git_provider.get_pr_labels()
for label in current_labels:
if label.lower().startswith('review effort [1-5]:'):
effort = int(label.split(':')[1].strip())
if effort > maximal_review_effort:
get_logger().info(
f"Auto-approve error: PR review effort ({effort}) is higher than the maximal review effort "
f"({maximal_review_effort}) allowed")
self.git_provider.publish_comment(
f"Auto-approve error: PR review effort ({effort}) is higher than the maximal review effort "
f"({maximal_review_effort}) allowed")
return
is_auto_approved = self.git_provider.auto_approve()
if is_auto_approved:
get_logger().info("Auto-approved PR")
self.git_provider.publish_comment("Auto-approved PR")
else:
get_logger().info("Auto-approval option is disabled")
self.git_provider.publish_comment("Auto-approval option for PR-Agent is disabled")

View File

@ -14,7 +14,7 @@ msrest==0.7.1
openai==0.27.8 openai==0.27.8
pinecone-client pinecone-client
pinecone-datasets @ git+https://github.com/mrT23/pinecone-datasets.git@main pinecone-datasets @ git+https://github.com/mrT23/pinecone-datasets.git@main
lancedb==0.3.4 lancedb==0.5.1
pytest==7.4.0 pytest==7.4.0
PyGithub==1.59.* PyGithub==1.59.*
PyYAML==6.0.1 PyYAML==6.0.1

View File

@ -3,7 +3,7 @@ from unittest.mock import patch
from pr_agent.git_providers.codecommit_provider import CodeCommitFile from pr_agent.git_providers.codecommit_provider import CodeCommitFile
from pr_agent.git_providers.codecommit_provider import CodeCommitProvider from pr_agent.git_providers.codecommit_provider import CodeCommitProvider
from pr_agent.git_providers.codecommit_provider import PullRequestCCMimic from pr_agent.git_providers.codecommit_provider import PullRequestCCMimic
from pr_agent.git_providers.git_provider import EDIT_TYPE from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
class TestCodeCommitFile: class TestCodeCommitFile:

View File

@ -1,5 +1,6 @@
# Generated by CodiumAI # Generated by CodiumAI
from pr_agent.algo.utils import convert_to_markdown from pr_agent.algo.utils import convert_to_markdown
from pr_agent.tools.pr_description import insert_br_after_x_chars
""" """
Code Analysis Code Analysis
@ -93,3 +94,35 @@ class TestConvertToMarkdown:
} }
expected_output = '' expected_output = ''
assert convert_to_markdown(input_data).strip() == expected_output.strip() assert convert_to_markdown(input_data).strip() == expected_output.strip()
class TestBR:
def test_br1(self):
file_change_description = '- Imported `FilePatchInfo` and `EDIT_TYPE` from `pr_agent.algo.types` instead of `pr_agent.git_providers.git_provider`.'
file_change_description_br = insert_br_after_x_chars(file_change_description)
expected_output = ('<li>Imported <code>FilePatchInfo</code> and <code>EDIT_TYPE</code> from '
'<code>pr_agent.algo.types</code> instead <br>of '
'<code>pr_agent.git_providers.git_provider</code>.')
assert file_change_description_br == expected_output
# print("-----")
# print(file_change_description_br)
def test_br2(self):
file_change_description = ('- Created a - new -class `ColorPaletteResourcesCollection ColorPaletteResourcesCollection '
'ColorPaletteResourcesCollection ColorPaletteResourcesCollection`')
file_change_description_br = insert_br_after_x_chars(file_change_description)
expected_output = ('<li>Created a - new -class <code>ColorPaletteResourcesCollection </code><br><code>'
'ColorPaletteResourcesCollection ColorPaletteResourcesCollection '
'</code><br><code>ColorPaletteResourcesCollection</code>')
assert file_change_description_br == expected_output
# print("-----")
# print(file_change_description_br)
def test_br3(self):
file_change_description = 'Created a new class `ColorPaletteResourcesCollection` which extends `AvaloniaDictionary<ThemeVariant, ColorPaletteResources>` and implements aaa'
file_change_description_br = insert_br_after_x_chars(file_change_description)
assert file_change_description_br == ('Created a new class <code>ColorPaletteResourcesCollection</code> which '
'extends <br><code>AvaloniaDictionary<ThemeVariant, ColorPaletteResources>'
'</code> and implements <br>aaa')
# print("-----")
# print(file_change_description_br)

View File

@ -1,8 +1,7 @@
# Generated by CodiumAI # Generated by CodiumAI
from pr_agent.git_providers.git_provider import FilePatchInfo from pr_agent.algo.types import FilePatchInfo
from pr_agent.algo.pr_processing import find_line_number_of_relevant_line_in_file from pr_agent.algo.utils import find_line_number_of_relevant_line_in_file
import pytest import pytest