Compare commits

...

78 Commits

Author SHA1 Message Date
Tal
9c87056263 Merge pull request #1890 from abishlal/feature/support-tickets-context-in-azdo
Enhance Azure DevOps Integration with Work Item Ticket Retrieval and Comment Thread Updates
2025-06-25 20:40:22 +03:00
3251f19a19 fix: change comment thread status to closed when publishing comments
Signed-off-by: abishlal <abishlalns03@gmail.com>
2025-06-25 20:59:34 +05:30
299a2c89d1 feat: add tags extraction from work item fields in Azure DevOps provider
Signed-off-by: abishlal <abishlalns03@gmail.com>
2025-06-25 20:57:56 +05:30
Tal
f166e7f497 Merge pull request #1894 from qodo-ai/es/improve_response_ai_search
More informative error message in case search returned an error
2025-06-24 20:17:52 +03:00
8dc08e4596 Modify search answers according to feedback. 2025-06-24 18:39:41 +03:00
5062543325 More code suggestion fixes 2025-06-24 13:02:12 +03:00
35e865bfb6 Implement suggestions 2025-06-24 12:57:57 +03:00
abb576c84f - More informative error message in case search returned an error
- Report no results found in case of an empty response
2025-06-24 12:34:32 +03:00
e75b863f3b docs: clarify PR feedback quota wording and formatting 2025-06-23 11:18:09 +03:00
ab80677e3a Merge remote-tracking branch 'origin/main' 2025-06-22 21:16:00 +03:00
bd7017d630 bitbucket json 2025-06-22 21:15:45 +03:00
Tal
6e2bc01294 Merge pull request #1884 from alessio-locatelli/handle_500_Internal_Server_Error
feat: wrap the command's entry point to catch all errors
2025-06-22 21:07:06 +03:00
e14834c84e docs: update PR benchmark results section header 2025-06-22 10:17:51 +03:00
915a1c563b docs: add Codex-mini model evaluation to PR benchmark results 2025-06-22 09:53:26 +03:00
bc99cf83dd feat: add a bare except to catch all errors 2025-06-21 15:34:02 +03:00
d00cbd4da7 Revert "fix: do not fail the CLI when GitLab API return 5xx code"
This reverts commit 68f78e1a30.
2025-06-21 15:28:27 +03:00
721ff18a63 Revert "fix: wrap _handle_request to handle 5xx responses"
This reverts commit 1a003fe4d3.
2025-06-21 15:27:41 +03:00
1a003fe4d3 fix: wrap _handle_request to handle 5xx responses 2025-06-21 15:26:48 +03:00
68f78e1a30 fix: do not fail the CLI when GitLab API return 5xx code 2025-06-21 15:26:48 +03:00
7759d1d3fc fix: remove redundant state and labels fields from ticket data extraction
Signed-off-by: abishlal <abishlalns03@gmail.com>
2025-06-21 17:31:24 +05:30
738f9856a4 feat: add WorkItemTrackingClient to Azure DevOps provider and update client return type
Signed-off-by: abishlal <abishlalns03@gmail.com>
2025-06-21 17:28:59 +05:30
fbce8cd2f5 fix: update comment thread status to active when publishing comments
Signed-off-by: abishlal <abishlalns03@gmail.com>
2025-06-21 17:22:24 +05:30
ea63c8e63a fix: remove redundant line for BasicAuthentication in Azure DevOps provider
Signed-off-by: abishlal <abishlalns03@gmail.com>
2025-06-21 17:22:04 +05:30
d8fea6afc4 feat: enhance Azure DevOps integration by adding work item as a ticket retrieval methods - Supporting ticket context for Azure DevOps
Signed-off-by: abishlal <abishlalns03@gmail.com>
2025-06-21 17:20:31 +05:30
ff16e1cd26 build: add wheel to build requirements and update license configuration 2025-06-21 13:12:27 +03:00
9b5ae1a322 chore: bump version to 0.3.0 and update license to AGPL-3.0 2025-06-21 12:52:45 +03:00
8b8464163d docs: update recent updates with v0.30 release and best practices hierarchy 2025-06-21 12:46:03 +03:00
eb4cdbb115 fix: remove branch parameter from get_content_of_file call in bitbucket server provider 2025-06-21 12:34:00 +03:00
Tal
7f54b14b4d Merge pull request #1874 from yoohya/feat/support-gitlab-lambda-webhooks
feat: Support GitLab webhooks in Lambda functions
2025-06-21 09:36:16 +03:00
938ab9a139 fix: remove redundant word 'possible' from security concerns field description 2025-06-21 09:31:55 +03:00
Tal
75bde39b03 Merge pull request #1888 from qodo-ai/tr/simplify_toDo
refactor: extract TODO formatting functions and simplify data structure
2025-06-21 09:30:18 +03:00
Tal
ee36c0208c Update pr_agent/settings/pr_reviewer_prompts.toml
Co-authored-by: qodo-merge-for-open-source[bot] <189517486+qodo-merge-for-open-source[bot]@users.noreply.github.com>
2025-06-21 09:23:11 +03:00
7c02678ba5 refactor: extract TODO formatting functions and simplify data structure 2025-06-21 09:20:43 +03:00
235df737d0 docs: update Docker image tags 2025-06-20 23:22:13 +09:00
37ef4bad8f docs: add bullet point formatting instruction to PR description prompts 2025-06-20 16:27:43 +03:00
ab7e0d9141 refactor: split serverless handlers into dedicated GitHub and GitLab Lambda entrypoints 2025-06-20 04:18:42 +09:00
Tal
7db4d97fc2 Merge pull request #1880 from alessio-locatelli/fix_yes_no
fix: typos, grammar
2025-06-18 20:22:02 +03:00
Tal
3e1cf2deed Merge pull request #1869 from PullPullers/feat/review-todo-section
feat: surface TODO comments in review tool
2025-06-18 20:15:43 +03:00
4a00854b15 fix: exclude TODO prompt if require_todo_scan is false 2025-06-18 18:50:11 +09:00
d4a52ffc93 refactor: clarify TODO field description and simplify 'No' return 2025-06-18 18:45:49 +09:00
c4ccfd865d fix: shorten todo summary prompt for balance
related comment: https://github.com/qodo-ai/pr-agent/pull/1869#discussion_r2151443243
2025-06-18 18:45:49 +09:00
38f10e10fa style: remove entry info from the todo section header
related comment: https://github.com/qodo-ai/pr-agent/pull/1869#issuecomment-2979088345
2025-06-18 18:45:49 +09:00
2cfe52e294 refactor: replace DynaBox import with string type checking 2025-06-18 10:04:51 +03:00
Tal
1c34450645 Merge pull request #1883 from letzya/patch-1
Update github_action_runner.py
2025-06-18 09:45:49 +03:00
8325a8aeb1 feat: improve TODO sections closed by default 2025-06-18 13:48:51 +09:00
081310b943 style: refine layout of TODO section (add <br><br>) 2025-06-18 13:08:05 +09:00
c75fb2137b style: replace todo emoji with 📝 2025-06-18 13:04:14 +09:00
6de821719f fix: update default value of require_todo_scan from true to false in docs 2025-06-18 12:52:26 +09:00
4662f65146 Update github_action_runner.py
9789e5d701/pr_agent/agent/pr_agent.py (L84)
2025-06-17 19:04:03 +01:00
9789e5d701 docs: clarify auto-approval ticket compliance applies to review tool path only 2025-06-17 16:31:46 +03:00
5b7f8a4bb6 fix: grammar 2025-06-17 09:52:34 +03:00
608065f2ad fix: typos 2025-06-17 09:26:57 +03:00
7ee4c4aad1 fix: yes/no spelling 2025-06-17 09:13:12 +03:00
1963b80b46 docs: fix indentation in GitLab installation guide 2025-06-16 03:41:17 +09:00
a5c61e33d3 docs: fix indentation in GitLab installation guide 2025-06-16 02:10:15 +09:00
99904601ce feat: Support GitLab webhooks in Lambda functions 2025-06-16 01:22:01 +09:00
7a0c350760 feat: Change default TODO flag value from true to false 2025-06-12 18:14:19 +09:00
ca05b798ca fix: display no TODO comments section 2025-06-10 22:56:28 +09:00
b0711929c3 Merge pull request #4 from PullPullers/feat/toggle-open-based-on-line-count-for-todo-section
todo section의 내용이 10줄 이하이면 기본적으로 펼쳐져 있도록 수정
2025-06-10 21:41:30 +09:00
f09e1edb13 refactor: remove count info from todo summary 2025-06-09 22:26:37 +09:00
9230be86e9 refactor: handle singular/plural forms of entry 2025-06-09 21:40:33 +09:00
6c05c6685e fix: use | for multiline in YAML instead of \n for todo content prompts 2025-06-09 20:54:14 +09:00
277c6abf0f refactor: standardizes todo_sections and todos_summary formatting 2025-06-07 08:42:28 +09:00
829417ce6e feat: toggle open or closed based on line count for todo section 2025-06-07 08:42:19 +09:00
dc9e9af9f8 refactor: rename file line to file reference 2025-06-05 22:26:03 +09:00
dc6460361b Merge pull request #3 from PullPullers/feedback/review-todo-section
Feedback/review todo section
2025-06-05 22:15:18 +09:00
d8fb24c971 refactor: remove unused HTML formatting function from utils 2025-06-04 20:18:46 +09:00
3f9cade14d feat: add todos_summary to review output in markdown conversion 2025-06-04 20:18:46 +09:00
520faa7f2c feat: include item count in TODO sections summary 2025-06-01 00:36:25 +09:00
8c7c087931 feat: TODO multi-line 2025-05-28 21:33:28 +09:00
53b913a4cb refactor: add TypedDict and type hints to todo item formatter 2025-05-27 17:52:36 +09:00
7d38814cae refactor: Change error messsage 'print' -> 'get_logger' 2025-05-27 16:51:38 +09:00
08440d8ebd feat: Add <details> to TODO sections 2025-05-27 16:19:19 +09:00
bab8ee9633 modify: ToDo -> TODO 2025-05-27 16:08:33 +09:00
ca3df352ab refactor: improve todo section handling and clarify todo content description 2025-05-25 00:02:21 +09:00
b83085ea00 fix: remove whitespace from relevant file 2025-05-24 22:01:29 +09:00
66131854c1 fix: avoid incorrect ToDo header 2025-05-24 03:04:59 +09:00
788c0c12e6 feat: add TODO comments to PR review output 2025-05-24 01:52:52 +09:00
29 changed files with 422 additions and 62 deletions

View File

@ -65,6 +65,11 @@ Zero-setup hosted solution with advanced features and priority support
## News and Updates
## Jun 21, 2025
v0.30 was [released](https://github.com/qodo-ai/pr-agent/releases)
## Jun 3, 2025
Qodo Merge now offers a simplified free tier 💎.

View File

@ -1,4 +1,4 @@
FROM public.ecr.aws/lambda/python:3.12
FROM public.ecr.aws/lambda/python:3.12 AS base
RUN dnf update -y && \
dnf install -y gcc python3-devel git && \
@ -9,4 +9,10 @@ RUN pip install --no-cache-dir . && rm pyproject.toml
RUN pip install --no-cache-dir mangum==0.17.0
COPY pr_agent/ ${LAMBDA_TASK_ROOT}/pr_agent/
CMD ["pr_agent.servers.serverless.serverless"]
FROM base AS github_lambda
CMD ["pr_agent.servers.github_lambda_webhook.lambda_handler"]
FROM base AS gitlab_lambda
CMD ["pr_agent.servers.gitlab_lambda_webhook.lambda_handler"]
FROM github_lambda

View File

@ -202,7 +202,23 @@ h1 {
<script>
window.addEventListener('load', function() {
function displayResults(responseText) {
function extractText(responseText) {
try {
console.log('responseText: ', responseText);
const results = JSON.parse(responseText);
const msg = results.message;
if (!msg || msg.trim() === '') {
return "No results found";
}
return msg;
} catch (error) {
console.error('Error parsing results:', error);
throw new Error("Failed parsing response message");
}
}
function displayResults(msg) {
const resultsContainer = document.getElementById('results');
const spinner = document.getElementById('spinner');
const searchContainer = document.querySelector('.search-container');
@ -214,8 +230,6 @@ window.addEventListener('load', function() {
searchContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
try {
const results = JSON.parse(responseText);
marked.setOptions({
breaks: true,
gfm: true,
@ -223,7 +237,7 @@ window.addEventListener('load', function() {
sanitize: false
});
const htmlContent = marked.parse(results.message);
const htmlContent = marked.parse(msg);
resultsContainer.className = 'markdown-content';
resultsContainer.innerHTML = htmlContent;
@ -242,7 +256,7 @@ window.addEventListener('load', function() {
}, 100);
} catch (error) {
console.error('Error parsing results:', error);
resultsContainer.innerHTML = '<div class="error-message">Error processing results</div>';
resultsContainer.innerHTML = '<div class="error-message">Cannot process results</div>';
}
}
@ -275,24 +289,25 @@ window.addEventListener('load', function() {
body: JSON.stringify(data)
};
// const API_ENDPOINT = 'http://0.0.0.0:3000/api/v1/docs_help';
//const API_ENDPOINT = 'http://0.0.0.0:3000/api/v1/docs_help';
const API_ENDPOINT = 'https://help.merge.qodo.ai/api/v1/docs_help';
const response = await fetch(API_ENDPOINT, options);
const responseText = await response.text();
const msg = extractText(responseText);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
throw new Error(`An error (${response.status}) occurred during search: "${msg}"`);
}
const responseText = await response.text();
displayResults(responseText);
displayResults(msg);
} catch (error) {
spinner.style.display = 'none';
resultsContainer.innerHTML = `
<div class="error-message">
An error occurred while searching. Please try again later.
</div>
`;
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.textContent = `${error}`;
resultsContainer.value = "";
resultsContainer.appendChild(errorDiv);
}
}

View File

@ -65,7 +65,7 @@ There are two possible paths leading to this auto-approval - one via the `review
ensure_ticket_compliance = true # Default is false
```
If `ensure_ticket_compliance` is set to `true`, auto-approval will be disabled if no ticket is linked to the PR, or if the PR is not fully compliant with a linked ticket. This ensures that PRs are only auto-approved if their associated tickets are properly resolved.
If `ensure_ticket_compliance` is set to `true`, auto-approval for the `review` toll path will be disabled if no ticket is linked to the PR, or if the PR is not fully compliant with a linked ticket. This ensures that PRs are only auto-approved if their associated tickets are properly resolved.
You can also prevent auto-approval if the PR exceeds the ticket's scope (see [here](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/#configuration-options)).

View File

@ -187,14 +187,15 @@ For example: `GITHUB.WEBHOOK_SECRET` --> `GITHUB__WEBHOOK_SECRET`
2. Build a docker image that can be used as a lambda function
```shell
docker buildx build --platform=linux/amd64 . -t codiumai/pr-agent:serverless -f docker/Dockerfile.lambda
# Note: --target github_lambda is optional as it's the default target
docker buildx build --platform=linux/amd64 . -t codiumai/pr-agent:github_lambda --target github_lambda -f docker/Dockerfile.lambda
```
3. Push image to ECR
```shell
docker tag codiumai/pr-agent:serverless <AWS_ACCOUNT>.dkr.ecr.<AWS_REGION>.amazonaws.com/codiumai/pr-agent:serverless
docker push <AWS_ACCOUNT>.dkr.ecr.<AWS_REGION>.amazonaws.com/codiumai/pr-agent:serverless
docker tag codiumai/pr-agent:github_lambda <AWS_ACCOUNT>.dkr.ecr.<AWS_REGION>.amazonaws.com/codiumai/pr-agent:github_lambda
docker push <AWS_ACCOUNT>.dkr.ecr.<AWS_REGION>.amazonaws.com/codiumai/pr-agent:github_lambda
```
4. Create a lambda function that uses the uploaded image. Set the lambda timeout to be at least 3m.

View File

@ -61,12 +61,12 @@ git clone https://github.com/qodo-ai/pr-agent.git
```
5. Prepare variables and secrets. Skip this step if you plan on setting these as environment variables when running the agent:
1. In the configuration file/variables:
- Set `config.git_provider` to "gitlab"
1. In the configuration file/variables:
- Set `config.git_provider` to "gitlab"
2. In the secrets file/variables:
- Set your AI model key in the respective section
- In the [gitlab] section, set `personal_access_token` (with token from step 2) and `shared_secret` (with secret from step 3)
2. In the secrets file/variables:
- Set your AI model key in the respective section
- In the [gitlab] section, set `personal_access_token` (with token from step 2) and `shared_secret` (with secret from step 3)
6. Build a Docker image for the app and optionally push it to a Docker repository. We'll use Dockerhub as an example:
@ -88,3 +88,63 @@ OPENAI__KEY=<your_openai_api_key>
8. Create a webhook in your GitLab project. Set the URL to `http[s]://<PR_AGENT_HOSTNAME>/webhook`, the secret token to the generated secret from step 3, and enable the triggers `push`, `comments` and `merge request events`.
9. Test your installation by opening a merge request or commenting on a merge request using one of PR Agent's commands.
## Deploy as a Lambda Function
Note that since AWS Lambda env vars cannot have "." in the name, you can replace each "." in an env variable with "__".<br>
For example: `GITLAB.PERSONAL_ACCESS_TOKEN` --> `GITLAB__PERSONAL_ACCESS_TOKEN`
1. Follow steps 1-5 from [Run a GitLab webhook server](#run-a-gitlab-webhook-server).
2. Build a docker image that can be used as a lambda function
```shell
docker buildx build --platform=linux/amd64 . -t codiumai/pr-agent:gitlab_lambda --target gitlab_lambda -f docker/Dockerfile.lambda
```
3. Push image to ECR
```shell
docker tag codiumai/pr-agent:gitlab_lambda <AWS_ACCOUNT>.dkr.ecr.<AWS_REGION>.amazonaws.com/codiumai/pr-agent:gitlab_lambda
docker push <AWS_ACCOUNT>.dkr.ecr.<AWS_REGION>.amazonaws.com/codiumai/pr-agent:gitlab_lambda
```
4. Create a lambda function that uses the uploaded image. Set the lambda timeout to be at least 3m.
5. Configure the lambda function to have a Function URL.
6. In the environment variables of the Lambda function, specify `AZURE_DEVOPS_CACHE_DIR` to a writable location such as /tmp. (see [link](https://github.com/Codium-ai/pr-agent/pull/450#issuecomment-1840242269))
7. Go back to steps 8-9 of [Run a GitLab webhook server](#run-a-gitlab-webhook-server) with the function url as your Webhook URL.
The Webhook URL would look like `https://<LAMBDA_FUNCTION_URL>/webhook`
### Using AWS Secrets Manager
For production Lambda deployments, use AWS Secrets Manager instead of environment variables:
1. Create individual secrets for each GitLab webhook with this JSON format (e.g., secret name: `project-webhook-secret-001`)
```json
{
"gitlab_token": "glpat-xxxxxxxxxxxxxxxxxxxxxxxx",
"token_name": "project-webhook-001"
}
```
2. Create a main configuration secret for common settings (e.g., secret name: `pr-agent-main-config`)
```json
{
"openai.key": "sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
```
3. Set these environment variables in your Lambda:
```bash
CONFIG__SECRET_PROVIDER=aws_secrets_manager
AWS_SECRETS_MANAGER__SECRET_ARN=arn:aws:secretsmanager:us-east-1:123456789012:secret:pr-agent-main-config-AbCdEf
```
4. In your GitLab webhook configuration, set the **Secret Token** to the **Secret name** created in step 1:
- Example: `project-webhook-secret-001`
**Important**: When using Secrets Manager, GitLab's webhook secret must be the Secrets Manager secret name.
5. Add IAM permission `secretsmanager:GetSecretValue` to your Lambda execution role

View File

@ -3,7 +3,8 @@
[Qodo Merge](https://www.codium.ai/pricing/){:target="_blank"} is a hosted version of the open-source [PR-Agent](https://github.com/Codium-ai/pr-agent){:target="_blank"}.
It is designed for companies and teams that require additional features and capabilities.
Free users receive a monthly quota of 75 PR reviews per git organization, while unlimited usage requires a paid subscription. See [details](https://qodo-merge-docs.qodo.ai/installation/qodo_merge/#cloud-users).
Free users receive a quota of 75 monthly PR feedbacks per git organization. Unlimited usage requires a paid subscription. See [details](https://qodo-merge-docs.qodo.ai/installation/qodo_merge/#cloud-users).
Qodo Merge provides the following benefits:

View File

@ -19,7 +19,7 @@ This approach provides not just a quantitative score but also a detailed analysi
[//]: # ()
## Results
## PR Benchmark Results
<table>
<thead>
@ -67,6 +67,12 @@ This approach provides not just a quantitative score but also a detailed analysi
<td style="text-align:left;"></td>
<td style="text-align:center;"><b>39.0</b></td>
</tr>
<tr>
<td style="text-align:left;">Codex-mini</td>
<td style="text-align:left;">2025-06-20</td>
<td style="text-align:left;"><a href="https://platform.openai.com/docs/models/codex-mini-latest">unknown</a></td>
<td style="text-align:center;"><b>37.2</b></td>
</tr>
<tr>
<td style="text-align:left;">Gemini-2.5-flash</td>
<td style="text-align:left;">2025-04-17</td>
@ -196,7 +202,7 @@ weaknesses:
- **Very low recall / shallow coverage:** In a large majority of cases it gives 0-1 suggestions and misses other evident, critical bugs highlighted by peer models, leading to inferior rankings.
- **Occasional incorrect or harmful fixes:** A noticeable subset of answers propose changes that break functionality or misunderstand the code (e.g. bad constant, wrong header logic, speculative rollbacks).
- **Non-actionable placeholders:** Some “improved_code” sections contain comments or “…” rather than real patches, reducing practical value.
-
### GPT-4.1
Final score: **26.5**
@ -214,6 +220,22 @@ weaknesses:
- **Occasional technical inaccuracies:** A noticeable subset of suggestions are wrong (mis-ordered assertions, harmful Bash `set` change, false dangling-reference claims) or carry metadata errors (mis-labeling files as “python”).
- **Repetitive / derivative fixes:** Many outputs duplicate earlier simplistic ideas (e.g., single null-check) without new insight, showing limited reasoning breadth.
### OpenAI codex-mini
final score: **37.2**
strengths:
- **Can spot high-impact defects:** When it “locks on”, codex-mini often identifies the main runtime or security regression (e.g., race-conditions, logic inversions, blocking I/O, resource leaks) and proposes a minimal, direct patch that compiles and respects neighbouring style.
- **Produces concise, scoped fixes:** Valid answers usually stay within the allowed 3-suggestion limit, reference only the added lines, and contain clear before/after snippets that reviewers can apply verbatim.
- **Occasional broad coverage:** In a minority of cases the model catches multiple independent issues (logic + tests + docs) and outperforms every baseline answer, showing good contextual understanding of heterogeneous diffs.
weaknesses:
- **Output instability / format errors:** A very large share of responses are unusable—plain refusals, shell commands, or malformed/empty YAML—indicating brittle adherence to the required schema and tanking overall usefulness.
- **Critical-miss rate:** Even when the format is correct the model frequently overlooks the single most serious bug the diff introduces, instead focusing on stylistic nits or speculative refactors.
- **Introduces new problems:** Several suggestions add unsupported APIs, undeclared variables, wrong types, or break compilation, hurting trust in the recommendations.
- **Rule violations:** It often edits lines outside the diff, exceeds the 3-suggestion cap, or labels cosmetic tweaks as “critical”, showing inconsistent guideline compliance.
## Appendix - models used for generating the benchmark baseline

View File

@ -7,6 +7,7 @@ This page summarizes recent enhancements to Qodo Merge (last three months).
It also outlines our development roadmap for the upcoming three months. Please note that the roadmap is subject to change, and features may be adjusted, added, or reprioritized.
=== "Recent Updates"
- **Best Practices Hierarchy**: Introducing support for structured best practices, such as for folders in monorepos or a unified best practice file for a group of repositories.
- **Simplified Free Tier**: Qodo Merge now offers a simplified free tier with a monthly limit of 75 PR reviews per organization, replacing the previous two-week trial. ([Learn more](https://qodo-merge-docs.qodo.ai/installation/qodo_merge/#cloud-users))
- **CLI Endpoint**: A new Qodo Merge endpoint that accepts a lists of before/after code changes, executes Qodo Merge commands, and return the results. Currently available for enterprise customers. Contact [Qodo](https://www.qodo.ai/contact/) for more information.
- **Linear tickets support**: Qodo Merge now supports Linear tickets. ([Learn more](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/#linear-integration))
@ -17,7 +18,6 @@ It also outlines our development roadmap for the upcoming three months. Please n
=== "Future Roadmap"
- **Best Practices Hierarchy**: Introducing support for structured best practices, such as for folders in monorepos or a unified best practice file for a group of repositories.
- **Enhanced `review` tool**: Enhancing the `review` tool validate compliance across multiple categories including security, tickets, and custom best practices.
- **Smarter context retrieval**: Leverage AST and LSP analysis to gather relevant context from across the entire repository.
- **Enhanced portal experience**: Improved user experience in the Qodo Merge portal with new options and capabilities.

View File

@ -98,6 +98,11 @@ extra_instructions = "..."
<tr>
<td><b>require_security_review</b></td>
<td>If set to true, the tool will add a section that checks if the PR contains a possible security or vulnerability issue. Default is true.</td>
</tr>
<tr>
<td><b>require_todo_scan</b></td>
<td>If set to true, the tool will add a section that lists TODO comments found in the PR code changes. Default is false.
</td>
</tr>
<tr>
<td><b>require_ticket_analysis_review</b></td>

View File

@ -90,7 +90,7 @@ duplicate_examples=true # will duplicate the examples in the prompt, to help the
api_base = "http://localhost:11434" # or whatever port you're running Ollama on
```
By default, Ollama uses a context window size of 2048 tokens. In most cases this is not enough to cover pr-agent promt and pull-request diff. Context window size can be overridden with the `OLLAMA_CONTEXT_LENGTH` environment variable. For example, to set the default context length to 8K, use: `OLLAMA_CONTEXT_LENGTH=8192 ollama serve`. More information you can find on the [official ollama faq](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-specify-the-context-window-size).
By default, Ollama uses a context window size of 2048 tokens. In most cases this is not enough to cover pr-agent prompt and pull-request diff. Context window size can be overridden with the `OLLAMA_CONTEXT_LENGTH` environment variable. For example, to set the default context length to 8K, use: `OLLAMA_CONTEXT_LENGTH=8192 ollama serve`. More information you can find on the [official ollama faq](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-can-i-specify-the-context-window-size).
Please note that the `custom_model_max_tokens` setting should be configured in accordance with the `OLLAMA_CONTEXT_LENGTH`. Failure to do so may result in unexpected model output.

View File

@ -51,7 +51,7 @@ class PRAgent:
def __init__(self, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
self.ai_handler = ai_handler # will be initialized in run_action
async def handle_request(self, pr_url, request, notify=None) -> bool:
async def _handle_request(self, pr_url, request, notify=None) -> bool:
# First, apply repo specific settings if exists
apply_repo_settings(pr_url)
@ -117,3 +117,10 @@ class PRAgent:
else:
return False
return True
async def handle_request(self, pr_url, request, notify=None) -> bool:
try:
return await self._handle_request(pr_url, request, notify)
except:
get_logger().exception("Failed to process the command.")
return False

View File

@ -131,7 +131,7 @@ class LiteLLMAIHandler(BaseAiHandler):
self.api_base = openrouter_api_base
litellm.api_base = openrouter_api_base
# Models that only use user meessage
# Models that only use user message
self.user_message_only_models = USER_MESSAGE_ONLY_MODELS
# Model that doesn't support temperature argument
@ -212,7 +212,7 @@ class LiteLLMAIHandler(BaseAiHandler):
return kwargs
def add_litellm_callbacks(selfs, kwargs) -> dict:
def add_litellm_callbacks(self, kwargs) -> dict:
captured_extra = []
def capture_logs(message):

View File

@ -1,5 +1,6 @@
from __future__ import annotations
import ast
import copy
import difflib
import hashlib
@ -14,7 +15,7 @@ import traceback
from datetime import datetime
from enum import Enum
from importlib.metadata import PackageNotFoundError, version
from typing import Any, List, Tuple
from typing import Any, List, Tuple, TypedDict
import html2text
import requests
@ -37,21 +38,31 @@ def get_model(model_type: str = "model_weak") -> str:
return get_settings().config.model_reasoning
return get_settings().config.model
class Range(BaseModel):
line_start: int # should be 0-indexed
line_end: int
column_start: int = -1
column_end: int = -1
class ModelType(str, Enum):
REGULAR = "regular"
WEAK = "weak"
REASONING = "reasoning"
class TodoItem(TypedDict):
relevant_file: str
line_range: Tuple[int, int]
content: str
class PRReviewHeader(str, Enum):
REGULAR = "## PR Reviewer Guide"
INCREMENTAL = "## Incremental PR Reviewer Guide"
class ReasoningEffort(str, Enum):
HIGH = "high"
MEDIUM = "medium"
@ -109,6 +120,7 @@ def unique_strings(input_list: List[str]) -> List[str]:
seen.add(item)
return unique_list
def convert_to_markdown_v2(output_data: dict,
gfm_supported: bool = True,
incremental_review=None,
@ -131,6 +143,7 @@ def convert_to_markdown_v2(output_data: dict,
"Focused PR": "",
"Relevant ticket": "🎫",
"Security concerns": "🔒",
"Todo sections": "📝",
"Insights from user's answers": "📝",
"Code feedback": "🤖",
"Estimated effort to review [1-5]": "⏱️",
@ -151,6 +164,7 @@ def convert_to_markdown_v2(output_data: dict,
if gfm_supported:
markdown_text += "<table>\n"
todo_summary = output_data['review'].pop('todo_summary', '')
for key, value in output_data['review'].items():
if value is None or value == '' or value == {} or value == []:
if key.lower() not in ['can_be_split', 'key_issues_to_review']:
@ -209,6 +223,23 @@ def convert_to_markdown_v2(output_data: dict,
markdown_text += f"### {emoji} Security concerns\n\n"
value = emphasize_header(value.strip(), only_markdown=True)
markdown_text += f"{value}\n\n"
elif 'todo sections' in key_nice.lower():
if gfm_supported:
markdown_text += "<tr><td>"
if is_value_no(value):
markdown_text += f"✅&nbsp;<strong>No TODO sections</strong>"
else:
markdown_todo_items = format_todo_items(value, git_provider, gfm_supported)
markdown_text += f"{emoji}&nbsp;<strong>TODO sections</strong>\n<br><br>\n"
markdown_text += markdown_todo_items
markdown_text += "</td></tr>\n"
else:
if is_value_no(value):
markdown_text += f"### ✅ No TODO sections\n\n"
else:
markdown_todo_items = format_todo_items(value, git_provider, gfm_supported)
markdown_text += f"### {emoji} TODO sections\n\n"
markdown_text += markdown_todo_items
elif 'can be split' in key_nice.lower():
if gfm_supported:
markdown_text += f"<tr><td>"
@ -1289,7 +1320,7 @@ def process_description(description_full: str) -> Tuple[str, List]:
pattern_back = r'<details>\s*<summary><strong>(.*?)</strong><dd><code>(.*?)</code>.*?</summary>\s*<hr>\s*(.*?)\n\n\s*(.*?)</details>'
res = re.search(pattern_back, file_data, re.DOTALL)
if not res or res.lastindex != 4:
pattern_back = r'<details>\s*<summary><strong>(.*?)</strong>\s*<dd><code>(.*?)</code>.*?</summary>\s*<hr>\s*(.*?)\s*-\s*(.*?)\s*</details>' # looking for hypen ('- ')
pattern_back = r'<details>\s*<summary><strong>(.*?)</strong>\s*<dd><code>(.*?)</code>.*?</summary>\s*<hr>\s*(.*?)\s*-\s*(.*?)\s*</details>' # looking for hyphen ('- ')
res = re.search(pattern_back, file_data, re.DOTALL)
if res and res.lastindex == 4:
short_filename = res.group(1).strip()
@ -1367,3 +1398,47 @@ def set_file_languages(diff_files) -> List[FilePatchInfo]:
get_logger().exception(f"Failed to set file languages: {e}")
return diff_files
def format_todo_item(todo_item: TodoItem, git_provider, gfm_supported) -> str:
relevant_file = todo_item.get('relevant_file', '').strip()
line_number = todo_item.get('line_number', '')
content = todo_item.get('content', '')
reference_link = git_provider.get_line_link(relevant_file, line_number, line_number)
file_ref = f"{relevant_file} [{line_number}]"
if reference_link:
if gfm_supported:
file_ref = f"<a href='{reference_link}'>{file_ref}</a>"
else:
file_ref = f"[{file_ref}]({reference_link})"
if content:
return f"{file_ref}: {content.strip()}"
else:
# if content is empty, return only the file reference
return file_ref
def format_todo_items(value: list[TodoItem] | TodoItem, git_provider, gfm_supported) -> str:
markdown_text = ""
MAX_ITEMS = 5 # limit the number of items to display
if gfm_supported:
if isinstance(value, list):
markdown_text += "<ul>\n"
if len(value) > MAX_ITEMS:
get_logger().debug(f"Truncating todo items to {MAX_ITEMS} items")
value = value[:MAX_ITEMS]
for todo_item in value:
markdown_text += f"<li>{format_todo_item(todo_item, git_provider, gfm_supported)}</li>\n"
markdown_text += "</ul>\n"
else:
markdown_text += f"<p>{format_todo_item(value, git_provider, gfm_supported)}</p>\n"
else:
if isinstance(value, list):
if len(value) > MAX_ITEMS:
get_logger().debug(f"Truncating todo items to {MAX_ITEMS} items")
value = value[:MAX_ITEMS]
for todo_item in value:
markdown_text += f"- {format_todo_item(todo_item, git_provider, gfm_supported)}\n"
else:
markdown_text += f"- {format_todo_item(value, git_provider, gfm_supported)}\n"
return markdown_text

View File

@ -22,6 +22,7 @@ try:
from azure.devops.connection import Connection
# noinspection PyUnresolvedReferences
from azure.devops.released.git import (Comment, CommentThread, GitPullRequest, GitVersionDescriptor, GitClient, CommentThreadContext, CommentPosition)
from azure.devops.released.work_item_tracking import WorkItemTrackingClient
# noinspection PyUnresolvedReferences
from azure.identity import DefaultAzureCredential
from msrest.authentication import BasicAuthentication
@ -39,7 +40,7 @@ class AzureDevopsProvider(GitProvider):
"Azure DevOps provider is not available. Please install the required dependencies."
)
self.azure_devops_client = self._get_azure_devops_client()
self.azure_devops_client, self.azure_devops_board_client = self._get_azure_devops_client()
self.diff_files = None
self.workspace_slug = None
self.repo_slug = None
@ -566,7 +567,7 @@ class AzureDevopsProvider(GitProvider):
return workspace_slug, repo_slug, pr_number
@staticmethod
def _get_azure_devops_client() -> GitClient:
def _get_azure_devops_client() -> Tuple[GitClient, WorkItemTrackingClient]:
org = get_settings().azure_devops.get("org", None)
pat = get_settings().azure_devops.get("pat", None)
@ -588,13 +589,12 @@ class AzureDevopsProvider(GitProvider):
get_logger().error(f"No PAT found in settings, and Azure Default Authentication failed, error: {e}")
raise
credentials = BasicAuthentication("", auth_token)
credentials = BasicAuthentication("", auth_token)
azure_devops_connection = Connection(base_url=org, creds=credentials)
azure_devops_client = azure_devops_connection.clients.get_git_client()
azure_devops_board_client = azure_devops_connection.clients.get_work_item_tracking_client()
return azure_devops_client
return azure_devops_client, azure_devops_board_client
def _get_repo(self):
if self.repo is None:
@ -635,4 +635,50 @@ class AzureDevopsProvider(GitProvider):
last = commits[0]
url = self.azure_devops_client.normalized_url + "/" + self.workspace_slug + "/_git/" + self.repo_slug + "/commit/" + last.commit_id
return url
def get_linked_work_items(self) -> list:
"""
Get linked work items from the PR.
"""
try:
work_items = self.azure_devops_client.get_pull_request_work_item_refs(
project=self.workspace_slug,
repository_id=self.repo_slug,
pull_request_id=self.pr_num,
)
ids = [work_item.id for work_item in work_items]
if not work_items:
return []
items = self.get_work_items(ids)
return items
except Exception as e:
get_logger().exception(f"Failed to get linked work items, error: {e}")
return []
def get_work_items(self, work_item_ids: list) -> list:
"""
Get work items by their IDs.
"""
try:
raw_work_items = self.azure_devops_board_client.get_work_items(
project=self.workspace_slug,
ids=work_item_ids,
)
work_items = []
for item in raw_work_items:
work_items.append(
{
"id": item.id,
"title": item.fields.get("System.Title", ""),
"url": item.url,
"body": item.fields.get("System.Description", ""),
"acceptance_criteria": item.fields.get(
"Microsoft.VSTS.Common.AcceptanceCriteria", ""
),
"tags": item.fields.get("System.Tags", "").split("; ") if item.fields.get("System.Tags") else [],
}
)
return work_items
except Exception as e:
get_logger().exception(f"Failed to get work items, error: {e}")
return []

View File

@ -86,7 +86,7 @@ class BitbucketServerProvider(GitProvider):
def get_repo_settings(self):
try:
content = self.bitbucket_client.get_content_of_file(self.workspace_slug, self.repo_slug, ".pr_agent.toml", self.get_pr_branch())
content = self.bitbucket_client.get_content_of_file(self.workspace_slug, self.repo_slug, ".pr_agent.toml")
return content
except Exception as e:

View File

@ -0,0 +1,38 @@
{
"name": "Qodo Merge",
"description": "Qodo Merge",
"key": "app_key",
"vendor": {
"name": "Qodo",
"url": "https://qodo.ai"
},
"authentication": {
"type": "jwt"
},
"baseUrl": "base_url",
"lifecycle": {
"installed": "/installed",
"uninstalled": "/uninstalled"
},
"scopes": [
"account",
"repository:write",
"pullrequest:write",
"wiki"
],
"contexts": [
"account"
],
"modules": {
"webhooks": [
{
"event": "*",
"url": "/webhook"
}
]
},
"links": {
"privacy": "https://qodo.ai/privacy-policy",
"terms": "https://qodo.ai/terms"
}
}

View File

@ -3,8 +3,6 @@ import json
import os
from typing import Union
from dynaconf.utils import DynaBox
from pr_agent.agent.pr_agent import PRAgent
from pr_agent.config_loader import get_settings
from pr_agent.git_providers import get_git_provider
@ -93,7 +91,7 @@ async def run_action():
for key in get_settings():
setting = get_settings().get(key)
if isinstance(setting, DynaBox):
if str(type(setting)) == "<class 'dynaconf.utils.boxing.DynaBox'>":
if key.lower() in ['pr_description', 'pr_code_suggestions', 'pr_reviewer']:
if hasattr(setting, 'extra_instructions'):
extra_instructions = setting.extra_instructions

View File

@ -23,5 +23,5 @@ app.include_router(router)
handler = Mangum(app, lifespan="off")
def serverless(event, context):
return handler(event, context)
def lambda_handler(event, context):
return handler(event, context)

View File

@ -0,0 +1,27 @@
from fastapi import FastAPI
from mangum import Mangum
from starlette.middleware import Middleware
from starlette_context.middleware import RawContextMiddleware
from pr_agent.servers.gitlab_webhook import router
try:
from pr_agent.config_loader import apply_secrets_manager_config
apply_secrets_manager_config()
except Exception as e:
try:
from pr_agent.log import get_logger
get_logger().debug(f"AWS Secrets Manager initialization failed, falling back to environment variables: {e}")
except:
# Fail completely silently if log module is not available
pass
middleware = [Middleware(RawContextMiddleware)]
app = FastAPI(middleware=middleware)
app.include_router(router)
handler = Mangum(app, lifespan="off")
def lambda_handler(event, context):
return handler(event, context)

View File

@ -78,6 +78,7 @@ require_tests_review=true
require_estimate_effort_to_review=true
require_can_be_split_review=false
require_security_review=true
require_todo_scan=false
require_ticket_analysis_review=true
# general options
publish_output_no_suggestions=true # Set to "false" if you only need the reviewer's remarks (not labels, not "security audit", etc.) and want to avoid noisy "No major issues detected" comments.

View File

@ -1,11 +1,12 @@
[pr_description_prompt]
system="""You are PR-Reviewer, a language model designed to review a Git Pull Request (PR).
Your task is to provide a full description for the PR content - type, description, title and files walkthrough.
Your task is to provide a full description for the PR content: type, description, title, and files walkthrough.
- Focus on the new PR code (lines starting with '+' in the 'PR Git Diff' section).
- Keep in mind that the 'Previous title', 'Previous description' and 'Commit messages' sections may be partial, simplistic, non-informative or out of date. Hence, compare them to the PR diff code, and use them only as a reference.
- The generated title and description should prioritize the most significant changes.
- If needed, each YAML output should be in block scalar indicator ('|')
- When quoting variables, names or file paths from the code, use backticks (`) instead of single quote (').
- When needed, use '- ' as bullets
{%- if extra_instructions %}
@ -181,4 +182,4 @@ pr_files:
Response (should be a valid YAML, and nothing else):
```yaml
"""
"""

View File

@ -1,12 +1,12 @@
[pr_help_prompts]
system="""You are Doc-helper, a language models designed to answer questions about a documentation website for an open-soure project called "PR-Agent" (recently renamed to "Qodo Merge").
You will recieve a question, and the full documentation website content.
You will receive a question, and the full documentation website content.
Your goal is to provide the best answer to the question using the documentation provided.
Additional instructions:
- Try to be short and concise in your answers. Try to give examples if needed.
- The main tools of PR-Agent are 'describe', 'review', 'improve'. If there is ambiguity to which tool the user is referring to, prioritize snippets of these tools over others.
- If the question has ambiguity and can relate to different tools or platfroms, provide the best answer possible based on what is available, but also state in your answer what additional information would be needed to give a more accurate answer.
- If the question has ambiguity and can relate to different tools or platforms, provide the best answer possible based on what is available, but also state in your answer what additional information would be needed to give a more accurate answer.
The output must be a YAML object equivalent to type $DocHelper, according to the following Pydantic definitions:

View File

@ -2,7 +2,7 @@
system="""You are PR-Reviewer, a language model designed to review a Git Pull Request (PR).
Given the PR Info and the PR Git Diff, generate 3 short questions about the PR code for the PR author.
The goal of the questions is to help the language model understand the PR better, so the questions should be insightful, informative, non-trivial, and relevant to the PR.
You should prefer asking yes\\no questions, or multiple choice questions. Also add at least one open-ended question, but make sure they are not too difficult, and can be answered in a sentence or two.
You should prefer asking yes/no questions, or multiple choice questions. Also add at least one open-ended question, but make sure they are not too difficult, and can be answered in a sentence or two.
Example output:

View File

@ -37,9 +37,9 @@ __new hunk__
======
- In the format above, the diff is organized into separate '__new hunk__' and '__old hunk__' sections for each code chunk. '__new hunk__' contains the updated code, while '__old hunk__' shows the removed code. If no code was removed in a specific chunk, the __old hunk__ section will be omitted.
- We also added line numbers for the '__new hunk__' code, to help you refer to the code lines in your suggestions. These line numbers are not part of the actual code, and should only used for reference.
- We also added line numbers for the '__new hunk__' code, to help you refer to the code lines in your suggestions. These line numbers are not part of the actual code, and should only be used for reference.
- Code lines are prefixed with symbols ('+', '-', ' '). The '+' symbol indicates new code added in the PR, the '-' symbol indicates code removed in the PR, and the ' ' symbol indicates unchanged code. \
The review should address new code added in the PR code diff (lines starting with '+')
The review should address new code added in the PR code diff (lines starting with '+').
{%- if is_ai_metadata %}
- If available, an AI-generated summary will appear and provide a high-level overview of the file changes. Note that this summary may not be fully accurate or complete.
{%- endif %}
@ -72,6 +72,13 @@ class KeyIssuesComponentLink(BaseModel):
start_line: int = Field(description="The start line that corresponds to this issue in the relevant file")
end_line: int = Field(description="The end line that corresponds to this issue in the relevant file")
{%- if require_todo_scan %}
class TodoSection(BaseModel):
relevant_file: str = Field(description="The full path of the file containing the TODO comment")
line_number: int = Field(description="The line number where the TODO comment starts")
content: str = Field(description="The content of the TODO comment. Only include actual TODO comments within code comments (e.g., comments starting with '#', '//', '/*', '<!--', ...). Remove leading 'TODO' prefixes. If more than 10 words, summarize the TODO comment to a single short sentence up to 10 words.")
{%- endif %}
{%- if related_tickets %}
class TicketCompliance(BaseModel):
@ -93,14 +100,17 @@ class Review(BaseModel):
score: str = Field(description="Rate this PR on a scale of 0-100 (inclusive), where 0 means the worst possible PR code, and 100 means PR code of the highest quality, without any bugs or performance issues, that is ready to be merged immediately and run in production at scale.")
{%- endif %}
{%- if require_tests %}
relevant_tests: str = Field(description="yes\\no question: does this PR have relevant tests added or updated ?")
relevant_tests: str = Field(description="yes/no question: does this PR have relevant tests added or updated ?")
{%- endif %}
{%- if question_str %}
insights_from_user_answers: str = Field(description="shortly summarize the insights you gained from the user's answers to the questions")
{%- endif %}
key_issues_to_review: List[KeyIssuesComponentLink] = Field("A short and diverse list (0-{{ num_max_findings }} issues) of high-priority bugs, problems or performance concerns introduced in the PR code, which the PR reviewer should further focus on and validate during the review process.")
{%- if require_security_review %}
security_concerns: str = Field(description="Does this PR code introduce possible vulnerabilities such as exposure of sensitive information (e.g., API keys, secrets, passwords), or security concerns like SQL injection, XSS, CSRF, and others ? Answer 'No' (without explaining why) if there are no possible issues. If there are security concerns or issues, start your answer with a short header, such as: 'Sensitive information exposure: ...', 'SQL injection: ...' etc. Explain your answer. Be specific and give examples if possible")
security_concerns: str = Field(description="Does this PR code introduce vulnerabilities such as exposure of sensitive information (e.g., API keys, secrets, passwords), or security concerns like SQL injection, XSS, CSRF, and others ? Answer 'No' (without explaining why) if there are no possible issues. If there are security concerns or issues, start your answer with a short header, such as: 'Sensitive information exposure: ...', 'SQL injection: ...', etc. Explain your answer. Be specific and give examples if possible")
{%- endif %}
{%- if require_todo_scan %}
todo_sections: Union[List[TodoSection], str] = Field(description="A list of TODO comments found in the PR code. Return 'No' (as a string) if there are no TODO comments in the PR")
{%- endif %}
{%- if require_can_be_split_review %}
can_be_split: List[SubPR] = Field(min_items=0, max_items=3, description="Can this PR, which contains {{ num_pr_files }} changed files in total, be divided into smaller sub-PRs with distinct tasks that can be reviewed and merged independently, regardless of the order ? Make sure that the sub-PRs are indeed independent, with no code dependencies between them, and that each sub-PR represent a meaningful independent task. Output an empty list if the PR code does not need to be split.")
@ -148,6 +158,10 @@ review:
- ...
security_concerns: |
No
{%- if require_todo_scan %}
todo_sections: |
No
{%- endif %}
{%- if require_can_be_split_review %}
can_be_split:
- relevant_files:
@ -182,6 +196,13 @@ Ticket Description:
{{ ticket.body }}
#####
{%- endif %}
{%- if ticket.requirements %}
Ticket Requirements:
#####
{{ ticket.requirements }}
#####
{%- endif %}
=====
{% endfor %}
{%- endif %}
@ -266,6 +287,10 @@ review:
- ...
security_concerns: |
No
{%- if require_todo_scan %}
todo_sections: |
No
{%- endif %}
{%- if require_can_be_split_review %}
can_be_split:
- relevant_files:

View File

@ -21,7 +21,7 @@ from pr_agent.servers.help import HelpMessage
#Common code that can be called from similar tools:
def modify_answer_section(ai_response: str) -> str | None:
# Gets the model's answer and relevant sources section, repacing the heading of the answer section with:
# Gets the model's answer and relevant sources section, replacing the heading of the answer section with:
# :bulb: Auto-generated documentation-based answer:
"""
For example: The following input:

View File

@ -87,6 +87,7 @@ class PRReviewer:
"require_estimate_effort_to_review": get_settings().pr_reviewer.require_estimate_effort_to_review,
'require_can_be_split_review': get_settings().pr_reviewer.require_can_be_split_review,
'require_security_review': get_settings().pr_reviewer.require_security_review,
'require_todo_scan': get_settings().pr_reviewer.get("require_todo_scan", False),
'question_str': question_str,
'answer_str': answer_str,
"extra_instructions": get_settings().pr_reviewer.extra_instructions,

View File

@ -3,6 +3,7 @@ import traceback
from pr_agent.config_loader import get_settings
from pr_agent.git_providers import GithubProvider
from pr_agent.git_providers import AzureDevopsProvider
from pr_agent.log import get_logger
# Compile the regex pattern once, outside the function
@ -131,6 +132,32 @@ async def extract_tickets(git_provider):
return tickets_content
elif isinstance(git_provider, AzureDevopsProvider):
tickets_info = git_provider.get_linked_work_items()
tickets_content = []
for ticket in tickets_info:
try:
ticket_body_str = ticket.get("body", "")
if len(ticket_body_str) > MAX_TICKET_CHARACTERS:
ticket_body_str = ticket_body_str[:MAX_TICKET_CHARACTERS] + "..."
tickets_content.append(
{
"ticket_id": ticket.get("id"),
"ticket_url": ticket.get("url"),
"title": ticket.get("title"),
"body": ticket_body_str,
"requirements": ticket.get("acceptance_criteria", ""),
"labels": ", ".join(ticket.get("labels", [])),
}
)
except Exception as e:
get_logger().error(
f"Error processing Azure DevOps ticket: {e}",
artifact={"traceback": traceback.format_exc()},
)
return tickets_content
except Exception as e:
get_logger().error(f"Error extracting tickets error= {e}",
artifact={"traceback": traceback.format_exc()})

View File

@ -1,10 +1,10 @@
[build-system]
requires = ["setuptools>=61.0"]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "pr-agent"
version = "0.2.7"
version = "0.3.0"
authors = [{ name = "QodoAI", email = "tal.r@qodo.ai" }]
@ -16,7 +16,7 @@ description = "QodoAI PR-Agent aims to help efficiently review and handle pull r
readme = "README.md"
requires-python = ">=3.12"
keywords = ["AI", "Agents", "Pull Request", "Automation", "Code Review"]
license = "Apache-2.0"
license = { file = "LICENSE" }
classifiers = [
"Intended Audience :: Developers",
@ -34,7 +34,6 @@ dependencies = { file = ["requirements.txt"] }
[tool.setuptools]
include-package-data = true
license-files = ["LICENSE"]
[tool.setuptools.packages.find]
where = ["."]