mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-04 12:50:38 +08:00
Merge remote-tracking branch 'origin/main' into tr/agent_logic
This commit is contained in:
180
INSTALL.md
Normal file
180
INSTALL.md
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Method 1: Use Docker image (no installation required)
|
||||||
|
|
||||||
|
To request a review for a PR, or ask a question about a PR, you can run directly from the Docker image. Here's how:
|
||||||
|
|
||||||
|
1. To request a review for a PR, run the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent --pr_url <pr_url> review
|
||||||
|
```
|
||||||
|
|
||||||
|
2. To ask a question about a PR, run the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent --pr_url <pr_url> ask "<your question>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Possible questions you can ask include:
|
||||||
|
|
||||||
|
- What is the main theme of this PR?
|
||||||
|
- Is the PR ready for merge?
|
||||||
|
- What are the main changes in this PR?
|
||||||
|
- Should this PR be split into smaller parts?
|
||||||
|
- Can you compose a rhymed song about this PR?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Method 2: Run as a GitHub Action
|
||||||
|
|
||||||
|
You can use our pre-built Github Action Docker image to run PR-Agent as a Github Action.
|
||||||
|
|
||||||
|
1. Add the following file to your repository under `.github/workflows/pr_agent.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
issue_comment:
|
||||||
|
jobs:
|
||||||
|
pr_agent_job:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Run pr agent on every pull request, respond to user comments
|
||||||
|
steps:
|
||||||
|
- name: PR Agent action step
|
||||||
|
id: pragent
|
||||||
|
uses: Codium-ai/pr-agent@main
|
||||||
|
env:
|
||||||
|
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add the following secret to your repository under `Settings > Secrets`:
|
||||||
|
|
||||||
|
```
|
||||||
|
OPENAI_KEY: <your key>
|
||||||
|
```
|
||||||
|
|
||||||
|
The GITHUB_TOKEN secret is automatically created by GitHub.
|
||||||
|
|
||||||
|
3. Merge this change to your main branch.
|
||||||
|
When you open your next PR, you should see a comment from `github-actions` bot with a review of your PR, and instructions on how to use the rest of the tools.
|
||||||
|
|
||||||
|
4. You may configure PR-Agent by adding environment variables under the env section corresponding to any configurable property in the [configuration](./CONFIGURATION.md) file. Some examples:
|
||||||
|
```yaml
|
||||||
|
env:
|
||||||
|
# ... previous environment values
|
||||||
|
OPENAI.ORG: "<Your organization name under your OpenAI account>"
|
||||||
|
PR_REVIEWER.REQUIRE_TESTS_REVIEW: "false" # Disable tests review
|
||||||
|
PR_CODE_SUGGESTIONS.NUM_CODE_SUGGESTIONS: 6 # Increase number of code suggestions
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Method 3: Run from source
|
||||||
|
|
||||||
|
1. Clone this repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/Codium-ai/pr-agent.git
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install the requirements in your favorite virtual environment:
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Copy the secrets template file and fill in your OpenAI key and your GitHub user token:
|
||||||
|
|
||||||
|
```
|
||||||
|
cp pr_agent/settings/.secrets_template.toml pr_agent/settings/.secrets.toml
|
||||||
|
# Edit .secrets.toml file
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run the appropriate Python scripts from the scripts folder:
|
||||||
|
|
||||||
|
```
|
||||||
|
python pr_agent/cli.py --pr_url <pr_url> review
|
||||||
|
python pr_agent/cli.py --pr_url <pr_url> ask <your question>
|
||||||
|
python pr_agent/cli.py --pr_url <pr_url> describe
|
||||||
|
python pr_agent/cli.py --pr_url <pr_url> improve
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Method 4: Run as a polling server
|
||||||
|
Request reviews by tagging your Github user on a PR
|
||||||
|
|
||||||
|
Follow steps 1-3 of method 2.
|
||||||
|
Run the following command to start the server:
|
||||||
|
|
||||||
|
```
|
||||||
|
python pr_agent/servers/github_polling.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Method 5: Run as a GitHub App
|
||||||
|
Allowing you to automate the review process on your private or public repositories.
|
||||||
|
|
||||||
|
1. Create a GitHub App from the [Github Developer Portal](https://docs.github.com/en/developers/apps/creating-a-github-app).
|
||||||
|
|
||||||
|
- Set the following permissions:
|
||||||
|
- Pull requests: Read & write
|
||||||
|
- Issue comment: Read & write
|
||||||
|
- Metadata: Read-only
|
||||||
|
- Set the following events:
|
||||||
|
- Issue comment
|
||||||
|
- Pull request
|
||||||
|
|
||||||
|
2. Generate a random secret for your app, and save it for later. For example, you can use:
|
||||||
|
|
||||||
|
```
|
||||||
|
WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))")
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Acquire the following pieces of information from your app's settings page:
|
||||||
|
|
||||||
|
- App private key (click "Generate a private key" and save the file)
|
||||||
|
- App ID
|
||||||
|
|
||||||
|
4. Clone this repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/Codium-ai/pr-agent.git
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Copy the secrets template file and fill in the following:
|
||||||
|
- Your OpenAI key.
|
||||||
|
- Set deployment_type to 'app'
|
||||||
|
- Copy your app's private key to the private_key field.
|
||||||
|
- Copy your app's ID to the app_id field.
|
||||||
|
- Copy your app's webhook secret to the webhook_secret field.
|
||||||
|
|
||||||
|
```
|
||||||
|
cp pr_agent/settings/.secrets_template.toml pr_agent/settings/.secrets.toml
|
||||||
|
# Edit .secrets.toml file
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Build a Docker image for the app and optionally push it to a Docker repository. We'll use Dockerhub as an example:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build . -t codiumai/pr-agent:github_app --target github_app -f docker/Dockerfile
|
||||||
|
docker push codiumai/pr-agent:github_app # Push to your Docker repository
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Host the app using a server, serverless function, or container environment. Alternatively, for development and
|
||||||
|
debugging, you may use tools like smee.io to forward webhooks to your local machine.
|
||||||
|
|
||||||
|
8. Go back to your app's settings, and set the following:
|
||||||
|
|
||||||
|
- Webhook URL: The URL of your app's server or the URL of the smee.io channel.
|
||||||
|
- Webhook secret: The secret you generated earlier.
|
||||||
|
|
||||||
|
9. Install the app by navigating to the "Install App" tab and selecting your desired repositories.
|
||||||
|
|
||||||
|
---
|
214
README.md
214
README.md
@ -16,30 +16,36 @@ CodiumAI `PR-Agent` is an open-source tool aiming to help developers review PRs
|
|||||||
|
|
||||||
**Auto-Description**: Automatically generating PR description - name, type, summary, and code walkthrough.
|
**Auto-Description**: Automatically generating PR description - name, type, summary, and code walkthrough.
|
||||||
\
|
\
|
||||||
**PR Review**: Feedback about the PR main theme, type, relevant tests, security issues, focused, and various suggestions for the PR content.
|
**PR Review**: Feedback about the PR main theme, type, relevant tests, security issues, focused PR, and various suggestions for the PR content.
|
||||||
\
|
\
|
||||||
**Question Answering**: Answering free-text questions about the PR.
|
**Question Answering**: Answering free-text questions about the PR.
|
||||||
\
|
\
|
||||||
**Code Suggestion**: Committable code suggestions for improving the PR.
|
**Code Suggestion**: Committable code suggestions for improving the PR.
|
||||||
|
|
||||||
Example results:
|
<h3>Example results:</h2>
|
||||||
</div>
|
</div>
|
||||||
<h>Describe:</h>
|
<h4>Describe:</h4>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<p float="center">
|
<p float="center">
|
||||||
<img src="./pics/describe.gif" width="800">
|
<img src="./pics/describe.gif" width="800">
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<h>Ask:</h>
|
<h4>Review:</h4>
|
||||||
|
<div align="center">
|
||||||
|
<p float="center">
|
||||||
|
<img src="./pics/review.gif" width="800">
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<h4>Ask:</h4>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<p float="center">
|
<p float="center">
|
||||||
<img src="./pics/ask.gif" width="800">
|
<img src="./pics/ask.gif" width="800">
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<h>Code Suggestion:</h>
|
<h4>Improve:</h4>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<p float="center">
|
<p float="center">
|
||||||
<img src="./pics/pr_code_suggestions.png" width="800">
|
<img src="./pics/improve.gif" width="800">
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div align="left">
|
<div align="left">
|
||||||
@ -56,22 +62,22 @@ Example results:
|
|||||||
|
|
||||||
## Live demo
|
## Live demo
|
||||||
|
|
||||||
Experience GPT-4 powered PR review on your public GitHub repository with our hosted PR-Agent. To try it, just mention `@CodiumAI-Agent` in any PR comment! The agent will generate a PR review in response.
|
Experience GPT-4 powered PR review on your public GitHub repository with our hosted PR-Agent. To try it, just mention `@CodiumAI-Agent` and add the desired command in any PR comment! The agent will generate a response based on your command.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
To set up your own PR-Agent, see the [Quickstart](#Quickstart) section
|
To set up your own PR-Agent, see the [Quickstart](#Quickstart) section
|
||||||
|
|
||||||
---
|
---
|
||||||
## Overview
|
## Overview
|
||||||
`PR-Agent` offers extensive pull request functionalities across various git providers:
|
`PR-Agent` offers extensive pull request functionalities across various git providers:
|
||||||
| | | Github | Gitlab | Bitbucket |
|
| | | GitHub | Gitlab | Bitbucket |
|
||||||
|-------|---------------------------------------------|--------|--------|-----------|
|
|-------|---------------------------------------------|--------|--------|-----------|
|
||||||
| TOOLS | Review | ✓ | ✓ | ✓ |
|
| TOOLS | Review | ✓ | ✓ | ✓ |
|
||||||
| | ⮑ Inline review | ✓ | ✓ | |
|
| | ⮑ Inline review | ✓ | ✓ | |
|
||||||
| | Ask | ✓ | ✓ | |
|
| | Ask | ✓ | ✓ | |
|
||||||
| | Auto-Description | ✓ | | |
|
| | Auto-Description | ✓ | | |
|
||||||
| | Improve Code | ✓ | | |
|
| | Improve Code | ✓ | ✓ | |
|
||||||
| | | | | |
|
| | | | | |
|
||||||
| USAGE | CLI | ✓ | ✓ | ✓ |
|
| USAGE | CLI | ✓ | ✓ | ✓ |
|
||||||
| | Tagging bot | ✓ | ✓ | |
|
| | Tagging bot | ✓ | ✓ | |
|
||||||
@ -89,7 +95,7 @@ Examples for invoking the different tools via the [CLI](#quickstart):
|
|||||||
|
|
||||||
"<pr_url>" is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50).
|
"<pr_url>" is the url of the relevant PR (for example: https://github.com/Codium-ai/pr-agent/pull/50).
|
||||||
|
|
||||||
In the [configuration](./CONFIGURATION.md) file you can select your git provider (Github, Gitlab, Bitbucket), and further configure the different tools.
|
In the [configuration](./CONFIGURATION.md) file you can select your git provider (GitHub, Gitlab, Bitbucket), and further configure the different tools.
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
@ -100,190 +106,26 @@ To get started with PR-Agent quickly, you first need to acquire two tokens:
|
|||||||
|
|
||||||
There are several ways to use PR-Agent. Let's start with the simplest one:
|
There are several ways to use PR-Agent. Let's start with the simplest one:
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Method 1: Use Docker image (no installation required)
|
## Install
|
||||||
|
Here are several ways to install and run PR-Agent:
|
||||||
|
|
||||||
To request a review for a PR, or ask a question about a PR, you can run directly from the Docker image. Here's how:
|
- [Method 1: Use Docker image (no installation required)](INSTALL.md#method-1-use-docker-image-no-installation-required)
|
||||||
|
- [Method 2: Run as a GitHub Action](INSTALL.md#method-2-run-as-a-github-action)
|
||||||
1. To request a review for a PR, run the following command:
|
- [Method 3: Run from source](INSTALL.md#method-3-run-from-source)
|
||||||
|
- [Method 4: Run as a polling server](INSTALL.md#method-4-run-as-a-polling-server)
|
||||||
```
|
- Request reviews by tagging your GitHub user on a PR
|
||||||
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent --pr_url <pr_url> review
|
- [Method 5: Run as a GitHub App](INSTALL.md#method-5-run-as-a-github-app)
|
||||||
```
|
- Allowing you to automate the review process on your private or public repositories
|
||||||
|
|
||||||
2. To ask a question about a PR, run the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent --pr_url <pr_url> ask "<your question>"
|
|
||||||
```
|
|
||||||
|
|
||||||
Possible questions you can ask include:
|
|
||||||
|
|
||||||
- What is the main theme of this PR?
|
|
||||||
- Is the PR ready for merge?
|
|
||||||
- What are the main changes in this PR?
|
|
||||||
- Should this PR be split into smaller parts?
|
|
||||||
- Can you compose a rhymed song about this PR.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Method 2: Run as a Github Action
|
|
||||||
|
|
||||||
You can use our pre-built Github Action Docker image to run PR-Agent as a Github Action.
|
|
||||||
|
|
||||||
1. Add the following file to your repository under `.github/workflows/pr_agent.yml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
issue_comment:
|
|
||||||
jobs:
|
|
||||||
pr_agent_job:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Run pr agent on every pull request, respond to user comments
|
|
||||||
steps:
|
|
||||||
- name: PR Agent action step
|
|
||||||
id: pragent
|
|
||||||
uses: Codium-ai/pr-agent@main
|
|
||||||
env:
|
|
||||||
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Add the following secret to your repository under `Settings > Secrets`:
|
|
||||||
|
|
||||||
```
|
|
||||||
OPENAI_KEY: <your key>
|
|
||||||
```
|
|
||||||
|
|
||||||
The GITHUB_TOKEN secret is automatically created by Github.
|
|
||||||
|
|
||||||
3. Merge this change to your main branch.
|
|
||||||
When you open your next PR, you should see a comment from `github-actions` bot with a review of your PR, and instructions on how to use the rest of the tools.
|
|
||||||
|
|
||||||
4. You may configure PR-Agent by adding environment variables under the env section that corresponds to any configurable property in the [configuration](./CONFIGURATION.md) file. Some examples:
|
|
||||||
```yaml
|
|
||||||
env:
|
|
||||||
# ... previous environment values
|
|
||||||
OPENAI.ORG: "<Your organization name under your OpenAI account>"
|
|
||||||
PR_REVIEWER.REQUIRE_TESTS_REVIEW: "false" # Disable tests review
|
|
||||||
PR_CODE_SUGGESTIONS.NUM_CODE_SUGGESTIONS: 6 # Increase number of code suggestions
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Method 3: Run from source
|
|
||||||
|
|
||||||
1. Clone this repository:
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone https://github.com/Codium-ai/pr-agent.git
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Install the requirements in your favorite virtual environment:
|
|
||||||
|
|
||||||
```
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Copy the secrets template file and fill in your OpenAI key and your GitHub user token:
|
|
||||||
|
|
||||||
```
|
|
||||||
cp pr_agent/settings/.secrets_template.toml pr_agent/settings/.secrets.toml
|
|
||||||
# Edit .secrets.toml file
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Run the appropriate Python scripts from the scripts folder:
|
|
||||||
|
|
||||||
```
|
|
||||||
python pr_agent/cli.py --pr_url <pr_url> review
|
|
||||||
python pr_agent/cli.py --pr_url <pr_url> ask <your question>
|
|
||||||
python pr_agent/cli.py --pr_url <pr_url> describe
|
|
||||||
python pr_agent/cli.py --pr_url <pr_url> improve
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Method 4: Run as a polling server; request reviews by tagging your Github user on a PR
|
|
||||||
|
|
||||||
Follow steps 1-3 of method 2.
|
|
||||||
Run the following command to start the server:
|
|
||||||
|
|
||||||
```
|
|
||||||
python pr_agent/servers/github_polling.py
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### Method 5: Run as a Github App, allowing you to automate the review process on your private or public repositories.
|
|
||||||
|
|
||||||
1. Create a GitHub App from the [Github Developer Portal](https://docs.github.com/en/developers/apps/creating-a-github-app).
|
|
||||||
|
|
||||||
- Set the following permissions:
|
|
||||||
- Pull requests: Read & write
|
|
||||||
- Issue comment: Read & write
|
|
||||||
- Metadata: Read-only
|
|
||||||
- Set the following events:
|
|
||||||
- Issue comment
|
|
||||||
- Pull request
|
|
||||||
|
|
||||||
2. Generate a random secret for your app, and save it for later. For example, you can use:
|
|
||||||
|
|
||||||
```
|
|
||||||
WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))")
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Acquire the following pieces of information from your app's settings page:
|
|
||||||
|
|
||||||
- App private key (click "Generate a private key", and save the file)
|
|
||||||
- App ID
|
|
||||||
|
|
||||||
4. Clone this repository:
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone https://github.com/Codium-ai/pr-agent.git
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Copy the secrets template file and fill in the following:
|
|
||||||
- Your OpenAI key.
|
|
||||||
- Set deployment_type to 'app'
|
|
||||||
- Copy your app's private key to the private_key field.
|
|
||||||
- Copy your app's ID to the app_id field.
|
|
||||||
- Copy your app's webhook secret to the webhook_secret field.
|
|
||||||
|
|
||||||
```
|
|
||||||
cp pr_agent/settings/.secrets_template.toml pr_agent/settings/.secrets.toml
|
|
||||||
# Edit .secrets.toml file
|
|
||||||
```
|
|
||||||
|
|
||||||
6. Build a Docker image for the app and optionally push it to a Docker repository. We'll use Dockerhub as an example:
|
|
||||||
|
|
||||||
```
|
|
||||||
docker build . -t codiumai/pr-agent:github_app --target github_app -f docker/Dockerfile
|
|
||||||
docker push codiumai/pr-agent:github_app # Push to your Docker repository
|
|
||||||
```
|
|
||||||
|
|
||||||
7. Host the app using a server, serverless function, or container environment. Alternatively, for development and
|
|
||||||
debugging, you may use tools like smee.io to forward webhooks to your local machine.
|
|
||||||
|
|
||||||
8. Go back to your app's settings, set the following:
|
|
||||||
|
|
||||||
- Webhook URL: The URL of your app's server, or the URL of the smee.io channel.
|
|
||||||
- Webhook secret: The secret you generated earlier.
|
|
||||||
|
|
||||||
9. Install the app by navigating to the "Install App" tab, and selecting your desired repositories.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Usage and Tools
|
## Usage and Tools
|
||||||
|
|
||||||
**PR-Agent** provides four types of interactions ("tools"): `"PR Reviewer"`, `"PR Q&A"`, `"PR Description"` and `"PR Code Sueggestions"`.
|
**PR-Agent** provides four types of interactions ("tools"): `"PR Reviewer"`, `"PR Q&A"`, `"PR Description"` and `"PR Code Sueggestions"`.
|
||||||
|
|
||||||
- The "PR Reviewer" tool automatically analyzes PRs, and provides different types of feedbacks.
|
- The "PR Reviewer" tool automatically analyzes PRs, and provides different types of feedback.
|
||||||
- The "PR Ask" tool answers free-text questions about the PR.
|
- The "PR Ask" tool answers free-text questions about the PR.
|
||||||
- The "PR Description" tool automatically sets the PR Title and body.
|
- The "PR Description" tool automatically sets the PR Title and body.
|
||||||
- The "PR Code Suggestion" tool provide inline code suggestions for the PR, that can be applied and committed.
|
- The "PR Code Suggestion" tool provide inline code suggestions for the PR that can be applied and committed.
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
|
BIN
pics/demo.gif
Normal file
BIN
pics/demo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 MiB |
BIN
pics/improve.gif
Normal file
BIN
pics/improve.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.3 MiB |
BIN
pics/review.gif
Normal file
BIN
pics/review.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 MiB |
@ -61,18 +61,24 @@ def parse_code_suggestion(code_suggestions: dict) -> str:
|
|||||||
return markdown_text
|
return markdown_text
|
||||||
|
|
||||||
|
|
||||||
def try_fix_json(review, max_iter=10):
|
def try_fix_json(review, max_iter=10, code_suggestions=False):
|
||||||
|
if review.endswith("}"):
|
||||||
|
return fix_json_escape_char(review)
|
||||||
# Try to fix JSON if it is broken/incomplete: parse until the last valid code suggestion
|
# Try to fix JSON if it is broken/incomplete: parse until the last valid code suggestion
|
||||||
data = {}
|
data = {}
|
||||||
|
if code_suggestions:
|
||||||
|
closing_bracket = "]}"
|
||||||
|
else:
|
||||||
|
closing_bracket = "]}}"
|
||||||
if review.rfind("'Code suggestions': [") > 0 or review.rfind('"Code suggestions": [') > 0:
|
if review.rfind("'Code suggestions': [") > 0 or review.rfind('"Code suggestions": [') > 0:
|
||||||
last_code_suggestion_ind = [m.end() for m in re.finditer(r"\}\s*,", review)][-1] - 1
|
last_code_suggestion_ind = [m.end() for m in re.finditer(r"\}\s*,", review)][-1] - 1
|
||||||
valid_json = False
|
valid_json = False
|
||||||
iter_count = 0
|
iter_count = 0
|
||||||
while last_code_suggestion_ind > 0 and not valid_json and iter_count < max_iter:
|
while last_code_suggestion_ind > 0 and not valid_json and iter_count < max_iter:
|
||||||
try:
|
try:
|
||||||
data = json.loads(review[:last_code_suggestion_ind] + "]}}")
|
data = json.loads(review[:last_code_suggestion_ind] + closing_bracket)
|
||||||
valid_json = True
|
valid_json = True
|
||||||
review = review[:last_code_suggestion_ind].strip() + "]}}"
|
review = review[:last_code_suggestion_ind].strip() + closing_bracket
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
review = review[:last_code_suggestion_ind]
|
review = review[:last_code_suggestion_ind]
|
||||||
# Use regular expression to find the last occurrence of "}," with any number of whitespaces or newlines
|
# Use regular expression to find the last occurrence of "}," with any number of whitespaces or newlines
|
||||||
@ -82,3 +88,17 @@ def try_fix_json(review, max_iter=10):
|
|||||||
logging.error("Unable to decode JSON response from AI")
|
logging.error("Unable to decode JSON response from AI")
|
||||||
data = {}
|
data = {}
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def fix_json_escape_char(json_message=None):
|
||||||
|
result = None
|
||||||
|
try:
|
||||||
|
result = json.loads(json_message)
|
||||||
|
except Exception as e:
|
||||||
|
# Find the offending character index:
|
||||||
|
idx_to_replace = int(str(e).split(' ')[-1].replace(')', ''))
|
||||||
|
# Remove the offending character:
|
||||||
|
json_message = list(json_message)
|
||||||
|
json_message[idx_to_replace] = ' '
|
||||||
|
new_message = ''.join(json_message)
|
||||||
|
return fix_JSON(json_message=new_message)
|
||||||
|
return result
|
@ -61,9 +61,6 @@ class BitbucketProvider:
|
|||||||
def get_title(self):
|
def get_title(self):
|
||||||
return self.pr.title
|
return self.pr.title
|
||||||
|
|
||||||
def get_description(self):
|
|
||||||
return self.pr.body
|
|
||||||
|
|
||||||
def get_languages(self):
|
def get_languages(self):
|
||||||
languages = {self._get_repo().get_data('language'): 0}
|
languages = {self._get_repo().get_data('language'): 0}
|
||||||
return languages
|
return languages
|
||||||
|
@ -134,9 +134,6 @@ class GithubProvider(GitProvider):
|
|||||||
def get_title(self):
|
def get_title(self):
|
||||||
return self.pr.title
|
return self.pr.title
|
||||||
|
|
||||||
def get_description(self):
|
|
||||||
return self.pr.body
|
|
||||||
|
|
||||||
def get_languages(self):
|
def get_languages(self):
|
||||||
languages = self._get_repo().get_languages()
|
languages = self._get_repo().get_languages()
|
||||||
return languages
|
return languages
|
||||||
|
@ -28,6 +28,8 @@ class GitLabProvider(GitProvider):
|
|||||||
self.diff_files = None
|
self.diff_files = None
|
||||||
self.temp_comments = []
|
self.temp_comments = []
|
||||||
self._set_merge_request(merge_request_url)
|
self._set_merge_request(merge_request_url)
|
||||||
|
self.RE_HUNK_HEADER = re.compile(
|
||||||
|
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pr(self):
|
def pr(self):
|
||||||
@ -84,25 +86,26 @@ class GitLabProvider(GitProvider):
|
|||||||
self.diff_files = self.diff_files if self.diff_files else self.get_diff_files()
|
self.diff_files = self.diff_files if self.diff_files else self.get_diff_files()
|
||||||
edit_type, found, source_line_no, target_file, target_line_no = self.search_line(relevant_file,
|
edit_type, found, source_line_no, target_file, target_line_no = self.search_line(relevant_file,
|
||||||
relevant_line_in_file)
|
relevant_line_in_file)
|
||||||
|
self.send_inline_comment(body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
|
||||||
|
target_file, target_line_no)
|
||||||
|
|
||||||
|
def send_inline_comment(self, body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
|
||||||
|
target_file, target_line_no):
|
||||||
if not found:
|
if not found:
|
||||||
logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}")
|
logging.info(f"Could not find position for {relevant_file} {relevant_line_in_file}")
|
||||||
else:
|
else:
|
||||||
if edit_type == 'addition':
|
|
||||||
position = target_line_no - 1
|
|
||||||
else:
|
|
||||||
position = source_line_no - 1
|
|
||||||
d = self.last_diff
|
d = self.last_diff
|
||||||
pos_obj = {'position_type': 'text',
|
pos_obj = {'position_type': 'text',
|
||||||
'new_path': target_file.filename,
|
'new_path': target_file.filename,
|
||||||
'old_path': target_file.old_filename if target_file.old_filename else target_file.filename,
|
'old_path': target_file.old_filename if target_file.old_filename else target_file.filename,
|
||||||
'base_sha': d.base_commit_sha, 'start_sha': d.start_commit_sha, 'head_sha': d.head_commit_sha}
|
'base_sha': d.base_commit_sha, 'start_sha': d.start_commit_sha, 'head_sha': d.head_commit_sha}
|
||||||
if edit_type == 'deletion':
|
if edit_type == 'deletion':
|
||||||
pos_obj['old_line'] = position
|
pos_obj['old_line'] = source_line_no - 1
|
||||||
elif edit_type == 'addition':
|
elif edit_type == 'addition':
|
||||||
pos_obj['new_line'] = position
|
pos_obj['new_line'] = target_line_no - 1
|
||||||
else:
|
else:
|
||||||
pos_obj['new_line'] = position
|
pos_obj['new_line'] = target_line_no - 1
|
||||||
pos_obj['old_line'] = position
|
pos_obj['old_line'] = source_line_no - 1
|
||||||
self.mr.discussions.create({'body': body,
|
self.mr.discussions.create({'body': body,
|
||||||
'position': pos_obj})
|
'position': pos_obj})
|
||||||
|
|
||||||
@ -110,24 +113,58 @@ class GitLabProvider(GitProvider):
|
|||||||
relevant_file: str,
|
relevant_file: str,
|
||||||
relevant_lines_start: int,
|
relevant_lines_start: int,
|
||||||
relevant_lines_end: int):
|
relevant_lines_end: int):
|
||||||
raise "not implemented yet for gitlab"
|
self.diff_files = self.diff_files if self.diff_files else self.get_diff_files()
|
||||||
|
target_file = None
|
||||||
|
for file in self.diff_files:
|
||||||
|
if file.filename == relevant_file:
|
||||||
|
if file.filename == relevant_file:
|
||||||
|
target_file = file
|
||||||
|
break
|
||||||
|
range = relevant_lines_end - relevant_lines_start + 1
|
||||||
|
body = body.replace('```suggestion', f'```suggestion:-0+{range}')
|
||||||
|
|
||||||
|
d = self.last_diff
|
||||||
|
#
|
||||||
|
# pos_obj = {'position_type': 'text',
|
||||||
|
# 'new_path': target_file.filename,
|
||||||
|
# 'old_path': target_file.old_filename if target_file.old_filename else target_file.filename,
|
||||||
|
# 'base_sha': d.base_commit_sha, 'start_sha': d.start_commit_sha, 'head_sha': d.head_commit_sha}
|
||||||
|
lines = target_file.head_file.splitlines()
|
||||||
|
relevant_line_in_file = lines[relevant_lines_start - 1]
|
||||||
|
edit_type, found, source_line_no, target_file, target_line_no = self.find_in_file(target_file, relevant_line_in_file)
|
||||||
|
self.send_inline_comment(body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
|
||||||
|
target_file, target_line_no)
|
||||||
|
# if lines[relevant_lines_start][0] == '-':
|
||||||
|
# pos_obj['old_line'] = relevant_lines_start
|
||||||
|
# elif lines[relevant_lines_start][0] == '+':
|
||||||
|
# pos_obj['new_line'] = relevant_lines_start
|
||||||
|
# else:
|
||||||
|
# pos_obj['new_line'] = relevant_lines_start
|
||||||
|
# pos_obj['old_line'] = relevant_lines_start
|
||||||
|
# self.mr.discussions.create({'body': body,
|
||||||
|
# 'position': pos_obj})
|
||||||
|
|
||||||
def search_line(self, relevant_file, relevant_line_in_file):
|
def search_line(self, relevant_file, relevant_line_in_file):
|
||||||
RE_HUNK_HEADER = re.compile(
|
|
||||||
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
|
|
||||||
target_file = None
|
target_file = None
|
||||||
source_line_no = 0
|
|
||||||
target_line_no = 0
|
|
||||||
found = False
|
|
||||||
edit_type = self.get_edit_type(relevant_line_in_file)
|
edit_type = self.get_edit_type(relevant_line_in_file)
|
||||||
for file in self.diff_files:
|
for file in self.diff_files:
|
||||||
if file.filename == relevant_file:
|
if file.filename == relevant_file:
|
||||||
|
edit_type, found, source_line_no, target_file, target_line_no = self.find_in_file(file,
|
||||||
|
relevant_line_in_file)
|
||||||
|
return edit_type, found, source_line_no, target_file, target_line_no
|
||||||
|
|
||||||
|
def find_in_file(self, file, relevant_line_in_file):
|
||||||
|
edit_type = 'context'
|
||||||
|
source_line_no = 0
|
||||||
|
target_line_no = 0
|
||||||
|
found = False
|
||||||
target_file = file
|
target_file = file
|
||||||
patch = file.patch
|
patch = file.patch
|
||||||
patch_lines = patch.splitlines()
|
patch_lines = patch.splitlines()
|
||||||
for i, line in enumerate(patch_lines):
|
for i, line in enumerate(patch_lines):
|
||||||
if line.startswith('@@'):
|
if line.startswith('@@'):
|
||||||
match = RE_HUNK_HEADER.match(line)
|
match = self.RE_HUNK_HEADER.match(line)
|
||||||
if not match:
|
if not match:
|
||||||
continue
|
continue
|
||||||
start_old, size_old, start_new, size_new, _ = match.groups()
|
start_old, size_old, start_new, size_new, _ = match.groups()
|
||||||
@ -171,9 +208,6 @@ class GitLabProvider(GitProvider):
|
|||||||
def get_title(self):
|
def get_title(self):
|
||||||
return self.mr.title
|
return self.mr.title
|
||||||
|
|
||||||
def get_description(self):
|
|
||||||
return self.mr.description
|
|
||||||
|
|
||||||
def get_languages(self):
|
def get_languages(self):
|
||||||
languages = self.gl.projects.get(self.id_project).languages()
|
languages = self.gl.projects.get(self.id_project).languages()
|
||||||
return languages
|
return languages
|
||||||
|
@ -94,7 +94,7 @@ async def polling_loop():
|
|||||||
success = await agent.handle_request(pr_url, rest_of_comment)
|
success = await agent.handle_request(pr_url, rest_of_comment)
|
||||||
if not success:
|
if not success:
|
||||||
git_provider.set_pr(pr_url)
|
git_provider.set_pr(pr_url)
|
||||||
git_provider.publish_comment("### How to user PR-Agent\n" +
|
git_provider.publish_comment("### How to use PR-Agent\n" +
|
||||||
bot_help_text(user_id))
|
bot_help_text(user_id))
|
||||||
|
|
||||||
elif response.status != 304:
|
elif response.status != 304:
|
||||||
|
@ -10,7 +10,7 @@ from pr_agent.algo.pr_processing import get_pr_diff
|
|||||||
from pr_agent.algo.token_handler import TokenHandler
|
from pr_agent.algo.token_handler import TokenHandler
|
||||||
from pr_agent.algo.utils import convert_to_markdown, try_fix_json
|
from pr_agent.algo.utils import convert_to_markdown, try_fix_json
|
||||||
from pr_agent.config_loader import settings
|
from pr_agent.config_loader import settings
|
||||||
from pr_agent.git_providers import get_git_provider, GithubProvider
|
from pr_agent.git_providers import get_git_provider, BitbucketProvider
|
||||||
from pr_agent.git_providers.git_provider import get_main_pr_language
|
from pr_agent.git_providers.git_provider import get_main_pr_language
|
||||||
|
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ class PRCodeSuggestions:
|
|||||||
settings.pr_code_suggestions_prompt.user)
|
settings.pr_code_suggestions_prompt.user)
|
||||||
|
|
||||||
async def suggest(self):
|
async def suggest(self):
|
||||||
assert type(self.git_provider) == GithubProvider, "Only Github is supported for now"
|
assert type(self.git_provider) != BitbucketProvider, "Bitbucket is not supported for now"
|
||||||
|
|
||||||
logging.info('Generating code suggestions for PR...')
|
logging.info('Generating code suggestions for PR...')
|
||||||
if settings.config.publish_review:
|
if settings.config.publish_review:
|
||||||
@ -86,7 +86,7 @@ class PRCodeSuggestions:
|
|||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
if settings.config.verbosity_level >= 2:
|
if settings.config.verbosity_level >= 2:
|
||||||
logging.info(f"Could not parse json response: {review}")
|
logging.info(f"Could not parse json response: {review}")
|
||||||
data = try_fix_json(review)
|
data = try_fix_json(review, code_suggestions=True)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def push_inline_code_suggestions(self, data):
|
def push_inline_code_suggestions(self, data):
|
||||||
|
@ -23,7 +23,7 @@ class PRDescription:
|
|||||||
self.vars = {
|
self.vars = {
|
||||||
"title": self.git_provider.pr.title,
|
"title": self.git_provider.pr.title,
|
||||||
"branch": self.git_provider.get_pr_branch(),
|
"branch": self.git_provider.get_pr_branch(),
|
||||||
"description": self.git_provider.get_description(),
|
"description": self.git_provider.get_pr_description(),
|
||||||
"language": self.main_pr_language,
|
"language": self.main_pr_language,
|
||||||
"diff": "", # empty diff for initial calculation
|
"diff": "", # empty diff for initial calculation
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ class PRQuestions:
|
|||||||
self.vars = {
|
self.vars = {
|
||||||
"title": self.git_provider.pr.title,
|
"title": self.git_provider.pr.title,
|
||||||
"branch": self.git_provider.get_pr_branch(),
|
"branch": self.git_provider.get_pr_branch(),
|
||||||
"description": self.git_provider.get_description(),
|
"description": self.git_provider.get_pr_description(),
|
||||||
"language": self.main_pr_language,
|
"language": self.main_pr_language,
|
||||||
"diff": "", # empty diff for initial calculation
|
"diff": "", # empty diff for initial calculation
|
||||||
"questions": self.question_str,
|
"questions": self.question_str,
|
||||||
|
Reference in New Issue
Block a user