More PR-Agent commands \n\n"
output += HelpMessage.get_general_bot_help_text()
@@ -186,6 +211,7 @@ To enable inline file summary, set `pr_description.inline_file_summary` in the c
- `true`: A collapsable file comment with changes title and a changes summary for each file in the PR.
- `false` (default): File changes walkthrough will be added only to the "Conversation" tab.
"""
+
# extra instructions
output += " Utilizing extra instructions \n\n"
output += '''\
@@ -309,8 +335,9 @@ Use triple quotes to write multi-line instructions. Use bullet points to make th
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.
- 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.
-- Best quality will be obtained by using 'improve --extended' mode.
+- 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
+- With large PRs, best quality will be obtained by using 'improve --extended' mode.
+
"""
output += "\n\n | \n\n"\
diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml
index 5d9a84ab..7555c292 100644
--- a/pr_agent/settings/configuration.toml
+++ b/pr_agent/settings/configuration.toml
@@ -1,5 +1,6 @@
[config]
-model="gpt-4" # "gpt-4-1106-preview"
+model="gpt-4" # "gpt-4-0125-preview"
+model_turbo="gpt-4-0125-preview"
fallback_models=["gpt-3.5-turbo-16k"]
git_provider="github"
publish_output=true
@@ -8,11 +9,11 @@ verbosity_level=0 # 0,1,2
use_extra_bad_extensions=false
use_repo_settings_file=true
use_global_settings_file=true
-ai_timeout=180
+ai_timeout=90
max_description_tokens = 500
max_commits_tokens = 500
max_model_tokens = 32000 # Limits the maximum number of tokens that can be used by any model, regardless of the model's default capabilities.
-patch_extra_lines = 3
+patch_extra_lines = 1
secret_provider="google_cloud_storage"
cli_mode=false
@@ -42,12 +43,16 @@ require_all_thresholds_for_incremental_review=false
minimal_commits_for_incremental_review=0
minimal_minutes_for_incremental_review=0
enable_help_text=true # Determines whether to include help text in the PR review. Enabled by default.
+# auto approval
+enable_auto_approval=false
+maximal_review_effort=5
+
[pr_description] # /describe #
publish_labels=true
publish_description_as_comment=false
add_original_user_description=true
-keep_original_user_title=false
+keep_original_user_title=true
use_bullet_points=true
extra_instructions = ""
enable_pr_type=true
@@ -68,17 +73,19 @@ enable_help_text=true
[pr_code_suggestions] # /improve #
+max_context_tokens=8000
num_code_suggestions=4
summarize = true
extra_instructions = ""
rank_suggestions = false
enable_help_text=true
# params for '/improve --extended' mode
-auto_extended_mode=false
-num_code_suggestions_per_chunk=8
-rank_extended_suggestions = true
-max_number_of_calls = 5
-final_clip_factor = 0.9
+auto_extended_mode=true
+num_code_suggestions_per_chunk=5
+max_number_of_calls = 3
+parallel_calls = true
+rank_extended_suggestions = false
+final_clip_factor = 0.8
[pr_add_docs] # /add_docs #
extra_instructions = ""
@@ -90,6 +97,15 @@ extra_instructions = ""
[pr_analyze] # /analyze #
+[pr_test] # /test #
+extra_instructions = ""
+testing_framework = "" # specify the testing framework you want to use
+num_tests=3 # number of tests to generate. max 5.
+avoid_mocks=true # if true, the generated tests will prefer to use real objects instead of mocks
+file = "" # in case there are several components with the same name, you can specify the relevant file
+class_name = "" # in case there are several methods with the same name in the same file, you can specify the relevant class name
+enable_help_text=true
+
[pr_config] # /config #
[github]
@@ -100,7 +116,7 @@ base_url = "https://api.github.com"
publish_inline_comments_fallback_with_verification = true
try_fix_invalid_inline_comments = true
-[github_action]
+[github_action_config]
# auto_review = true # set as env var in .github/workflows/pr-agent.yaml
# auto_describe = true # set as env var in .github/workflows/pr-agent.yaml
# auto_improve = true # set as env var in .github/workflows/pr-agent.yaml
diff --git a/pr_agent/settings/pr_add_docs.toml b/pr_agent/settings/pr_add_docs.toml
index 44e9f091..cc33eee5 100644
--- a/pr_agent/settings/pr_add_docs.toml
+++ b/pr_agent/settings/pr_add_docs.toml
@@ -5,7 +5,7 @@ Your task is to generate {{ docs_for_language }} for code components in the PR D
Example for the PR Diff format:
======
-## src/file1.py
+## file: 'src/file1.py'
@@ -12,3 +12,4 @@ def func1():
__new hunk__
@@ -18,7 +18,6 @@ __old hunk__
-code line that was removed in the PR
code line2 that remained unchanged in the PR
-
@@ ... @@ def func2():
__new hunk__
...
@@ -26,7 +25,7 @@ __old hunk__
...
-## src/file2.py
+## file: 'src/file2.py'
...
======
diff --git a/pr_agent/settings/pr_code_suggestions_prompts.toml b/pr_agent/settings/pr_code_suggestions_prompts.toml
index 2fb224c7..22a699ec 100644
--- a/pr_agent/settings/pr_code_suggestions_prompts.toml
+++ b/pr_agent/settings/pr_code_suggestions_prompts.toml
@@ -4,19 +4,18 @@ Your task is to provide meaningful and actionable code suggestions, to improve t
Example for the PR Diff format:
======
-## src/file1.py
+## file: 'src/file1.py'
@@ ... @@ def func1():
__new hunk__
12 code line1 that remained unchanged in the PR
-13 +new code line2 added in the PR
+13 +new hunk code line2 added in the PR
14 code line3 that remained unchanged in the PR
__old hunk__
code line1 that remained unchanged in the PR
--old code line2 that was removed in the PR
+-old hunk code line2 that was removed in the PR
code line3 that remained unchanged in the PR
-
@@ ... @@ def func2():
__new hunk__
...
@@ -24,7 +23,7 @@ __old hunk__
...
-## src/file2.py
+## file: 'src/file2.py'
...
======
@@ -34,7 +33,7 @@ Specific instructions:
- The suggestions should refer only to code from the '__new hunk__' sections, and focus on new lines of code (lines starting with '+').
- Prioritize suggestions that address major problems, issues and bugs in the PR code. As a second priority, suggestions should focus on enhancement, best practice, performance, maintainability, and other aspects.
- Don't suggest to add docstring, type hints, or comments, or to remove unused imports.
-- Avoid making suggestions that have already been implemented in the PR code. For example, if you want to add logs, or change a variable to const, or anything else, make sure it isn't already in the '__new hunk__' code.
+- Suggestions should not repeat code already present in the '__new hunk__' sections.
- Provide the exact line numbers range (inclusive) for each suggestion.
- When quoting variables or names from the code, use backticks (`) instead of single quote (').
@@ -51,6 +50,7 @@ The output must be a YAML object equivalent to type $PRCodeSuggestions, accordin
=====
class CodeSuggestion(BaseModel):
relevant_file: str = Field(description="the relevant file full path")
+ language: str = Field(description="the code language of the relevant file")
suggestion_content: str = Field(description="an actionable suggestion for meaningfully improving the new code introduced in the PR")
{%- if summarize_mode %}
existing_code: str = Field(description="a short code snippet from a '__new hunk__' section to illustrate the relevant existing code. Don't show the line numbers.")
@@ -72,44 +72,41 @@ class PRCodeSuggestions(BaseModel):
Example output:
```yaml
code_suggestions:
-- relevant_file: |-
+- relevant_file: |
src/file1.py
- suggestion_content: |-
+ language: |
+ python
+ suggestion_content: |
Add a docstring to func1()
{%- if summarize_mode %}
- existing_code: |-
+ existing_code: |
def func1():
- improved_code: |-
+ improved_code: |
...
- one_sentence_summary: |-
+ one_sentence_summary: |
...
relevant_lines_start: 12
relevant_lines_end: 12
{%- else %}
- existing_code: |-
+ existing_code: |
def func1():
relevant_lines_start: 12
relevant_lines_end: 12
- improved_code: |-
+ improved_code: |
...
{%- endif %}
- label: |-
+ label: |
...
```
-Each YAML output MUST be after a newline, indented, with block scalar indicator ('|-').
+Each YAML output MUST be after a newline, indented, with block scalar indicator ('|').
"""
user="""PR Info:
Title: '{{title}}'
-{%- if language %}
-
-Main PR language: '{{ language }}'
-{%- endif %}
-
The PR Diff:
======
diff --git a/pr_agent/settings/pr_description_prompts.toml b/pr_agent/settings/pr_description_prompts.toml
index b9c5ce39..b36b0183 100644
--- a/pr_agent/settings/pr_description_prompts.toml
+++ b/pr_agent/settings/pr_description_prompts.toml
@@ -39,6 +39,7 @@ class PRType(str, Enum):
Class FileDescription(BaseModel):
filename: str = Field(description="the relevant file full path")
+ language: str = Field(description="the relevant file language")
changes_summary: str = Field(description="concise summary of the changes in the relevant file, in bullet points (1-4 bullet points).")
changes_title: str = Field(description="an informative title for the changes in the files, describing its main theme (5-10 words).")
label: str = Field(description="a single semantic label that represents a type of code changes that occurred in the File. Possible values (partial list): 'bug fix', 'tests', 'enhancement', 'documentation', 'error handling', 'configuration changes', 'dependencies', 'formatting', 'miscellaneous', ...")
@@ -67,6 +68,8 @@ type:
pr_files:
- filename: |
...
+ language: |
+ ...
changes_summary: |
...
changes_title: |
@@ -104,10 +107,7 @@ Previous description:
{%- endif %}
Branch: '{{branch}}'
-{%- if language %}
-Main PR language: '{{ language }}'
-{%- endif %}
{%- if commit_messages_str %}
Commit messages:
diff --git a/pr_agent/settings/pr_reviewer_prompts.toml b/pr_agent/settings/pr_reviewer_prompts.toml
index 6705efea..adc72172 100644
--- a/pr_agent/settings/pr_reviewer_prompts.toml
+++ b/pr_agent/settings/pr_reviewer_prompts.toml
@@ -5,7 +5,7 @@ The review should focus on new code added in the PR diff (lines starting with '+
Example PR Diff:
======
-## src/file1.py
+## file: 'src/file1.py'
@@ -12,5 +12,5 @@ def func1():
code line 1 that remained unchanged in the PR
@@ -14,12 +14,11 @@ code line 2 that remained unchanged in the PR
+code line added in the PR
code line 3 that remained unchanged in the PR
-
@@ ... @@ def func2():
...
-## src/file2.py
+## file: 'src/file2.py'
...
======
@@ -96,6 +95,9 @@ PR Feedback:
relevant file:
type: string
description: the relevant file full path
+ language:
+ type: string
+ description: the language of the relevant file
suggestion:
type: string
description: |-
@@ -141,6 +143,8 @@ PR Feedback:
Code feedback:
- relevant file: |-
directory/xxx.py
+ language: |-
+ python
suggestion: |-
xxx [important]
relevant line: |-
@@ -170,10 +174,6 @@ Description:
======
{%- endif %}
-{%- if language %}
-
-Main PR language: '{{ language }}'
-{%- endif %}
{%- if commit_messages_str %}
Commit messages:
diff --git a/pr_agent/tools/pr_code_suggestions.py b/pr_agent/tools/pr_code_suggestions.py
index 97c183f2..b944047e 100644
--- a/pr_agent/tools/pr_code_suggestions.py
+++ b/pr_agent/tools/pr_code_suggestions.py
@@ -1,3 +1,4 @@
+import asyncio
import copy
import textwrap
from functools import partial
@@ -8,7 +9,7 @@ from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.pr_processing import get_pr_diff, get_pr_multi_diffs, retry_with_fallback_models
from pr_agent.algo.token_handler import TokenHandler
-from pr_agent.algo.utils import load_yaml, replace_code_tags
+from pr_agent.algo.utils import load_yaml, replace_code_tags, ModelType
from pr_agent.config_loader import get_settings
from pr_agent.git_providers import get_git_provider
from pr_agent.git_providers.git_provider import get_main_pr_language
@@ -26,6 +27,14 @@ class PRCodeSuggestions:
self.git_provider.get_languages(), self.git_provider.get_files()
)
+ # limit context specifically for the improve command, which has hard input to parse:
+ if get_settings().pr_code_suggestions.max_context_tokens:
+ MAX_CONTEXT_TOKENS_IMPROVE = get_settings().pr_code_suggestions.max_context_tokens
+ if get_settings().config.max_model_tokens > MAX_CONTEXT_TOKENS_IMPROVE:
+ get_logger().info(f"Setting max_model_tokens to {MAX_CONTEXT_TOKENS_IMPROVE} for PR improve")
+ get_settings().config.max_model_tokens = MAX_CONTEXT_TOKENS_IMPROVE
+
+
# extended mode
try:
self.is_extended = self._get_is_extended(args or [])
@@ -64,10 +73,10 @@ class PRCodeSuggestions:
get_logger().info('Preparing PR code suggestions...')
if not self.is_extended:
- await retry_with_fallback_models(self._prepare_prediction)
+ await retry_with_fallback_models(self._prepare_prediction, ModelType.TURBO)
data = self._prepare_pr_code_suggestions()
else:
- data = await retry_with_fallback_models(self._prepare_prediction_extended)
+ data = await retry_with_fallback_models(self._prepare_prediction_extended, ModelType.TURBO)
if (not data) or (not 'code_suggestions' in data):
@@ -103,18 +112,18 @@ class PRCodeSuggestions:
async def _prepare_prediction(self, model: str):
get_logger().info('Getting PR diff...')
- self.patches_diff = get_pr_diff(self.git_provider,
+ patches_diff = get_pr_diff(self.git_provider,
self.token_handler,
model,
add_line_numbers_to_hunks=True,
disable_extra_lines=True)
get_logger().info('Getting AI prediction...')
- self.prediction = await self._get_prediction(model)
+ self.prediction = await self._get_prediction(model, patches_diff)
- async def _get_prediction(self, model: str):
+ async def _get_prediction(self, model: str, patches_diff: str):
variables = copy.deepcopy(self.vars)
- variables["diff"] = self.patches_diff # update diff
+ variables["diff"] = patches_diff # update diff
environment = Environment(undefined=StrictUndefined)
system_prompt = environment.from_string(get_settings().pr_code_suggestions_prompt.system).render(variables)
user_prompt = environment.from_string(get_settings().pr_code_suggestions_prompt.user).render(variables)
@@ -190,7 +199,8 @@ class PRCodeSuggestions:
original_initial_line = None
for file in self.diff_files:
if file.filename.strip() == relevant_file:
- original_initial_line = file.head_file.splitlines()[relevant_lines_start - 1]
+ if file.head_file: # in bitbucket, head_file is empty. toDo: fix this
+ original_initial_line = file.head_file.splitlines()[relevant_lines_start - 1]
break
if original_initial_line:
suggested_initial_line = new_code_snippet.splitlines()[0]
@@ -220,14 +230,18 @@ class PRCodeSuggestions:
patches_diff_list = get_pr_multi_diffs(self.git_provider, self.token_handler, model,
max_calls=get_settings().pr_code_suggestions.max_number_of_calls)
- get_logger().info('Getting multi AI predictions...')
- prediction_list = []
- for i, patches_diff in enumerate(patches_diff_list):
- get_logger().info(f"Processing chunk {i + 1} of {len(patches_diff_list)}")
- self.patches_diff = patches_diff
- prediction = await self._get_prediction(model)
- prediction_list.append(prediction)
- self.prediction_list = prediction_list
+ # parallelize calls to AI:
+ if get_settings().pr_code_suggestions.parallel_calls:
+ get_logger().info('Getting multi AI predictions in parallel...')
+ prediction_list = await asyncio.gather(*[self._get_prediction(model, patches_diff) for patches_diff in patches_diff_list])
+ self.prediction_list = prediction_list
+ else:
+ get_logger().info('Getting multi AI predictions...')
+ prediction_list = []
+ for i, patches_diff in enumerate(patches_diff_list):
+ get_logger().info(f"Processing chunk {i + 1} of {len(patches_diff_list)}")
+ prediction = await self._get_prediction(model, patches_diff)
+ prediction_list.append(prediction)
data = {}
for prediction in prediction_list:
@@ -252,10 +266,15 @@ class PRCodeSuggestions:
"""
suggestion_list = []
+ if not data:
+ return suggestion_list
for suggestion in data:
suggestion_list.append(suggestion)
data_sorted = [[]] * len(suggestion_list)
+ if len(suggestion_list ) == 1:
+ return suggestion_list
+
try:
suggestion_str = ""
for i, suggestion in enumerate(suggestion_list):
@@ -311,7 +330,7 @@ class PRCodeSuggestions:
pr_body += ""
header = f"Suggestions"
- delta = 77
+ delta = 75
header += " " * delta
pr_body += f""" | {header} | """
pr_body += """"""
diff --git a/pr_agent/tools/pr_description.py b/pr_agent/tools/pr_description.py
index 0cc32b7f..135b850b 100644
--- a/pr_agent/tools/pr_description.py
+++ b/pr_agent/tools/pr_description.py
@@ -9,7 +9,7 @@ from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
from pr_agent.algo.pr_processing import get_pr_diff, retry_with_fallback_models
from pr_agent.algo.token_handler import TokenHandler
-from pr_agent.algo.utils import load_yaml, set_custom_labels, get_user_labels
+from pr_agent.algo.utils import load_yaml, set_custom_labels, get_user_labels, ModelType
from pr_agent.config_loader import get_settings
from pr_agent.git_providers import get_git_provider
from pr_agent.git_providers.git_provider import get_main_pr_language
@@ -80,7 +80,7 @@ class PRDescription:
if get_settings().config.publish_output:
self.git_provider.publish_comment("Preparing PR description...", is_temporary=True)
- await retry_with_fallback_models(self._prepare_prediction)
+ await retry_with_fallback_models(self._prepare_prediction, ModelType.TURBO) # turbo model because larger context
get_logger().info(f"Preparing answer {self.pr_id}")
if self.prediction:
@@ -113,22 +113,27 @@ class PRDescription:
if get_settings().config.publish_output:
get_logger().info(f"Pushing answer {self.pr_id}")
+
+ # publish labels
+ if get_settings().pr_description.publish_labels and self.git_provider.is_supported("get_labels"):
+ current_labels = self.git_provider.get_pr_labels()
+ user_labels = get_user_labels(current_labels)
+ self.git_provider.publish_labels(pr_labels + user_labels)
+
+ # publish description
if get_settings().pr_description.publish_description_as_comment:
get_logger().info(f"Publishing answer as comment")
self.git_provider.publish_comment(full_markdown_description)
else:
self.git_provider.publish_description(pr_title, pr_body)
- if get_settings().pr_description.publish_labels and self.git_provider.is_supported("get_labels"):
- current_labels = self.git_provider.get_pr_labels()
- user_labels = get_user_labels(current_labels)
- self.git_provider.publish_labels(pr_labels + user_labels)
+ # publish final update message
if (get_settings().pr_description.final_update_message and
hasattr(self.git_provider, 'pr_url') and self.git_provider.pr_url):
latest_commit_url = self.git_provider.get_latest_commit_url()
if latest_commit_url:
self.git_provider.publish_comment(
- f"**[PR Description]({self.git_provider.pr_url})** updated to latest commit ({latest_commit_url})")
+ f"**[PR Description]({self.git_provider.get_pr_url()})** updated to latest commit ({latest_commit_url})")
self.git_provider.remove_initial_comment()
except Exception as e:
get_logger().error(f"Error generating PR description {self.pr_id}: {e}")
@@ -358,7 +363,7 @@ class PRDescription:
try:
pr_body += ""
header = f"Relevant files"
- delta = 77
+ delta = 75
# header += " " * delta
pr_body += f""" | {header} | """
pr_body += """"""
@@ -374,8 +379,7 @@ class PRDescription:
for filename, file_changes_title, file_change_description in list_tuples:
filename = filename.replace("'", "`")
filename_publish = filename.split("/")[-1]
- file_changes_title_br = insert_br_after_x_chars(file_changes_title, x=(delta - 5),
- new_line_char="\n\n")
+ file_changes_title_br = insert_br_after_x_chars(file_changes_title, x=(delta - 5))
file_changes_title_extended = file_changes_title_br.strip() + ""
if len(file_changes_title_extended) < (delta - 5):
file_changes_title_extended += " " * ((delta - 5) - len(file_changes_title_extended))
@@ -407,7 +411,11 @@ class PRDescription:
{filename}
{file_change_description_br}
-
+
+
+
+
+
{diff_plus_minus}{delta_nbsp} |
@@ -423,48 +431,74 @@ class PRDescription:
pass
return pr_body
-def insert_br_after_x_chars(text, x=70, new_line_char=" "):
+def insert_br_after_x_chars(text, x=70):
"""
Insert into a string after a word that increases its length above x characters.
+ Use proper HTML tags for code and new lines.
"""
if len(text) < x:
return text
- lines = text.splitlines()
+ # replace odd instances of ` with and even instances of ` with
+ text = replace_code_tags(text)
+
+ # convert list items to
+ if text.startswith("- "):
+ text = "" + text[2:]
+ text = text.replace("\n- ", ' ').replace("\n - ", ' ')
+
+ # convert new lines to
+ text = text.replace("\n", ' ')
+
+ # split text into lines
+ lines = text.split(' ')
words = []
- for i,line in enumerate(lines):
+ for i, line in enumerate(lines):
words += line.split(' ')
- if i"
+ def count_chars_without_html(string):
+ if '<' not in string:
+ return len(string)
+ no_html_string = re.sub('<[^>]+>', '', string)
+ return len(no_html_string)
- # words = text.split(' ')
-
- new_text = ""
- current_length = 0
+ new_text = []
is_inside_code = False
+ current_length = 0
for word in words:
- # Check if adding this word exceeds x characters
- if current_length + len(word) > x:
- if not is_inside_code:
- new_text += f"{new_line_char} " # Insert line break
- current_length = 0 # Reset counter
+ is_saved_word = False
+ if word == "" or word == " " or word == "" or word == " ":
+ is_saved_word = True
+
+ len_word = count_chars_without_html(word)
+ if not is_saved_word and (current_length + len_word > x):
+ if is_inside_code:
+ new_text.append("
")
else:
- new_text += f"`{new_line_char} `"
- # check if inside tag
- if word.startswith("`") and not is_inside_code and not word.endswith("`"):
- is_inside_code = True
- if word.endswith("`"):
- is_inside_code = False
+ new_text.append(" ")
+ current_length = 0 # Reset counter
+ new_text.append(word + " ")
- # Add the word to the new text
- if word.endswith("\n"):
- new_text += word
- else:
- new_text += word + " "
- current_length += len(word) + 1 # Add 1 for the space
+ if not is_saved_word:
+ current_length += len_word + 1 # Add 1 for the space
-
- if word.endswith("\n"):
+ if word == "" or word == " ":
current_length = 0
- return new_text.strip() # Remove trailing space
+
+ if "" in word:
+ is_inside_code = True
+ if " " in word:
+ is_inside_code = False
+ return ''.join(new_text).strip()
+
+def replace_code_tags(text):
+ """
+ Replace odd instances of ` with and even instances of ` with
+ """
+ parts = text.split('`')
+ for i in range(1, len(parts), 2):
+ parts[i] = '' + parts[i] + ' '
+ return ''.join(parts)
+
diff --git a/pr_agent/tools/pr_reviewer.py b/pr_agent/tools/pr_reviewer.py
index 31cc1c43..e714b194 100644
--- a/pr_agent/tools/pr_reviewer.py
+++ b/pr_agent/tools/pr_reviewer.py
@@ -36,6 +36,7 @@ class PRReviewer:
ai_handler (BaseAiHandler): The AI handler to be used for the review. Defaults to None.
args (list, optional): List of arguments passed to the PRReviewer class. Defaults to None.
"""
+ self.args = args
self.parse_args(args) # -i command
self.git_provider = get_git_provider()(pr_url, incremental=self.incremental)
@@ -102,6 +103,11 @@ class PRReviewer:
if self.incremental.is_incremental and not self._can_run_incremental_review():
return None
+ if isinstance(self.args, list) and self.args and self.args[0] == 'auto_approve':
+ get_logger().info(f'Auto approve flow PR: {self.pr_url} ...')
+ self.auto_approve_logic()
+ return None
+
get_logger().info(f'Reviewing PR: {self.pr_url} ...')
if get_settings().config.publish_output:
@@ -392,3 +398,30 @@ class PRReviewer:
self.git_provider.publish_labels(review_labels + current_labels_filtered)
except Exception as e:
get_logger().error(f"Failed to set review labels, error: {e}")
+
+ def auto_approve_logic(self):
+ """
+ Auto-approve a pull request if it meets the conditions for auto-approval.
+ """
+ if get_settings().pr_reviewer.enable_auto_approval:
+ maximal_review_effort = get_settings().pr_reviewer.maximal_review_effort
+ if maximal_review_effort < 5:
+ current_labels = self.git_provider.get_pr_labels()
+ for label in current_labels:
+ if label.lower().startswith('review effort [1-5]:'):
+ effort = int(label.split(':')[1].strip())
+ if effort > maximal_review_effort:
+ get_logger().info(
+ f"Auto-approve error: PR review effort ({effort}) is higher than the maximal review effort "
+ f"({maximal_review_effort}) allowed")
+ self.git_provider.publish_comment(
+ f"Auto-approve error: PR review effort ({effort}) is higher than the maximal review effort "
+ f"({maximal_review_effort}) allowed")
+ return
+ is_auto_approved = self.git_provider.auto_approve()
+ if is_auto_approved:
+ get_logger().info("Auto-approved PR")
+ self.git_provider.publish_comment("Auto-approved PR")
+ else:
+ get_logger().info("Auto-approval option is disabled")
+ self.git_provider.publish_comment("Auto-approval option for PR-Agent is disabled")
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 8a465c6f..b649f304 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -14,7 +14,7 @@ msrest==0.7.1
openai==0.27.8
pinecone-client
pinecone-datasets @ git+https://github.com/mrT23/pinecone-datasets.git@main
-lancedb==0.3.4
+lancedb==0.5.1
pytest==7.4.0
PyGithub==1.59.*
PyYAML==6.0.1
diff --git a/tests/unittest/test_codecommit_provider.py b/tests/unittest/test_codecommit_provider.py
index 6f187de7..56312d73 100644
--- a/tests/unittest/test_codecommit_provider.py
+++ b/tests/unittest/test_codecommit_provider.py
@@ -3,7 +3,7 @@ from unittest.mock import patch
from pr_agent.git_providers.codecommit_provider import CodeCommitFile
from pr_agent.git_providers.codecommit_provider import CodeCommitProvider
from pr_agent.git_providers.codecommit_provider import PullRequestCCMimic
-from pr_agent.git_providers.git_provider import EDIT_TYPE
+from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
class TestCodeCommitFile:
diff --git a/tests/unittest/test_convert_to_markdown.py b/tests/unittest/test_convert_to_markdown.py
index 87f8f983..36b0371d 100644
--- a/tests/unittest/test_convert_to_markdown.py
+++ b/tests/unittest/test_convert_to_markdown.py
@@ -1,5 +1,6 @@
# Generated by CodiumAI
from pr_agent.algo.utils import convert_to_markdown
+from pr_agent.tools.pr_description import insert_br_after_x_chars
"""
Code Analysis
@@ -93,3 +94,35 @@ class TestConvertToMarkdown:
}
expected_output = ''
assert convert_to_markdown(input_data).strip() == expected_output.strip()
+
+
+class TestBR:
+ def test_br1(self):
+ file_change_description = '- Imported `FilePatchInfo` and `EDIT_TYPE` from `pr_agent.algo.types` instead of `pr_agent.git_providers.git_provider`.'
+ file_change_description_br = insert_br_after_x_chars(file_change_description)
+ expected_output = ('Imported FilePatchInfo and EDIT_TYPE from '
+ 'pr_agent.algo.types instead of '
+ 'pr_agent.git_providers.git_provider .')
+ assert file_change_description_br == expected_output
+ # print("-----")
+ # print(file_change_description_br)
+
+ def test_br2(self):
+ file_change_description = ('- Created a - new -class `ColorPaletteResourcesCollection ColorPaletteResourcesCollection '
+ 'ColorPaletteResourcesCollection ColorPaletteResourcesCollection`')
+ file_change_description_br = insert_br_after_x_chars(file_change_description)
+ expected_output = ('Created a - new -class ColorPaletteResourcesCollection
'
+ 'ColorPaletteResourcesCollection ColorPaletteResourcesCollection '
+ '
ColorPaletteResourcesCollection ')
+ assert file_change_description_br == expected_output
+ # print("-----")
+ # print(file_change_description_br)
+
+ def test_br3(self):
+ file_change_description = 'Created a new class `ColorPaletteResourcesCollection` which extends `AvaloniaDictionary` and implements aaa'
+ file_change_description_br = insert_br_after_x_chars(file_change_description)
+ assert file_change_description_br == ('Created a new class ColorPaletteResourcesCollection which '
+ 'extends
AvaloniaDictionary'
+ ' and implements aaa')
+ # print("-----")
+ # print(file_change_description_br)
diff --git a/tests/unittest/test_find_line_number_of_relevant_line_in_file.py b/tests/unittest/test_find_line_number_of_relevant_line_in_file.py
index 7488c6df..fcb028ca 100644
--- a/tests/unittest/test_find_line_number_of_relevant_line_in_file.py
+++ b/tests/unittest/test_find_line_number_of_relevant_line_in_file.py
@@ -1,8 +1,7 @@
# Generated by CodiumAI
-from pr_agent.git_providers.git_provider import FilePatchInfo
-from pr_agent.algo.pr_processing import find_line_number_of_relevant_line_in_file
-
+from pr_agent.algo.types import FilePatchInfo
+from pr_agent.algo.utils import find_line_number_of_relevant_line_in_file
import pytest
|