Merge remote-tracking branch 'origin/main' into tr/split

This commit is contained in:
mrT23
2024-03-17 09:00:04 +02:00
37 changed files with 439 additions and 258 deletions

View File

@ -59,7 +59,7 @@ CodiumAI PR-Agent is an open-source tool to help efficiently review and handle p
- See the [Usage Guide](https://pr-agent-docs.codium.ai/usage-guide/) for instructions on running the PR-Agent commands via different interfaces, including _CLI_, _online usage_, or by _automatically triggering_ them when a new PR is opened. - See the [Usage Guide](https://pr-agent-docs.codium.ai/usage-guide/) for instructions on running the PR-Agent commands via different interfaces, including _CLI_, _online usage_, or by _automatically triggering_ them when a new PR is opened.
- See the [Tools Guide](https://pr-agent-docs.codium.ai/tools/) for a detailed description of the different tools (tools are run via the commands). - See the [Tools Guide](https://pr-agent-docs.codium.ai/tools/) for a detailed description of the different tools.
Supported commands per platform: Supported commands per platform:

140
docs/docs/assets/logo.svg Normal file
View File

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 28.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
<g>
<defs>
<rect id="SVGID_1_" x="0.4" y="0.1" width="63.4" height="63.4"/>
</defs>
<clipPath id="SVGID_00000008836131916906499950000015813697852011234749_">
<use xlink:href="#SVGID_1_" overflow="visible"/>
</clipPath>
<g clip-path="url(#SVGID_00000008836131916906499950000015813697852011234749_)">
<path fill="#05E5AD" d="M21.4,9.8c3,0,5.9,0.7,8.5,1.9c-5.7,3.4-9.8,11.1-9.8,20.1c0,9,4,16.7,9.8,20.1c-2.6,1.2-5.5,1.9-8.5,1.9
c-11.6,0-21-9.8-21-22S9.8,9.8,21.4,9.8z"/>
<radialGradient id="SVGID_00000150822754378345238340000008985053211526864828_" cx="-140.0905" cy="350.1757" r="4.8781" gradientTransform="matrix(-4.7708 -6.961580e-02 -0.1061 7.2704 -601.3099 -2523.8489)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#6447FF"/>
<stop offset="6.666670e-02" style="stop-color:#6348FE"/>
<stop offset="0.1333" style="stop-color:#614DFC"/>
<stop offset="0.2" style="stop-color:#5C54F8"/>
<stop offset="0.2667" style="stop-color:#565EF3"/>
<stop offset="0.3333" style="stop-color:#4E6CEC"/>
<stop offset="0.4" style="stop-color:#447BE4"/>
<stop offset="0.4667" style="stop-color:#3A8DDB"/>
<stop offset="0.5333" style="stop-color:#2F9FD1"/>
<stop offset="0.6" style="stop-color:#25B1C8"/>
<stop offset="0.6667" style="stop-color:#1BC0C0"/>
<stop offset="0.7333" style="stop-color:#13CEB9"/>
<stop offset="0.8" style="stop-color:#0DD8B4"/>
<stop offset="0.8667" style="stop-color:#08DFB0"/>
<stop offset="0.9333" style="stop-color:#06E4AE"/>
<stop offset="1" style="stop-color:#05E5AD"/>
</radialGradient>
<path fill="url(#SVGID_00000150822754378345238340000008985053211526864828_)" d="M21.4,9.8c3,0,5.9,0.7,8.5,1.9
c-5.7,3.4-9.8,11.1-9.8,20.1c0,9,4,16.7,9.8,20.1c-2.6,1.2-5.5,1.9-8.5,1.9c-11.6,0-21-9.8-21-22S9.8,9.8,21.4,9.8z"/>
<radialGradient id="SVGID_00000022560571240417802950000012439139323268113305_" cx="-191.7649" cy="385.7387" r="4.8781" gradientTransform="matrix(-2.5514 -0.7616 -0.8125 2.7217 -130.733 -1180.2209)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#6447FF"/>
<stop offset="6.666670e-02" style="stop-color:#6348FE"/>
<stop offset="0.1333" style="stop-color:#614DFC"/>
<stop offset="0.2" style="stop-color:#5C54F8"/>
<stop offset="0.2667" style="stop-color:#565EF3"/>
<stop offset="0.3333" style="stop-color:#4E6CEC"/>
<stop offset="0.4" style="stop-color:#447BE4"/>
<stop offset="0.4667" style="stop-color:#3A8DDB"/>
<stop offset="0.5333" style="stop-color:#2F9FD1"/>
<stop offset="0.6" style="stop-color:#25B1C8"/>
<stop offset="0.6667" style="stop-color:#1BC0C0"/>
<stop offset="0.7333" style="stop-color:#13CEB9"/>
<stop offset="0.8" style="stop-color:#0DD8B4"/>
<stop offset="0.8667" style="stop-color:#08DFB0"/>
<stop offset="0.9333" style="stop-color:#06E4AE"/>
<stop offset="1" style="stop-color:#05E5AD"/>
</radialGradient>
<path fill="url(#SVGID_00000022560571240417802950000012439139323268113305_)" d="M38,18.3c-2.1-2.8-4.9-5.1-8.1-6.6
c2-1.2,4.2-1.9,6.6-1.9c2.2,0,4.3,0.6,6.2,1.7C40.8,12.9,39.2,15.3,38,18.3L38,18.3z"/>
<radialGradient id="SVGID_00000143611122169386473660000017673587931016751800_" cx="-194.7918" cy="395.2442" r="4.8781" gradientTransform="matrix(-2.5514 -0.7616 -0.8125 2.7217 -130.733 -1172.9556)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#6447FF"/>
<stop offset="6.666670e-02" style="stop-color:#6348FE"/>
<stop offset="0.1333" style="stop-color:#614DFC"/>
<stop offset="0.2" style="stop-color:#5C54F8"/>
<stop offset="0.2667" style="stop-color:#565EF3"/>
<stop offset="0.3333" style="stop-color:#4E6CEC"/>
<stop offset="0.4" style="stop-color:#447BE4"/>
<stop offset="0.4667" style="stop-color:#3A8DDB"/>
<stop offset="0.5333" style="stop-color:#2F9FD1"/>
<stop offset="0.6" style="stop-color:#25B1C8"/>
<stop offset="0.6667" style="stop-color:#1BC0C0"/>
<stop offset="0.7333" style="stop-color:#13CEB9"/>
<stop offset="0.8" style="stop-color:#0DD8B4"/>
<stop offset="0.8667" style="stop-color:#08DFB0"/>
<stop offset="0.9333" style="stop-color:#06E4AE"/>
<stop offset="1" style="stop-color:#05E5AD"/>
</radialGradient>
<path fill="url(#SVGID_00000143611122169386473660000017673587931016751800_)" d="M38,45.2c1.2,3,2.9,5.3,4.7,6.8
c-1.9,1.1-4,1.7-6.2,1.7c-2.3,0-4.6-0.7-6.6-1.9C33.1,50.4,35.8,48.1,38,45.2L38,45.2z"/>
<path fill="#684BFE" d="M20.1,31.8c0-9,4-16.7,9.8-20.1c3.2,1.5,6,3.8,8.1,6.6c-1.5,3.7-2.5,8.4-2.5,13.5s0.9,9.8,2.5,13.5
c-2.1,2.8-4.9,5.1-8.1,6.6C24.1,48.4,20.1,40.7,20.1,31.8z"/>
<radialGradient id="SVGID_00000147942998054305738810000004710078864578628519_" cx="-212.7358" cy="363.2475" r="4.8781" gradientTransform="matrix(-2.3342 -1.063 -1.623 3.5638 149.3813 -1470.1027)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#6447FF"/>
<stop offset="6.666670e-02" style="stop-color:#6348FE"/>
<stop offset="0.1333" style="stop-color:#614DFC"/>
<stop offset="0.2" style="stop-color:#5C54F8"/>
<stop offset="0.2667" style="stop-color:#565EF3"/>
<stop offset="0.3333" style="stop-color:#4E6CEC"/>
<stop offset="0.4" style="stop-color:#447BE4"/>
<stop offset="0.4667" style="stop-color:#3A8DDB"/>
<stop offset="0.5333" style="stop-color:#2F9FD1"/>
<stop offset="0.6" style="stop-color:#25B1C8"/>
<stop offset="0.6667" style="stop-color:#1BC0C0"/>
<stop offset="0.7333" style="stop-color:#13CEB9"/>
<stop offset="0.8" style="stop-color:#0DD8B4"/>
<stop offset="0.8667" style="stop-color:#08DFB0"/>
<stop offset="0.9333" style="stop-color:#06E4AE"/>
<stop offset="1" style="stop-color:#05E5AD"/>
</radialGradient>
<path fill="url(#SVGID_00000147942998054305738810000004710078864578628519_)" d="M50.7,42.5c0.6,3.3,1.5,6.1,2.5,8
c-1.8,2-3.8,3.1-6,3.1c-1.6,0-3.1-0.6-4.5-1.7C46.1,50.2,48.9,46.8,50.7,42.5L50.7,42.5z"/>
<radialGradient id="SVGID_00000083770737908230256670000016126156495859285174_" cx="-208.5327" cy="357.2025" r="4.8781" gradientTransform="matrix(-2.3342 -1.063 -1.623 3.5638 149.3813 -1476.8097)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#6447FF"/>
<stop offset="6.666670e-02" style="stop-color:#6348FE"/>
<stop offset="0.1333" style="stop-color:#614DFC"/>
<stop offset="0.2" style="stop-color:#5C54F8"/>
<stop offset="0.2667" style="stop-color:#565EF3"/>
<stop offset="0.3333" style="stop-color:#4E6CEC"/>
<stop offset="0.4" style="stop-color:#447BE4"/>
<stop offset="0.4667" style="stop-color:#3A8DDB"/>
<stop offset="0.5333" style="stop-color:#2F9FD1"/>
<stop offset="0.6" style="stop-color:#25B1C8"/>
<stop offset="0.6667" style="stop-color:#1BC0C0"/>
<stop offset="0.7333" style="stop-color:#13CEB9"/>
<stop offset="0.8" style="stop-color:#0DD8B4"/>
<stop offset="0.8667" style="stop-color:#08DFB0"/>
<stop offset="0.9333" style="stop-color:#06E4AE"/>
<stop offset="1" style="stop-color:#05E5AD"/>
</radialGradient>
<path fill="url(#SVGID_00000083770737908230256670000016126156495859285174_)" d="M42.7,11.5c1.4-1.1,2.9-1.7,4.5-1.7
c2.2,0,4.3,1.1,6,3.1c-1,2-1.9,4.7-2.5,8C48.9,16.7,46.1,13.4,42.7,11.5L42.7,11.5z"/>
<path fill="#684BFE" d="M38,45.2c2.8-3.7,4.4-8.4,4.4-13.5c0-5.1-1.7-9.8-4.4-13.5c1.2-3,2.9-5.3,4.7-6.8c3.4,1.9,6.2,5.3,8,9.5
c-0.6,3.2-0.9,6.9-0.9,10.8s0.3,7.6,0.9,10.8c-1.8,4.3-4.6,7.6-8,9.5C40.8,50.6,39.2,48.2,38,45.2L38,45.2z"/>
<path fill="#321BB2" d="M38,45.2c-1.5-3.7-2.5-8.4-2.5-13.5S36.4,22,38,18.3c2.8,3.7,4.4,8.4,4.4,13.5S40.8,41.5,38,45.2z"/>
<path fill="#05E6AD" d="M53.2,12.9c1.1-2,2.3-3.1,3.6-3.1c3.9,0,7,9.8,7,22s-3.1,22-7,22c-1.3,0-2.6-1.1-3.6-3.1
c3.4-3.8,5.7-10.8,5.7-18.8C58.8,23.8,56.6,16.8,53.2,12.9z"/>
<radialGradient id="SVGID_00000009565123575973598080000009335550354766300606_" cx="-7.8671" cy="278.2442" r="4.8781" gradientTransform="matrix(1.5187 0 0 -7.8271 69.237 2209.3281)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#05E5AD"/>
<stop offset="0.32" style="stop-color:#05E5AD;stop-opacity:0"/>
<stop offset="0.9028" style="stop-color:#6447FF"/>
</radialGradient>
<path fill="url(#SVGID_00000009565123575973598080000009335550354766300606_)" d="M53.2,12.9c1.1-2,2.3-3.1,3.6-3.1
c3.9,0,7,9.8,7,22s-3.1,22-7,22c-1.3,0-2.6-1.1-3.6-3.1c3.4-3.8,5.7-10.8,5.7-18.8C58.8,23.8,56.6,16.8,53.2,12.9z"/>
<path fill="#684BFE" d="M52.8,31.8c0-3.9-0.8-7.6-2.1-10.8c0.6-3.3,1.5-6.1,2.5-8c3.4,3.8,5.7,10.8,5.7,18.8c0,8-2.3,15-5.7,18.8
c-1-2-1.9-4.7-2.5-8C52,39.3,52.8,35.7,52.8,31.8z"/>
<path fill="#321BB2" d="M50.7,42.5c-0.6-3.2-0.9-6.9-0.9-10.8s0.3-7.6,0.9-10.8c1.3,3.2,2.1,6.9,2.1,10.8S52,39.3,50.7,42.5z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -5,94 +5,14 @@
--md-accent-fg-color: #AEA1F1; --md-accent-fg-color: #AEA1F1;
} }
.md-nav__title, .md-nav__link { .md-nav__title, .md-nav__link {
font-size: 16px; /* Adjust the font size as needed */ font-size: 16px;
} }
.md-tabs__link { .md-tabs__link {
font-size: 16px; /* Adjust the font size as needed */ font-size: 16px;
} }
.md-header__title { .md-header__title {
font-size: 20px; /* Adjust the font size as needed */ font-size: 20px;
margin-left: 0px !important;
} }
/*
@media (prefers-color-scheme: light) {
body {
--md-primary-fg-color: #00ffee !important;
--md-primary-bg-color: #ff0000 !important;
}
body, .md-main, .md-content {
background-color: #4312f5 !important;
}
}
@media (prefers-color-scheme: dark) {
body {
--md-primary-fg-color: #171518 !important;
--md-primary-bg-color: #171518 !important;
}
body, .md-main, .md-content {
background-color: #171518 !important;
}
.md-header__title {
color: #ffffff !important;
}
.md-tabs .md-tabs__link {
color: #ffffff !important;
}
.md-tabs .md-tabs__link:hover,
.md-tabs .md-tabs__link:focus {
color: #ffffff !important;
}
.md-header__button {
color: #ffffff !important;
}
.md-header__button svg {
fill: currentColor !important;
}
.md-header__button:hover,
.md-header__button:focus {
color: #ffffff !important;
}
.md-header__button:hover svg,
.md-header__button:focus svg {
fill: currentColor !important;
}
.md-search__icon svg {
fill: #ffffff !important;
}
.md-search__input {
color: #ffffff !important;
}
.md-nav__item--active > .md-nav__link--active,
.md-nav__link--active {
color: #AEA1F1 !important;
}
.md-nav--secondary .md-nav__title {
background: #171518;
box-shadow: 0 0 0.4rem 0.4rem #171518;
}
.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link {
background: #171518;
box-shadow: 0 0 0.4rem 0.4rem #171518;
}
.md-content a {
color: #AEA1F1 !important;
}
} */

View File

@ -6,7 +6,7 @@ CodiumAI PR-Agent is an open-source tool to help efficiently review and handle p
- See the [Usage Guide](./usage-guide/index.md) for instructions on running the PR-Agent commands via different interfaces, including _CLI_, _online usage_, or by _automatically triggering_ them when a new PR is opened. - See the [Usage Guide](./usage-guide/index.md) for instructions on running the PR-Agent commands via different interfaces, including _CLI_, _online usage_, or by _automatically triggering_ them when a new PR is opened.
- See the [Tools Guide](./tools/index.md) for a detailed description of the different tools (tools are run via the commands). - See the [Tools Guide](./tools/index.md) for a detailed description of the different tools.
## PR-Agent Features ## PR-Agent Features

View File

@ -4,7 +4,6 @@ To use Azure DevOps provider use the following settings in configuration.toml:
``` ```
[config] [config]
git_provider="azure" git_provider="azure"
use_repo_settings_file=false
``` ```
Azure DevOps provider supports [PAT token](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows) or [DefaultAzureCredential](https://learn.microsoft.com/en-us/azure/developer/python/sdk/authentication-overview#authentication-in-server-environments) authentication. Azure DevOps provider supports [PAT token](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows) or [DefaultAzureCredential](https://learn.microsoft.com/en-us/azure/developer/python/sdk/authentication-overview#authentication-in-server-environments) authentication.

View File

@ -2,7 +2,7 @@
You can use our pre-built Github Action Docker image to run PR-Agent 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`: 1) Add the following file to your repository under `.github/workflows/pr_agent.yml`:
```yaml ```yaml
on: on:
@ -46,7 +46,7 @@ jobs:
OPENAI_KEY: ${{ secrets.OPENAI_KEY }} OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
``` ```
2. Add the following secret to your repository under `Settings > Secrets and variables > Actions > New repository secret > Add secret`: 2) Add the following secret to your repository under `Settings > Secrets and variables > Actions > New repository secret > Add secret`:
``` ```
Name = OPENAI_KEY Name = OPENAI_KEY
@ -55,10 +55,10 @@ Secret = <your key>
The GITHUB_TOKEN secret is automatically created by GitHub. The GITHUB_TOKEN secret is automatically created by GitHub.
3. Merge this change to your main branch. 3) Merge this change to your main branch.
When you open your next PR, you should see a comment from `github-actions` bot with a review of your PR, and instructions on how to use the rest of the tools. When you open your next PR, you should see a comment from `github-actions` bot with a review of your PR, and instructions on how to use the rest of the tools.
4. You may configure PR-Agent by adding environment variables under the env section corresponding to any configurable property in the [configuration](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) file. Some examples: 4) You may configure PR-Agent by adding environment variables under the env section corresponding to any configurable property in the [configuration](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) file. Some examples:
```yaml ```yaml
env: env:
# ... previous environment values # ... previous environment values
@ -67,24 +67,12 @@ When you open your next PR, you should see a comment from `github-actions` bot w
PR_CODE_SUGGESTIONS.NUM_CODE_SUGGESTIONS: 6 # Increase number of code suggestions PR_CODE_SUGGESTIONS.NUM_CODE_SUGGESTIONS: 6 # Increase number of code suggestions
``` ```
---
## Run as a polling server
Request reviews by tagging your GitHub user on a PR
Follow [steps 1-3](#run-as-a-github-action) of the GitHub Action setup.
Run the following command to start the server:
```
python pr_agent/servers/github_polling.py
```
--- ---
## Run as a GitHub App ## 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.
1. Create a GitHub App from the [Github Developer Portal](https://docs.github.com/en/developers/apps/creating-a-github-app). 1) Create a GitHub App from the [Github Developer Portal](https://docs.github.com/en/developers/apps/creating-a-github-app).
- Set the following permissions: - Set the following permissions:
- Pull requests: Read & write - Pull requests: Read & write
@ -96,60 +84,62 @@ Allowing you to automate the review process on your private or public repositori
- Pull request - Pull request
- Push (if you need to enable triggering on PR update) - Push (if you need to enable triggering on PR update)
2. Generate a random secret for your app, and save it for later. For example, you can use: 2) Generate a random secret for your app, and save it for later. For example, you can use:
``` ```
WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))") WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))")
``` ```
3. Acquire the following pieces of information from your app's settings page: 3) Acquire the following pieces of information from your app's settings page:
- App private key (click "Generate a private key" and save the file) - App private key (click "Generate a private key" and save the file)
- App ID - App ID
4. Clone this repository: 4) Clone this repository:
``` ```
git clone https://github.com/Codium-ai/pr-agent.git git clone https://github.com/Codium-ai/pr-agent.git
``` ```
5. Copy the secrets template file and fill in the following: 5) Copy the secrets template file and fill in the following:
```
cp pr_agent/settings/.secrets_template.toml pr_agent/settings/.secrets.toml ```
# Edit .secrets.toml file cp pr_agent/settings/.secrets_template.toml pr_agent/settings/.secrets.toml
``` # Edit .secrets.toml file
```
- Your OpenAI key. - Your OpenAI key.
- Copy your app's private key to the private_key field. - 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 ID to the app_id field.
- Copy your app's webhook secret to the webhook_secret field. - Copy your app's webhook secret to the webhook_secret field.
- Set deployment_type to 'app' in [configuration.toml](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) - Set deployment_type to 'app' in [configuration.toml](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml)
> The .secrets.toml file is not copied to the Docker image by default, and is only used for local development. > The .secrets.toml file is not copied to the Docker image by default, and is only used for local development.
> If you want to use the .secrets.toml file in your Docker image, you can add remove it from the .dockerignore file. > If you want to use the .secrets.toml file in your Docker image, you can add remove it from the .dockerignore file.
> In most production environments, you would inject the secrets file as environment variables or as mounted volumes. > In most production environments, you would inject the secrets file as environment variables or as mounted volumes.
> For example, in order to inject a secrets file as a volume in a Kubernetes environment you can update your pod spec to include the following, > For example, in order to inject a secrets file as a volume in a Kubernetes environment you can update your pod spec to include the following,
> assuming you have a secret named `pr-agent-settings` with a key named `.secrets.toml`: > assuming you have a secret named `pr-agent-settings` with a key named `.secrets.toml`:
``` ```
volumes: volumes:
- name: settings-volume - name: settings-volume
secret: secret:
secretName: pr-agent-settings secretName: pr-agent-settings
// ... // ...
containers: containers:
// ... // ...
volumeMounts: volumeMounts:
- mountPath: /app/pr_agent/settings_prod - mountPath: /app/pr_agent/settings_prod
name: settings-volume name: settings-volume
``` ```
> Another option is to set the secrets as environment variables in your deployment environment, for example `OPENAI.KEY` and `GITHUB.USER_TOKEN`.
> Another option is to set the secrets as environment variables in your deployment environment, for example `OPENAI.KEY` and `GITHUB.USER_TOKEN`. 6) Build a Docker image for the app and optionally push it to a Docker repository. We'll use Dockerhub as an example:
6. Build a Docker image for the app and optionally push it to a Docker repository. We'll use Dockerhub as an example: ```
docker build . -t codiumai/pr-agent:github_app --target github_app -f docker/Dockerfile
``` docker push codiumai/pr-agent:github_app # Push to your Docker repository
docker build . -t codiumai/pr-agent:github_app --target github_app -f docker/Dockerfile ```
docker push codiumai/pr-agent:github_app # Push to your Docker repository
```
7. Host the app using a server, serverless function, or container environment. Alternatively, for development and 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. debugging, you may use tools like smee.io to forward webhooks to your local machine.
@ -169,7 +159,7 @@ docker push codiumai/pr-agent:github_app # Push to your Docker repository
## Deploy as a Lambda Function ## Deploy as a Lambda Function
1. Follow steps 1-5 of [Method 5](#run-as-a-github-app). 1. Follow steps 1-5 from [here](#run-as-a-github-app).
2. Build a docker image that can be used as a lambda function 2. Build a docker image that can be used as a lambda function
```shell ```shell
docker buildx build --platform=linux/amd64 . -t codiumai/pr-agent:serverless -f docker/Dockerfile.lambda docker buildx build --platform=linux/amd64 . -t codiumai/pr-agent:serverless -f docker/Dockerfile.lambda

View File

@ -1,16 +1,20 @@
## Run a GitLab webhook server ## Run a GitLab webhook server
1. From the GitLab workspace or group, create an access token. Enable the "api" scope only. 1. From the GitLab workspace or group, create an access token. Enable the "api" scope only.
2. Generate a random secret for your app, and save it for later. For example, you can use: 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))") WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))")
``` ```
3. Follow the instructions to build the Docker image, setup a secrets file and deploy on your own server from [Method 5](#run-as-a-github-app) steps 4-7. 3. Follow the instructions to build the Docker image, setup a secrets file and deploy on your own server from [here](https://pr-agent-docs.codium.ai/installation/github/#run-as-a-github-app) steps 4-7.
4. In the secrets file, fill in the following: 4. In the secrets file, fill in the following:
- Your OpenAI key. - Your OpenAI key.
- In the [gitlab] section, fill in personal_access_token and shared_secret. The access token can be a personal access token, or a group or project access token. - In the [gitlab] section, fill in personal_access_token and shared_secret. The access token can be a personal access token, or a group or project access token.
- Set deployment_type to 'gitlab' in [configuration.toml](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) - Set deployment_type to 'gitlab' in [configuration.toml](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml)
5. Create a webhook in GitLab. Set the URL to the URL of your app's server. Set the secret token to the generated secret from step 2. 5. Create a webhook in GitLab. Set the URL to the URL of your app's server. Set the secret token to the generated secret from step 2.
In the "Trigger" section, check the comments and merge request events boxes. In the "Trigger" section, check the comments and merge request events boxes.
6. Test your installation by opening a merge request or commenting or a merge request using one of CodiumAI's commands. 6. Test your installation by opening a merge request or commenting or a merge request using one of CodiumAI's commands.

View File

@ -0,0 +1,37 @@
## Install Hosted PR-Agent Pro for GitLab (Teams & Enterprise)
### Step 1
Acquire a personal, project or group level access token. Enable the “api” scope in order to allow PR-Agent to read pull requests, comment and respond to requests.
<kbd><img src=https://www.codium.ai/images/pr_agent/gitlab_pro_pat.png></kbd>
Store the token in a safe place, you wont be able to access it again after it was generated.
### Step 2
Generate a shared secret and link it to the access token. Browse to [https://register.gitlab.pr-agent.codium.ai](https://register.gitlab.pr-agent.codium.ai).
Fill in your generated GitLab token and your company or personal name in the appropriate fields and click "Submit".
You should see "Success!" displayed above the Submit button, and a shared secret will be generated. Store it in a safe place, you wont be able to access it again after it was generated.
### Step 3
Install a webhook for your repository or groups, by clicking “webhooks” on the settings menu. Click the “Add new webhook” button.
<kbd><img src=https://www.codium.ai/images/pr_agent/gitlab_pro_add_webhook.png></kbd>
In the webhook definition form, fill in the following fields:
URL: https://pro.gitlab.pr-agent.codium.ai/webhook
Secret token: Your CodiumAI key
Trigger: Check the comments and merge request events boxes.
Enable SSL verification: Check the box.
<kbd><img src=https://www.codium.ai/images/pr_agent/gitlab_pro_webhooks.png></kbd>
### Step 4
Youre all set!
Open a new merge request or add a MR comment with one of PR-Agents commands such as /review, /describe or /improve.

View File

@ -170,7 +170,6 @@ To use Azure DevOps provider use the following settings in configuration.toml:
``` ```
[config] [config]
git_provider="azure" git_provider="azure"
use_repo_settings_file=false
``` ```
Azure DevOps provider supports [PAT token](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows) or [DefaultAzureCredential](https://learn.microsoft.com/en-us/azure/developer/python/sdk/authentication-overview#authentication-in-server-environments) authentication. Azure DevOps provider supports [PAT token](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows) or [DefaultAzureCredential](https://learn.microsoft.com/en-us/azure/developer/python/sdk/authentication-overview#authentication-in-server-environments) authentication.

View File

@ -7,6 +7,7 @@ nav:
- Locally: 'installation/locally.md' - Locally: 'installation/locally.md'
- GitHub: 'installation/github.md' - GitHub: 'installation/github.md'
- GitLab: 'installation/gitlab.md' - GitLab: 'installation/gitlab.md'
- 💎 GitLab Pro: 'installation/gitlab_pro.md'
- BitBucket: 'installation/bitbucket.md' - BitBucket: 'installation/bitbucket.md'
- Azure DevOps: 'installation/azure.md' - Azure DevOps: 'installation/azure.md'
- Usage Guide: - Usage Guide:
@ -34,7 +35,7 @@ nav:
- Core Abilities: 'core-abilities/index.md' - Core Abilities: 'core-abilities/index.md'
theme: theme:
logo: assets/logo.png logo: assets/logo.svg
favicon: assets/favicon.ico favicon: assets/favicon.ico
name: material name: material
features: features:
@ -92,6 +93,9 @@ extra:
link: https://twitter.com/CodiumAI link: https://twitter.com/CodiumAI
- icon: fontawesome/brands/instagram - icon: fontawesome/brands/instagram
link: https://www.instagram.com/codiumai/ link: https://www.instagram.com/codiumai/
analytics:
provider: google
property: ${{ secrets.GOOGLE_ANALYTICS_ID }}
extra_css: extra_css:
- css/custom.css - css/custom.css

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Footer Customization</title> <title>Footer</title>
<style> <style>
body { body {
margin: 0; margin: 0;
@ -27,13 +27,7 @@
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
.footer-links {
display: flex;
justify-content: center;
gap: 20px;
margin: 10px 0;
}
.footer-links, .social-icons { .footer-links, .social-icons {
padding: 0; padding: 0;
list-style-type: none; list-style-type: none;
@ -42,25 +36,46 @@
gap: 20px; gap: 20px;
align-items: center; align-items: center;
} }
.footer-links a:hover {
color: #AEA1F1; .footer-links a:hover, .social-icons a:hover {
}
.social-icons {
display: flex;
gap: 20px;
align-items: center;
}
.social-icons a:hover {
color: #AEA1F1; color: #AEA1F1;
} }
.social-icons svg { .social-icons svg {
width: 24px; width: 24px;
height: auto; height: auto;
fill: white; fill: white;
} }
.footer-text { .footer-text {
width: 240px; width: 240px;
} }
@media (max-width: 768px) {
.container {
flex-direction: column;
align-items: center;
text-align: center;
}
.footer-links, .social-icons, .footer-text {
width: 100%;
justify-content: center;
margin: 10px 0;
}
.footer-links {
order: 1;
}
.social-icons {
order: 2;
}
.footer-text {
order: 3;
}
}
</style> </style>
</head> </head>
<body> <body>

View File

@ -73,6 +73,18 @@ class LiteLLMAIHandler(BaseAiHandler):
region_name=get_settings().aws.bedrock_region, region_name=get_settings().aws.bedrock_region,
) )
def prepare_logs(self, response, system, user, resp, finish_reason):
response_log = response.dict().copy()
response_log['system'] = system
response_log['user'] = user
response_log['output'] = resp
response_log['finish_reason'] = finish_reason
if hasattr(self, 'main_pr_language'):
response_log['main_pr_language'] = self.main_pr_language
else:
response_log['main_pr_language'] = 'unknown'
return response_log
@property @property
def deployment_id(self): def deployment_id(self):
""" """
@ -125,10 +137,13 @@ class LiteLLMAIHandler(BaseAiHandler):
else: else:
resp = response["choices"][0]['message']['content'] resp = response["choices"][0]['message']['content']
finish_reason = response["choices"][0]["finish_reason"] finish_reason = response["choices"][0]["finish_reason"]
# usage = response.get("usage")
get_logger().debug(f"\nAI response:\n{resp}") get_logger().debug(f"\nAI response:\n{resp}")
get_logger().debug("Full_response", artifact=response)
# log the full response for debugging
response_log = self.prepare_logs(response, system, user, resp, finish_reason)
get_logger().debug("Full_response", artifact=response_log)
# for CLI debugging
if get_settings().config.verbosity_level >= 2: if get_settings().config.verbosity_level >= 2:
get_logger().info(f"\nAI response:\n{resp}") get_logger().info(f"\nAI response:\n{resp}")

View File

@ -60,7 +60,7 @@ def unique_strings(input_list: List[str]) -> List[str]:
return unique_list return unique_list
def convert_to_markdown(output_data: dict, gfm_supported: bool = True) -> str: def convert_to_markdown(output_data: dict, gfm_supported: bool = True, incremental_review=None) -> str:
""" """
Convert a dictionary of data into markdown format. Convert a dictionary of data into markdown format.
Args: Args:
@ -81,7 +81,11 @@ def convert_to_markdown(output_data: dict, gfm_supported: bool = True) -> str:
"Estimated effort to review [1-5]": "⏱️", "Estimated effort to review [1-5]": "⏱️",
} }
markdown_text = "" markdown_text = ""
markdown_text += f"## PR Review\n\n" if not incremental_review:
markdown_text += f"## PR Review\n\n"
else:
markdown_text += f"## Incremental PR Review\n\n"
markdown_text += f"⏮️ Review for commits since previous PR-Agent review {incremental_review}.\n\n"
if gfm_supported: if gfm_supported:
markdown_text += "<table>\n<tr>\n" markdown_text += "<table>\n<tr>\n"
# markdown_text += """<td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Feedback&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td> <td></td></tr>""" # markdown_text += """<td> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Feedback&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td> <td></td></tr>"""

View File

@ -165,7 +165,7 @@ class AzureDevopsProvider(GitProvider):
except Exception as e: except Exception as e:
get_logger().exception(f"Failed to publish labels, error: {e}") get_logger().exception(f"Failed to publish labels, error: {e}")
def get_pr_labels(self): def get_pr_labels(self, update=False):
try: try:
labels = self.azure_devops_client.get_pull_request_labels( labels = self.azure_devops_client.get_pull_request_labels(
project=self.workspace_slug, project=self.workspace_slug,

View File

@ -404,5 +404,5 @@ class BitbucketProvider(GitProvider):
pass pass
# bitbucket does not support labels # bitbucket does not support labels
def get_pr_labels(self): def get_pr_labels(self, update=False):
pass pass

View File

@ -347,7 +347,7 @@ class BitbucketServerProvider(GitProvider):
pass pass
# bitbucket does not support labels # bitbucket does not support labels
def get_pr_labels(self): def get_pr_labels(self, update=False):
pass pass
def _get_pr_comments_url(self): def _get_pr_comments_url(self):

View File

@ -216,7 +216,7 @@ class CodeCommitProvider(GitProvider):
def publish_labels(self, labels): def publish_labels(self, labels):
return [""] # not implemented yet return [""] # not implemented yet
def get_pr_labels(self): def get_pr_labels(self, update=False):
return [""] # not implemented yet return [""] # not implemented yet
def remove_initial_comment(self): def remove_initial_comment(self):

View File

@ -208,7 +208,7 @@ class GerritProvider(GitProvider):
Comment = namedtuple('Comment', ['body']) Comment = namedtuple('Comment', ['body'])
return Comments([Comment(c['message']) for c in reversed(comments)]) return Comments([Comment(c['message']) for c in reversed(comments)])
def get_pr_labels(self): def get_pr_labels(self, update=False):
raise NotImplementedError( raise NotImplementedError(
'Getting labels is not implemented for the gerrit provider') 'Getting labels is not implemented for the gerrit provider')

View File

@ -164,7 +164,7 @@ class GitProvider(ABC):
pass pass
@abstractmethod @abstractmethod
def get_pr_labels(self): def get_pr_labels(self, update=False):
pass pass
def get_repo_labels(self): def get_repo_labels(self):

View File

@ -38,6 +38,7 @@ class GithubProvider(GitProvider):
self.set_pr(pr_url) self.set_pr(pr_url)
self.pr_commits = list(self.pr.get_commits()) self.pr_commits = list(self.pr.get_commits())
if self.incremental.is_incremental: if self.incremental.is_incremental:
self.unreviewed_files_set = dict()
self.get_incremental_commits() self.get_incremental_commits()
self.last_commit_id = self.pr_commits[-1] self.last_commit_id = self.pr_commits[-1]
self.pr_url = self.get_pr_url() # pr_url for github actions can be as api.github.com, so we need to get the url from the pr object self.pr_url = self.get_pr_url() # pr_url for github actions can be as api.github.com, so we need to get the url from the pr object
@ -62,14 +63,15 @@ class GithubProvider(GitProvider):
if self.previous_review: if self.previous_review:
self.incremental.commits_range = self.get_commit_range() self.incremental.commits_range = self.get_commit_range()
# Get all files changed during the commit range # Get all files changed during the commit range
self.file_set = dict()
for commit in self.incremental.commits_range: for commit in self.incremental.commits_range:
if commit.commit.message.startswith(f"Merge branch '{self._get_repo().default_branch}'"): if commit.commit.message.startswith(f"Merge branch '{self._get_repo().default_branch}'"):
get_logger().info(f"Skipping merge commit {commit.commit.message}") get_logger().info(f"Skipping merge commit {commit.commit.message}")
continue continue
self.file_set.update({file.filename: file for file in commit.files}) self.unreviewed_files_set.update({file.filename: file for file in commit.files})
else: else:
raise ValueError("No previous review found") get_logger().info("No previous review found, will review the entire PR")
self.incremental.is_incremental = False
def get_commit_range(self): def get_commit_range(self):
last_review_time = self.previous_review.created_at last_review_time = self.previous_review.created_at
@ -98,8 +100,8 @@ class GithubProvider(GitProvider):
return self.comments[index] return self.comments[index]
def get_files(self): def get_files(self):
if self.incremental.is_incremental and self.file_set: if self.incremental.is_incremental and self.unreviewed_files_set:
return self.file_set.values() return self.unreviewed_files_set.values()
try: try:
git_files = context.get("git_files", None) git_files = context.get("git_files", None)
if git_files: if git_files:
@ -150,10 +152,10 @@ class GithubProvider(GitProvider):
new_file_content_str = self._get_pr_file_content(file, self.pr.head.sha) # communication with GitHub new_file_content_str = self._get_pr_file_content(file, self.pr.head.sha) # communication with GitHub
patch = file.patch patch = file.patch
if self.incremental.is_incremental and self.file_set: if self.incremental.is_incremental and self.unreviewed_files_set:
original_file_content_str = self._get_pr_file_content(file, self.incremental.last_seen_commit_sha) original_file_content_str = self._get_pr_file_content(file, self.incremental.last_seen_commit_sha)
patch = load_large_diff(file.filename, new_file_content_str, original_file_content_str) patch = load_large_diff(file.filename, new_file_content_str, original_file_content_str)
self.file_set[file.filename] = patch self.unreviewed_files_set[file.filename] = patch
else: else:
original_file_content_str = self._get_pr_file_content(file, self.pr.base.sha) original_file_content_str = self._get_pr_file_content(file, self.pr.base.sha)
if not patch: if not patch:
@ -653,9 +655,16 @@ class GithubProvider(GitProvider):
except Exception as e: except Exception as e:
get_logger().exception(f"Failed to publish labels, error: {e}") get_logger().exception(f"Failed to publish labels, error: {e}")
def get_pr_labels(self): def get_pr_labels(self, update=False):
try: try:
return [label.name for label in self.pr.labels] if not update:
labels =self.pr.labels
return [label.name for label in labels]
else: # obtain the latest labels. Maybe they changed while the AI was running
headers, labels = self.pr._requester.requestJsonAndCheck(
"GET", f"{self.pr.issue_url}/labels")
return [label['name'] for label in labels]
except Exception as e: except Exception as e:
get_logger().exception(f"Failed to get labels, error: {e}") get_logger().exception(f"Failed to get labels, error: {e}")
return [] return []

View File

@ -419,7 +419,7 @@ class GitLabProvider(GitProvider):
def publish_inline_comments(self, comments: list[dict]): def publish_inline_comments(self, comments: list[dict]):
pass pass
def get_pr_labels(self): def get_pr_labels(self, update=False):
return self.mr.labels return self.mr.labels
def get_repo_labels(self): def get_repo_labels(self):

View File

@ -176,5 +176,5 @@ class LocalGitProvider(GitProvider):
def get_issue_comments(self): def get_issue_comments(self):
raise NotImplementedError('Getting issue comments is not implemented for the local git provider') raise NotImplementedError('Getting issue comments is not implemented for the local git provider')
def get_pr_labels(self): def get_pr_labels(self, update=False):
raise NotImplementedError('Getting labels is not implemented for the local git provider') raise NotImplementedError('Getting labels is not implemented for the local git provider')

View File

@ -6,6 +6,8 @@ import json
import os import os
import re import re
import secrets import secrets
from urllib.parse import unquote
import uvicorn import uvicorn
from fastapi import APIRouter, Depends, FastAPI, HTTPException from fastapi import APIRouter, Depends, FastAPI, HTTPException
from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.security import HTTPBasic, HTTPBasicCredentials
@ -81,7 +83,7 @@ async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
actions = [] actions = []
if data["eventType"] == "git.pullrequest.created": if data["eventType"] == "git.pullrequest.created":
# API V1 (latest) # API V1 (latest)
pr_url = data["resource"]["_links"]["web"]["href"].replace("_apis/git/repositories", "_git") pr_url = unquote(data["resource"]["_links"]["web"]["href"].replace("_apis/git/repositories", "_git"))
log_context["event"] = data["eventType"] log_context["event"] = data["eventType"]
log_context["api_url"] = pr_url log_context["api_url"] = pr_url
await _perform_commands_azure("pr_commands", PRAgent(), pr_url, log_context) await _perform_commands_azure("pr_commands", PRAgent(), pr_url, log_context)
@ -90,7 +92,7 @@ async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
if available_commands_rgx.match(data["resource"]["comment"]["content"]): if available_commands_rgx.match(data["resource"]["comment"]["content"]):
if(data["resourceVersion"] == "2.0"): if(data["resourceVersion"] == "2.0"):
repo = data["resource"]["pullRequest"]["repository"]["webUrl"] repo = data["resource"]["pullRequest"]["repository"]["webUrl"]
pr_url = f'{repo}/pullrequest/{data["resource"]["pullRequest"]["pullRequestId"]}' pr_url = unquote(f'{repo}/pullrequest/{data["resource"]["pullRequest"]["pullRequestId"]}')
actions = [data["resource"]["comment"]["content"]] actions = [data["resource"]["comment"]["content"]]
else: else:
# API V1 not supported as it does not contain the PR URL # API V1 not supported as it does not contain the PR URL

View File

@ -28,7 +28,7 @@ from pr_agent.tools.pr_code_suggestions import PRCodeSuggestions
from pr_agent.tools.pr_description import PRDescription from pr_agent.tools.pr_description import PRDescription
from pr_agent.tools.pr_reviewer import PRReviewer from pr_agent.tools.pr_reviewer import PRReviewer
setup_logger(fmt=LoggingFormat.JSON) setup_logger(fmt=LoggingFormat.JSON, level="DEBUG")
router = APIRouter() router = APIRouter()
secret_provider = get_secret_provider() if get_settings().get("CONFIG.SECRET_PROVIDER") else None secret_provider = get_secret_provider() if get_settings().get("CONFIG.SECRET_PROVIDER") else None

View File

@ -8,7 +8,7 @@ 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.log import LoggingFormat, get_logger, setup_logger from pr_agent.log import LoggingFormat, get_logger, setup_logger
setup_logger(fmt=LoggingFormat.JSON) setup_logger(fmt=LoggingFormat.JSON, level="DEBUG")
NOTIFICATION_URL = "https://api.github.com/notifications" NOTIFICATION_URL = "https://api.github.com/notifications"

View File

@ -17,7 +17,7 @@ from pr_agent.git_providers.utils import apply_repo_settings
from pr_agent.log import LoggingFormat, get_logger, setup_logger from pr_agent.log import LoggingFormat, get_logger, setup_logger
from pr_agent.secret_providers import get_secret_provider from pr_agent.secret_providers import get_secret_provider
setup_logger(fmt=LoggingFormat.JSON) setup_logger(fmt=LoggingFormat.JSON, level="DEBUG")
router = APIRouter() router = APIRouter()
secret_provider = get_secret_provider() if get_settings().get("CONFIG.SECRET_PROVIDER") else None secret_provider = get_secret_provider() if get_settings().get("CONFIG.SECRET_PROVIDER") else None
@ -51,6 +51,9 @@ async def _perform_commands_gitlab(commands_conf: str, agent: PRAgent, api_url:
@router.post("/webhook") @router.post("/webhook")
async def gitlab_webhook(background_tasks: BackgroundTasks, request: Request): async def gitlab_webhook(background_tasks: BackgroundTasks, request: Request):
log_context = {"server_type": "gitlab_app"} log_context = {"server_type": "gitlab_app"}
get_logger().debug("Received a GitLab webhook")
# Check if the request is authorized
if request.headers.get("X-Gitlab-Token") and secret_provider: if request.headers.get("X-Gitlab-Token") and secret_provider:
request_token = request.headers.get("X-Gitlab-Token") request_token = request.headers.get("X-Gitlab-Token")
secret = secret_provider.get_secret(request_token) secret = secret_provider.get_secret(request_token)
@ -66,46 +69,57 @@ async def gitlab_webhook(background_tasks: BackgroundTasks, request: Request):
elif get_settings().get("GITLAB.SHARED_SECRET"): elif get_settings().get("GITLAB.SHARED_SECRET"):
secret = get_settings().get("GITLAB.SHARED_SECRET") secret = get_settings().get("GITLAB.SHARED_SECRET")
if not request.headers.get("X-Gitlab-Token") == secret: if not request.headers.get("X-Gitlab-Token") == secret:
get_logger().error(f"Failed to validate secret")
return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content=jsonable_encoder({"message": "unauthorized"})) return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content=jsonable_encoder({"message": "unauthorized"}))
else: else:
get_logger().error(f"Failed to validate secret")
return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content=jsonable_encoder({"message": "unauthorized"})) return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content=jsonable_encoder({"message": "unauthorized"}))
gitlab_token = get_settings().get("GITLAB.PERSONAL_ACCESS_TOKEN", None) gitlab_token = get_settings().get("GITLAB.PERSONAL_ACCESS_TOKEN", None)
if not gitlab_token: if not gitlab_token:
get_logger().error(f"No gitlab token found")
return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content=jsonable_encoder({"message": "unauthorized"})) return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED, content=jsonable_encoder({"message": "unauthorized"}))
data = await request.json() data = await request.json()
get_logger().info(json.dumps(data)) get_logger().info("GitLab data", artifact=data)
if data.get('object_kind') == 'merge_request' and data['object_attributes'].get('action') in ['open', 'reopen']: if data.get('object_kind') == 'merge_request' and data['object_attributes'].get('action') in ['open', 'reopen']:
get_logger().info(f"A merge request has been opened: {data['object_attributes'].get('title')}")
url = data['object_attributes'].get('url') url = data['object_attributes'].get('url')
get_logger().info(f"New merge request: {url}")
await _perform_commands_gitlab("pr_commands", PRAgent(), url, log_context) await _perform_commands_gitlab("pr_commands", PRAgent(), url, log_context)
# handle_request(background_tasks, url, "/review", log_context) elif data.get('object_kind') == 'note' and data['event_type'] == 'note': # comment on MR
elif data.get('object_kind') == 'note' and data['event_type'] == 'note':
if 'merge_request' in data: if 'merge_request' in data:
mr = data['merge_request'] mr = data['merge_request']
url = mr.get('url') url = mr.get('url')
get_logger().info(f"A comment has been added to a merge request: {url}")
body = data.get('object_attributes', {}).get('note') body = data.get('object_attributes', {}).get('note')
if data.get('object_attributes', {}).get('type') == 'DiffNote' and '/ask' in body: if data.get('object_attributes', {}).get('type') == 'DiffNote' and '/ask' in body: # /ask_line
line_range_ = data['object_attributes']['position']['line_range'] body = handle_ask_line(body, data)
# if line_range_['start']['type'] == 'new':
start_line = line_range_['start']['new_line']
end_line = line_range_['end']['new_line']
# else:
# start_line = line_range_['start']['old_line']
# end_line = line_range_['end']['old_line']
question = body.replace('/ask', '').strip()
path = data['object_attributes']['position']['new_path']
side = 'RIGHT'# if line_range_['start']['type'] == 'new' else 'LEFT'
comment_id = data['object_attributes']["discussion_id"]
get_logger().info(f"Handling line comment")
body = f"/ask_line --line_start={start_line} --line_end={end_line} --side={side} --file_name={path} --comment_id={comment_id} {question}"
handle_request(background_tasks, url, body, log_context) handle_request(background_tasks, url, body, log_context)
return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder({"message": "success"})) return JSONResponse(status_code=status.HTTP_200_OK, content=jsonable_encoder({"message": "success"}))
def handle_ask_line(body, data):
try:
line_range_ = data['object_attributes']['position']['line_range']
# if line_range_['start']['type'] == 'new':
start_line = line_range_['start']['new_line']
end_line = line_range_['end']['new_line']
# else:
# start_line = line_range_['start']['old_line']
# end_line = line_range_['end']['old_line']
question = body.replace('/ask', '').strip()
path = data['object_attributes']['position']['new_path']
side = 'RIGHT' # if line_range_['start']['type'] == 'new' else 'LEFT'
comment_id = data['object_attributes']["discussion_id"]
get_logger().info(f"Handling line comment")
body = f"/ask_line --line_start={start_line} --line_end={end_line} --side={side} --file_name={path} --comment_id={comment_id} {question}"
except Exception as e:
get_logger().error(f"Failed to handle ask line comment: {e}")
return body
@router.get("/") @router.get("/")
async def root(): async def root():
return {"status": "ok"} return {"status": "ok"}

View File

@ -9,7 +9,7 @@ class HelpMessage:
"> - **/add_docs** 💎: Generate docstring for new components introduced in the PR. \n" \ "> - **/add_docs** 💎: Generate docstring for new components introduced in the PR. \n" \
"> - **/generate_labels** 💎: Generate labels for the PR based on the PR's contents. \n" \ "> - **/generate_labels** 💎: Generate labels for the PR based on the PR's contents. \n" \
"> - **/analyze** 💎: Automatically analyzes the PR, and presents changes walkthrough for each component. \n\n" \ "> - **/analyze** 💎: Automatically analyzes the PR, and presents changes walkthrough for each component. \n\n" \
">See the [tools guide](https://github.com/Codium-ai/pr-agent/blob/main/docs/TOOLS_GUIDE.md) for more details.\n" \ ">See the [tools guide](https://pr-agent-docs.codium.ai/tools/) for more details.\n" \
">To list the possible configuration parameters, add a **/config** comment. \n" ">To list the possible configuration parameters, add a **/config** comment. \n"
return commands_text return commands_text
@ -22,13 +22,13 @@ class HelpMessage:
@staticmethod @staticmethod
def get_review_usage_guide(): def get_review_usage_guide():
output ="**Overview:**\n" output ="**Overview:**\n"
output +="The `review` tool scans the PR code changes, and generates a PR review. The tool can be triggered [automatically](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) every time a new PR is opened, or can be invoked manually by commenting on any PR.\n" output +="The `review` tool scans the PR code changes, and generates a PR review. The tool can be triggered [automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) every time a new PR is opened, or can be invoked manually by commenting on any PR.\n"
output +="""\ output +="""\
When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L19) related to the review tool (`pr_reviewer` section), use the following template: When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L19) related to the review tool (`pr_reviewer` section), use the following template:
``` ```
/review --pr_reviewer.some_config1=... --pr_reviewer.some_config2=... /review --pr_reviewer.some_config1=... --pr_reviewer.some_config2=...
``` ```
With a [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-app), use the following template: With a [configuration file](https://pr-agent-docs.codium.ai/usage-guide/configuration_options/), use the following template:
``` ```
[pr_reviewer] [pr_reviewer]
some_config1=... some_config1=...
@ -62,7 +62,7 @@ Use triple quotes to write multi-line instructions. Use bullet points to make th
# automation # automation
output += "<tr><td><details> <summary><strong> How to enable\\disable automation</strong></summary><hr>\n\n" output += "<tr><td><details> <summary><strong> How to enable\\disable automation</strong></summary><hr>\n\n"
output += """\ output += """\
- When you first install PR-Agent app, the [default mode](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) for the `review` tool is: - When you first install PR-Agent app, the [default mode](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) for the `review` tool is:
``` ```
pr_commands = ["/review", ...] pr_commands = ["/review", ...]
``` ```
@ -93,7 +93,7 @@ The `review` tool can auto-generate two specific types of labels for a PR:
output += "<tr><td><details> <summary><strong> Extra sub-tools</strong></summary><hr>\n\n" output += "<tr><td><details> <summary><strong> Extra sub-tools</strong></summary><hr>\n\n"
output += """\ output += """\
The `review` tool provides a collection of possible feedbacks about a PR. The `review` tool provides a collection of possible feedbacks about a PR.
It is recommended to review the [possible options](https://github.com/Codium-ai/pr-agent/blob/main/docs/REVIEW.md#enabledisable-features), and choose the ones relevant for your use case. It is recommended to review the [possible options](https://pr-agent-docs.codium.ai/tools/review/#enabledisable-features), and choose the ones relevant for your use case.
Some of the feature that are disabled by default are quite useful, and should be considered for enabling. For example: Some of the feature that are disabled by default are quite useful, and should be considered for enabling. For example:
`require_score_review`, `require_soc2_ticket`, `require_can_be_split_review`, and more. `require_score_review`, `require_soc2_ticket`, `require_can_be_split_review`, and more.
""" """
@ -131,7 +131,7 @@ maximal_review_effort = 5
output += "</table>" output += "</table>"
output += f"\n\nSee the [review usage](https://github.com/Codium-ai/pr-agent/blob/main/docs/REVIEW.md) page for a comprehensive guide on using this tool.\n\n" output += f"\n\nSee the [review usage](https://pr-agent-docs.codium.ai/tools/review/) page for a comprehensive guide on using this tool.\n\n"
return output return output
@ -141,14 +141,14 @@ maximal_review_effort = 5
def get_describe_usage_guide(): def get_describe_usage_guide():
output = "**Overview:**\n" output = "**Overview:**\n"
output += "The `describe` tool scans the PR code changes, and generates a description for the PR - title, type, summary, walkthrough and labels. " output += "The `describe` tool scans the PR code changes, and generates a description for the PR - title, type, summary, walkthrough and labels. "
output += "The tool can be triggered [automatically](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) every time a new PR is opened, or can be invoked manually by commenting on a PR.\n" output += "The tool can be triggered [automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) every time a new PR is opened, or can be invoked manually by commenting on a PR.\n"
output += """\ output += """\
When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L46) related to the describe tool (`pr_description` section), use the following template: When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L46) related to the describe tool (`pr_description` section), use the following template:
``` ```
/describe --pr_description.some_config1=... --pr_description.some_config2=... /describe --pr_description.some_config1=... --pr_description.some_config2=...
``` ```
With a [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-app), use the following template: With a [configuration file](https://pr-agent-docs.codium.ai/usage-guide/configuration_options/), use the following template:
``` ```
[pr_description] [pr_description]
some_config1=... some_config1=...
@ -160,7 +160,7 @@ some_config2=...
# automation # automation
output += "<tr><td><details> <summary><strong> Enabling\\disabling automation </strong></summary><hr>\n\n" output += "<tr><td><details> <summary><strong> Enabling\\disabling automation </strong></summary><hr>\n\n"
output += """\ output += """\
- When you first install the app, the [default mode](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) for the describe tool is: - When you first install the app, the [default mode](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) for the describe tool is:
``` ```
pr_commands = ["/describe --pr_description.add_original_user_description=true" pr_commands = ["/describe --pr_description.add_original_user_description=true"
"--pr_description.keep_original_user_title=true", ...] "--pr_description.keep_original_user_title=true", ...]
@ -186,7 +186,7 @@ Note that when markers are enabled, if the original PR description does not cont
output += """\ output += """\
The default labels of the `describe` tool are quite generic: [`Bug fix`, `Tests`, `Enhancement`, `Documentation`, `Other`]. The default labels of the `describe` tool are quite generic: [`Bug fix`, `Tests`, `Enhancement`, `Documentation`, `Other`].
If you specify [custom labels](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md#handle-custom-labels-from-the-repos-labels-page-gem) in the repo's labels page or via configuration file, you can get tailored labels for your use cases. If you specify [custom labels](https://pr-agent-docs.codium.ai/tools/describe/#handle-custom-labels-from-the-repos-labels-page) in the repo's labels page or via configuration file, you can get tailored labels for your use cases.
Examples for custom labels: Examples for custom labels:
- `Main topic:performance` - pr_agent:The main topic of this PR is performance - `Main topic:performance` - pr_agent:The main topic of this PR is performance
- `New endpoint` - pr_agent:A new endpoint was added in this PR - `New endpoint` - pr_agent:A new endpoint was added in this PR
@ -240,7 +240,7 @@ Use triple quotes to write multi-line instructions. Use bullet points to make th
output += "</table>" output += "</table>"
output += f"\n\nSee the [describe usage](https://github.com/Codium-ai/pr-agent/blob/main/docs/DESCRIBE.md) page for a comprehensive guide on using this tool.\n\n" output += f"\n\nSee the [describe usage](https://pr-agent-docs.codium.ai/tools/describe/) page for a comprehensive guide on using this tool.\n\n"
return output return output
@ -265,7 +265,7 @@ Note that the tool does not have "memory" of previous questions, and answers eac
output += "</table>" output += "</table>"
output += f"\n\nSee the [ask usage](https://github.com/Codium-ai/pr-agent/blob/main/docs/ASK.md) page for a comprehensive guide on using this tool.\n\n" output += f"\n\nSee the [ask usage](https://pr-agent-docs.codium.ai/tools/ask/) page for a comprehensive guide on using this tool.\n\n"
return output return output
@ -274,7 +274,7 @@ Note that the tool does not have "memory" of previous questions, and answers eac
def get_improve_usage_guide(): def get_improve_usage_guide():
output = "**Overview:**\n" output = "**Overview:**\n"
output += "The `improve` tool scans the PR code changes, and automatically generates suggestions for improving the PR code. " output += "The `improve` tool scans the PR code changes, and automatically generates suggestions for improving the PR code. "
output += "The tool can be triggered [automatically](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) every time a new PR is opened, or can be invoked manually by commenting on a PR.\n" output += "The tool can be triggered [automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) every time a new PR is opened, or can be invoked manually by commenting on a PR.\n"
output += """\ output += """\
When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L69) related to the improve tool (`pr_code_suggestions` section), use the following template: When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L69) related to the improve tool (`pr_code_suggestions` section), use the following template:
@ -282,7 +282,7 @@ When commenting, to edit [configurations](https://github.com/Codium-ai/pr-agent/
/improve --pr_code_suggestions.some_config1=... --pr_code_suggestions.some_config2=... /improve --pr_code_suggestions.some_config1=... --pr_code_suggestions.some_config2=...
``` ```
With a [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#working-with-github-app), use the following template: With a [configuration file](https://pr-agent-docs.codium.ai/usage-guide/configuration_options/), use the following template:
``` ```
[pr_code_suggestions] [pr_code_suggestions]
@ -296,7 +296,7 @@ some_config2=...
# automation # automation
output += "<tr><td><details> <summary><strong> Enabling\\disabling automation </strong></summary><hr>\n\n" output += "<tr><td><details> <summary><strong> Enabling\\disabling automation </strong></summary><hr>\n\n"
output += """\ output += """\
When you first install the app, the [default mode](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools) for the improve tool is: When you first install the app, the [default mode](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-app-automatic-tools-when-a-new-pr-is-opened) for the improve tool is:
``` ```
pr_commands = ["/improve --pr_code_suggestions.summarize=true", ...] pr_commands = ["/improve --pr_code_suggestions.summarize=true", ...]
@ -335,7 +335,7 @@ Use triple quotes to write multi-line instructions. Use bullet points to make th
output += """\ 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, or use the [custom suggestions :gem:](https://github.com/Codium-ai/pr-agent/blob/main/docs/CUSTOM_SUGGESTIONS.md) tool - Recommended to use the 'extra_instructions' field to guide the model to suggestions that are more relevant to the specific needs of the project, or use the [custom suggestions :gem:](https://pr-agent-docs.codium.ai/tools/custom_suggestions/) tool
- With large PRs, best quality will be obtained by using 'improve --extended' mode. - With large PRs, best quality will be obtained by using 'improve --extended' mode.
@ -349,6 +349,6 @@ Use triple quotes to write multi-line instructions. Use bullet points to make th
output += "</table>" output += "</table>"
output += f"\n\nSee the [improve usage](https://github.com/Codium-ai/pr-agent/blob/main/docs/IMPROVE.md) page for a more comprehensive guide on using this tool.\n\n" output += f"\n\nSee the [improve usage](https://pr-agent-docs.codium.ai/tools/improve/) page for a more comprehensive guide on using this tool.\n\n"
return output return output

View File

@ -26,6 +26,8 @@ class PRAddDocs:
) )
self.ai_handler = ai_handler() self.ai_handler = ai_handler()
self.ai_handler.main_pr_language = self.main_language
self.patches_diff = None self.patches_diff = None
self.prediction = None self.prediction = None
self.cli_mode = cli_mode self.cli_mode = cli_mode

View File

@ -46,6 +46,7 @@ class PRCodeSuggestions:
num_code_suggestions = get_settings().pr_code_suggestions.num_code_suggestions num_code_suggestions = get_settings().pr_code_suggestions.num_code_suggestions
self.ai_handler = ai_handler() self.ai_handler = ai_handler()
self.ai_handler.main_pr_language = self.main_language
self.patches_diff = None self.patches_diff = None
self.prediction = None self.prediction = None
self.cli_mode = cli_mode self.cli_mode = cli_mode
@ -393,11 +394,7 @@ class PRCodeSuggestions:
for label, suggestions in suggestions_labels.items(): for label, suggestions in suggestions_labels.items():
num_suggestions=len(suggestions) num_suggestions=len(suggestions)
# pr_body += f"""<tr><td><strong>{label}</strong></td>"""
pr_body += f"""<tr><td rowspan={num_suggestions}><strong>{label.capitalize()}</strong></td>\n""" pr_body += f"""<tr><td rowspan={num_suggestions}><strong>{label.capitalize()}</strong></td>\n"""
# pr_body += f"""<td>"""
# pr_body += f"""<details><summary>{len(suggestions)} suggestions</summary>"""
# pr_body += f"""<table>"""
for i, suggestion in enumerate(suggestions): for i, suggestion in enumerate(suggestions):
relevant_file = suggestion['relevant_file'].strip() relevant_file = suggestion['relevant_file'].strip()
@ -444,11 +441,11 @@ class PRCodeSuggestions:
{example_code} {example_code}
""" """
pr_body += f"</details>" pr_body += f"</details>"
pr_body += f"</td></tr>" pr_body += f"</td></tr>"
# pr_body += "</details>" # pr_body += "</details>"
pr_body += """</td></tr>""" # pr_body += """</td></tr>"""
pr_body += """</tr></tbody></table>""" pr_body += """</tr></tbody></table>"""
return pr_body return pr_body
except Exception as e: except Exception as e:

View File

@ -41,6 +41,7 @@ class PRDescription:
# Initialize the AI handler # Initialize the AI handler
self.ai_handler = ai_handler() self.ai_handler = ai_handler()
self.ai_handler.main_pr_language = self.main_pr_language
# Initialize the variables dictionary # Initialize the variables dictionary
@ -118,11 +119,15 @@ class PRDescription:
if get_settings().config.publish_output: if get_settings().config.publish_output:
# publish labels # publish labels
if get_settings().pr_description.publish_labels and self.git_provider.is_supported("get_labels"): if get_settings().pr_description.publish_labels and self.git_provider.is_supported("get_labels"):
original_labels = self.git_provider.get_pr_labels() original_labels = self.git_provider.get_pr_labels(update=True)
get_logger().debug(f"original labels", artifact=original_labels) get_logger().debug(f"original labels", artifact=original_labels)
user_labels = get_user_labels(original_labels) user_labels = get_user_labels(original_labels)
get_logger().debug(f"published labels:\n{pr_labels + user_labels}") new_labels = pr_labels + user_labels
self.git_provider.publish_labels(pr_labels + user_labels) get_logger().debug(f"published labels", artifact=new_labels)
if sorted(new_labels) != sorted(original_labels):
self.git_provider.publish_labels(new_labels)
else:
get_logger().debug(f"Labels are the same, not updating")
# publish description # publish description
if get_settings().pr_description.publish_description_as_comment: if get_settings().pr_description.publish_description_as_comment:

View File

@ -35,7 +35,8 @@ class PRGenerateLabels:
# Initialize the AI handler # Initialize the AI handler
self.ai_handler = ai_handler() self.ai_handler = ai_handler()
self.ai_handler.main_pr_language = self.main_pr_language
# Initialize the variables dictionary # Initialize the variables dictionary
self.vars = { self.vars = {
"title": self.git_provider.pr.title, "title": self.git_provider.pr.title,

View File

@ -21,21 +21,21 @@ class PRHelpMessage:
pr_comment = "## PR Agent Walkthrough\n\n" pr_comment = "## PR Agent Walkthrough\n\n"
pr_comment += "🤖 Welcome to the PR Agent, an AI-powered tool for automated pull request analysis, feedback, suggestions and more.""" pr_comment += "🤖 Welcome to the PR Agent, an AI-powered tool for automated pull request analysis, feedback, suggestions and more."""
pr_comment += "\n\nHere is a list of tools you can use to interact with the PR Agent:\n" pr_comment += "\n\nHere is a list of tools you can use to interact with the PR Agent:\n"
base_path = "https://github.com/Codium-ai/pr-agent/tree/main/docs" base_path = "https://pr-agent-docs.codium.ai/tools"
tool_names = [] tool_names = []
tool_names.append(f"[DESCRIBE]({base_path}/DESCRIBE.md)") tool_names.append(f"[DESCRIBE]({base_path}/describe/)")
tool_names.append(f"[REVIEW]({base_path}/REVIEW.md)") tool_names.append(f"[REVIEW]({base_path}/review/)")
tool_names.append(f"[IMPROVE]({base_path}/IMPROVE.md)") tool_names.append(f"[IMPROVE]({base_path}/improve/)")
tool_names.append(f"[ANALYZE]({base_path}/Analyze.md) 💎") tool_names.append(f"[ANALYZE]({base_path}/analyze/) 💎")
tool_names.append(f"[UPDATE CHANGELOG]({base_path}/UPDATE_CHANGELOG.md)") tool_names.append(f"[UPDATE CHANGELOG]({base_path}/update_changelog/)")
tool_names.append(f"[ADD DOCUMENTATION]({base_path}/ADD_DOCUMENTATION.md) 💎") tool_names.append(f"[ADD DOCUMENTATION]({base_path}/documentation/) 💎")
tool_names.append(f"[ASK]({base_path}/ASK.md)") tool_names.append(f"[ASK]({base_path}/ask/)")
tool_names.append(f"[GENERATE CUSTOM LABELS]({base_path}/GENERATE_CUSTOM_LABELS.md)") tool_names.append(f"[GENERATE CUSTOM LABELS]({base_path}/custom_labels/)")
tool_names.append(f"[TEST]({base_path}/TEST.md) 💎") tool_names.append(f"[TEST]({base_path}/test/) 💎")
tool_names.append(f"[CI FEEDBACK]({base_path}/CI_FEEDBACK.md) 💎") tool_names.append(f"[CI FEEDBACK]({base_path}/ci_feedback/) 💎")
tool_names.append(f"[CUSTOM SUGGESTIONS]({base_path}/CUSTOM_SUGGESTIONS.md) 💎") tool_names.append(f"[CUSTOM SUGGESTIONS]({base_path}/custom_suggestions/) 💎")
tool_names.append(f"[SIMILAR ISSUE]({base_path}/SIMILAR_ISSUE.md)") tool_names.append(f"[SIMILAR ISSUE]({base_path}/similar_issues/)")
descriptions = [] descriptions = []
descriptions.append("Generates PR description - title, type, summary, code walkthrough and labels") descriptions.append("Generates PR description - title, type, summary, code walkthrough and labels")
@ -91,7 +91,7 @@ class PRHelpMessage:
for i in range(len(tool_names)): for i in range(len(tool_names)):
pr_comment += f"\n<tr><td align='center'>\n\n<strong>{tool_names[i]}</strong></td><td>{commands[i]}</td><td>{descriptions[i]}</td></tr>" pr_comment += f"\n<tr><td align='center'>\n\n<strong>{tool_names[i]}</strong></td><td>{commands[i]}</td><td>{descriptions[i]}</td></tr>"
pr_comment += "</table>\n\n" pr_comment += "</table>\n\n"
pr_comment += f"""\n\nNote that each tool be [invoked automatically](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#github-app-automatic-tools-for-pr-actions) when a new PR is opened, or called manually by [commenting on a PR](https://github.com/Codium-ai/pr-agent/blob/main/Usage.md#online-usage).""" pr_comment += f"""\n\nNote that each tool be [invoked automatically](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/) when a new PR is opened, or called manually by [commenting on a PR](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#online-usage)."""
if get_settings().config.publish_output: if get_settings().config.publish_output:
self.git_provider.publish_comment(pr_comment) self.git_provider.publish_comment(pr_comment)
except Exception as e: except Exception as e:

View File

@ -21,6 +21,8 @@ class PRInformationFromUser:
self.git_provider.get_languages(), self.git_provider.get_files() self.git_provider.get_languages(), self.git_provider.get_files()
) )
self.ai_handler = ai_handler() self.ai_handler = ai_handler()
self.ai_handler.main_pr_language = self.main_pr_language
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(),

View File

@ -22,8 +22,11 @@ class PR_LineQuestions:
def __init__(self, pr_url: str, args=None, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler): def __init__(self, pr_url: str, args=None, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
self.question_str = self.parse_args(args) self.question_str = self.parse_args(args)
self.git_provider = get_git_provider()(pr_url) self.git_provider = get_git_provider()(pr_url)
self.main_pr_language = get_main_pr_language(
self.git_provider.get_languages(), self.git_provider.get_files()
)
self.ai_handler = ai_handler() self.ai_handler = ai_handler()
self.ai_handler.main_pr_language = self.main_pr_language
self.vars = { self.vars = {
"title": self.git_provider.pr.title, "title": self.git_provider.pr.title,

View File

@ -22,6 +22,8 @@ class PRQuestions:
self.git_provider.get_languages(), self.git_provider.get_files() self.git_provider.get_languages(), self.git_provider.get_files()
) )
self.ai_handler = ai_handler() self.ai_handler = ai_handler()
self.ai_handler.main_pr_language = self.main_pr_language
self.question_str = question_str self.question_str = question_str
self.vars = { self.vars = {
"title": self.git_provider.pr.title, "title": self.git_provider.pr.title,

View File

@ -46,6 +46,8 @@ class PRReviewer:
if self.is_answer and not self.git_provider.is_supported("get_issue_comments"): if self.is_answer and not self.git_provider.is_supported("get_issue_comments"):
raise Exception(f"Answer mode is not supported for {get_settings().config.git_provider} for now") raise Exception(f"Answer mode is not supported for {get_settings().config.git_provider} for now")
self.ai_handler = ai_handler() self.ai_handler = ai_handler()
self.ai_handler.main_pr_language = self.main_language
self.patches_diff = None self.patches_diff = None
self.prediction = None self.prediction = None
@ -108,6 +110,17 @@ class PRReviewer:
relevant_configs = {'pr_reviewer': dict(get_settings().pr_reviewer), relevant_configs = {'pr_reviewer': dict(get_settings().pr_reviewer),
'config': dict(get_settings().config)} 'config': dict(get_settings().config)}
get_logger().debug("Relevant configs", artifacts=relevant_configs) get_logger().debug("Relevant configs", artifacts=relevant_configs)
if self.incremental.is_incremental and hasattr(self.git_provider, "unreviewed_files_set") and not self.git_provider.unreviewed_files_set:
get_logger().info(f"Incremental review is enabled for {self.pr_url} but there are no new files")
previous_review_url = ""
if hasattr(self.git_provider, "previous_review"):
previous_review_url = self.git_provider.previous_review.html_url
if get_settings().config.publish_output:
self.git_provider.publish_comment(f"Incremental Review Skipped\n"
f"No files were changed since the [previous PR Review]({previous_review_url})")
return None
if get_settings().config.publish_output: if get_settings().config.publish_output:
self.git_provider.publish_comment("Preparing review...", is_temporary=True) self.git_provider.publish_comment("Preparing review...", is_temporary=True)
@ -208,21 +221,15 @@ class PRReviewer:
pass pass
incremental_review_markdown_text = None
# Add incremental review section # Add incremental review section
if self.incremental.is_incremental: if self.incremental.is_incremental:
last_commit_url = f"{self.git_provider.get_pr_url()}/commits/" \ last_commit_url = f"{self.git_provider.get_pr_url()}/commits/" \
f"{self.git_provider.incremental.first_new_commit_sha}" f"{self.git_provider.incremental.first_new_commit_sha}"
last_commit_msg = self.incremental.commits_range[0].commit.message if self.incremental.commits_range else ""
incremental_review_markdown_text = f"Starting from commit {last_commit_url}" incremental_review_markdown_text = f"Starting from commit {last_commit_url}"
if last_commit_msg:
replacement = last_commit_msg.splitlines(keepends=False)[0].replace('_', r'\_')
incremental_review_markdown_text += f" \n_({replacement})_"
data = OrderedDict(data)
data.update({'Incremental PR Review': {
"⏮️ Review for commits since previous PR-Agent review": incremental_review_markdown_text}})
data.move_to_end('Incremental PR Review', last=False)
markdown_text = convert_to_markdown(data, self.git_provider.is_supported("gfm_markdown")) markdown_text = convert_to_markdown(data, self.git_provider.is_supported("gfm_markdown"),
incremental_review_markdown_text)
# Add help text if gfm_markdown is supported # Add help text if gfm_markdown is supported
if self.git_provider.is_supported("gfm_markdown") and get_settings().pr_reviewer.enable_help_text: if self.git_provider.is_supported("gfm_markdown") and get_settings().pr_reviewer.enable_help_text:
@ -320,6 +327,10 @@ class PRReviewer:
if self.is_auto and not self.incremental.first_new_commit_sha: if self.is_auto and not self.incremental.first_new_commit_sha:
get_logger().info(f"Incremental review is enabled for {self.pr_url} but there are no new commits") get_logger().info(f"Incremental review is enabled for {self.pr_url} but there are no new commits")
return False return False
if not hasattr(self.git_provider, "get_incremental_commits"):
get_logger().info(f"Incremental review is not supported for {get_settings().config.git_provider}")
return False
# checking if there are enough commits to start the review # checking if there are enough commits to start the review
num_new_commits = len(self.incremental.commits_range) num_new_commits = len(self.incremental.commits_range)
num_commits_threshold = get_settings().pr_reviewer.minimal_commits_for_incremental_review num_commits_threshold = get_settings().pr_reviewer.minimal_commits_for_incremental_review
@ -361,17 +372,20 @@ class PRReviewer:
if security_concerns_bool: if security_concerns_bool:
review_labels.append('Possible security concern') review_labels.append('Possible security concern')
current_labels = self.git_provider.get_pr_labels() current_labels = self.git_provider.get_pr_labels(update=True)
get_logger().debug(f"Current labels:\n{current_labels}")
if current_labels: if current_labels:
current_labels_filtered = [label for label in current_labels if current_labels_filtered = [label for label in current_labels if
not label.lower().startswith('review effort [1-5]:') and not label.lower().startswith( not label.lower().startswith('review effort [1-5]:') and not label.lower().startswith(
'possible security concern')] 'possible security concern')]
else: else:
current_labels_filtered = [] current_labels_filtered = []
if current_labels or review_labels: new_labels = review_labels + current_labels_filtered
get_logger().debug(f"Current labels:\n{current_labels}") if (current_labels or review_labels) and sorted(new_labels) != sorted(current_labels):
get_logger().info(f"Setting review labels:\n{review_labels + current_labels_filtered}") get_logger().info(f"Setting review labels:\n{review_labels + current_labels_filtered}")
self.git_provider.publish_labels(review_labels + current_labels_filtered) self.git_provider.publish_labels(new_labels)
else:
get_logger().info(f"Review labels are already set:\n{review_labels + current_labels_filtered}")
except Exception as e: 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}")

View File

@ -26,7 +26,10 @@ class PRUpdateChangelog:
) )
self.commit_changelog = get_settings().pr_update_changelog.push_changelog_changes self.commit_changelog = get_settings().pr_update_changelog.push_changelog_changes
self._get_changlog_file() # self.changelog_file_str self._get_changlog_file() # self.changelog_file_str
self.ai_handler = ai_handler() self.ai_handler = ai_handler()
self.ai_handler.main_pr_language = self.main_language
self.patches_diff = None self.patches_diff = None
self.prediction = None self.prediction = None
self.cli_mode = cli_mode self.cli_mode = cli_mode