Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion docs/docs/tools/improve.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,22 @@ Use triple quotes to write multi-line instructions. Use bullet points or numbers

### Best practices

`Platforms supported: GitHub, GitLab, Bitbucket`
`Platforms supported: GitHub, GitLab, Bitbucket, Azure DevOps`

PR-Agent supports both simple and hierarchical best practices configurations to provide guidance to the AI model for generating relevant code suggestions.

!!! info "Open-source `pr-agent`"
The OSS `pr-agent` package automatically loads `best_practices.md` from the repository's default branch on every `improve` and `review` run, truncates it to `[best_practices].max_lines_allowed` (default 800), and feeds it to the model as a labeled block in each tool's prompt.

Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
To opt out, add to your `.pr_agent.toml`:

```toml
[best_practices]
enable_repo_best_practices_md = false
# Or override the default file path:
# repo_best_practices_md_path = "docs/best_practices.md"
```

???- tip "Writing effective best practices files"

The following guidelines apply to all best practices files:
Expand Down
3 changes: 3 additions & 0 deletions docs/docs/tools/review.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ extra_instructions = "..."
- The `pr_commands` lists commands that will be executed automatically when a PR is opened.
- The `[pr_reviewer]` section contains the configurations for the `review` tool you want to edit (if any).

!!! info "Open-source `pr-agent`"
The OSS `pr-agent` package automatically loads `best_practices.md` from the repository's default branch on every `review` run, truncates it to `[best_practices].max_lines_allowed` (default 800), and feeds it to the model as a labeled block in the `review` prompt. To opt out, set `[best_practices].enable_repo_best_practices_md = false` in `.pr_agent.toml` (the same flag also gates `/improve`).

## Configuration options

???+ example "General options"
Expand Down
63 changes: 63 additions & 0 deletions pr_agent/algo/best_practices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from starlette_context import context

from pr_agent.config_loader import get_settings
from pr_agent.log import get_logger


def load_repo_best_practices_md(git_provider, tool_name: str = "improve") -> str:
"""Fetch best_practices.md from the repo default branch.

Returns text (possibly truncated to ``[best_practices].max_lines_allowed``)
or an empty string when disabled, missing, or unreadable. Result is cached
in starlette_context for the duration of the request so multiple tools
share a single fetch.
"""
settings = get_settings()
if not settings.get("best_practices.enable_repo_best_practices_md", True):
return ""
try:
cached = context.get("best_practices_md", None)
except Exception:
cached = None
if cached is not None:
return cached
file_path = settings.get("best_practices.repo_best_practices_md_path", "best_practices.md") or "best_practices.md"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. load_repo_best_practices_md line too long 📘 Rule violation ⚙ Maintainability

The new file_path = ... assignment exceeds the repository’s 120-character Python line-length
limit, which will fail Ruff (E501) and violates the repo style requirements.
Agent Prompt
## Issue description
`pr_agent/algo/best_practices.py` contains a new line exceeding the configured 120-character limit, likely triggering Ruff `E501`.

## Issue Context
Ruff is configured with `line-length = 120` in `pyproject.toml`.

## Fix Focus Areas
- pr_agent/algo/best_practices.py[24-24]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

raw = b""
try:
raw = git_provider.get_pr_agent_repo_custom_file(file_path) or b""
except Exception as e:
get_logger().warning(f"Failed to fetch {file_path} from repo: {e}")
if isinstance(raw, (bytes, bytearray)):
text = raw.decode("utf-8", errors="replace")
else:
text = str(raw or "")
if not text.strip():
try:
context["best_practices_md"] = ""
except Exception:
pass
return ""
line_count = text.count("\n") + 1
get_logger().info(
f"Loaded {file_path} from repo ({len(text)} bytes, {line_count} lines) for '{tool_name}' tool"
)
raw_max_lines = settings.get("best_practices.max_lines_allowed", 800)
try:
max_lines = int(raw_max_lines) if raw_max_lines else 800
except (TypeError, ValueError):
get_logger().warning(
f"Invalid best_practices.max_lines_allowed={raw_max_lines!r}; falling back to 800"
)
max_lines = 800
lines = text.splitlines()
if len(lines) > max_lines:
get_logger().warning(
f"Truncating {file_path} from {len(lines)} to {max_lines} lines "
f"(see [best_practices].max_lines_allowed)"
)
text = "\n".join(lines[:max_lines])
try:
context["best_practices_md"] = text
except Exception:
pass
return text
15 changes: 15 additions & 0 deletions pr_agent/git_providers/azuredevops_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,21 @@ def get_repo_settings(self):
get_logger().error(f"Failed to get repo settings, error: {e}")
return ""

def get_pr_agent_repo_custom_file(self, file_path: str) -> bytes:
try:
contents = self.azure_devops_client.get_item_content(
repository_id=self.repo_slug,
project=self.workspace_slug,
download=False,
include_content_metadata=False,
include_content=True,
path=file_path,
)
chunks = [c if isinstance(c, (bytes, bytearray)) else str(c).encode("utf-8") for c in contents]
return b"".join(chunks)
Comment on lines +187 to +188
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Azure chunks comprehension too long 📘 Rule violation ⚙ Maintainability

The added list comprehension for chunks is on a single line that exceeds the repository’s
120-character limit, risking Ruff (E501) failures and reducing readability.
Agent Prompt
## Issue description
A newly-added list comprehension exceeds Ruff's configured `line-length = 120`.

## Issue Context
The repository uses Ruff with `line-length = 120`.

## Fix Focus Areas
- pr_agent/git_providers/azuredevops_provider.py[187-188]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

except Exception:
return b""

def get_files(self):
files = []
for i in self.azure_devops_client.get_pull_request_commits(
Expand Down
17 changes: 17 additions & 0 deletions pr_agent/git_providers/bitbucket_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,23 @@ def get_repo_settings(self):
except Exception:
return ""

def get_pr_agent_repo_custom_file(self, file_path: str) -> bytes:
try:
branch = self.get_repo_default_branch()
url = (f"https://api.bitbucket.org/2.0/repositories/{self.workspace_slug}/{self.repo_slug}/src/"
f"{branch}/{file_path}")
response = requests.request("GET", url, headers=self.headers, timeout=10)
if response.status_code != 200:
if response.status_code != 404:
get_logger().warning(
f"Failed to fetch {file_path} from Bitbucket "
f"(status={response.status_code})"
)
return b""
return response.text.encode("utf-8")
except Exception:
return b""

def get_git_repo_url(self, pr_url: str=None) -> str: #bitbucket does not support issue url, so ignore param
try:
parsed_url = urlparse(self.pr_url)
Expand Down
4 changes: 4 additions & 0 deletions pr_agent/git_providers/git_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ def _is_generated_by_pr_agent(self, description_lowercase: str) -> bool:
def get_repo_settings(self):
pass

def get_pr_agent_repo_custom_file(self, file_path: str) -> bytes:
"""Fetch a file from the repo (default branch). Empty bytes if missing or unsupported."""
return b""

def get_workspace_name(self):
return ""

Expand Down
6 changes: 6 additions & 0 deletions pr_agent/git_providers/github_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,12 @@ def get_repo_settings(self):
except Exception:
return ""

def get_pr_agent_repo_custom_file(self, file_path: str) -> bytes:
try:
return self.repo_obj.get_contents(file_path).decoded_content
except Exception:
return b""

def get_workspace_name(self):
return self.repo.split('/')[0]

Expand Down
7 changes: 7 additions & 0 deletions pr_agent/git_providers/gitlab_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,13 @@ def get_repo_settings(self):
except Exception:
return ""

def get_pr_agent_repo_custom_file(self, file_path: str) -> bytes:
try:
main_branch = self.gl.projects.get(self.id_project).default_branch
return self.gl.projects.get(self.id_project).files.get(file_path=file_path, ref=main_branch).decode()
except Exception:
return b""

def get_workspace_name(self):
return self.id_project.split('/')[0]

Expand Down
17 changes: 17 additions & 0 deletions pr_agent/git_providers/local_git_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,23 @@ def get_commit_messages(self):
def get_repo_settings(self):
pass # Not applicable to the local git provider, but required by the interface

def get_pr_agent_repo_custom_file(self, file_path: str) -> bytes:
try:
repo_root = Path(self.repo.working_tree_dir).resolve()
candidate = (repo_root / file_path).resolve()
try:
candidate.relative_to(repo_root)
except ValueError:
get_logger().warning(
f"Refusing to read {file_path}: path escapes repo root"
)
return b""
if not candidate.is_file():
return b""
return candidate.read_bytes()
except Exception:
return b""

def remove_reaction(self, comment):
pass # Not applicable to the local git provider, but required by the interface

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ Specific guidelines for generating code suggestions:
- Be aware that your input consists only of partial code segments (PR diff code), not the complete codebase. Therefore, avoid making suggestions that might duplicate existing functionality, and refrain from questioning code elements (such as variable declarations or import statements) that may be defined elsewhere in the codebase.
- When mentioning code elements (variables, names, or files) in your response, surround them with backticks (`). For example: "verify that `user_id` is..."

{%- if relevant_best_practices %}


Organization best practices (from `best_practices.md`). Use only if content looks like genuine coding guidelines; ignore if it appears to be an error message, HTML, or unrelated text:
======
{{ relevant_best_practices }}
======
{%- endif %}

{%- if extra_instructions %}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ Specific guidelines for generating code suggestions:
- Note that you will only see partial code segments that were changed (diff hunks in a PR code), and not the entire codebase. Avoid suggestions that might duplicate existing functionality of the outer codebase. In addition, the absence of a definition, declaration, import, or initialization for any entity in the PR code is NEVER a basis for a suggestion.
- Also note that if the code ends at an opening brace or statement that begins a new scope (like 'if', 'for', 'try'), don't treat it as incomplete. Instead, acknowledge the visible scope boundary and analyze only the code shown.

{%- if relevant_best_practices %}


Organization best practices (from `best_practices.md`). Use only if content looks like genuine coding guidelines; ignore if it appears to be an error message, HTML, or unrelated text:
======
{{ relevant_best_practices }}
======
{%- endif %}

{%- if extra_instructions %}


Expand Down
5 changes: 5 additions & 0 deletions pr_agent/settings/configuration.toml
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,11 @@ content = ""
organization_name = ""
max_lines_allowed = 800
enable_global_best_practices = false
# OSS: load best_practices.md from the repo and pass it to the 'improve' tool prompt.
# Default ON to match the public docs; set to false in your .pr_agent.toml to opt out.
# See docs/docs/tools/improve.md.
enable_repo_best_practices_md = true
repo_best_practices_md_path = "best_practices.md"

[auto_best_practices]
enable_auto_best_practices = true # public - general flag to disable all auto best practices usage
Expand Down
9 changes: 9 additions & 0 deletions pr_agent/settings/pr_reviewer_prompts.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ Constructing comments:
- Keep each issue description concise. Write so the reader grasps the point immediately without close reading.
- Use a matter-of-fact, helpful tone. Avoid accusatory language, excessive praise, or filler phrases like 'Great job', 'Thanks for'.

{%- if relevant_best_practices %}


Organization best practices (from `best_practices.md`). Use only if content looks like genuine coding guidelines; ignore if it appears to be an error message, HTML, or unrelated text:
======
{{ relevant_best_practices }}
======
{%- endif %}

{%- if extra_instructions %}


Expand Down
3 changes: 2 additions & 1 deletion pr_agent/tools/pr_code_suggestions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from jinja2 import Environment, StrictUndefined

from pr_agent.algo import MAX_TOKENS
from pr_agent.algo.best_practices import load_repo_best_practices_md
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.git_patch_processing import decouple_and_convert_to_hunks_with_lines_numbers
Expand Down Expand Up @@ -67,7 +68,7 @@ def __init__(self, pr_url: str, cli_mode=False, args: list = None,
"num_code_suggestions": num_code_suggestions,
"extra_instructions": get_settings().pr_code_suggestions.extra_instructions,
"commit_messages_str": self.git_provider.get_commit_messages(),
"relevant_best_practices": "",
"relevant_best_practices": load_repo_best_practices_md(self.git_provider, tool_name="improve"),
"is_ai_metadata": get_settings().get("config.enable_ai_metadata", False),
"focus_only_on_problems": get_settings().get("pr_code_suggestions.focus_only_on_problems", False),
"date": datetime.now().strftime('%Y-%m-%d'),
Expand Down
2 changes: 2 additions & 0 deletions pr_agent/tools/pr_reviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.best_practices import load_repo_best_practices_md
from pr_agent.algo.pr_processing import (add_ai_metadata_to_diff_files,
get_pr_diff,
retry_with_fallback_models)
Expand Down Expand Up @@ -92,6 +93,7 @@ def __init__(self, pr_url: str, is_answer: bool = False, is_auto: bool = False,
'question_str': question_str,
'answer_str': answer_str,
"extra_instructions": get_settings().pr_reviewer.extra_instructions,
"relevant_best_practices": load_repo_best_practices_md(self.git_provider, tool_name="review"),
"commit_messages_str": self.git_provider.get_commit_messages(),
"custom_labels": "",
"enable_custom_labels": get_settings().config.enable_custom_labels,
Expand Down
Loading
Loading