From 9ef0c451bf684444472c5ad4d780c0372be29f4a Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Fri, 16 May 2025 16:30:10 +0700 Subject: [PATCH 01/68] Create provider module for --- pr_agent/git_providers/gitea_provider.py | 982 +++++++++++++++++++++++ 1 file changed, 982 insertions(+) create mode 100644 pr_agent/git_providers/gitea_provider.py diff --git a/pr_agent/git_providers/gitea_provider.py b/pr_agent/git_providers/gitea_provider.py new file mode 100644 index 00000000..9f271556 --- /dev/null +++ b/pr_agent/git_providers/gitea_provider.py @@ -0,0 +1,982 @@ +import hashlib +import json +from typing import Any, Dict, List, Optional, Set, Tuple +from urllib.parse import urlparse + +import giteapy +from giteapy.rest import ApiException + +from pr_agent.algo.file_filter import filter_ignored +from pr_agent.algo.language_handler import is_valid_file +from pr_agent.algo.types import EDIT_TYPE +from pr_agent.algo.utils import (clip_tokens, + find_line_number_of_relevant_line_in_file) +from pr_agent.config_loader import get_settings +from pr_agent.git_providers.git_provider import (MAX_FILES_ALLOWED_FULL, + FilePatchInfo, GitProvider, + IncrementalPR) +from pr_agent.log import get_logger + + +class GiteaProvider(GitProvider): + def __init__(self, url: Optional[str] = None): + super().__init__() + self.logger = get_logger() + + if not url: + self.logger.error("PR URL not provided.") + raise ValueError("PR URL not provided.") + + self.base_url = get_settings().get("GITEA.URL", "https://gitea.com").rstrip("/") + self.pr_url = "" + self.issue_url = "" + + gitea_access_token = get_settings().get("GITEA.PERSONAL_ACCESS_TOKEN", None) + if not gitea_access_token: + self.logger.error("Gitea access token not found in settings.") + raise ValueError("Gitea access token not found in settings.") + + self.repo_settings = get_settings().get("GITEA.REPO_SETTING", None) + configuration = giteapy.Configuration() + configuration.host = "{}/api/v1".format(self.base_url) + configuration.api_key['Authorization'] = f'token {gitea_access_token}' + + client = giteapy.ApiClient(configuration) + self.repo_api = RepoApi(client) + self.owner = None + self.repo = None + self.pr_number = None + self.issue_number = None + self.max_comment_chars = 65000 + self.enabled_pr = False + self.enabled_issue = False + self.temp_comments = [] + self.pr = None + self.git_files = [] + self.file_contents = {} + self.file_diffs = {} + self.sha = None + self.diff_files = [] + self.incremental = IncrementalPR(False) + self.comments_list = [] + self.unreviewed_files_set = dict() + + if "pulls" in url: + self.pr_url = url + self.__set_repo_and_owner_from_pr() + self.enabled_pr = True + self.pr = self.repo_api.get_pull_request( + owner=self.owner, + repo=self.repo, + pr_number=self.pr_number + ) + self.git_files = self.repo_api.get_change_file_pull_request( + owner=self.owner, + repo=self.repo, + pr_number=self.pr_number + ) + # Optional ignore with user custom + self.git_files = filter_ignored(self.git_files, platform="gitea") + + self.sha = self.pr.head.sha if self.pr.head.sha else "" + self.__add_file_content() + self.__add_file_diff() + self.pr_commits = self.repo_api.list_all_commits( + owner=self.owner, + repo=self.repo + ) + self.last_commit = self.pr_commits[-1] + self.base_sha = self.pr.base.sha if self.pr.base.sha else "" + self.base_ref = self.pr.base.ref if self.pr.base.ref else "" + elif "issues" in url: + self.issue_url = url + self.__set_repo_and_owner_from_issue() + self.enabled_issue = True + else: + self.pr_commits = None + + def __add_file_content(self): + for file in self.git_files: + file_path = file.get("filename") + # Ignore file from default settings + if not is_valid_file(file_path): + continue + + if file_path: + try: + content = self.repo_api.get_file_content( + owner=self.owner, + repo=self.repo, + commit_sha=self.sha, + filepath=file_path + ) + self.file_contents[file_path] = content + except ApiException as e: + self.logger.error(f"Error getting file content for {file_path}: {str(e)}") + + def __add_file_diff(self): + try: + diff_contents = self.repo_api.get_pull_request_diff( + owner=self.owner, + repo=self.repo, + pr_number=self.pr_number + ) + + lines = diff_contents.splitlines() + current_file = None + current_patch = [] + file_patches = {} + for line in lines: + if line.startswith('diff --git'): + if current_file and current_patch: + file_patches[current_file] = '\n'.join(current_patch) + current_patch = [] + current_file = line.split(' b/')[-1] + elif line.startswith('@@'): + current_patch = [line] + elif current_patch: + current_patch.append(line) + + if current_file and current_patch: + file_patches[current_file] = '\n'.join(current_patch) + + self.file_diffs = file_patches + except Exception as e: + self.logger.error(f"Error getting diff content: {str(e)}") + + def _parse_pr_url(self, pr_url: str) -> Tuple[str, str, int]: + parsed_url = urlparse(pr_url) + + if parsed_url.path.startswith('/api/v1'): + parsed_url = urlparse(pr_url.replace("/api/v1", "")) + + path_parts = parsed_url.path.strip('/').split('/') + if len(path_parts) < 4 or path_parts[2] != 'pulls': + raise ValueError("The provided URL does not appear to be a Gitea PR URL") + + try: + pr_number = int(path_parts[3]) + except ValueError as e: + raise ValueError("Unable to convert PR number to integer") from e + + owner = path_parts[0] + repo = path_parts[1] + + return owner, repo, pr_number + + def _parse_issue_url(self, issue_url: str) -> Tuple[str, str, int]: + parsed_url = urlparse(issue_url) + + if parsed_url.path.startswith('/api/v1'): + parsed_url = urlparse(issue_url.replace("/api/v1", "")) + + path_parts = parsed_url.path.strip('/').split('/') + if len(path_parts) < 4 or path_parts[2] != 'issues': + raise ValueError("The provided URL does not appear to be a Gitea issue URL") + + try: + issue_number = int(path_parts[3]) + except ValueError as e: + raise ValueError("Unable to convert issue number to integer") from e + + owner = path_parts[0] + repo = path_parts[1] + + return owner, repo, issue_number + + def __set_repo_and_owner_from_pr(self): + """Extract owner and repo from the PR URL""" + try: + owner, repo, pr_number = self._parse_pr_url(self.pr_url) + self.owner = owner + self.repo = repo + self.pr_number = pr_number + self.logger.info(f"Owner: {self.owner}, Repo: {self.repo}, PR Number: {self.pr_number}") + except ValueError as e: + self.logger.error(f"Error parsing PR URL: {str(e)}") + except Exception as e: + self.logger.error(f"Unexpected error: {str(e)}") + + def __set_repo_and_owner_from_issue(self): + """Extract owner and repo from the issue URL""" + try: + owner, repo, issue_number = self._parse_issue_url(self.issue_url) + self.owner = owner + self.repo = repo + self.issue_number = issue_number + self.logger.info(f"Owner: {self.owner}, Repo: {self.repo}, Issue Number: {self.issue_number}") + except ValueError as e: + self.logger.error(f"Error parsing issue URL: {str(e)}") + except Exception as e: + self.logger.error(f"Unexpected error: {str(e)}") + + def get_pr_url(self) -> str: + return self.pr_url + + def get_issue_url(self) -> str: + return self.issue_url + + def publish_comment(self, comment: str,is_temporary: bool = False) -> None: + """Publish a comment to the pull request""" + if is_temporary and not get_settings().config.publish_output_progress: + get_logger().debug(f"Skipping publish_comment for temporary comment") + return None + + if self.enabled_issue: + index = self.issue_number + elif self.enabled_pr: + index = self.pr_number + else: + self.logger.error("Neither PR nor issue URL provided.") + return None + + comment = self.limit_output_characters(comment, self.max_comment_chars) + reponse = self.repo_api.create_comment( + owner=self.owner, + repo=self.repo, + index=index, + comment=comment + ) + + if not reponse: + self.logger.error("Failed to publish comment") + return None + + if is_temporary: + self.temp_comments.append(comment) + + self.comments_list.append({ + "is_temporary": is_temporary, + "comment": comment, + "comment_id": reponse.id if isinstance(reponse, tuple) else reponse.id + }) + self.logger.info("Comment published") + + def edit_comment(self, comment, body : str): + body = self.limit_output_characters(body, self.max_comment_chars) + try: + self.repo_api.edit_comment( + owner=self.owner, + repo=self.repo, + comment_id=comment.get("comment_id") if isinstance(comment, dict) else comment.id, + comment=body + ) + except ApiException as e: + self.logger.error(f"Error editing comment: {e}") + return None + except Exception as e: + self.logger.error(f"Unexpected error: {e}") + return None + + + def publish_inline_comment(self,body: str, relevant_file: str, relevant_line_in_file: str, original_suggestion=None): + """Publish an inline comment on a specific line""" + body = self.limit_output_characters(body, self.max_comment_chars) + position, absolute_position = find_line_number_of_relevant_line_in_file(self.diff_files, + relevant_file.strip('`'), + relevant_line_in_file, + ) + if position == -1: + get_logger().info(f"Could not find position for {relevant_file} {relevant_line_in_file}") + subject_type = "FILE" + else: + subject_type = "LINE" + + path = relevant_file.strip() + payload = dict(body=body, path=path, old_position=position,new_position = absolute_position) if subject_type == "LINE" else {} + self.publish_inline_comments([payload]) + + + def publish_inline_comments(self, comments: List[Dict[str, Any]],body : str = "Inline comment") -> None: + response = self.repo_api.create_inline_comment( + owner=self.owner, + repo=self.repo, + pr_number=self.pr_number if self.enabled_pr else self.issue_number, + body=body, + commit_id=self.last_commit.sha if self.last_commit else "", + comments=comments + ) + + if not response: + self.logger.error("Failed to publish inline comment") + return None + + self.logger.info("Inline comment published") + + def publish_code_suggestions(self, suggestions: List[Dict[str, Any]]): + """Publish code suggestions""" + for suggestion in suggestions: + body = suggestion.get("body","") + if not body: + self.logger.error("No body provided for the suggestion") + continue + + path = suggestion.get("relevant_file","") + new_position = suggestion.get("relevant_lines_start",0) + old_position = suggestion.get("relevant_lines_start",0) if "original_suggestion" not in suggestion else suggestion["original_suggestion"].get("relevant_lines_start",0) + title_body = suggestion["original_suggestion"].get("suggestion_content","") if "original_suggestion" in suggestion else "" + payload = dict(body=body, path=path, old_position=old_position,new_position = new_position) + if title_body: + title_body = f"**Suggestion:** {title_body}" + self.publish_inline_comments([payload],title_body) + else: + self.publish_inline_comments([payload]) + + def add_eyes_reaction(self, issue_comment_id: int, disable_eyes: bool = False) -> Optional[int]: + """Add eyes reaction to a comment""" + try: + if disable_eyes: + return None + + comments = self.repo_api.list_all_comments( + owner=self.owner, + repo=self.repo, + index=self.pr_number if self.enabled_pr else self.issue_number + ) + + comment_ids = [comment.id for comment in comments] + if issue_comment_id not in comment_ids: + self.logger.error(f"Comment ID {issue_comment_id} not found. Available IDs: {comment_ids}") + return None + + response = self.repo_api.add_reaction_comment( + owner=self.owner, + repo=self.repo, + comment_id=issue_comment_id, + reaction="eyes" + ) + + if not response: + self.logger.error("Failed to add eyes reaction") + return None + + return response[0].id if isinstance(response, tuple) else response.id + + except ApiException as e: + self.logger.error(f"Error adding eyes reaction: {e}") + return None + except Exception as e: + self.logger.error(f"Unexpected error: {e}") + return None + + def remove_reaction(self, comment_id: int) -> None: + """Remove reaction from a comment""" + try: + response = self.repo_api.remove_reaction_comment( + owner=self.owner, + repo=self.repo, + comment_id=comment_id + ) + if not response: + self.logger.error("Failed to remove reaction") + except ApiException as e: + self.logger.error(f"Error removing reaction: {e}") + except Exception as e: + self.logger.error(f"Unexpected error: {e}") + + def get_commit_messages(self)-> str: + """Get commit messages for the PR""" + max_tokens = get_settings().get("CONFIG.MAX_COMMITS_TOKENS", None) + pr_commits = self.repo_api.get_pr_commits( + owner=self.owner, + repo=self.repo, + pr_number=self.pr_number + ) + + if not pr_commits: + self.logger.error("Failed to get commit messages") + return "" + + try: + commit_messages = [commit["commit"]["message"] for commit in pr_commits if commit] + + if not commit_messages: + self.logger.error("No commit messages found") + return "" + + commit_message = "".join(commit_messages) + if max_tokens: + commit_message = clip_tokens(commit_message, max_tokens) + + return commit_message + except Exception as e: + self.logger.error(f"Error processing commit messages: {str(e)}") + return "" + + def _get_file_content_from_base(self, filename: str) -> str: + return self.repo_api.get_file_content( + owner=self.owner, + repo=self.base_ref, + commit_sha=self.base_sha, + filepath=filename + ) + + def _get_file_content_from_latest_commit(self, filename: str) -> str: + return self.repo_api.get_file_content( + owner=self.owner, + repo=self.base_ref, + commit_sha=self.last_commit.sha, + filepath=filename + ) + + def get_diff_files(self) -> List[FilePatchInfo]: + """Get files that were modified in the PR""" + if self.diff_files: + return self.diff_files + + invalid_files_names = [] + counter_valid = 0 + diff_files = [] + for file in self.git_files: + filename = file.get("filename") + if not filename: + continue + + if not is_valid_file(filename): + invalid_files_names.append(filename) + continue + + counter_valid += 1 + avoid_load = False + patch = self.file_diffs.get(filename,"") + head_file = "" + base_file = "" + + if counter_valid >= MAX_FILES_ALLOWED_FULL and patch and not self.incremental.is_incremental: + avoid_load = True + if counter_valid == MAX_FILES_ALLOWED_FULL: + self.logger.info("Too many files in PR, will avoid loading full content for rest of files") + + if avoid_load: + head_file = "" + else: + # Get file content from this pr + head_file = self.file_contents.get(filename,"") + + if self.incremental.is_incremental and self.unreviewed_files_set: + base_file = self._get_file_content_from_latest_commit(filename) + self.unreviewed_files_set[filename] = patch + else: + if avoid_load: + base_file = "" + else: + base_file = self._get_file_content_from_base(filename) + + num_plus_lines = file.get("additions",0) + num_minus_lines = file.get("deletions",0) + status = file.get("status","") + + if status == 'added': + edit_type = EDIT_TYPE.ADDED + elif status == 'removed': + edit_type = EDIT_TYPE.DELETED + elif status == 'renamed': + edit_type = EDIT_TYPE.RENAMED + elif status == 'modified': + edit_type = EDIT_TYPE.MODIFIED + else: + self.logger.error(f"Unknown edit type: {status}") + edit_type = EDIT_TYPE.UNKNOWN + + file_patch_info = FilePatchInfo( + base_file=base_file, + head_file=head_file, + patch=patch, + filename=filename, + num_minus_lines=num_minus_lines, + num_plus_lines=num_plus_lines, + edit_type=edit_type + ) + diff_files.append(file_patch_info) + + if invalid_files_names: + self.logger.info(f"Filtered out files with invalid extensions: {invalid_files_names}") + + self.diff_files = diff_files + return diff_files + + def get_line_link(self, relevant_file, relevant_line_start, relevant_line_end = None) -> str: + if relevant_line_start == -1: + link = f"{self.base_url}/{self.owner}/{self.repo}/src/branch/{self.get_pr_branch()}/{relevant_file}" + elif relevant_line_end: + link = f"{self.base_url}/{self.owner}/{self.repo}/src/branch/{self.get_pr_branch()}/{relevant_file}#L{relevant_line_start}-L{relevant_line_end}" + else: + link = f"{self.base_url}/{self.owner}/{self.repo}/src/branch/{self.get_pr_branch()}/{relevant_file}#L{relevant_line_start}" + + self.logger.info(f"Generated link: {link}") + return link + + def get_files(self) -> List[Dict[str, Any]]: + """Get all files in the PR""" + return [file.get("filename","") for file in self.git_files] + + def get_num_of_files(self) -> int: + """Get number of files changed in the PR""" + return len(self.git_files) + + def get_issue_comments(self) -> List[Dict[str, Any]]: + """Get all comments in the PR""" + index = self.issue_number if self.enabled_issue else self.pr_number + comments = self.repo_api.list_all_comments( + owner=self.owner, + repo=self.repo, + index=index + ) + if not comments: + self.logger.error("Failed to get comments") + return [] + + return comments + + def get_languages(self) -> Set[str]: + """Get programming languages used in the repository""" + languages = self.repo_api.get_languages( + owner=self.owner, + repo=self.repo + ) + + return languages + + def get_pr_branch(self) -> str: + """Get the branch name of the PR""" + if not self.pr: + self.logger.error("Failed to get PR branch") + return "" + + return self.pr.head.ref if self.pr.head.ref else "" + + def get_pr_description_full(self) -> str: + """Get full PR description with metadata""" + if not self.pr: + self.logger.error("Failed to get PR description") + return "" + + return self.pr.body if self.pr.body else "" + + def get_pr_labels(self,update=False) -> List[str]: + """Get labels assigned to the PR""" + if not update: + if not self.pr.labels: + self.logger.error("Failed to get PR labels") + return [] + return [label.name for label in self.pr.labels] + + labels = self.repo_api.get_issue_labels( + owner=self.owner, + repo=self.repo, + issue_number=self.pr_number + ) + if not labels: + self.logger.error("Failed to get PR labels") + return [] + + return [label.name for label in labels] + + def get_repo_settings(self) -> str: + """Get repository settings""" + if not self.repo_settings: + self.logger.error("Repository settings not found") + return "" + + response = self.repo_api.get_file_content( + owner=self.owner, + repo=self.repo, + commit_sha=self.sha, + filepath=self.repo_settings + ) + if not response: + self.logger.error("Failed to get repository settings") + return "" + + return response + + def get_user_id(self) -> str: + """Get the ID of the authenticated user""" + return f"{self.pr.user.id}" if self.pr else "" + + def is_supported(self, capability) -> bool: + """Check if the provider is supported""" + return True + + def publish_description(self, pr_title: str, pr_body: str) -> None: + """Publish PR description""" + response = self.repo_api.edit_pull_request( + owner=self.owner, + repo=self.repo, + pr_number=self.pr_number if self.enabled_pr else self.issue_number, + title=pr_title, + body=pr_body + ) + + if not response: + self.logger.error("Failed to publish PR description") + return None + + self.logger.info("PR description published successfully") + if self.enabled_pr: + self.pr = self.repo_api.get_pull_request( + owner=self.owner, + repo=self.repo, + pr_number=self.pr_number + ) + + def publish_labels(self, labels: List[int]) -> None: + """Publish labels to the PR""" + if not labels: + self.logger.error("No labels provided to publish") + return None + + response = self.repo_api.add_labels( + owner=self.owner, + repo=self.repo, + issue_number=self.pr_number if self.enabled_pr else self.issue_number, + labels=labels + ) + + if response: + self.logger.info("Labels added successfully") + + def remove_comment(self, comment) -> None: + """Remove a specific comment""" + if not comment: + return + + try: + comment_id = comment.get("comment_id") + if not comment_id: + self.logger.error("Comment ID not found") + return None + self.repo_api.remove_comment( + owner=self.owner, + repo=self.repo, + comment_id=comment_id + ) + + if self.comments_list: + self.comments_list.remove(comment) + + self.logger.info(f"Comment removed successfully: {comment}") + except ApiException as e: + self.logger.error(f"Error removing comment: {e}") + raise e + + def remove_initial_comment(self) -> None: + """Remove the initial comment""" + for comment in self.comments_list: + try: + self.remove_comment(comment) + except Exception as e: + self.logger.error(f"Error removing comment: {e}") + continue + self.logger.info(f"Removed initial comment: {comment.get('comment_id')}") + + +class RepoApi(giteapy.RepositoryApi): + def __init__(self, client: giteapy.ApiClient): + self.repository = giteapy.RepositoryApi(client) + self.issue = giteapy.IssueApi(client) + self.logger = get_logger() + super().__init__(client) + + def create_inline_comment(self, owner: str, repo: str, pr_number: int, body : str ,commit_id : str, comments: List[Dict[str, Any]]) -> None: + body = { + "body": body, + "comments": comments, + "commit_id": commit_id, + } + return self.api_client.call_api( + '/repos/{owner}/{repo}/pulls/{pr_number}/reviews', + 'POST', + path_params={'owner': owner, 'repo': repo, 'pr_number': pr_number}, + body=body, + response_type='Repository', + auth_settings=['AuthorizationHeaderToken'] + ) + + def create_comment(self, owner: str, repo: str, index: int, comment: str): + body = { + "body": comment + } + return self.issue.issue_create_comment( + owner=owner, + repo=repo, + index=index, + body=body + ) + + def edit_comment(self, owner: str, repo: str, comment_id: int, comment: str): + body = { + "body": comment + } + return self.issue.issue_edit_comment( + owner=owner, + repo=repo, + id=comment_id, + body=body + ) + + def remove_comment(self, owner: str, repo: str, comment_id: int): + return self.issue.issue_delete_comment( + owner=owner, + repo=repo, + id=comment_id + ) + + def list_all_comments(self, owner: str, repo: str, index: int): + return self.issue.issue_get_comments( + owner=owner, + repo=repo, + index=index + ) + + def get_pull_request_diff(self, owner: str, repo: str, pr_number: int) -> str: + """Get the diff content of a pull request using direct API call""" + try: + token = self.api_client.configuration.api_key.get('Authorization', '').replace('token ', '') + url = f'/repos/{owner}/{repo}/pulls/{pr_number}.diff' + if token: + url = f'{url}?token={token}' + + response = self.api_client.call_api( + url, + 'GET', + path_params={}, + response_type=None, + _return_http_data_only=False, + _preload_content=False + ) + + if hasattr(response, 'data'): + raw_data = response.data.read() + return raw_data.decode('utf-8') + elif isinstance(response, tuple): + raw_data = response[0].read() + return raw_data.decode('utf-8') + else: + self.logger.error("Unexpected response format") + return "" + + except ApiException as e: + self.logger.error(f"Error getting diff: {str(e)}") + raise e + except Exception as e: + self.logger.error(f"Unexpected error: {str(e)}") + raise e + + def get_pull_request(self, owner: str, repo: str, pr_number: int): + """Get pull request details including description""" + return self.repository.repo_get_pull_request( + owner=owner, + repo=repo, + index=pr_number + ) + + def edit_pull_request(self, owner: str, repo: str, pr_number: int,title : str, body: str): + """Edit pull request description""" + body = { + "body": body, + "title" : title + } + return self.repository.repo_edit_pull_request( + owner=owner, + repo=repo, + index=pr_number, + body=body + ) + + def get_change_file_pull_request(self, owner: str, repo: str, pr_number: int): + """Get changed files in the pull request""" + try: + token = self.api_client.configuration.api_key.get('Authorization', '').replace('token ', '') + url = f'/repos/{owner}/{repo}/pulls/{pr_number}/files' + if token: + url = f'{url}?token={token}' + + response = self.api_client.call_api( + url, + 'GET', + path_params={}, + response_type=None, + _return_http_data_only=False, + _preload_content=False + ) + + if hasattr(response, 'data'): + raw_data = response.data.read() + diff_content = raw_data.decode('utf-8') + return json.loads(diff_content) if isinstance(diff_content, str) else diff_content + elif isinstance(response, tuple): + raw_data = response[0].read() + diff_content = raw_data.decode('utf-8') + return json.loads(diff_content) if isinstance(diff_content, str) else diff_content + + return [] + + except ApiException as e: + self.logger.error(f"Error getting changed files: {e}") + return [] + except Exception as e: + self.logger.error(f"Unexpected error: {e}") + return [] + + def get_languages(self, owner: str, repo: str): + """Get programming languages used in the repository""" + try: + token = self.api_client.configuration.api_key.get('Authorization', '').replace('token ', '') + url = f'/repos/{owner}/{repo}/languages' + if token: + url = f'{url}?token={token}' + + response = self.api_client.call_api( + url, + 'GET', + path_params={}, + response_type=None, + _return_http_data_only=False, + _preload_content=False + ) + + if hasattr(response, 'data'): + raw_data = response.data.read() + return json.loads(raw_data.decode('utf-8')) + elif isinstance(response, tuple): + raw_data = response[0].read() + return json.loads(raw_data.decode('utf-8')) + + return {} + + except ApiException as e: + self.logger.error(f"Error getting languages: {e}") + return {} + except Exception as e: + self.logger.error(f"Unexpected error: {e}") + return {} + + def get_file_content(self, owner: str, repo: str, commit_sha: str, filepath: str) -> str: + """Get raw file content from a specific commit""" + + try: + token = self.api_client.configuration.api_key.get('Authorization', '').replace('token ', '') + url = f'/repos/{owner}/{repo}/raw/{filepath}' + if token: + url = f'{url}?token={token}&ref={commit_sha}' + + response = self.api_client.call_api( + url, + 'GET', + path_params={}, + response_type=None, + _return_http_data_only=False, + _preload_content=False + ) + + if hasattr(response, 'data'): + raw_data = response.data.read() + return raw_data.decode('utf-8') + elif isinstance(response, tuple): + raw_data = response[0].read() + return raw_data.decode('utf-8') + + return "" + + except ApiException as e: + self.logger.error(f"Error getting file: {filepath}, content: {e}") + return "" + except Exception as e: + self.logger.error(f"Unexpected error: {e}") + return "" + + def get_issue_labels(self, owner: str, repo: str, issue_number: int): + """Get labels assigned to the issue""" + return self.issue.issue_get_labels( + owner=owner, + repo=repo, + index=issue_number + ) + + def list_all_commits(self, owner: str, repo: str): + return self.repository.repo_get_all_commits( + owner=owner, + repo=repo + ) + + def add_reviewer(self, owner: str, repo: str, pr_number: int, reviewers: List[str]): + body = { + "reviewers": reviewers + } + return self.api_client.call_api( + '/repos/{owner}/{repo}/pulls/{pr_number}/requested_reviewers', + 'POST', + path_params={'owner': owner, 'repo': repo, 'pr_number': pr_number}, + body=body, + response_type='Repository', + auth_settings=['AuthorizationHeaderToken'] + ) + + def add_reaction_comment(self, owner: str, repo: str, comment_id: int, reaction: str): + body = { + "content": reaction + } + return self.api_client.call_api( + '/repos/{owner}/{repo}/issues/comments/{id}/reactions', + 'POST', + path_params={'owner': owner, 'repo': repo, 'id': comment_id}, + body=body, + response_type='Repository', + auth_settings=['AuthorizationHeaderToken'] + ) + + def remove_reaction_comment(self, owner: str, repo: str, comment_id: int): + return self.api_client.call_api( + '/repos/{owner}/{repo}/issues/comments/{id}/reactions', + 'DELETE', + path_params={'owner': owner, 'repo': repo, 'id': comment_id}, + response_type='Repository', + auth_settings=['AuthorizationHeaderToken'] + ) + + def add_labels(self, owner: str, repo: str, issue_number: int, labels: List[int]): + body = { + "labels": labels + } + return self.issue.issue_add_label( + owner=owner, + repo=repo, + index=issue_number, + body=body + ) + + def get_pr_commits(self, owner: str, repo: str, pr_number: int): + """Get all commits in a pull request""" + try: + token = self.api_client.configuration.api_key.get('Authorization', '').replace('token ', '') + url = f'/repos/{owner}/{repo}/pulls/{pr_number}/commits' + if token: + url = f'{url}?token={token}' + + response = self.api_client.call_api( + url, + 'GET', + path_params={}, + response_type=None, + _return_http_data_only=False, + _preload_content=False + ) + + if hasattr(response, 'data'): + raw_data = response.data.read() + commits_data = json.loads(raw_data.decode('utf-8')) + return commits_data + elif isinstance(response, tuple): + raw_data = response[0].read() + commits_data = json.loads(raw_data.decode('utf-8')) + return commits_data + + return [] + + except ApiException as e: + self.logger.error(f"Error getting PR commits: {e}") + return [] + except Exception as e: + self.logger.error(f"Unexpected error: {e}") + return [] From cf2b95b7663346fa0492d6d43512b32b4db1488c Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Fri, 16 May 2025 16:30:50 +0700 Subject: [PATCH 02/68] Create webhook server implement for --- pr_agent/servers/gitea_app.py | 120 ++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 pr_agent/servers/gitea_app.py diff --git a/pr_agent/servers/gitea_app.py b/pr_agent/servers/gitea_app.py new file mode 100644 index 00000000..4df8b84c --- /dev/null +++ b/pr_agent/servers/gitea_app.py @@ -0,0 +1,120 @@ +import asyncio +import copy +import os +from typing import Any, Dict + +from fastapi import APIRouter, FastAPI, HTTPException, Request, Response +from starlette.background import BackgroundTasks +from starlette.middleware import Middleware +from starlette_context import context +from starlette_context.middleware import RawContextMiddleware + +from pr_agent.agent.pr_agent import PRAgent +from pr_agent.config_loader import get_settings, global_settings +from pr_agent.log import LoggingFormat, get_logger, setup_logger +from pr_agent.servers.utils import verify_signature + +# Setup logging and router +setup_logger(fmt=LoggingFormat.JSON, level=get_settings().get("CONFIG.LOG_LEVEL", "DEBUG")) +router = APIRouter() + +@router.post("/api/v1/gitea_webhooks") +async def handle_gitea_webhooks(background_tasks: BackgroundTasks, request: Request, response: Response): + """Handle incoming Gitea webhook requests""" + get_logger().debug("Received a Gitea webhook") + + body = await get_body(request) + + # Set context for the request + context["settings"] = copy.deepcopy(global_settings) + context["git_provider"] = {} + + # Handle the webhook in background + background_tasks.add_task(handle_request, body, event=request.headers.get("X-Gitea-Event", None)) + return {} + +async def get_body(request: Request): + """Parse and verify webhook request body""" + try: + body = await request.json() + except Exception as e: + get_logger().error("Error parsing request body", artifact={'error': e}) + raise HTTPException(status_code=400, detail="Error parsing request body") from e + + + # Verify webhook signature + webhook_secret = getattr(get_settings().gitea, 'webhook_secret', None) + if webhook_secret: + body_bytes = await request.body() + signature_header = request.headers.get('x-gitea-signature', None) + verify_signature(body_bytes, webhook_secret, f"sha256={signature_header}") + + return body + +async def handle_request(body: Dict[str, Any], event: str): + """Process Gitea webhook events""" + action = body.get("action") + if not action: + get_logger().debug("No action found in request body") + return {} + + agent = PRAgent() + + # Handle different event types + if event == "pull_request": + if action in ["opened", "reopened", "synchronized"]: + await handle_pr_event(body, event, action, agent) + elif event == "issue_comment": + if action == "created": + await handle_comment_event(body, event, action, agent) + + return {} + +async def handle_pr_event(body: Dict[str, Any], event: str, action: str, agent: PRAgent): + """Handle pull request events""" + pr = body.get("pull_request", {}) + if not pr: + return + + api_url = pr.get("url") + if not api_url: + return + + # Handle PR based on action + if action in ["opened", "reopened"]: + commands = get_settings().get("gitea.pr_commands", []) + for command in commands: + await agent.handle_request(api_url, command) + elif action == "synchronized": + # Handle push to PR + await agent.handle_request(api_url, "/review --incremental") + +async def handle_comment_event(body: Dict[str, Any], event: str, action: str, agent: PRAgent): + """Handle comment events""" + comment = body.get("comment", {}) + if not comment: + return + + comment_body = comment.get("body", "") + if not comment_body or not comment_body.startswith("/"): + return + + pr_url = body.get("pull_request", {}).get("url") + if not pr_url: + return + + await agent.handle_request(pr_url, comment_body) + +# FastAPI app setup +middleware = [Middleware(RawContextMiddleware)] +app = FastAPI(middleware=middleware) +app.include_router(router) + +def start(): + """Start the Gitea webhook server""" + port = int(os.environ.get("PORT", "3000")) + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=port) + +if __name__ == "__main__": + start() From 2d7636543c5981bdbbe85112390c568f64719886 Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Fri, 16 May 2025 16:31:49 +0700 Subject: [PATCH 03/68] Implement provider --- pr_agent/algo/file_filter.py | 3 +++ pr_agent/git_providers/__init__.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/pr_agent/algo/file_filter.py b/pr_agent/algo/file_filter.py index 5c575eef..79bb4d8e 100644 --- a/pr_agent/algo/file_filter.py +++ b/pr_agent/algo/file_filter.py @@ -58,6 +58,9 @@ def filter_ignored(files, platform = 'github'): files = files_o elif platform == 'azure': files = [f for f in files if not r.match(f)] + elif platform == 'gitea': + files = [f for f in files if not r.match(f.get("filename", ""))] + except Exception as e: print(f"Could not filter file list: {e}") diff --git a/pr_agent/git_providers/__init__.py b/pr_agent/git_providers/__init__.py index 16547d90..8ee2db08 100644 --- a/pr_agent/git_providers/__init__.py +++ b/pr_agent/git_providers/__init__.py @@ -8,6 +8,7 @@ from pr_agent.git_providers.bitbucket_server_provider import \ from pr_agent.git_providers.codecommit_provider import CodeCommitProvider from pr_agent.git_providers.gerrit_provider import GerritProvider from pr_agent.git_providers.git_provider import GitProvider +from pr_agent.git_providers.gitea_provider import GiteaProvider from pr_agent.git_providers.github_provider import GithubProvider from pr_agent.git_providers.gitlab_provider import GitLabProvider from pr_agent.git_providers.local_git_provider import LocalGitProvider @@ -21,6 +22,7 @@ _GIT_PROVIDERS = { 'codecommit': CodeCommitProvider, 'local': LocalGitProvider, 'gerrit': GerritProvider, + 'gitea': GiteaProvider } From fab8573c4d81530fca33507d5ddc53d617a665a7 Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Fri, 16 May 2025 16:33:36 +0700 Subject: [PATCH 04/68] Set default configuration --- pr_agent/settings/configuration.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index e63b7ea8..50dcbdb9 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -277,6 +277,14 @@ push_commands = [ "/review", ] +[gitea_app] +handle_push_trigger = true +pr_commands = [ + "/describe --pr_description.final_update_message=false", + "/review", + "/improve", +] + [bitbucket_app] pr_commands = [ "/describe --pr_description.final_update_message=false", From a692a700274fda7ca6b4f84e63f7b33ffbbe5aff Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Fri, 16 May 2025 16:34:11 +0700 Subject: [PATCH 05/68] Implement for docker --- docker/Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9e83e37b..ce609e48 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -33,6 +33,11 @@ FROM base AS azure_devops_webhook ADD pr_agent pr_agent CMD ["python", "pr_agent/servers/azuredevops_server_webhook.py"] +FROM base AS gitea_app +ADD pr_agent pr_agent +CMD ["python", "-m", "gunicorn", "-k", "uvicorn.workers.UvicornWorker", "-c", "pr_agent/servers/gunicorn_config.py","pr_agent.servers.gitea_app:app"] + + FROM base AS test ADD requirements-dev.txt . RUN pip install --no-cache-dir -r requirements-dev.txt && rm requirements-dev.txt From 8b1abbcc2c41004fca988b3872e73985695de256 Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Fri, 16 May 2025 16:34:53 +0700 Subject: [PATCH 06/68] Add lib dependency --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 86a50c84..1038af69 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,6 +31,7 @@ gunicorn==22.0.0 pytest-cov==5.0.0 pydantic==2.8.2 html2text==2024.2.26 +giteapy==1.0.8 # Uncomment the following lines to enable the 'similar issue' tool # pinecone-client # pinecone-datasets @ git+https://github.com/mrT23/pinecone-datasets.git@main From 1b74942919f63ab8cafec68a3b17470426650bcf Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Tue, 20 May 2025 15:18:07 +0700 Subject: [PATCH 07/68] Set default configuration of Gitea --- pr_agent/settings/.secrets_template.toml | 5 +++++ pr_agent/settings/configuration.toml | 1 + 2 files changed, 6 insertions(+) diff --git a/pr_agent/settings/.secrets_template.toml b/pr_agent/settings/.secrets_template.toml index 6572677d..9590a84c 100644 --- a/pr_agent/settings/.secrets_template.toml +++ b/pr_agent/settings/.secrets_template.toml @@ -68,6 +68,11 @@ webhook_secret = "" # Optional, may be commented out. personal_access_token = "" shared_secret = "" # webhook secret +[gitea] +# Gitea personal access token +personal_access_token="" +webhook_secret="" # webhook secret + [bitbucket] # For Bitbucket authentication auth_type = "bearer" # "bearer" or "basic" diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index 50dcbdb9..421ecff4 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -278,6 +278,7 @@ push_commands = [ ] [gitea_app] +url = "https://gitea.com" handle_push_trigger = true pr_commands = [ "/describe --pr_description.final_update_message=false", From 2d619564f259fdacc7d51775c1cd3862b113c4cd Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Tue, 20 May 2025 15:51:50 +0700 Subject: [PATCH 08/68] Update README for Gitea --- README.md | 82 +++++++++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index c18bfa7c..67fa57a7 100644 --- a/README.md +++ b/README.md @@ -91,47 +91,47 @@ This version includes a new tool, [Help Docs](https://qodo-merge-docs.qodo.ai/to Supported commands per platform: -| | | GitHub | GitLab | Bitbucket | Azure DevOps | -| ----- |---------------------------------------------------------------------------------------------------------|:------:|:------:|:---------:|:------------:| -| TOOLS | [Review](https://qodo-merge-docs.qodo.ai/tools/review/) | ✅ | ✅ | ✅ | ✅ | -| | [Describe](https://qodo-merge-docs.qodo.ai/tools/describe/) | ✅ | ✅ | ✅ | ✅ | -| | [Improve](https://qodo-merge-docs.qodo.ai/tools/improve/) | ✅ | ✅ | ✅ | ✅ | -| | [Ask](https://qodo-merge-docs.qodo.ai/tools/ask/) | ✅ | ✅ | ✅ | ✅ | -| | ⮑ [Ask on code lines](https://qodo-merge-docs.qodo.ai/tools/ask/#ask-lines) | ✅ | ✅ | | | -| | [Update CHANGELOG](https://qodo-merge-docs.qodo.ai/tools/update_changelog/) | ✅ | ✅ | ✅ | ✅ | -| | [Help Docs](https://qodo-merge-docs.qodo.ai/tools/help_docs/?h=auto#auto-approval) | ✅ | ✅ | ✅ | | -| | [Ticket Context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) 💎 | ✅ | ✅ | ✅ | | -| | [Utilizing Best Practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) 💎 | ✅ | ✅ | ✅ | | -| | [PR Chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat) 💎 | ✅ | | | | -| | [Suggestion Tracking](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking) 💎 | ✅ | ✅ | | | -| | [CI Feedback](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) 💎 | ✅ | | | | -| | [PR Documentation](https://qodo-merge-docs.qodo.ai/tools/documentation/) 💎 | ✅ | ✅ | | | -| | [Custom Labels](https://qodo-merge-docs.qodo.ai/tools/custom_labels/) 💎 | ✅ | ✅ | | | -| | [Analyze](https://qodo-merge-docs.qodo.ai/tools/analyze/) 💎 | ✅ | ✅ | | | -| | [Similar Code](https://qodo-merge-docs.qodo.ai/tools/similar_code/) 💎 | ✅ | | | | -| | [Custom Prompt](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | | -| | [Test](https://qodo-merge-docs.qodo.ai/tools/test/) 💎 | ✅ | ✅ | | | -| | [Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | | -| | [Scan Repo Discussions](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/) 💎 | ✅ | | | | -| | [Auto-Approve](https://qodo-merge-docs.qodo.ai/tools/improve/?h=auto#auto-approval) 💎 | ✅ | ✅ | ✅ | | -| | | | | | | -| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ | -| | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ | -| | [Tagging bot](https://github.com/Codium-ai/pr-agent#try-it-now) | ✅ | | | | -| | [Actions](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) | ✅ | ✅ | ✅ | ✅ | -| | | | | | | -| CORE | [PR compression](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ | -| | Adaptive and token-aware file patch fitting | ✅ | ✅ | ✅ | ✅ | -| | [Multiple models support](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/) | ✅ | ✅ | ✅ | ✅ | -| | [Local and global metadata](https://qodo-merge-docs.qodo.ai/core-abilities/metadata/) | ✅ | ✅ | ✅ | ✅ | -| | [Dynamic context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/) | ✅ | ✅ | ✅ | ✅ | -| | [Self reflection](https://qodo-merge-docs.qodo.ai/core-abilities/self_reflection/) | ✅ | ✅ | ✅ | ✅ | -| | [Static code analysis](https://qodo-merge-docs.qodo.ai/core-abilities/static_code_analysis/) 💎 | ✅ | ✅ | | | -| | [Global and wiki configurations](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) 💎 | ✅ | ✅ | ✅ | | -| | [PR interactive actions](https://www.qodo.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | ✅ | | | -| | [Impact Evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) 💎 | ✅ | ✅ | | | -| | [Code Validation 💎](https://qodo-merge-docs.qodo.ai/core-abilities/code_validation/) | ✅ | ✅ | ✅ | ✅ | -| | [Auto Best Practices 💎](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/) | ✅ | | | | +| | | GitHub | GitLab | Bitbucket | Azure DevOps | Gitea | +| ----- |---------------------------------------------------------------------------------------------------------|:------:|:------:|:---------:|:------------:|:-----:| +| TOOLS | [Review](https://qodo-merge-docs.qodo.ai/tools/review/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [Describe](https://qodo-merge-docs.qodo.ai/tools/describe/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [Improve](https://qodo-merge-docs.qodo.ai/tools/improve/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [Ask](https://qodo-merge-docs.qodo.ai/tools/ask/) | ✅ | ✅ | ✅ | ✅ | | +| | ⮑ [Ask on code lines](https://qodo-merge-docs.qodo.ai/tools/ask/#ask-lines) | ✅ | ✅ | | | | +| | [Update CHANGELOG](https://qodo-merge-docs.qodo.ai/tools/update_changelog/) | ✅ | ✅ | ✅ | ✅ | | +| | [Help Docs](https://qodo-merge-docs.qodo.ai/tools/help_docs/?h=auto#auto-approval) | ✅ | ✅ | ✅ | | | +| | [Ticket Context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) 💎 | ✅ | ✅ | ✅ | | | +| | [Utilizing Best Practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) 💎 | ✅ | ✅ | ✅ | | | +| | [PR Chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat) 💎 | ✅ | | | | | +| | [Suggestion Tracking](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking) 💎 | ✅ | ✅ | | | | +| | [CI Feedback](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) 💎 | ✅ | | | | | +| | [PR Documentation](https://qodo-merge-docs.qodo.ai/tools/documentation/) 💎 | ✅ | ✅ | | | | +| | [Custom Labels](https://qodo-merge-docs.qodo.ai/tools/custom_labels/) 💎 | ✅ | ✅ | | | | +| | [Analyze](https://qodo-merge-docs.qodo.ai/tools/analyze/) 💎 | ✅ | ✅ | | | | +| | [Similar Code](https://qodo-merge-docs.qodo.ai/tools/similar_code/) 💎 | ✅ | | | | | +| | [Custom Prompt](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | | | +| | [Test](https://qodo-merge-docs.qodo.ai/tools/test/) 💎 | ✅ | ✅ | | | | +| | [Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | | | +| | [Scan Repo Discussions](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/) 💎 | ✅ | | | | | +| | [Auto-Approve](https://qodo-merge-docs.qodo.ai/tools/improve/?h=auto#auto-approval) 💎 | ✅ | ✅ | ✅ | | | +| | | | | | | | +| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [Tagging bot](https://github.com/Codium-ai/pr-agent#try-it-now) | ✅ | | | | | +| | [Actions](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) | ✅ | ✅ | ✅ | ✅ | | +| | | | | | | | +| CORE | [PR compression](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ | | +| | Adaptive and token-aware file patch fitting | ✅ | ✅ | ✅ | ✅ | | +| | [Multiple models support](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/) | ✅ | ✅ | ✅ | ✅ | | +| | [Local and global metadata](https://qodo-merge-docs.qodo.ai/core-abilities/metadata/) | ✅ | ✅ | ✅ | ✅ | | +| | [Dynamic context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/) | ✅ | ✅ | ✅ | ✅ | | +| | [Self reflection](https://qodo-merge-docs.qodo.ai/core-abilities/self_reflection/) | ✅ | ✅ | ✅ | ✅ | | +| | [Static code analysis](https://qodo-merge-docs.qodo.ai/core-abilities/static_code_analysis/) 💎 | ✅ | ✅ | | | | +| | [Global and wiki configurations](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) 💎 | ✅ | ✅ | ✅ | | | +| | [PR interactive actions](https://www.qodo.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | ✅ | | | | +| | [Impact Evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) 💎 | ✅ | ✅ | | | | +| | [Code Validation 💎](https://qodo-merge-docs.qodo.ai/core-abilities/code_validation/) | ✅ | ✅ | ✅ | ✅ | | +| | [Auto Best Practices 💎](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/) | ✅ | | | | | - 💎 means this feature is available only in [Qodo Merge](https://www.qodo.ai/pricing/) [//]: # (- Support for additional git providers is described in [here](./docs/Full_environments.md)) From bd68a0de559611bfd37dc51653f6f3d9921aeb3b Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Tue, 20 May 2025 16:46:32 +0700 Subject: [PATCH 09/68] Update Gitea documents --- docs/docs/installation/gitea.md | 46 +++++++++++++++++++ docs/docs/installation/index.md | 1 + docs/docs/installation/locally.md | 15 +++++- docs/docs/installation/pr_agent.md | 8 ++++ .../docs/usage-guide/automations_and_usage.md | 15 +++++- docs/docs/usage-guide/index.md | 1 + docs/docs/usage-guide/introduction.md | 2 +- 7 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 docs/docs/installation/gitea.md diff --git a/docs/docs/installation/gitea.md b/docs/docs/installation/gitea.md new file mode 100644 index 00000000..476497f7 --- /dev/null +++ b/docs/docs/installation/gitea.md @@ -0,0 +1,46 @@ +## Run a Gitea webhook server + +1. In Gitea create a new user and give it "Reporter" role ("Developer" if using Pro version of the agent) for the intended group or project. + +2. For the user from step 1. generate a `personal_access_token` with `api` access. + +3. Generate a random secret for your app, and save it for later (`webhook_secret`). For example, you can use: + +```bash +WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))") +``` + +4. Clone this repository: + +```bash +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 "gitea" + +2. In the secrets file/variables: + - Set your AI model key in the respective section + - In the [Gitea] section, set `personal_access_token` (with token from step 2) and `webhook_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: + +```bash +docker build -f /docker/Dockerfile -t pr-agent:gitea_app --target gitea_app . +docker push codiumai/pr-agent:gitea_webhook # Push to your Docker repository +``` + +7. Set the environmental variables, the method depends on your docker runtime. Skip this step if you included your secrets/configuration directly in the Docker image. + +```bash +CONFIG__GIT_PROVIDER=gitea +GITEA__PERSONAL_ACCESS_TOKEN= +GITEA__WEBHOOK_SECRET= +GITEA__URL=https://gitea.com # Or self host +OPENAI__KEY= +``` + +8. Create a webhook in your Gitea project. Set the URL to `http[s]:///api/v1/gitea_webhooks`, 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. diff --git a/docs/docs/installation/index.md b/docs/docs/installation/index.md index 9831078d..cc593deb 100644 --- a/docs/docs/installation/index.md +++ b/docs/docs/installation/index.md @@ -9,6 +9,7 @@ There are several ways to use self-hosted PR-Agent: - [GitLab integration](./gitlab.md) - [BitBucket integration](./bitbucket.md) - [Azure DevOps integration](./azure.md) +- [Gitea integration](./gitea.md) ## Qodo Merge 💎 diff --git a/docs/docs/installation/locally.md b/docs/docs/installation/locally.md index cd981f96..9ceb077b 100644 --- a/docs/docs/installation/locally.md +++ b/docs/docs/installation/locally.md @@ -1,7 +1,7 @@ To run PR-Agent locally, you first need to acquire two keys: 1. An OpenAI key from [here](https://platform.openai.com/api-keys){:target="_blank"}, with access to GPT-4 and o4-mini (or a key for other [language models](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/), if you prefer). -2. A personal access token from your Git platform (GitHub, GitLab, BitBucket) with repo scope. GitHub token, for example, can be issued from [here](https://github.com/settings/tokens){:target="_blank"} +2. A personal access token from your Git platform (GitHub, GitLab, BitBucket,Gitea) with repo scope. GitHub token, for example, can be issued from [here](https://github.com/settings/tokens){:target="_blank"} ## Using Docker image @@ -40,6 +40,19 @@ To invoke a tool (for example `review`), you can run PR-Agent directly from the docker run --rm -it -e CONFIG.GIT_PROVIDER=bitbucket -e OPENAI.KEY=$OPENAI_API_KEY -e BITBUCKET.BEARER_TOKEN=$BITBUCKET_BEARER_TOKEN codiumai/pr-agent:latest --pr_url= review ``` +- For Gitea: + + ```bash + docker run --rm -it -e OPENAI.KEY= -e CONFIG.GIT_PROVIDER=gitea -e GITEA.PERSONAL_ACCESS_TOKEN= codiumai/pr-agent:latest --pr_url review + ``` + + If you have a dedicated Gitea instance, you need to specify the custom url as variable: + + ```bash + -e GITEA.URL= + ``` + + For other git providers, update `CONFIG.GIT_PROVIDER` accordingly and check the [`pr_agent/settings/.secrets_template.toml`](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/.secrets_template.toml) file for environment variables expected names and values. ### Utilizing environment variables diff --git a/docs/docs/installation/pr_agent.md b/docs/docs/installation/pr_agent.md index 1982b7a1..9a0e3f29 100644 --- a/docs/docs/installation/pr_agent.md +++ b/docs/docs/installation/pr_agent.md @@ -47,3 +47,11 @@ Configure PR-Agent with Azure DevOps as: - Local Azure DevOps webhook [View Azure DevOps Integration Guide →](https://qodo-merge-docs.qodo.ai/installation/azure/) + +## 🔷 Gitea Integration + +Deploy PR-Agent on Gitea as: + +- Local Gitea webhook server + +[View Gitea Integration Guide →](https://qodo-merge-docs.qodo.ai/installation/gitea/) diff --git a/docs/docs/usage-guide/automations_and_usage.md b/docs/docs/usage-guide/automations_and_usage.md index 9c3e29fd..0a634e77 100644 --- a/docs/docs/usage-guide/automations_and_usage.md +++ b/docs/docs/usage-guide/automations_and_usage.md @@ -30,7 +30,7 @@ verbosity_level=2 This is useful for debugging or experimenting with different tools. 3. **git provider**: The [git_provider](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L5) field in a configuration file determines the GIT provider that will be used by Qodo Merge. Currently, the following providers are supported: -`github` **(default)**, `gitlab`, `bitbucket`, `azure`, `codecommit`, `local`, and `gerrit`. +`github` **(default)**, `gitlab`, `bitbucket`, `azure`, `codecommit`, `local`,`gitea`, and `gerrit`. ### CLI Health Check @@ -312,3 +312,16 @@ pr_commands = [ "/improve", ] ``` + +### Gitea Webhook + +After setting up a Gitea webhook, to control which commands will run automatically when a new MR is opened, you can set the `pr_commands` parameter in the configuration file, similar to the GitHub App: + +```toml +[gitea] +pr_commands = [ + "/describe", + "/review", + "/improve", +] +``` diff --git a/docs/docs/usage-guide/index.md b/docs/docs/usage-guide/index.md index dba5a569..79df0be6 100644 --- a/docs/docs/usage-guide/index.md +++ b/docs/docs/usage-guide/index.md @@ -12,6 +12,7 @@ It includes information on how to adjust Qodo Merge configurations, define which - [GitHub App](./automations_and_usage.md#github-app) - [GitHub Action](./automations_and_usage.md#github-action) - [GitLab Webhook](./automations_and_usage.md#gitlab-webhook) + - [Gitea Webhook](./automations_and_usage.md#gitea-webhook) - [BitBucket App](./automations_and_usage.md#bitbucket-app) - [Azure DevOps Provider](./automations_and_usage.md#azure-devops-provider) - [Managing Mail Notifications](./mail_notifications.md) diff --git a/docs/docs/usage-guide/introduction.md b/docs/docs/usage-guide/introduction.md index 11e56b32..74838c1c 100644 --- a/docs/docs/usage-guide/introduction.md +++ b/docs/docs/usage-guide/introduction.md @@ -7,5 +7,5 @@ After [installation](https://qodo-merge-docs.qodo.ai/installation/), there are t Specifically, CLI commands can be issued by invoking a pre-built [docker image](https://qodo-merge-docs.qodo.ai/installation/locally/#using-docker-image), or by invoking a [locally cloned repo](https://qodo-merge-docs.qodo.ai/installation/locally/#run-from-source). -For online usage, you will need to setup either a [GitHub App](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-app) or a [GitHub Action](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) (GitHub), a [GitLab webhook](https://qodo-merge-docs.qodo.ai/installation/gitlab/#run-a-gitlab-webhook-server) (GitLab), or a [BitBucket App](https://qodo-merge-docs.qodo.ai/installation/bitbucket/#run-using-codiumai-hosted-bitbucket-app) (BitBucket). +For online usage, you will need to setup either a [GitHub App](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-app) or a [GitHub Action](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) (GitHub), a [GitLab webhook](https://qodo-merge-docs.qodo.ai/installation/gitlab/#run-a-gitlab-webhook-server) (GitLab), or a [BitBucket App](https://qodo-merge-docs.qodo.ai/installation/bitbucket/#run-using-codiumai-hosted-bitbucket-app) (BitBucket) or a [Gitea webhook](https://qodo-merge-docs.qodo.ai/installation/gitea/#run-a-gitea-webhook-server) (Gitea). These platforms also enable to run Qodo Merge specific tools automatically when a new PR is opened, or on each push to a branch. From b686a707a43bb8f1cdc2618616062c4ca898137e Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Tue, 20 May 2025 16:54:20 +0700 Subject: [PATCH 10/68] Not implement online --- docs/docs/installation/pr_agent.md | 8 -------- docs/docs/usage-guide/introduction.md | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/docs/docs/installation/pr_agent.md b/docs/docs/installation/pr_agent.md index 9a0e3f29..1982b7a1 100644 --- a/docs/docs/installation/pr_agent.md +++ b/docs/docs/installation/pr_agent.md @@ -47,11 +47,3 @@ Configure PR-Agent with Azure DevOps as: - Local Azure DevOps webhook [View Azure DevOps Integration Guide →](https://qodo-merge-docs.qodo.ai/installation/azure/) - -## 🔷 Gitea Integration - -Deploy PR-Agent on Gitea as: - -- Local Gitea webhook server - -[View Gitea Integration Guide →](https://qodo-merge-docs.qodo.ai/installation/gitea/) diff --git a/docs/docs/usage-guide/introduction.md b/docs/docs/usage-guide/introduction.md index 74838c1c..11e56b32 100644 --- a/docs/docs/usage-guide/introduction.md +++ b/docs/docs/usage-guide/introduction.md @@ -7,5 +7,5 @@ After [installation](https://qodo-merge-docs.qodo.ai/installation/), there are t Specifically, CLI commands can be issued by invoking a pre-built [docker image](https://qodo-merge-docs.qodo.ai/installation/locally/#using-docker-image), or by invoking a [locally cloned repo](https://qodo-merge-docs.qodo.ai/installation/locally/#run-from-source). -For online usage, you will need to setup either a [GitHub App](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-app) or a [GitHub Action](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) (GitHub), a [GitLab webhook](https://qodo-merge-docs.qodo.ai/installation/gitlab/#run-a-gitlab-webhook-server) (GitLab), or a [BitBucket App](https://qodo-merge-docs.qodo.ai/installation/bitbucket/#run-using-codiumai-hosted-bitbucket-app) (BitBucket) or a [Gitea webhook](https://qodo-merge-docs.qodo.ai/installation/gitea/#run-a-gitea-webhook-server) (Gitea). +For online usage, you will need to setup either a [GitHub App](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-app) or a [GitHub Action](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) (GitHub), a [GitLab webhook](https://qodo-merge-docs.qodo.ai/installation/gitlab/#run-a-gitlab-webhook-server) (GitLab), or a [BitBucket App](https://qodo-merge-docs.qodo.ai/installation/bitbucket/#run-using-codiumai-hosted-bitbucket-app) (BitBucket). These platforms also enable to run Qodo Merge specific tools automatically when a new PR is opened, or on each push to a branch. From 48c29c9ffa0c9329b56568ecbf45044eb6ad576b Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Thu, 22 May 2025 14:59:29 +0700 Subject: [PATCH 11/68] Add null check --- pr_agent/git_providers/gitea_provider.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pr_agent/git_providers/gitea_provider.py b/pr_agent/git_providers/gitea_provider.py index 9f271556..5eef4362 100644 --- a/pr_agent/git_providers/gitea_provider.py +++ b/pr_agent/git_providers/gitea_provider.py @@ -542,6 +542,10 @@ class GiteaProvider(GitProvider): if not self.pr: self.logger.error("Failed to get PR branch") return "" + + if not self.pr.head: + self.logger.error("PR head not found") + return "" return self.pr.head.ref if self.pr.head.ref else "" From 000f0ba93ebd123338fc7ec67387839cbefbbb05 Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Thu, 22 May 2025 15:01:08 +0700 Subject: [PATCH 12/68] Fixed ensure SHA --- pr_agent/git_providers/gitea_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pr_agent/git_providers/gitea_provider.py b/pr_agent/git_providers/gitea_provider.py index 5eef4362..91100cab 100644 --- a/pr_agent/git_providers/gitea_provider.py +++ b/pr_agent/git_providers/gitea_provider.py @@ -102,7 +102,7 @@ class GiteaProvider(GitProvider): if not is_valid_file(file_path): continue - if file_path: + if file_path and self.sha: try: content = self.repo_api.get_file_content( owner=self.owner, From 0f893bc4926cf0296c7e29c7153b67ecb321346c Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Thu, 22 May 2025 15:03:15 +0700 Subject: [PATCH 13/68] Fixed webhook security concern --- pr_agent/servers/gitea_app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pr_agent/servers/gitea_app.py b/pr_agent/servers/gitea_app.py index 4df8b84c..85399f2c 100644 --- a/pr_agent/servers/gitea_app.py +++ b/pr_agent/servers/gitea_app.py @@ -47,6 +47,10 @@ async def get_body(request: Request): if webhook_secret: body_bytes = await request.body() signature_header = request.headers.get('x-gitea-signature', None) + if not signature_header: + get_logger().error("Missing signature header") + raise HTTPException(status_code=400, detail="Missing signature header") + verify_signature(body_bytes, webhook_secret, f"sha256={signature_header}") return body From 162cc9d833612085da746f3fa94631187fee6fb2 Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Thu, 22 May 2025 15:06:35 +0700 Subject: [PATCH 14/68] Fixed error propagation --- pr_agent/git_providers/gitea_provider.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pr_agent/git_providers/gitea_provider.py b/pr_agent/git_providers/gitea_provider.py index 91100cab..4ac2e63b 100644 --- a/pr_agent/git_providers/gitea_provider.py +++ b/pr_agent/git_providers/gitea_provider.py @@ -757,8 +757,9 @@ class RepoApi(giteapy.RepositoryApi): raw_data = response[0].read() return raw_data.decode('utf-8') else: - self.logger.error("Unexpected response format") - return "" + error_msg = f"Unexpected response format received from API: {type(response)}" + self.logger.error(error_msg) + return RuntimeError(error_msg) except ApiException as e: self.logger.error(f"Error getting diff: {str(e)}") From d791e9f3d1f3f400fc63ff67022f3846282a974c Mon Sep 17 00:00:00 2001 From: Akileo Date: Sat, 17 May 2025 19:47:42 +0900 Subject: [PATCH 15/68] Fix: Improve langchain import error handling and add img_path to handler Addresses issue #1784: - Raises ImportError if langchain is not installed when LangChainOpenAIHandler is initialized. - Adds img_path parameter to LangChainOpenAIHandler.chat_completion for interface consistency. - Logs a warning if img_path is used with LangChainOpenAIHandler. --- pr_agent/algo/ai_handlers/langchain_ai_handler.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pr_agent/algo/ai_handlers/langchain_ai_handler.py b/pr_agent/algo/ai_handlers/langchain_ai_handler.py index 4d708fcb..b796f859 100644 --- a/pr_agent/algo/ai_handlers/langchain_ai_handler.py +++ b/pr_agent/algo/ai_handlers/langchain_ai_handler.py @@ -1,10 +1,14 @@ +_LANGCHAIN_INSTALLED = False + try: from langchain_core.messages import HumanMessage, SystemMessage from langchain_openai import AzureChatOpenAI, ChatOpenAI + _LANGCHAIN_INSTALLED = True except: # we don't enforce langchain as a dependency, so if it's not installed, just move on pass import functools +from typing import Optional import openai from tenacity import retry, retry_if_exception_type, retry_if_not_exception_type, stop_after_attempt @@ -18,7 +22,9 @@ OPENAI_RETRIES = 5 class LangChainOpenAIHandler(BaseAiHandler): def __init__(self): - # Initialize OpenAIHandler specific attributes here + if not _LANGCHAIN_INSTALLED: + raise ImportError("LangChain is not installed. Please install it with `pip install langchain`.") + super().__init__() self.azure = get_settings().get("OPENAI.API_TYPE", "").lower() == "azure" @@ -40,7 +46,9 @@ class LangChainOpenAIHandler(BaseAiHandler): retry=retry_if_exception_type(openai.APIError) & retry_if_not_exception_type(openai.RateLimitError), stop=stop_after_attempt(OPENAI_RETRIES), ) - async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2): + async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2, img_path: Optional[str] = None): + if img_path: + get_logger().warning(f"Image path is not supported for LangChainOpenAIHandler. Ignoring image path: {img_path}") try: messages = [SystemMessage(content=system), HumanMessage(content=user)] From ff52ae9281dbaff8672049f6b4a46ec38066cb24 Mon Sep 17 00:00:00 2001 From: Akileo Date: Sun, 25 May 2025 14:53:17 +0900 Subject: [PATCH 16/68] add img_path and _create_chat_async --- .../algo/ai_handlers/langchain_ai_handler.py | 58 +++++++++++++++++-- .../algo/ai_handlers/openai_ai_handler.py | 4 +- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/pr_agent/algo/ai_handlers/langchain_ai_handler.py b/pr_agent/algo/ai_handlers/langchain_ai_handler.py index b796f859..8e29b8bb 100644 --- a/pr_agent/algo/ai_handlers/langchain_ai_handler.py +++ b/pr_agent/algo/ai_handlers/langchain_ai_handler.py @@ -23,7 +23,9 @@ OPENAI_RETRIES = 5 class LangChainOpenAIHandler(BaseAiHandler): def __init__(self): if not _LANGCHAIN_INSTALLED: - raise ImportError("LangChain is not installed. Please install it with `pip install langchain`.") + error_msg = "LangChain is not installed. Please install it with `pip install langchain`." + get_logger().error(error_msg) + raise ImportError(error_msg) super().__init__() self.azure = get_settings().get("OPENAI.API_TYPE", "").lower() == "azure" @@ -42,18 +44,66 @@ class LangChainOpenAIHandler(BaseAiHandler): """ return get_settings().get("OPENAI.DEPLOYMENT_ID", None) + async def _create_chat_async(self, deployment_id=None): + try: + if self.azure: + # Using Azure OpenAI service + return AzureChatOpenAI( + openai_api_key=get_settings().openai.key, + openai_api_version=get_settings().openai.api_version, + azure_deployment=deployment_id, + azure_endpoint=get_settings().openai.api_base, + ) + else: + # Using standard OpenAI or other LLM services + openai_api_base = get_settings().get("OPENAI.API_BASE", None) + if openai_api_base is None or len(openai_api_base) == 0: + return ChatOpenAI(openai_api_key=get_settings().openai.key) + else: + return ChatOpenAI( + openai_api_key=get_settings().openai.key, + openai_api_base=openai_api_base + ) + except AttributeError as e: + # Handle configuration errors + error_msg = f"OpenAI {e.name} is required" if getattr(e, "name") else str(e) + get_logger().error(error_msg) + raise ValueError(error_msg) from e + @retry( retry=retry_if_exception_type(openai.APIError) & retry_if_not_exception_type(openai.RateLimitError), stop=stop_after_attempt(OPENAI_RETRIES), ) - async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2, img_path: Optional[str] = None): + async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2, img_path: str = None): if img_path: get_logger().warning(f"Image path is not supported for LangChainOpenAIHandler. Ignoring image path: {img_path}") try: messages = [SystemMessage(content=system), HumanMessage(content=user)] + llm = await self._create_chat_async(deployment_id=self.deployment_id) + + if not hasattr(llm, 'ainvoke'): + error_message = ( + f"The Langchain LLM object ({type(llm)}) does not have an 'ainvoke' async method. " + f"Please update your Langchain library to the latest version or " + f"check your LLM configuration to support async calls. " + f"PR-Agent is designed to utilize Langchain's async capabilities." + ) + get_logger().error(error_message) + raise NotImplementedError(error_message) + + # Handle parameters based on LLM type + if isinstance(llm, (ChatOpenAI, AzureChatOpenAI)): + # OpenAI models support all parameters + resp = await llm.ainvoke( + input=messages, + model=model, + temperature=temperature + ) + else: + # Other LLMs (like Gemini) only support input parameter + get_logger().info(f"Using simplified ainvoke for {type(llm)}") + resp = await llm.ainvoke(input=messages) - # get a chat completion from the formatted messages - resp = self.chat(messages, model=model, temperature=temperature) finish_reason = "completed" return resp.content, finish_reason diff --git a/pr_agent/algo/ai_handlers/openai_ai_handler.py b/pr_agent/algo/ai_handlers/openai_ai_handler.py index 253282b0..f5fb99f6 100644 --- a/pr_agent/algo/ai_handlers/openai_ai_handler.py +++ b/pr_agent/algo/ai_handlers/openai_ai_handler.py @@ -42,8 +42,10 @@ class OpenAIHandler(BaseAiHandler): retry=retry_if_exception_type(openai.APIError) & retry_if_not_exception_type(openai.RateLimitError), stop=stop_after_attempt(OPENAI_RETRIES), ) - async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2): + async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2, img_path: str = None): try: + if img_path: + get_logger().warning(f"Image path is not supported for OpenAIHandler. Ignoring image path: {img_path}") get_logger().info("System: ", system) get_logger().info("User: ", user) messages = [{"role": "system", "content": system}, {"role": "user", "content": user}] From d9f64e52e49e2b30d20adcce87c0deb1f4e2a7af Mon Sep 17 00:00:00 2001 From: Akileo Date: Sun, 25 May 2025 15:16:30 +0900 Subject: [PATCH 17/68] Refactor LangChain AI Handler for Async and Performance - Add async LLM object creation and invocation - Branch parameter handling by LLM type - Enhance error logging and handler consistency - Add performance and concurrency test code --- tests/unittest/test_langchain.py | 89 ++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tests/unittest/test_langchain.py diff --git a/tests/unittest/test_langchain.py b/tests/unittest/test_langchain.py new file mode 100644 index 00000000..7e9f33d7 --- /dev/null +++ b/tests/unittest/test_langchain.py @@ -0,0 +1,89 @@ +import asyncio +import os +import time +from pr_agent.algo.ai_handlers.langchain_ai_handler import LangChainOpenAIHandler +from pr_agent.config_loader import get_settings + +def check_settings(): + print('Checking settings...') + settings = get_settings() + + # Check OpenAI settings + if not hasattr(settings, 'openai'): + print('OpenAI settings not found') + return False + + if not hasattr(settings.openai, 'key'): + print('OpenAI API key not found') + return False + + print('OpenAI API key found') + return True + +async def measure_performance(handler, num_requests=3): + print(f'\nRunning performance test with {num_requests} requests...') + start_time = time.time() + + # Create multiple requests + tasks = [ + handler.chat_completion( + model='gpt-3.5-turbo', + system='You are a helpful assistant', + user=f'Test message {i}', + temperature=0.2 + ) for i in range(num_requests) + ] + + # Execute requests concurrently + responses = await asyncio.gather(*tasks) + + end_time = time.time() + total_time = end_time - start_time + avg_time = total_time / num_requests + + print(f'Performance results:') + print(f'Total time: {total_time:.2f} seconds') + print(f'Average time per request: {avg_time:.2f} seconds') + print(f'Requests per second: {num_requests/total_time:.2f}') + + return responses + +async def test(): + print('Starting test...') + + # Check settings first + if not check_settings(): + print('Please set up your environment variables or configuration file') + print('Required: OPENAI_API_KEY') + return + + try: + handler = LangChainOpenAIHandler() + print('Handler created') + + # Basic functionality test + response = await handler.chat_completion( + model='gpt-3.5-turbo', + system='You are a helpful assistant', + user='Hello', + temperature=0.2, + img_path='test.jpg' + ) + print('Response:', response) + + # Performance test + await measure_performance(handler) + + except Exception as e: + print('Error:', str(e)) + print('Error type:', type(e)) + print('Error details:', e.__dict__ if hasattr(e, '__dict__') else 'No additional details') + +if __name__ == '__main__': + # Print environment variables (without showing the actual key) + print('Environment variables:') + print('OPENAI_API_KEY:', 'Set' if os.getenv('OPENAI_API_KEY') else 'Not set') + print('OPENAI_API_TYPE:', os.getenv('OPENAI_API_TYPE', 'Not set')) + print('OPENAI_API_BASE:', os.getenv('OPENAI_API_BASE', 'Not set')) + + asyncio.run(test()) \ No newline at end of file From f78762cf2e93c69fa26a3ac6a8c2439a7d925412 Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Mon, 26 May 2025 11:04:11 +0700 Subject: [PATCH 18/68] Change the default value of is --- pr_agent/settings/configuration.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index 9c80e9fb..7ef6c4f2 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -283,7 +283,7 @@ push_commands = [ [gitea_app] url = "https://gitea.com" -handle_push_trigger = true +handle_push_trigger = false pr_commands = [ "/describe --pr_description.final_update_message=false", "/review", From 5e9c56b96c5f0ce3e31a6679de2a88d156867a3c Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Mon, 26 May 2025 11:05:58 +0700 Subject: [PATCH 19/68] Remove the unnecessary flag '--pr_description.final_update_message=false' --- pr_agent/settings/configuration.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index 7ef6c4f2..cdb6d5b9 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -285,7 +285,7 @@ push_commands = [ url = "https://gitea.com" handle_push_trigger = false pr_commands = [ - "/describe --pr_description.final_update_message=false", + "/describe", "/review", "/improve", ] From a975b323760e4d6ddf41e3cc5c0528d037ea342f Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Mon, 26 May 2025 11:26:16 +0700 Subject: [PATCH 20/68] Get empty content when exception --- pr_agent/git_providers/gitea_provider.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pr_agent/git_providers/gitea_provider.py b/pr_agent/git_providers/gitea_provider.py index 4ac2e63b..01b2fca7 100644 --- a/pr_agent/git_providers/gitea_provider.py +++ b/pr_agent/git_providers/gitea_provider.py @@ -113,6 +113,7 @@ class GiteaProvider(GitProvider): self.file_contents[file_path] = content except ApiException as e: self.logger.error(f"Error getting file content for {file_path}: {str(e)}") + self.file_contents[file_path] = "" def __add_file_diff(self): try: From b264f42e3d5be04ab0360f6f66566a2a70753e8d Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Mon, 26 May 2025 11:31:40 +0700 Subject: [PATCH 21/68] Fixed handle verify signature when has failed --- pr_agent/servers/gitea_app.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pr_agent/servers/gitea_app.py b/pr_agent/servers/gitea_app.py index 85399f2c..018a746d 100644 --- a/pr_agent/servers/gitea_app.py +++ b/pr_agent/servers/gitea_app.py @@ -51,7 +51,11 @@ async def get_body(request: Request): get_logger().error("Missing signature header") raise HTTPException(status_code=400, detail="Missing signature header") - verify_signature(body_bytes, webhook_secret, f"sha256={signature_header}") + try: + verify_signature(body_bytes, webhook_secret, f"sha256={signature_header}") + except Exception as ex: + get_logger().error(f"Invalid signature: {ex}") + raise HTTPException(status_code=401, detail="Invalid signature") return body From f06ee951d7761c336df79d1bcd59ebeb77c7b0e8 Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Mon, 26 May 2025 11:36:49 +0700 Subject: [PATCH 22/68] Change raise runtime error --- pr_agent/git_providers/gitea_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pr_agent/git_providers/gitea_provider.py b/pr_agent/git_providers/gitea_provider.py index 01b2fca7..974c6a97 100644 --- a/pr_agent/git_providers/gitea_provider.py +++ b/pr_agent/git_providers/gitea_provider.py @@ -760,7 +760,7 @@ class RepoApi(giteapy.RepositoryApi): else: error_msg = f"Unexpected response format received from API: {type(response)}" self.logger.error(error_msg) - return RuntimeError(error_msg) + raise RuntimeError(error_msg) except ApiException as e: self.logger.error(f"Error getting diff: {str(e)}") From 5d105c64d2a6c24de08f5cc9e8f9409583135196 Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Mon, 26 May 2025 11:40:29 +0700 Subject: [PATCH 23/68] Rename & Return comment object after published --- pr_agent/git_providers/gitea_provider.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pr_agent/git_providers/gitea_provider.py b/pr_agent/git_providers/gitea_provider.py index 974c6a97..9e9e1bef 100644 --- a/pr_agent/git_providers/gitea_provider.py +++ b/pr_agent/git_providers/gitea_provider.py @@ -232,26 +232,28 @@ class GiteaProvider(GitProvider): return None comment = self.limit_output_characters(comment, self.max_comment_chars) - reponse = self.repo_api.create_comment( + response = self.repo_api.create_comment( owner=self.owner, repo=self.repo, index=index, comment=comment ) - if not reponse: + if not response: self.logger.error("Failed to publish comment") return None if is_temporary: self.temp_comments.append(comment) - self.comments_list.append({ + comment_obj = { "is_temporary": is_temporary, "comment": comment, - "comment_id": reponse.id if isinstance(reponse, tuple) else reponse.id - }) + "comment_id": response.id if isinstance(response, tuple) else response.id + } + self.comments_list.append(comment_obj) self.logger.info("Comment published") + return comment_obj def edit_comment(self, comment, body : str): body = self.limit_output_characters(body, self.max_comment_chars) From 6063bf59789fe0f3258e6bb97f5ad784b9d0ca5d Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Mon, 26 May 2025 11:42:09 +0700 Subject: [PATCH 24/68] Check is tempolary before remove it --- pr_agent/git_providers/gitea_provider.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pr_agent/git_providers/gitea_provider.py b/pr_agent/git_providers/gitea_provider.py index 9e9e1bef..8805d8f4 100644 --- a/pr_agent/git_providers/gitea_provider.py +++ b/pr_agent/git_providers/gitea_provider.py @@ -649,7 +649,7 @@ class GiteaProvider(GitProvider): return try: - comment_id = comment.get("comment_id") + comment_id = comment.get("comment_id") if isinstance(comment, dict) else comment.id if not comment_id: self.logger.error("Comment ID not found") return None @@ -659,7 +659,7 @@ class GiteaProvider(GitProvider): comment_id=comment_id ) - if self.comments_list: + if self.comments_list and comment in self.comments_list: self.comments_list.remove(comment) self.logger.info(f"Comment removed successfully: {comment}") @@ -671,6 +671,8 @@ class GiteaProvider(GitProvider): """Remove the initial comment""" for comment in self.comments_list: try: + if not comment.get("is_temporary"): + continue self.remove_comment(comment) except Exception as e: self.logger.error(f"Error removing comment: {e}") From b18a509120e3ac7289b968c45b32ed411f1e7054 Mon Sep 17 00:00:00 2001 From: Pinyoo Thotaboot Date: Mon, 26 May 2025 11:44:39 +0700 Subject: [PATCH 25/68] Use current --- pr_agent/git_providers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pr_agent/git_providers/__init__.py b/pr_agent/git_providers/__init__.py index e4acfc22..055cdbf1 100644 --- a/pr_agent/git_providers/__init__.py +++ b/pr_agent/git_providers/__init__.py @@ -23,7 +23,7 @@ _GIT_PROVIDERS = { 'codecommit': CodeCommitProvider, 'local': LocalGitProvider, 'gerrit': GerritProvider, - 'gitea': GiteaProvider, + 'gitea': GiteaProvider } From 80b535f41a106826b1b65d29b6227fcd1c5407b2 Mon Sep 17 00:00:00 2001 From: Akileo Date: Mon, 26 May 2025 14:15:53 +0900 Subject: [PATCH 26/68] Change test_langchain.py unittest to e2e --- .../test_langchain.py => e2e_tests/langchain_ai_handler.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{unittest/test_langchain.py => e2e_tests/langchain_ai_handler.py} (100%) diff --git a/tests/unittest/test_langchain.py b/tests/e2e_tests/langchain_ai_handler.py similarity index 100% rename from tests/unittest/test_langchain.py rename to tests/e2e_tests/langchain_ai_handler.py From 3ec66e6aec214a92296f9a6e30ea890c015e7bbc Mon Sep 17 00:00:00 2001 From: Akileo Date: Mon, 26 May 2025 14:19:46 +0900 Subject: [PATCH 27/68] Change test_langchain.py --- tests/e2e_tests/langchain_ai_handler.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/langchain_ai_handler.py b/tests/e2e_tests/langchain_ai_handler.py index 7e9f33d7..d75c4292 100644 --- a/tests/e2e_tests/langchain_ai_handler.py +++ b/tests/e2e_tests/langchain_ai_handler.py @@ -80,10 +80,11 @@ async def test(): print('Error details:', e.__dict__ if hasattr(e, '__dict__') else 'No additional details') if __name__ == '__main__': - # Print environment variables (without showing the actual key) print('Environment variables:') print('OPENAI_API_KEY:', 'Set' if os.getenv('OPENAI_API_KEY') else 'Not set') print('OPENAI_API_TYPE:', os.getenv('OPENAI_API_TYPE', 'Not set')) print('OPENAI_API_BASE:', os.getenv('OPENAI_API_BASE', 'Not set')) - asyncio.run(test()) \ No newline at end of file + asyncio.run(test()) + + \ No newline at end of file From e8ace9fcf9fec76c496a44db00a6fa2bf2b4a619 Mon Sep 17 00:00:00 2001 From: Akileo Date: Mon, 26 May 2025 14:52:45 +0900 Subject: [PATCH 28/68] change type check and remove useless sync --- .../algo/ai_handlers/langchain_ai_handler.py | 37 ++----------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/pr_agent/algo/ai_handlers/langchain_ai_handler.py b/pr_agent/algo/ai_handlers/langchain_ai_handler.py index 8e29b8bb..2d4fa08b 100644 --- a/pr_agent/algo/ai_handlers/langchain_ai_handler.py +++ b/pr_agent/algo/ai_handlers/langchain_ai_handler.py @@ -8,10 +8,10 @@ except: # we don't enforce langchain as a dependency, so if it's not installed, pass import functools -from typing import Optional import openai from tenacity import retry, retry_if_exception_type, retry_if_not_exception_type, stop_after_attempt +from langchain_core.runnables import Runnable from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler from pr_agent.config_loader import get_settings @@ -30,13 +30,6 @@ class LangChainOpenAIHandler(BaseAiHandler): super().__init__() self.azure = get_settings().get("OPENAI.API_TYPE", "").lower() == "azure" - # Create a default unused chat object to trigger early validation - self._create_chat(self.deployment_id) - - def chat(self, messages: list, model: str, temperature: float): - chat = self._create_chat(self.deployment_id) - return chat.invoke(input=messages, model=model, temperature=temperature) - @property def deployment_id(self): """ @@ -81,9 +74,9 @@ class LangChainOpenAIHandler(BaseAiHandler): messages = [SystemMessage(content=system), HumanMessage(content=user)] llm = await self._create_chat_async(deployment_id=self.deployment_id) - if not hasattr(llm, 'ainvoke'): + if not isinstance(llm, Runnable): error_message = ( - f"The Langchain LLM object ({type(llm)}) does not have an 'ainvoke' async method. " + f"The Langchain LLM object ({type(llm)}) does not implement the Runnable interface. " f"Please update your Langchain library to the latest version or " f"check your LLM configuration to support async calls. " f"PR-Agent is designed to utilize Langchain's async capabilities." @@ -116,27 +109,3 @@ class LangChainOpenAIHandler(BaseAiHandler): except Exception as e: get_logger().warning(f"Unknown error during LLM inference: {e}") raise openai.APIError from e - - def _create_chat(self, deployment_id=None): - try: - if self.azure: - # using a partial function so we can set the deployment_id later to support fallback_deployments - # but still need to access the other settings now so we can raise a proper exception if they're missing - return AzureChatOpenAI( - openai_api_key=get_settings().openai.key, - openai_api_version=get_settings().openai.api_version, - azure_deployment=deployment_id, - azure_endpoint=get_settings().openai.api_base, - ) - else: - # for llms that compatible with openai, should use custom api base - openai_api_base = get_settings().get("OPENAI.API_BASE", None) - if openai_api_base is None or len(openai_api_base) == 0: - return ChatOpenAI(openai_api_key=get_settings().openai.key) - else: - return ChatOpenAI(openai_api_key=get_settings().openai.key, openai_api_base=openai_api_base) - except AttributeError as e: - if getattr(e, "name"): - raise ValueError(f"OpenAI {e.name} is required") from e - else: - raise e From 4baf52292d3fce38b3262803ef4c078233eb1d99 Mon Sep 17 00:00:00 2001 From: dst03106 Date: Mon, 26 May 2025 15:49:06 +0900 Subject: [PATCH 29/68] test: add tests for converting to markdown --- tests/unittest/test_convert_to_markdown.py | 139 ++++++++++++++++++++- 1 file changed, 138 insertions(+), 1 deletion(-) diff --git a/tests/unittest/test_convert_to_markdown.py b/tests/unittest/test_convert_to_markdown.py index 483787aa..da9e95f2 100644 --- a/tests/unittest/test_convert_to_markdown.py +++ b/tests/unittest/test_convert_to_markdown.py @@ -1,4 +1,7 @@ # Generated by CodiumAI +import textwrap +from unittest.mock import Mock + from pr_agent.algo.utils import PRReviewHeader, convert_to_markdown_v2 from pr_agent.tools.pr_description import insert_br_after_x_chars @@ -48,9 +51,143 @@ class TestConvertToMarkdown: input_data = {'review': { 'estimated_effort_to_review_[1-5]': '1, because the changes are minimal and straightforward, focusing on a single functionality addition.\n', 'relevant_tests': 'No\n', 'possible_issues': 'No\n', 'security_concerns': 'No\n'}} + + expected_output = textwrap.dedent(f"""\ + {PRReviewHeader.REGULAR.value} 🔍 + + Here are some key observations to aid the review process: + + + + + + +
⏱️ Estimated effort to review: 1 🔵⚪⚪⚪⚪
🧪 No relevant tests
 Possible issues: No +
🔒 No security concerns identified
+ """) + + assert convert_to_markdown_v2(input_data).strip() == expected_output.strip() + + def test_simple_dictionary_input_without_gfm_supported(self): + input_data = {'review': { + 'estimated_effort_to_review_[1-5]': '1, because the changes are minimal and straightforward, focusing on a single functionality addition.\n', + 'relevant_tests': 'No\n', 'possible_issues': 'No\n', 'security_concerns': 'No\n'}} + + expected_output = textwrap.dedent("""\ + ## PR Reviewer Guide 🔍 + + Here are some key observations to aid the review process: + + ### ⏱️ Estimated effort to review: 1 🔵⚪⚪⚪⚪ + + ### 🧪 No relevant tests + + ### Possible issues: No - expected_output = f'{PRReviewHeader.REGULAR.value} 🔍\n\nHere are some key observations to aid the review process:\n\n\n\n\n\n\n
⏱️ Estimated effort to review: 1 🔵⚪⚪⚪⚪
🧪 No relevant tests
 Possible issues: No\n
🔒 No security concerns identified
' + ### 🔒 No security concerns identified + """) + + assert convert_to_markdown_v2(input_data, gfm_supported=False).strip() == expected_output.strip() + + def test_key_issues_to_review(self): + input_data = {'review': { + 'key_issues_to_review': [ + { + 'relevant_file' : 'src/utils.py', + 'issue_header' : 'Code Smell', + 'issue_content' : 'The function is too long and complex.', + 'start_line': 30, + 'end_line': 50, + } + ] + }} + mock_git_provider = Mock() + reference_link = 'https://github.com/qodo/pr-agent/pull/1/files#diff-hashvalue-R174' + mock_git_provider.get_line_link.return_value = reference_link + + expected_output = textwrap.dedent(f"""\ + ## PR Reviewer Guide 🔍 + + Here are some key observations to aid the review process: + + + +
⚡ Recommended focus areas for review

+ + Code Smell
The function is too long and complex. + +
+ """) + + assert convert_to_markdown_v2(input_data, git_provider=mock_git_provider).strip() == expected_output.strip() + mock_git_provider.get_line_link.assert_called_with('src/utils.py', 30, 50) + + def test_ticket_compliance(self): + input_data = {'review': { + 'ticket_compliance_check': [ + { + 'ticket_url': 'https://example.com/ticket/123', + 'ticket_requirements': '- Requirement 1\n- Requirement 2\n', + 'fully_compliant_requirements': '- Requirement 1\n- Requirement 2\n', + 'not_compliant_requirements': '', + 'requires_further_human_verification': '', + } + ] + }} + + expected_output = textwrap.dedent("""\ + ## PR Reviewer Guide 🔍 + + Here are some key observations to aid the review process: + + + +
+ + **🎫 Ticket compliance analysis ✅** + + + + **[123](https://example.com/ticket/123) - Fully compliant** + + Compliant requirements: + + - Requirement 1 + - Requirement 2 + + + +
+ """) + + assert convert_to_markdown_v2(input_data).strip() == expected_output.strip() + + def test_can_be_split(self): + input_data = {'review': { + 'can_be_split': [ + { + 'relevant_files': [ + 'src/file1.py', + 'src/file2.py' + ], + 'title': 'Split PR into smaller parts', + } + ] + } + } + + expected_output = textwrap.dedent("""\ + ## PR Reviewer Guide 🔍 + + Here are some key observations to aid the review process: + + + +
🔀 No multiple PR themes + +
+ """) assert convert_to_markdown_v2(input_data).strip() == expected_output.strip() From df6b00aa3668fb28999f09f9939c57d3711aeb7f Mon Sep 17 00:00:00 2001 From: dst03106 Date: Mon, 26 May 2025 16:15:16 +0900 Subject: [PATCH 30/68] test: modify test data to support multiple sub-PR themes with separate titles and relevant files --- tests/unittest/test_convert_to_markdown.py | 37 ++++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/tests/unittest/test_convert_to_markdown.py b/tests/unittest/test_convert_to_markdown.py index da9e95f2..84e01942 100644 --- a/tests/unittest/test_convert_to_markdown.py +++ b/tests/unittest/test_convert_to_markdown.py @@ -171,7 +171,13 @@ class TestConvertToMarkdown: 'src/file1.py', 'src/file2.py' ], - 'title': 'Split PR into smaller parts', + 'title': 'Refactoring', + }, + { + 'relevant_files': [ + 'src/file3.py' + ], + 'title': 'Bug Fix', } ] } @@ -183,8 +189,33 @@ class TestConvertToMarkdown: Here are some key observations to aid the review process: -
🔀 No multiple PR themes - +
🔀 Multiple PR themes

+ +
+ Sub-PR theme: Refactoring + + ___ + + Relevant files: + + - src/file1.py + - src/file2.py + ___ + +
+ +
+ Sub-PR theme: Bug Fix + + ___ + + Relevant files: + + - src/file3.py + ___ + +
+
""") From 495ac565b01416c6ca1fac437e6b728ab75d71f0 Mon Sep 17 00:00:00 2001 From: isExample Date: Tue, 27 May 2025 12:46:26 +0900 Subject: [PATCH 31/68] docs: correct parameter name typo and update description --- docs/docs/tools/describe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/tools/describe.md b/docs/docs/tools/describe.md index 1114ffdc..143fd2d6 100644 --- a/docs/docs/tools/describe.md +++ b/docs/docs/tools/describe.md @@ -125,8 +125,8 @@ enable_pr_diagram = true If set to true, the tool will display a help text in the comment. Default is false. - add_diagram - If set to true, the tool will generate a Mermaid sequence diagram (in code block format) describing component interactions based on the code changes. Default is false. + enable_pr_diagram + If set to true, the tool will generate a horizontal Mermaid flowchart summarizing the main pull request changes. This field remains empty if not applicable. Default is false. From daf6c25f9a2bc2219e92e51266b2867949eeaab3 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Tue, 27 May 2025 18:50:43 +0300 Subject: [PATCH 32/68] fix: correct broken security vulnerability link in review.md documentation --- docs/docs/tools/review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/tools/review.md b/docs/docs/tools/review.md index 899b58a3..62d75634 100644 --- a/docs/docs/tools/review.md +++ b/docs/docs/tools/review.md @@ -150,7 +150,7 @@ extra_instructions = "..." The `review` tool automatically adds two specific labels to your Pull Requests: - - **`possible security issue`**: This label is applied if the tool detects a potential [security vulnerability](hhttps://github.com/qodo-ai/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml#L103) in the PR's code. This feedback is controlled by the 'enable_review_labels_security' flag. + - **`possible security issue`**: This label is applied if the tool detects a potential [security vulnerability](https://github.com/qodo-ai/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml#L103) in the PR's code. This feedback is controlled by the 'enable_review_labels_security' flag. - **`review effort [x/5]`**: This label estimates the [effort](https://github.com/qodo-ai/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml#L90) required to review the PR on a relative scale of 1 to 5, where 'x' represents the assessed effort. This feedback is controlled by the 'enable_review_labels_effort' flag. Note: The `possible security issue` label highlights potential security risks. You can configure a GitHub Action to [prevent merging](https://medium.com/sequra-tech/quick-tip-block-pull-request-merge-using-labels-6cc326936221) PRs that have this label. From 95e4604abeaf2a6d0d26feb3f90cc0b0e73f5eb3 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Wed, 28 May 2025 08:34:59 +0300 Subject: [PATCH 33/68] test: comment out GiteaProvider unit tests and update token key for compatibility --- tests/unittest/test_gitea_provider.py | 252 +++++++++++++------------- 1 file changed, 126 insertions(+), 126 deletions(-) diff --git a/tests/unittest/test_gitea_provider.py b/tests/unittest/test_gitea_provider.py index d88de0e0..95f7ec21 100644 --- a/tests/unittest/test_gitea_provider.py +++ b/tests/unittest/test_gitea_provider.py @@ -1,126 +1,126 @@ -from unittest.mock import MagicMock, patch - -import pytest - -from pr_agent.algo.types import EDIT_TYPE -from pr_agent.git_providers.gitea_provider import GiteaProvider - - -class TestGiteaProvider: - """Unit-tests for GiteaProvider following project style (explicit object construction, minimal patching).""" - - def _provider(self): - """Create provider instance with patched settings and avoid real HTTP calls.""" - with patch('pr_agent.git_providers.gitea_provider.get_settings') as mock_get_settings, \ - patch('requests.get') as mock_get: - settings = MagicMock() - settings.get.side_effect = lambda k, d=None: { - 'GITEA.URL': 'https://gitea.example.com', - 'GITEA.TOKEN': 'test-token' - }.get(k, d) - mock_get_settings.return_value = settings - # Stub the PR fetch triggered during provider initialization - pr_resp = MagicMock() - pr_resp.json.return_value = { - 'title': 'stub', - 'body': 'stub', - 'head': {'ref': 'main'}, - 'user': {'id': 1} - } - pr_resp.raise_for_status = MagicMock() - mock_get.return_value = pr_resp - return GiteaProvider('https://gitea.example.com/owner/repo/pulls/123') - - # ---------------- URL parsing ---------------- - def test_parse_pr_url_valid(self): - owner, repo, pr_num = GiteaProvider._parse_pr_url('https://gitea.example.com/owner/repo/pulls/123') - assert (owner, repo, pr_num) == ('owner', 'repo', '123') - - def test_parse_pr_url_invalid(self): - with pytest.raises(ValueError): - GiteaProvider._parse_pr_url('https://gitea.example.com/owner/repo') - - # ---------------- simple getters ---------------- - def test_get_files(self): - provider = self._provider() - mock_resp = MagicMock() - mock_resp.json.return_value = [{'filename': 'a.txt'}, {'filename': 'b.txt'}] - mock_resp.raise_for_status = MagicMock() - with patch('requests.get', return_value=mock_resp) as mock_get: - assert provider.get_files() == ['a.txt', 'b.txt'] - mock_get.assert_called_once() - - def test_get_diff_files(self): - provider = self._provider() - mock_resp = MagicMock() - mock_resp.json.return_value = [ - {'filename': 'f1', 'previous_filename': 'old_f1', 'status': 'renamed', 'patch': ''}, - {'filename': 'f2', 'status': 'added', 'patch': ''}, - {'filename': 'f3', 'status': 'deleted', 'patch': ''}, - {'filename': 'f4', 'status': 'modified', 'patch': ''} - ] - mock_resp.raise_for_status = MagicMock() - with patch('requests.get', return_value=mock_resp): - res = provider.get_diff_files() - assert [f.edit_type for f in res] == [EDIT_TYPE.RENAMED, EDIT_TYPE.ADDED, EDIT_TYPE.DELETED, - EDIT_TYPE.MODIFIED] - - # ---------------- publishing methods ---------------- - def test_publish_description(self): - provider = self._provider() - mock_resp = MagicMock(); - mock_resp.raise_for_status = MagicMock() - with patch('requests.patch', return_value=mock_resp) as mock_patch: - provider.publish_description('t', 'b'); - mock_patch.assert_called_once() - - def test_publish_comment(self): - provider = self._provider() - mock_resp = MagicMock(); - mock_resp.raise_for_status = MagicMock() - with patch('requests.post', return_value=mock_resp) as mock_post: - provider.publish_comment('c'); - mock_post.assert_called_once() - - def test_publish_inline_comment(self): - provider = self._provider() - mock_resp = MagicMock(); - mock_resp.raise_for_status = MagicMock() - with patch('requests.post', return_value=mock_resp) as mock_post: - provider.publish_inline_comment('body', 'file', '10'); - mock_post.assert_called_once() - - # ---------------- labels & reactions ---------------- - def test_get_pr_labels(self): - provider = self._provider() - mock_resp = MagicMock(); - mock_resp.raise_for_status = MagicMock(); - mock_resp.json.return_value = [{'name': 'l1'}] - with patch('requests.get', return_value=mock_resp): - assert provider.get_pr_labels() == ['l1'] - - def test_add_eyes_reaction(self): - provider = self._provider() - mock_resp = MagicMock(); - mock_resp.raise_for_status = MagicMock(); - mock_resp.json.return_value = {'id': 7} - with patch('requests.post', return_value=mock_resp): - assert provider.add_eyes_reaction(1) == 7 - - # ---------------- commit messages & url helpers ---------------- - def test_get_commit_messages(self): - provider = self._provider() - mock_resp = MagicMock(); - mock_resp.raise_for_status = MagicMock() - mock_resp.json.return_value = [ - {'commit': {'message': 'm1'}}, {'commit': {'message': 'm2'}}] - with patch('requests.get', return_value=mock_resp): - assert provider.get_commit_messages() == ['m1', 'm2'] - - def test_git_url_helpers(self): - provider = self._provider() - issues_url = 'https://gitea.example.com/owner/repo/pulls/3' - assert provider.get_git_repo_url(issues_url) == 'https://gitea.example.com/owner/repo.git' - prefix, suffix = provider.get_canonical_url_parts('https://gitea.example.com/owner/repo.git', 'dev') - assert prefix == 'https://gitea.example.com/owner/repo/src/branch/dev' - assert suffix == '' +# from unittest.mock import MagicMock, patch +# +# import pytest +# +# from pr_agent.algo.types import EDIT_TYPE +# from pr_agent.git_providers.gitea_provider import GiteaProvider +# +# +# class TestGiteaProvider: +# """Unit-tests for GiteaProvider following project style (explicit object construction, minimal patching).""" +# +# def _provider(self): +# """Create provider instance with patched settings and avoid real HTTP calls.""" +# with patch('pr_agent.git_providers.gitea_provider.get_settings') as mock_get_settings, \ +# patch('requests.get') as mock_get: +# settings = MagicMock() +# settings.get.side_effect = lambda k, d=None: { +# 'GITEA.URL': 'https://gitea.example.com', +# 'GITEA.PERSONAL_ACCESS_TOKEN': 'test-token' +# }.get(k, d) +# mock_get_settings.return_value = settings +# # Stub the PR fetch triggered during provider initialization +# pr_resp = MagicMock() +# pr_resp.json.return_value = { +# 'title': 'stub', +# 'body': 'stub', +# 'head': {'ref': 'main'}, +# 'user': {'id': 1} +# } +# pr_resp.raise_for_status = MagicMock() +# mock_get.return_value = pr_resp +# return GiteaProvider('https://gitea.example.com/owner/repo/pulls/123') +# +# # ---------------- URL parsing ---------------- +# def test_parse_pr_url_valid(self): +# owner, repo, pr_num = self._provider()._parse_pr_url('https://gitea.example.com/owner/repo/pulls/123') +# assert (owner, repo, pr_num) == ('owner', 'repo', '123') +# +# def test_parse_pr_url_invalid(self): +# with pytest.raises(ValueError): +# GiteaProvider._parse_pr_url('https://gitea.example.com/owner/repo') +# +# # ---------------- simple getters ---------------- +# def test_get_files(self): +# provider = self._provider() +# mock_resp = MagicMock() +# mock_resp.json.return_value = [{'filename': 'a.txt'}, {'filename': 'b.txt'}] +# mock_resp.raise_for_status = MagicMock() +# with patch('requests.get', return_value=mock_resp) as mock_get: +# assert provider.get_files() == ['a.txt', 'b.txt'] +# mock_get.assert_called_once() +# +# def test_get_diff_files(self): +# provider = self._provider() +# mock_resp = MagicMock() +# mock_resp.json.return_value = [ +# {'filename': 'f1', 'previous_filename': 'old_f1', 'status': 'renamed', 'patch': ''}, +# {'filename': 'f2', 'status': 'added', 'patch': ''}, +# {'filename': 'f3', 'status': 'deleted', 'patch': ''}, +# {'filename': 'f4', 'status': 'modified', 'patch': ''} +# ] +# mock_resp.raise_for_status = MagicMock() +# with patch('requests.get', return_value=mock_resp): +# res = provider.get_diff_files() +# assert [f.edit_type for f in res] == [EDIT_TYPE.RENAMED, EDIT_TYPE.ADDED, EDIT_TYPE.DELETED, +# EDIT_TYPE.MODIFIED] +# +# # ---------------- publishing methods ---------------- +# def test_publish_description(self): +# provider = self._provider() +# mock_resp = MagicMock(); +# mock_resp.raise_for_status = MagicMock() +# with patch('requests.patch', return_value=mock_resp) as mock_patch: +# provider.publish_description('t', 'b'); +# mock_patch.assert_called_once() +# +# def test_publish_comment(self): +# provider = self._provider() +# mock_resp = MagicMock(); +# mock_resp.raise_for_status = MagicMock() +# with patch('requests.post', return_value=mock_resp) as mock_post: +# provider.publish_comment('c'); +# mock_post.assert_called_once() +# +# def test_publish_inline_comment(self): +# provider = self._provider() +# mock_resp = MagicMock(); +# mock_resp.raise_for_status = MagicMock() +# with patch('requests.post', return_value=mock_resp) as mock_post: +# provider.publish_inline_comment('body', 'file', '10'); +# mock_post.assert_called_once() +# +# # ---------------- labels & reactions ---------------- +# def test_get_pr_labels(self): +# provider = self._provider() +# mock_resp = MagicMock(); +# mock_resp.raise_for_status = MagicMock(); +# mock_resp.json.return_value = [{'name': 'l1'}] +# with patch('requests.get', return_value=mock_resp): +# assert provider.get_pr_labels() == ['l1'] +# +# def test_add_eyes_reaction(self): +# provider = self._provider() +# mock_resp = MagicMock(); +# mock_resp.raise_for_status = MagicMock(); +# mock_resp.json.return_value = {'id': 7} +# with patch('requests.post', return_value=mock_resp): +# assert provider.add_eyes_reaction(1) == 7 +# +# # ---------------- commit messages & url helpers ---------------- +# def test_get_commit_messages(self): +# provider = self._provider() +# mock_resp = MagicMock(); +# mock_resp.raise_for_status = MagicMock() +# mock_resp.json.return_value = [ +# {'commit': {'message': 'm1'}}, {'commit': {'message': 'm2'}}] +# with patch('requests.get', return_value=mock_resp): +# assert provider.get_commit_messages() == ['m1', 'm2'] +# +# def test_git_url_helpers(self): +# provider = self._provider() +# issues_url = 'https://gitea.example.com/owner/repo/pulls/3' +# assert provider.get_git_repo_url(issues_url) == 'https://gitea.example.com/owner/repo.git' +# prefix, suffix = provider.get_canonical_url_parts('https://gitea.example.com/owner/repo.git', 'dev') +# assert prefix == 'https://gitea.example.com/owner/repo/src/branch/dev' +# assert suffix == '' From fd1a27c2ac3f13363c65e1322513c34abd5bb84e Mon Sep 17 00:00:00 2001 From: ofir-frd Date: Wed, 28 May 2025 09:12:13 +0300 Subject: [PATCH 34/68] docs: add dedicated "Chat on code suggestions" core ability page and update related documentation - Introduced a new core abilities page detailing the "Chat on code suggestions" feature, including setup, activation, and usage patterns. - Removed the corresponding section from the improve tool documentation to avoid duplication. - Updated the core abilities index and mkdocs navigation to include the new page. - Improved formatting in the implement tool documentation for clarity and consistency. --- .../chat_on_code_suggestions.md | 55 +++++++++++++++++++ docs/docs/core-abilities/index.md | 1 + docs/docs/tools/implement.md | 54 +++++++++--------- docs/docs/tools/improve.md | 39 ------------- docs/mkdocs.yml | 1 + 5 files changed, 84 insertions(+), 66 deletions(-) create mode 100644 docs/docs/core-abilities/chat_on_code_suggestions.md diff --git a/docs/docs/core-abilities/chat_on_code_suggestions.md b/docs/docs/core-abilities/chat_on_code_suggestions.md new file mode 100644 index 00000000..086c70ee --- /dev/null +++ b/docs/docs/core-abilities/chat_on_code_suggestions.md @@ -0,0 +1,55 @@ +# Chat on code suggestions 💎 + +`Supported Git Platforms: GitHub, GitLab` + +## Overview + +Qodo Merge implements an orchestrator agent that enables interactive code discussions, listening and responding to comments without requiring explicit tool calls. +The orchestrator intelligently analyzes your responses to determine if you want to implement a suggestion, ask a question, or request help, then delegates to the appropriate specialized tool. + +To minimize unnecessary notifications and maintain focused discussions, the orchestrator agent will only respond to comments made directly within the inline code suggestion discussions it has created (`/improve`) or within discussions initiated by the `/implement` command. + +## Getting Started + +### Setup + +Enable interactive code discussions by adding the following to your configuration file (default is `True`): + +```toml +[pr_code_suggestions] +enable_chat_in_code_suggestions = true +``` + + +### Activation + +#### `/improve` + +To obtain dynamic responses, the following steps are required: + +1. Run the `/improve` command (mostly automatic) +2. Check the `/improve` recommendation checkboxes (_Apply this suggestion_) to have Qodo Merge generate a new inline code suggestion discussion +3. The orchestrator agent will then automatically listen to and reply to comments within the discussion without requiring additional commands + +#### `/implement` + +To obtain dynamic responses, the following steps are required: + +1. Select code lines in the PR diff and run the `/implement` command +2. Wait for Qodo Merge to generate a new inline code suggestion +3. The orchestrator agent will then automatically listen to and reply to comments within the discussion without requiring additional commands + + +## Explore the available interaction patterns + +!!! tip "Tip: Direct the agent with keywords" + Use "implement" or "apply" for code generation. Use "explain", "why", or "how" for information and help. + +=== "Asking for Details" + ![Chat on code suggestions ask](https://codium.ai/images/pr_agent/improve_chat_on_code_suggestions_ask.png){width=512} + +=== "Implementing Suggestions" + ![Chat on code suggestions implement](https://codium.ai/images/pr_agent/improve_chat_on_code_suggestions_implement.png){width=512} + +=== "Providing Additional Help" + ![Chat on code suggestions help](https://codium.ai/images/pr_agent/improve_chat_on_code_suggestions_help.png){width=512} diff --git a/docs/docs/core-abilities/index.md b/docs/docs/core-abilities/index.md index b97260ee..8e07f24f 100644 --- a/docs/docs/core-abilities/index.md +++ b/docs/docs/core-abilities/index.md @@ -3,6 +3,7 @@ Qodo Merge utilizes a variety of core abilities to provide a comprehensive and efficient code review experience. These abilities include: - [Auto best practices](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/) +- [Chat on code suggestions](https://qodo-merge-docs.qodo.ai/core-abilities/chat_on_code_suggestions/) - [Code validation](https://qodo-merge-docs.qodo.ai/core-abilities/code_validation/) - [Compression strategy](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) - [Dynamic context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/) diff --git a/docs/docs/tools/implement.md b/docs/docs/tools/implement.md index 93401425..83ccd101 100644 --- a/docs/docs/tools/implement.md +++ b/docs/docs/tools/implement.md @@ -7,50 +7,50 @@ It leverages LLM technology to transform PR comments and review suggestions into ## Usage Scenarios -### For Reviewers +=== "For Reviewers" -Reviewers can request code changes by: + Reviewers can request code changes by: -1. Selecting the code block to be modified. -2. Adding a comment with the syntax: + 1. Selecting the code block to be modified. + 2. Adding a comment with the syntax: -``` -/implement -``` + ``` + /implement + ``` -![implement1](https://codium.ai/images/pr_agent/implement1.png){width=640} + ![implement1](https://codium.ai/images/pr_agent/implement1.png){width=640} -### For PR Authors +=== "For PR Authors" -PR authors can implement suggested changes by replying to a review comment using either:
+ PR authors can implement suggested changes by replying to a review comment using either: -1. Add specific implementation details as described above + 1. Add specific implementation details as described above -``` -/implement -``` + ``` + /implement + ``` -2. Use the original review comment as instructions + 2. Use the original review comment as instructions -``` -/implement -``` + ``` + /implement + ``` -![implement2](https://codium.ai/images/pr_agent/implement2.png){width=640} + ![implement2](https://codium.ai/images/pr_agent/implement2.png){width=640} -### For Referencing Comments +=== "For Referencing Comments" -You can reference and implement changes from any comment by: + You can reference and implement changes from any comment by: -``` -/implement -``` + ``` + /implement + ``` -![implement3](https://codium.ai/images/pr_agent/implement3.png){width=640} + ![implement3](https://codium.ai/images/pr_agent/implement3.png){width=640} -Note that the implementation will occur within the review discussion thread. + Note that the implementation will occur within the review discussion thread. -**Configuration options** +## Configuration options - Use `/implement` to implement code change within and based on the review discussion. - Use `/implement ` inside a review discussion to implement specific instructions. diff --git a/docs/docs/tools/improve.md b/docs/docs/tools/improve.md index 54ece175..2ca0c74c 100644 --- a/docs/docs/tools/improve.md +++ b/docs/docs/tools/improve.md @@ -288,45 +288,6 @@ We advise users to apply critical analysis and judgment when implementing the pr In addition to mistakes (which may happen, but are rare), sometimes the presented code modification may serve more as an _illustrative example_ than a directly applicable solution. In such cases, we recommend prioritizing the suggestion's detailed description, using the diff snippet primarily as a supporting reference. - -### Chat on code suggestions - -> `💎 feature` Platforms supported: GitHub, GitLab - -Qodo Merge implements an orchestrator agent that enables interactive code discussions, listening and responding to comments without requiring explicit tool calls. -The orchestrator intelligently analyzes your responses to determine if you want to implement a suggestion, ask a question, or request help, then delegates to the appropriate specialized tool. - -#### Setup and Activation - -Enable interactive code discussions by adding the following to your configuration file (default is `True`): - -```toml -[pr_code_suggestions] -enable_chat_in_code_suggestions = true -``` - -!!! info "Activating Dynamic Responses" - To obtain dynamic responses, the following steps are required: - - 1. Run the `/improve` command (mostly automatic) - 2. Tick the `/improve` recommendation checkboxes (_Apply this suggestion_) to have Qodo Merge generate a new inline code suggestion discussion - 3. The orchestrator agent will then automatically listen and reply to comments within the discussion without requiring additional commands - -#### Explore the available interaction patterns: - -!!! tip "Tip: Direct the agent with keywords" - Use "implement" or "apply" for code generation. Use "explain", "why", or "how" for information and help. - -=== "Asking for Details" - ![Chat on code suggestions ask](https://codium.ai/images/pr_agent/improve_chat_on_code_suggestions_ask.png){width=512} - -=== "Implementing Suggestions" - ![Chat on code suggestions implement](https://codium.ai/images/pr_agent/improve_chat_on_code_suggestions_implement.png){width=512} - -=== "Providing Additional Help" - ![Chat on code suggestions help](https://codium.ai/images/pr_agent/improve_chat_on_code_suggestions_help.png){width=512} - - ### Dual publishing mode Our recommended approach for presenting code suggestions is through a [table](https://qodo-merge-docs.qodo.ai/tools/improve/#overview) (`--pr_code_suggestions.commitable_code_suggestions=false`). diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index a25c081b..740488ad 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -44,6 +44,7 @@ nav: - Core Abilities: - 'core-abilities/index.md' - Auto best practices: 'core-abilities/auto_best_practices.md' + - Chat on code suggestions: 'core-abilities/chat_on_code_suggestions.md' - Code validation: 'core-abilities/code_validation.md' - Compression strategy: 'core-abilities/compression_strategy.md' - Dynamic context: 'core-abilities/dynamic_context.md' From 2b614330ec6c4a139c3606f4d45ed3a2b7378cf9 Mon Sep 17 00:00:00 2001 From: ofir-frd <85901822+ofir-frd@users.noreply.github.com> Date: Wed, 28 May 2025 18:52:09 +0300 Subject: [PATCH 35/68] docs: move Installation Metrics feature from Future Plans to Recent Updates --- docs/docs/recent_updates/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/docs/recent_updates/index.md b/docs/docs/recent_updates/index.md index 158fb5ca..85f21c07 100644 --- a/docs/docs/recent_updates/index.md +++ b/docs/docs/recent_updates/index.md @@ -12,10 +12,11 @@ It also outlines our development roadmap for the upcoming three months. Please n - **Scan Repo Discussions Tool**: A new tool that analyzes past code discussions to generate a `best_practices.md` file, distilling key insights and recommendations. ([Learn more](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/)) - **Enhanced Models**: Qodo Merge now defaults to a combination of top models (Claude Sonnet 3.7 and Gemini 2.5 Pro) and incorporates dedicated code validation logic for improved results. ([Details 1](https://qodo-merge-docs.qodo.ai/usage-guide/qodo_merge_models/), [Details 2](https://qodo-merge-docs.qodo.ai/core-abilities/code_validation/)) - **Chrome Extension Update**: Qodo Merge Chrome extension now supports single-tenant users. ([Learn more](https://qodo-merge-docs.qodo.ai/chrome-extension/options/#configuration-options/)) + - **Installation Metrics**: Upon installation, Qodo Merge analyzes past PRs for key metrics (e.g., time to merge, time to first reviewer feedback), enabling pre/post-installation comparison to calculate ROI. + === "Future Roadmap" - **Smart Update**: Upon PR updates, Qodo Merge will offer tailored code suggestions, addressing both the entire PR and the specific incremental changes since the last feedback. - **CLI Endpoint**: A new Qodo Merge endpoint will accept lists of before/after code changes, execute Qodo Merge commands, and return the results. - **Simplified Free Tier**: We plan to transition from a two-week free trial to a free tier offering a limited number of suggestions per month per organization. - **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. - - **Installation Metrics**: Upon installation, Qodo Merge will analyze past PRs for key metrics (e.g., time to merge, time to first reviewer feedback), enabling pre/post-installation comparison to calculate ROI. \ No newline at end of file From b4cef661e6128a9fa6be6755f2463ca13a873ae2 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Wed, 28 May 2025 19:35:31 +0300 Subject: [PATCH 36/68] docs: enhance review.md with ticket compliance labels and merge blocking guidance --- docs/docs/tools/review.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/docs/tools/review.md b/docs/docs/tools/review.md index 62d75634..56029c7f 100644 --- a/docs/docs/tools/review.md +++ b/docs/docs/tools/review.md @@ -152,8 +152,16 @@ extra_instructions = "..." - **`possible security issue`**: This label is applied if the tool detects a potential [security vulnerability](https://github.com/qodo-ai/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml#L103) in the PR's code. This feedback is controlled by the 'enable_review_labels_security' flag. - **`review effort [x/5]`**: This label estimates the [effort](https://github.com/qodo-ai/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml#L90) required to review the PR on a relative scale of 1 to 5, where 'x' represents the assessed effort. This feedback is controlled by the 'enable_review_labels_effort' flag. + - **`ticket compliance`**: Adds a label indicating code compliance level ("Fully compliant" | "PR Code Verified" | "Partially compliant" | "Not compliant") to any GitHub/Jira/Linea ticket linked in the PR. Controlled by the 'require_ticket_labels' flag (default: false). If 'require_no_ticket_labels' is also enabled, PRs without ticket links will receive a "No ticket found" label. - Note: The `possible security issue` label highlights potential security risks. You can configure a GitHub Action to [prevent merging](https://medium.com/sequra-tech/quick-tip-block-pull-request-merge-using-labels-6cc326936221) PRs that have this label. + +### Blocking PRs from merging based on the generated labels + +!!! tip "" + + You can configure a CI/CD Action to prevent merging PRs with specific labels. For example, implement a dedicated [GitHub Action](https://medium.com/sequra-tech/quick-tip-block-pull-request-merge-using-labels-6cc326936221). + This approach helps ensure PRs with potential security issues or ticket compliance problems will not be merged without further review. + Since AI may make mistakes or lack complete context, use this feature judiciously. For flexibility, users with appropriate permissions can remove generated labels when necessary. Any label removal will be documented in the PR discussion, clearly indicating it was a deliberate action by an authorized user to override the AI blocking the merge. ### Extra instructions From e9ce3ae8699c9da6a879f6249a9c0f5946f804f8 Mon Sep 17 00:00:00 2001 From: Tal Date: Wed, 28 May 2025 19:37:23 +0300 Subject: [PATCH 37/68] Update docs/docs/tools/review.md Co-authored-by: qodo-merge-pro-for-open-source[bot] <189517486+qodo-merge-pro-for-open-source[bot]@users.noreply.github.com> --- docs/docs/tools/review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/tools/review.md b/docs/docs/tools/review.md index 56029c7f..67b5f024 100644 --- a/docs/docs/tools/review.md +++ b/docs/docs/tools/review.md @@ -161,7 +161,7 @@ extra_instructions = "..." You can configure a CI/CD Action to prevent merging PRs with specific labels. For example, implement a dedicated [GitHub Action](https://medium.com/sequra-tech/quick-tip-block-pull-request-merge-using-labels-6cc326936221). This approach helps ensure PRs with potential security issues or ticket compliance problems will not be merged without further review. - Since AI may make mistakes or lack complete context, use this feature judiciously. For flexibility, users with appropriate permissions can remove generated labels when necessary. Any label removal will be documented in the PR discussion, clearly indicating it was a deliberate action by an authorized user to override the AI blocking the merge. + Since AI may make mistakes or lack complete context, use this feature judiciously. For flexibility, users with appropriate permissions can remove generated labels when necessary. When a label is removed, this action will be automatically documented in the PR discussion, clearly indicating it was a deliberate override by an authorized user to allow the merge. ### Extra instructions From a17100e51259464a4d729d03fe9b7449b87b9d89 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Wed, 28 May 2025 19:42:27 +0300 Subject: [PATCH 38/68] docs: improve clarity and formatting of review tool labels documentation --- docs/docs/tools/review.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/docs/tools/review.md b/docs/docs/tools/review.md index 67b5f024..b94d6394 100644 --- a/docs/docs/tools/review.md +++ b/docs/docs/tools/review.md @@ -144,14 +144,14 @@ extra_instructions = "..." Meaning the `review` tool will run automatically on every PR, without any additional configurations. Edit this field to enable/disable the tool, or to change the configurations used. -### Auto-generated PR labels from the Review Tool +### Auto-generated PR labels by the Review Tool !!! tip "" - The `review` tool automatically adds two specific labels to your Pull Requests: + The `review` can tool automatically add labels to your Pull Requests: - - **`possible security issue`**: This label is applied if the tool detects a potential [security vulnerability](https://github.com/qodo-ai/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml#L103) in the PR's code. This feedback is controlled by the 'enable_review_labels_security' flag. - - **`review effort [x/5]`**: This label estimates the [effort](https://github.com/qodo-ai/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml#L90) required to review the PR on a relative scale of 1 to 5, where 'x' represents the assessed effort. This feedback is controlled by the 'enable_review_labels_effort' flag. + - **`possible security issue`**: This label is applied if the tool detects a potential [security vulnerability](https://github.com/qodo-ai/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml#L103) in the PR's code. This feedback is controlled by the 'enable_review_labels_security' flag (default is true). + - **`review effort [x/5]`**: This label estimates the [effort](https://github.com/qodo-ai/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml#L90) required to review the PR on a relative scale of 1 to 5, where 'x' represents the assessed effort. This feedback is controlled by the 'enable_review_labels_effort' flag (default is true). - **`ticket compliance`**: Adds a label indicating code compliance level ("Fully compliant" | "PR Code Verified" | "Partially compliant" | "Not compliant") to any GitHub/Jira/Linea ticket linked in the PR. Controlled by the 'require_ticket_labels' flag (default: false). If 'require_no_ticket_labels' is also enabled, PRs without ticket links will receive a "No ticket found" label. @@ -160,7 +160,9 @@ extra_instructions = "..." !!! tip "" You can configure a CI/CD Action to prevent merging PRs with specific labels. For example, implement a dedicated [GitHub Action](https://medium.com/sequra-tech/quick-tip-block-pull-request-merge-using-labels-6cc326936221). + This approach helps ensure PRs with potential security issues or ticket compliance problems will not be merged without further review. + Since AI may make mistakes or lack complete context, use this feature judiciously. For flexibility, users with appropriate permissions can remove generated labels when necessary. When a label is removed, this action will be automatically documented in the PR discussion, clearly indicating it was a deliberate override by an authorized user to allow the merge. ### Extra instructions From 8143f4b35b130b164b60c97f1fbb94779fa0b77b Mon Sep 17 00:00:00 2001 From: "Hussam.lawen" Date: Wed, 28 May 2025 22:23:24 +0300 Subject: [PATCH 39/68] docs: add Linear integration to ticket context fetching documentation --- .../core-abilities/fetching_ticket_context.md | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/docs/docs/core-abilities/fetching_ticket_context.md b/docs/docs/core-abilities/fetching_ticket_context.md index c6424ad1..db6e72e1 100644 --- a/docs/docs/core-abilities/fetching_ticket_context.md +++ b/docs/docs/core-abilities/fetching_ticket_context.md @@ -11,6 +11,7 @@ This integration enriches the review process by automatically surfacing relevant - GitHub - Jira (💎) +- Linear (💎) **Ticket data fetched:** @@ -75,13 +76,17 @@ The recommended way to authenticate with Jira Cloud is to install the Qodo Merge Installation steps: -1. Click [here](https://auth.atlassian.com/authorize?audience=api.atlassian.com&client_id=8krKmA4gMD8mM8z24aRCgPCSepZNP1xf&scope=read%3Ajira-work%20offline_access&redirect_uri=https%3A%2F%2Fregister.jira.pr-agent.codium.ai&state=qodomerge&response_type=code&prompt=consent) to install the Qodo Merge app in your Jira Cloud instance, click the `accept` button.
+1. Go to the [Qodo Merge integrations page](https://app.qodo.ai/qodo-merge/integrations) + +2. Click on the Connect **Jira Cloud** button to connect the Jira Cloud app + +3. Click the `accept` button.
![Jira Cloud App Installation](https://www.qodo.ai/images/pr_agent/jira_app_installation1.png){width=384} -2. After installing the app, you will be redirected to the Qodo Merge registration page. and you will see a success message.
+4. After installing the app, you will be redirected to the Qodo Merge registration page. and you will see a success message.
![Jira Cloud App success message](https://www.qodo.ai/images/pr_agent/jira_app_success.png){width=384} -3. Now Qodo Merge will be able to fetch Jira ticket context for your PRs. +5. Now Qodo Merge will be able to fetch Jira ticket context for your PRs. **2) Email/Token Authentication** @@ -300,3 +305,45 @@ Name your branch with the ticket ID as a prefix (e.g., `ISSUE-123-feature-descri [jira] jira_base_url = "https://.atlassian.net" ``` + +## Linear Integration 💎 + +### Linear App Authentication + +The recommended way to authenticate with Linear is to connect the Linear app through the Qodo Merge portal. + +Installation steps: + +1. Go to the [Qodo Merge integrations page](https://app.qodo.ai/qodo-merge/integrations) + +2. Navigate to the **Integrations** tab + +3. Click on the **Linear** button to connect the Linear app + +4. Follow the authentication flow to authorize Qodo Merge to access your Linear workspace + +5. Once connected, Qodo Merge will be able to fetch Linear ticket context for your PRs + +### How to link a PR to a Linear ticket + +Qodo Merge will automatically detect Linear tickets using either of these methods: + +**Method 1: Description Reference:** + +Include a ticket reference in your PR description using either: +- The complete Linear ticket URL: `https://linear.app/[ORG_ID]/issue/[TICKET_ID]` +- The shortened ticket ID: `[TICKET_ID]` (e.g., `ABC-123`) - requires linear_base_url configuration (see below). + +**Method 2: Branch Name Detection:** + +Name your branch with the ticket ID as a prefix (e.g., `ABC-123-feature-description` or `feature/ABC-123/feature-description`). + +!!! note "Linear Base URL" + For shortened ticket IDs or branch detection (method 2), you must configure the Linear base URL in your configuration file under the [linear] section: + + ```toml + [linear] + linear_base_url = "https://linear.app/[ORG_ID]" + ``` + + Replace `[ORG_ID]` with your Linear organization identifier. \ No newline at end of file From 1955157e9ac682a79b9c605199e4f1f1245546d4 Mon Sep 17 00:00:00 2001 From: tomoya-kawaguchi Date: Thu, 29 May 2025 12:42:05 +0900 Subject: [PATCH 40/68] feat: add AWS Secrets Manager Integration --- docs/docs/installation/github.md | 15 +++ .../usage-guide/additional_configurations.md | 13 +- docs/docs/usage-guide/aws_secrets_manager.md | 111 ++++++++++++++++ docs/mkdocs.yml | 1 + pr_agent/config_loader.py | 62 +++++++++ pr_agent/secret_providers/__init__.py | 7 + .../aws_secrets_manager_provider.py | 79 ++++++++++++ pr_agent/servers/serverless.py | 14 ++ pr_agent/settings/.secrets_template.toml | 16 ++- pr_agent/settings/configuration.toml | 2 +- .../test_aws_secrets_manager_provider.py | 102 +++++++++++++++ tests/unittest/test_config_loader_secrets.py | 120 ++++++++++++++++++ .../unittest/test_secret_provider_factory.py | 69 ++++++++++ 13 files changed, 608 insertions(+), 3 deletions(-) create mode 100644 docs/docs/usage-guide/aws_secrets_manager.md create mode 100644 pr_agent/secret_providers/aws_secrets_manager_provider.py create mode 100644 tests/unittest/test_aws_secrets_manager_provider.py create mode 100644 tests/unittest/test_config_loader_secrets.py create mode 100644 tests/unittest/test_secret_provider_factory.py diff --git a/docs/docs/installation/github.md b/docs/docs/installation/github.md index 3eeace4f..9ed1effa 100644 --- a/docs/docs/installation/github.md +++ b/docs/docs/installation/github.md @@ -203,6 +203,21 @@ For example: `GITHUB.WEBHOOK_SECRET` --> `GITHUB__WEBHOOK_SECRET` 7. Go back to steps 8-9 of [Method 5](#run-as-a-github-app) with the function url as your Webhook URL. The Webhook URL would look like `https:///api/v1/github_webhooks` +### Using AWS Secrets Manager (Recommended) + +For production Lambda deployments, use AWS Secrets Manager instead of environment variables: + +1. Create a secret in AWS Secrets Manager with your configuration +2. Add IAM permissions for `secretsmanager:GetSecretValue` +3. Set the secret ARN in your Lambda environment: + +```bash +AWS_SECRETS_MANAGER__SECRET_ARN=arn:aws:secretsmanager:region:account:secret:name +CONFIG__SECRET_PROVIDER=aws_secrets_manager +``` + +For detailed setup instructions, see [AWS Secrets Manager Integration](../usage-guide/aws_secrets_manager.md). + --- ## AWS CodeCommit Setup diff --git a/docs/docs/usage-guide/additional_configurations.md b/docs/docs/usage-guide/additional_configurations.md index 9f9202f6..1967453d 100644 --- a/docs/docs/usage-guide/additional_configurations.md +++ b/docs/docs/usage-guide/additional_configurations.md @@ -249,4 +249,15 @@ ignore_pr_authors = ["my-special-bot-user", ...] Where the `ignore_pr_authors` is a list of usernames that you want to ignore. !!! note - There is one specific case where bots will receive an automatic response - when they generated a PR with a _failed test_. In that case, the [`ci_feedback`](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) tool will be invoked. \ No newline at end of file + There is one specific case where bots will receive an automatic response - when they generated a PR with a _failed test_. In that case, the [`ci_feedback`](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) tool will be invoked. + +## Secret Management + +For production deployments, consider using external secret management: + +- **AWS Secrets Manager**: Recommended for AWS Lambda deployments +- **Google Cloud Storage**: For Google Cloud environments + +External secret providers automatically override environment variables at startup, providing enhanced security for sensitive information like API keys and webhook secrets. + +See [Configuration Options](configuration_options.md#secret-providers) for setup details. diff --git a/docs/docs/usage-guide/aws_secrets_manager.md b/docs/docs/usage-guide/aws_secrets_manager.md new file mode 100644 index 00000000..f97774ad --- /dev/null +++ b/docs/docs/usage-guide/aws_secrets_manager.md @@ -0,0 +1,111 @@ +# AWS Secrets Manager Integration + +Securely manage sensitive information such as API keys and webhook secrets when running PR-Agent in AWS Lambda environments. + +## Overview + +AWS Secrets Manager integration allows you to: + +- Store sensitive configuration in AWS Secrets Manager instead of environment variables +- Automatically retrieve and apply secrets at application startup +- Improve security for Lambda deployments +- Centrally manage secrets across multiple environments + +## Prerequisites + +- AWS Lambda deployment of PR-Agent +- AWS Secrets Manager access permissions +- Boto3 library (already included in PR-Agent dependencies) + +## Configuration + +### Step 1: Create Secret in AWS Secrets Manager + +Create a secret in AWS Secrets Manager with JSON format: + +```json +{ + "openai.key": "sk-...", + "github.webhook_secret": "your-webhook-secret", + "github.user_token": "ghp_...", + "gitlab.personal_access_token": "glpat-..." +} +``` + +### Step 2: Configure PR-Agent + +Add the following to your configuration: + +```toml +# configuration.toml +[config] +secret_provider = "aws_secrets_manager" + +# .secrets.toml or environment variables +[aws_secrets_manager] +secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:pr-agent-secrets-AbCdEf" +region_name = "" # Optional: specific region (defaults to Lambda's region) +``` + +### Step 3: Set IAM Permissions + +Your Lambda execution role needs the following permissions: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["secretsmanager:GetSecretValue"], + "Resource": "arn:aws:secretsmanager:region:account:secret:pr-agent/*" + } + ] +} +``` + +## Environment Variable Mapping + +Secrets Manager keys should use dot notation that maps to configuration sections: + +| Secret Key | Configuration Section | Environment Variable | +| ----------------------- | --------------------- | ------------------------ | +| `openai.key` | `[openai]` | `OPENAI__KEY` | +| `github.webhook_secret` | `[github]` | `GITHUB__WEBHOOK_SECRET` | +| `github.user_token` | `[github]` | `GITHUB__USER_TOKEN` | + +## Fallback Behavior + +If AWS Secrets Manager is unavailable or misconfigured: + +- PR-Agent will fall back to environment variables +- A debug log message will be recorded +- No service interruption occurs + +## Troubleshooting + +### Common Issues + +1. **Permission Denied**: Ensure Lambda execution role has `secretsmanager:GetSecretValue` permission +2. **Secret Not Found**: Verify the secret ARN is correct and exists in the specified region +3. **JSON Parse Error**: Ensure the secret value is valid JSON format +4. **Connection Issues**: Check network connectivity and AWS region settings + +### Debug Logging + +Enable debug logging to troubleshoot: + +```toml +[config] +log_level = "DEBUG" +``` + +Check CloudWatch logs for warning/error messages related to AWS Secrets Manager access. + +## Security Best Practices + +1. Use least-privilege IAM policies +2. Rotate secrets regularly +3. Use separate secrets for different environments +4. Monitor CloudTrail for secret access +5. Enable secret versioning for rollback capability diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 740488ad..e3791551 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -16,6 +16,7 @@ nav: - Introduction: 'usage-guide/introduction.md' - Enabling a Wiki: 'usage-guide/enabling_a_wiki.md' - Configuration File: 'usage-guide/configuration_options.md' + - AWS Secrets Manager: 'usage-guide/aws_secrets_manager.md' - Usage and Automation: 'usage-guide/automations_and_usage.md' - Managing Mail Notifications: 'usage-guide/mail_notifications.md' - Changing a Model: 'usage-guide/changing_a_model.md' diff --git a/pr_agent/config_loader.py b/pr_agent/config_loader.py index 7a62adec..2b0ad880 100644 --- a/pr_agent/config_loader.py +++ b/pr_agent/config_loader.py @@ -81,3 +81,65 @@ def _find_pyproject() -> Optional[Path]: pyproject_path = _find_pyproject() if pyproject_path is not None: get_settings().load_file(pyproject_path, env=f'tool.{PR_AGENT_TOML_KEY}') + + +def apply_secrets_manager_config(): + """ + Retrieve configuration from AWS Secrets Manager and override existing settings + """ + try: + from pr_agent.secret_providers import get_secret_provider + from pr_agent.log import get_logger + + secret_provider = get_secret_provider() + if not secret_provider: + return + + # Execute only when AWS Secrets Manager specific method is available + if (hasattr(secret_provider, 'get_all_secrets') and + get_settings().get("CONFIG.SECRET_PROVIDER") == 'aws_secrets_manager'): + try: + secrets = secret_provider.get_all_secrets() + if secrets: + apply_secrets_to_config(secrets) + get_logger().info("Applied AWS Secrets Manager configuration") + except Exception as e: + get_logger().error(f"Failed to apply AWS Secrets Manager config: {e}") + except Exception as e: + # Fail silently when secret provider is not configured + try: + from pr_agent.log import get_logger + get_logger().debug(f"Secret provider not configured: {e}") + except: + # Fail completely silently if log module is not available + pass + + +def apply_secrets_to_config(secrets: dict): + """ + Apply secret dictionary to configuration + Configuration override with same pattern as Google Cloud Storage + """ + try: + from pr_agent.log import get_logger + except: + # Do nothing if logging is not available + def get_logger(): + class DummyLogger: + def debug(self, msg): pass + return DummyLogger() + + for key, value in secrets.items(): + if '.' in key: # nested key like "openai.key" + parts = key.split('.') + if len(parts) == 2: + section, setting = parts + # Convert case to match Dynaconf pattern + section_upper = section.upper() + setting_upper = setting.upper() + + # Set only when no existing value (prioritize environment variables) + current_value = get_settings().get(f"{section_upper}.{setting_upper}") + if current_value is None or current_value == "": + get_settings().set(f"{section_upper}.{setting_upper}", value) + get_logger().debug(f"Set {section}.{setting} from AWS Secrets Manager") diff --git a/pr_agent/secret_providers/__init__.py b/pr_agent/secret_providers/__init__.py index c9faf480..204872e2 100644 --- a/pr_agent/secret_providers/__init__.py +++ b/pr_agent/secret_providers/__init__.py @@ -13,5 +13,12 @@ def get_secret_provider(): return GoogleCloudStorageSecretProvider() except Exception as e: raise ValueError(f"Failed to initialize google_cloud_storage secret provider {provider_id}") from e + elif provider_id == 'aws_secrets_manager': + try: + from pr_agent.secret_providers.aws_secrets_manager_provider import \ + AWSSecretsManagerProvider + return AWSSecretsManagerProvider() + except Exception as e: + raise ValueError(f"Failed to initialize aws_secrets_manager secret provider {provider_id}") from e else: raise ValueError("Unknown SECRET_PROVIDER") diff --git a/pr_agent/secret_providers/aws_secrets_manager_provider.py b/pr_agent/secret_providers/aws_secrets_manager_provider.py new file mode 100644 index 00000000..82248458 --- /dev/null +++ b/pr_agent/secret_providers/aws_secrets_manager_provider.py @@ -0,0 +1,79 @@ +import json +import boto3 +from botocore.exceptions import ClientError + +from pr_agent.config_loader import get_settings +from pr_agent.log import get_logger +from pr_agent.secret_providers.secret_provider import SecretProvider + + +class AWSSecretsManagerProvider(SecretProvider): + def __init__(self): + try: + # AWS credentials are automatically retrieved from environment variables or IAM roles + # Region configuration is flexible like Google Cloud Storage pattern + region_name = get_settings().get("aws_secrets_manager.region_name") or \ + get_settings().get("aws.AWS_REGION_NAME") + if region_name: + self.client = boto3.client('secretsmanager', region_name=region_name) + else: + self.client = boto3.client('secretsmanager') + + # Require secret_arn similar to Google Cloud Storage pattern + self.secret_arn = get_settings().aws_secrets_manager.secret_arn + + except Exception as e: + get_logger().error(f"Failed to initialize AWS Secrets Manager Provider: {e}") + raise e + + def get_secret(self, secret_name: str) -> str: + """ + Retrieve individual secret by name (for webhook tokens) + Same error handling pattern as Google Cloud Storage + """ + try: + response = self.client.get_secret_value(SecretId=secret_name) + return response['SecretString'] + except Exception as e: + get_logger().warning(f"Failed to get secret {secret_name} from AWS Secrets Manager: {e}") + return "" + + def get_all_secrets(self) -> dict: + """ + Retrieve all secrets for configuration override + AWS Secrets Manager specific method (not available in Google Cloud Storage) + """ + try: + response = self.client.get_secret_value(SecretId=self.secret_arn) + return json.loads(response['SecretString']) + except Exception as e: + get_logger().error(f"Failed to get secrets from AWS Secrets Manager {self.secret_arn}: {e}") + return {} + + def store_secret(self, secret_name: str, secret_value: str): + """ + Same error handling pattern as Google Cloud Storage + """ + try: + # Update existing secret + self.client.update_secret( + SecretId=secret_name, + SecretString=secret_value + ) + except ClientError as e: + if e.response['Error']['Code'] == 'ResourceNotFoundException': + # Create new secret if it doesn't exist + try: + self.client.create_secret( + Name=secret_name, + SecretString=secret_value + ) + except Exception as create_error: + get_logger().error(f"Failed to store secret {secret_name} in AWS Secrets Manager: {create_error}") + raise create_error + else: + get_logger().error(f"Failed to store secret {secret_name} in AWS Secrets Manager: {e}") + raise e + except Exception as e: + get_logger().error(f"Failed to store secret {secret_name} in AWS Secrets Manager: {e}") + raise e \ No newline at end of file diff --git a/pr_agent/servers/serverless.py b/pr_agent/servers/serverless.py index a46eb80a..8e2ab08a 100644 --- a/pr_agent/servers/serverless.py +++ b/pr_agent/servers/serverless.py @@ -5,6 +5,20 @@ from starlette_context.middleware import RawContextMiddleware from pr_agent.servers.github_app import router +# Execute AWS Secrets Manager configuration override at module load time +# Initialize with same pattern as Google Cloud Storage provider +try: + from pr_agent.config_loader import apply_secrets_manager_config + apply_secrets_manager_config() +except Exception as e: + # Handle initialization failure silently (fallback to environment variables) + 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) diff --git a/pr_agent/settings/.secrets_template.toml b/pr_agent/settings/.secrets_template.toml index 460711cb..f27b6bee 100644 --- a/pr_agent/settings/.secrets_template.toml +++ b/pr_agent/settings/.secrets_template.toml @@ -121,4 +121,18 @@ api_base = "" [aws] AWS_ACCESS_KEY_ID = "" AWS_SECRET_ACCESS_KEY = "" -AWS_REGION_NAME = "" \ No newline at end of file +AWS_REGION_NAME = "" + +# AWS Secrets Manager (for secure secret management in Lambda environments) +[aws_secrets_manager] +secret_arn = "" # The ARN of the AWS Secrets Manager secret containing PR-Agent configuration +region_name = "" # Optional: specific AWS region (defaults to AWS_REGION_NAME or Lambda region) + +# AWS Secrets Manager secret should contain JSON with configuration overrides: +# Example secret value: +# { +# "openai.key": "sk-...", +# "github.webhook_secret": "your-webhook-secret", +# "github.user_token": "ghp_...", +# "gitlab.personal_access_token": "glpat-..." +# } \ No newline at end of file diff --git a/pr_agent/settings/configuration.toml b/pr_agent/settings/configuration.toml index cdb6d5b9..a93ea1f2 100644 --- a/pr_agent/settings/configuration.toml +++ b/pr_agent/settings/configuration.toml @@ -39,7 +39,7 @@ allow_dynamic_context=true max_extra_lines_before_dynamic_context = 10 # will try to include up to 10 extra lines before the hunk in the patch, until we reach an enclosing function or class patch_extra_lines_before = 5 # Number of extra lines (+3 default ones) to include before each hunk in the patch patch_extra_lines_after = 1 # Number of extra lines (+3 default ones) to include after each hunk in the patch -secret_provider="" +secret_provider="" # "" (disabled), "google_cloud_storage", or "aws_secrets_manager" for secure secret management cli_mode=false ai_disclaimer_title="" # Pro feature, title for a collapsible disclaimer to AI outputs ai_disclaimer="" # Pro feature, full text for the AI disclaimer diff --git a/tests/unittest/test_aws_secrets_manager_provider.py b/tests/unittest/test_aws_secrets_manager_provider.py new file mode 100644 index 00000000..7111189b --- /dev/null +++ b/tests/unittest/test_aws_secrets_manager_provider.py @@ -0,0 +1,102 @@ +import json +import pytest +from unittest.mock import MagicMock, patch +from botocore.exceptions import ClientError + +from pr_agent.secret_providers.aws_secrets_manager_provider import AWSSecretsManagerProvider + + +class TestAWSSecretsManagerProvider: + + def _provider(self): + """Create provider following existing pattern""" + with patch('pr_agent.secret_providers.aws_secrets_manager_provider.get_settings') as mock_get_settings, \ + patch('pr_agent.secret_providers.aws_secrets_manager_provider.boto3.client') as mock_boto3_client: + + settings = MagicMock() + settings.get.side_effect = lambda k, d=None: { + 'aws_secrets_manager.secret_arn': 'arn:aws:secretsmanager:us-east-1:123456789012:secret:test-secret', + 'aws_secrets_manager.region_name': 'us-east-1', + 'aws.AWS_REGION_NAME': 'us-east-1' + }.get(k, d) + settings.aws_secrets_manager.secret_arn = 'arn:aws:secretsmanager:us-east-1:123456789012:secret:test-secret' + mock_get_settings.return_value = settings + + # Mock boto3 client + mock_client = MagicMock() + mock_boto3_client.return_value = mock_client + + provider = AWSSecretsManagerProvider() + provider.client = mock_client # Set client directly for testing + return provider, mock_client + + # Positive test cases + def test_get_secret_success(self): + provider, mock_client = self._provider() + mock_client.get_secret_value.return_value = {'SecretString': 'test-secret-value'} + + result = provider.get_secret('test-secret-name') + assert result == 'test-secret-value' + mock_client.get_secret_value.assert_called_once_with(SecretId='test-secret-name') + + def test_get_all_secrets_success(self): + provider, mock_client = self._provider() + secret_data = {'openai.key': 'sk-test', 'github.webhook_secret': 'webhook-secret'} + mock_client.get_secret_value.return_value = {'SecretString': json.dumps(secret_data)} + + result = provider.get_all_secrets() + assert result == secret_data + + # Negative test cases (following Google Cloud Storage pattern) + def test_get_secret_failure(self): + provider, mock_client = self._provider() + mock_client.get_secret_value.side_effect = Exception("AWS error") + + result = provider.get_secret('nonexistent-secret') + assert result == "" # Confirm empty string is returned + + def test_get_all_secrets_failure(self): + provider, mock_client = self._provider() + mock_client.get_secret_value.side_effect = Exception("AWS error") + + result = provider.get_all_secrets() + assert result == {} # Confirm empty dictionary is returned + + def test_store_secret_update_existing(self): + provider, mock_client = self._provider() + mock_client.update_secret.return_value = {} + + provider.store_secret('test-secret', 'test-value') + mock_client.update_secret.assert_called_once_with( + SecretId='test-secret', + SecretString='test-value' + ) + + def test_store_secret_create_new(self): + provider, mock_client = self._provider() + mock_client.update_secret.side_effect = ClientError( + {'Error': {'Code': 'ResourceNotFoundException'}}, 'UpdateSecret' + ) + mock_client.create_secret.return_value = {} + + provider.store_secret('new-secret', 'test-value') + mock_client.create_secret.assert_called_once_with( + Name='new-secret', + SecretString='test-value' + ) + + def test_init_failure_invalid_config(self): + with patch('pr_agent.secret_providers.aws_secrets_manager_provider.get_settings') as mock_get_settings: + settings = MagicMock() + settings.aws_secrets_manager.secret_arn = None # Configuration error + mock_get_settings.return_value = settings + + with pytest.raises(Exception): + AWSSecretsManagerProvider() + + def test_store_secret_failure(self): + provider, mock_client = self._provider() + mock_client.update_secret.side_effect = Exception("AWS error") + + with pytest.raises(Exception): + provider.store_secret('test-secret', 'test-value') \ No newline at end of file diff --git a/tests/unittest/test_config_loader_secrets.py b/tests/unittest/test_config_loader_secrets.py new file mode 100644 index 00000000..b0d11811 --- /dev/null +++ b/tests/unittest/test_config_loader_secrets.py @@ -0,0 +1,120 @@ +import pytest +from unittest.mock import MagicMock, patch + +from pr_agent.config_loader import apply_secrets_manager_config, apply_secrets_to_config + + +class TestConfigLoaderSecrets: + + def test_apply_secrets_manager_config_success(self): + with patch('pr_agent.config_loader.get_secret_provider') as mock_get_provider, \ + patch('pr_agent.config_loader.apply_secrets_to_config') as mock_apply_secrets, \ + patch('pr_agent.config_loader.get_settings') as mock_get_settings: + + # Mock secret provider + mock_provider = MagicMock() + mock_provider.get_all_secrets.return_value = {'openai.key': 'sk-test'} + mock_get_provider.return_value = mock_provider + + # Mock settings + settings = MagicMock() + settings.get.return_value = "aws_secrets_manager" + mock_get_settings.return_value = settings + + apply_secrets_manager_config() + + mock_apply_secrets.assert_called_once_with({'openai.key': 'sk-test'}) + + def test_apply_secrets_manager_config_no_provider(self): + with patch('pr_agent.config_loader.get_secret_provider') as mock_get_provider: + mock_get_provider.return_value = None + + # Confirm no exception is raised + apply_secrets_manager_config() + + def test_apply_secrets_manager_config_not_aws(self): + with patch('pr_agent.config_loader.get_secret_provider') as mock_get_provider, \ + patch('pr_agent.config_loader.get_settings') as mock_get_settings: + + # Mock Google Cloud Storage provider + mock_provider = MagicMock() + mock_get_provider.return_value = mock_provider + + # Mock settings (Google Cloud Storage) + settings = MagicMock() + settings.get.return_value = "google_cloud_storage" + mock_get_settings.return_value = settings + + # Confirm execution is skipped for non-AWS Secrets Manager + apply_secrets_manager_config() + + # Confirm get_all_secrets is not called + assert not hasattr(mock_provider, 'get_all_secrets') or \ + not mock_provider.get_all_secrets.called + + def test_apply_secrets_to_config_nested_keys(self): + with patch('pr_agent.config_loader.get_settings') as mock_get_settings: + settings = MagicMock() + settings.get.return_value = None # No existing value + settings.set = MagicMock() + mock_get_settings.return_value = settings + + secrets = { + 'openai.key': 'sk-test', + 'github.webhook_secret': 'webhook-secret' + } + + apply_secrets_to_config(secrets) + + # Confirm settings are applied correctly + settings.set.assert_any_call('OPENAI.KEY', 'sk-test') + settings.set.assert_any_call('GITHUB.WEBHOOK_SECRET', 'webhook-secret') + + def test_apply_secrets_to_config_existing_value_preserved(self): + with patch('pr_agent.config_loader.get_settings') as mock_get_settings: + settings = MagicMock() + settings.get.return_value = "existing-value" # Existing value present + settings.set = MagicMock() + mock_get_settings.return_value = settings + + secrets = {'openai.key': 'sk-test'} + + apply_secrets_to_config(secrets) + + # Confirm settings are not overridden when existing value present + settings.set.assert_not_called() + + def test_apply_secrets_to_config_single_key(self): + with patch('pr_agent.config_loader.get_settings') as mock_get_settings: + settings = MagicMock() + settings.get.return_value = None + settings.set = MagicMock() + mock_get_settings.return_value = settings + + secrets = {'simple_key': 'simple_value'} + + apply_secrets_to_config(secrets) + + # Confirm non-dot notation keys are ignored + settings.set.assert_not_called() + + def test_apply_secrets_to_config_multiple_dots(self): + with patch('pr_agent.config_loader.get_settings') as mock_get_settings: + settings = MagicMock() + settings.get.return_value = None + settings.set = MagicMock() + mock_get_settings.return_value = settings + + secrets = {'section.subsection.key': 'value'} + + apply_secrets_to_config(secrets) + + # Confirm keys with multiple dots are ignored + settings.set.assert_not_called() + + def test_apply_secrets_manager_config_exception_handling(self): + with patch('pr_agent.config_loader.get_secret_provider') as mock_get_provider: + mock_get_provider.side_effect = Exception("Provider error") + + # Confirm processing continues even when exception occurs + apply_secrets_manager_config() # Confirm no exception is raised \ No newline at end of file diff --git a/tests/unittest/test_secret_provider_factory.py b/tests/unittest/test_secret_provider_factory.py new file mode 100644 index 00000000..e2ce1413 --- /dev/null +++ b/tests/unittest/test_secret_provider_factory.py @@ -0,0 +1,69 @@ +import pytest +from unittest.mock import MagicMock, patch + +from pr_agent.secret_providers import get_secret_provider + + +class TestSecretProviderFactory: + + def test_get_secret_provider_none_when_not_configured(self): + with patch('pr_agent.secret_providers.get_settings') as mock_get_settings: + settings = MagicMock() + settings.get.return_value = None + mock_get_settings.return_value = settings + + result = get_secret_provider() + assert result is None + + def test_get_secret_provider_google_cloud_storage(self): + with patch('pr_agent.secret_providers.get_settings') as mock_get_settings: + settings = MagicMock() + settings.get.return_value = "google_cloud_storage" + settings.config.secret_provider = "google_cloud_storage" + mock_get_settings.return_value = settings + + with patch('pr_agent.secret_providers.google_cloud_storage_secret_provider.GoogleCloudStorageSecretProvider') as MockProvider: + mock_instance = MagicMock() + MockProvider.return_value = mock_instance + + result = get_secret_provider() + assert result is mock_instance + MockProvider.assert_called_once() + + def test_get_secret_provider_aws_secrets_manager(self): + with patch('pr_agent.secret_providers.get_settings') as mock_get_settings: + settings = MagicMock() + settings.get.return_value = "aws_secrets_manager" + settings.config.secret_provider = "aws_secrets_manager" + mock_get_settings.return_value = settings + + with patch('pr_agent.secret_providers.aws_secrets_manager_provider.AWSSecretsManagerProvider') as MockProvider: + mock_instance = MagicMock() + MockProvider.return_value = mock_instance + + result = get_secret_provider() + assert result is mock_instance + MockProvider.assert_called_once() + + def test_get_secret_provider_unknown_provider(self): + with patch('pr_agent.secret_providers.get_settings') as mock_get_settings: + settings = MagicMock() + settings.get.return_value = "unknown_provider" + settings.config.secret_provider = "unknown_provider" + mock_get_settings.return_value = settings + + with pytest.raises(ValueError, match="Unknown SECRET_PROVIDER"): + get_secret_provider() + + def test_get_secret_provider_initialization_error(self): + with patch('pr_agent.secret_providers.get_settings') as mock_get_settings: + settings = MagicMock() + settings.get.return_value = "aws_secrets_manager" + settings.config.secret_provider = "aws_secrets_manager" + mock_get_settings.return_value = settings + + with patch('pr_agent.secret_providers.aws_secrets_manager_provider.AWSSecretsManagerProvider') as MockProvider: + MockProvider.side_effect = Exception("Initialization failed") + + with pytest.raises(ValueError, match="Failed to initialize aws_secrets_manager secret provider"): + get_secret_provider() \ No newline at end of file From cd96f6b911f76fdb219c1a83209c80b54d1dae39 Mon Sep 17 00:00:00 2001 From: tomoya-kawaguchi Date: Thu, 29 May 2025 13:03:21 +0900 Subject: [PATCH 41/68] chore: organize comments --- pr_agent/config_loader.py | 5 ----- .../secret_providers/aws_secrets_manager_provider.py | 9 --------- pr_agent/servers/serverless.py | 3 --- pr_agent/settings/.secrets_template.toml | 10 ---------- 4 files changed, 27 deletions(-) diff --git a/pr_agent/config_loader.py b/pr_agent/config_loader.py index 2b0ad880..a4fbb130 100644 --- a/pr_agent/config_loader.py +++ b/pr_agent/config_loader.py @@ -95,7 +95,6 @@ def apply_secrets_manager_config(): if not secret_provider: return - # Execute only when AWS Secrets Manager specific method is available if (hasattr(secret_provider, 'get_all_secrets') and get_settings().get("CONFIG.SECRET_PROVIDER") == 'aws_secrets_manager'): try: @@ -106,7 +105,6 @@ def apply_secrets_manager_config(): except Exception as e: get_logger().error(f"Failed to apply AWS Secrets Manager config: {e}") except Exception as e: - # Fail silently when secret provider is not configured try: from pr_agent.log import get_logger get_logger().debug(f"Secret provider not configured: {e}") @@ -118,12 +116,10 @@ def apply_secrets_manager_config(): def apply_secrets_to_config(secrets: dict): """ Apply secret dictionary to configuration - Configuration override with same pattern as Google Cloud Storage """ try: from pr_agent.log import get_logger except: - # Do nothing if logging is not available def get_logger(): class DummyLogger: def debug(self, msg): pass @@ -134,7 +130,6 @@ def apply_secrets_to_config(secrets: dict): parts = key.split('.') if len(parts) == 2: section, setting = parts - # Convert case to match Dynaconf pattern section_upper = section.upper() setting_upper = setting.upper() diff --git a/pr_agent/secret_providers/aws_secrets_manager_provider.py b/pr_agent/secret_providers/aws_secrets_manager_provider.py index 82248458..7368f95b 100644 --- a/pr_agent/secret_providers/aws_secrets_manager_provider.py +++ b/pr_agent/secret_providers/aws_secrets_manager_provider.py @@ -10,8 +10,6 @@ from pr_agent.secret_providers.secret_provider import SecretProvider class AWSSecretsManagerProvider(SecretProvider): def __init__(self): try: - # AWS credentials are automatically retrieved from environment variables or IAM roles - # Region configuration is flexible like Google Cloud Storage pattern region_name = get_settings().get("aws_secrets_manager.region_name") or \ get_settings().get("aws.AWS_REGION_NAME") if region_name: @@ -19,7 +17,6 @@ class AWSSecretsManagerProvider(SecretProvider): else: self.client = boto3.client('secretsmanager') - # Require secret_arn similar to Google Cloud Storage pattern self.secret_arn = get_settings().aws_secrets_manager.secret_arn except Exception as e: @@ -29,7 +26,6 @@ class AWSSecretsManagerProvider(SecretProvider): def get_secret(self, secret_name: str) -> str: """ Retrieve individual secret by name (for webhook tokens) - Same error handling pattern as Google Cloud Storage """ try: response = self.client.get_secret_value(SecretId=secret_name) @@ -41,7 +37,6 @@ class AWSSecretsManagerProvider(SecretProvider): def get_all_secrets(self) -> dict: """ Retrieve all secrets for configuration override - AWS Secrets Manager specific method (not available in Google Cloud Storage) """ try: response = self.client.get_secret_value(SecretId=self.secret_arn) @@ -51,11 +46,7 @@ class AWSSecretsManagerProvider(SecretProvider): return {} def store_secret(self, secret_name: str, secret_value: str): - """ - Same error handling pattern as Google Cloud Storage - """ try: - # Update existing secret self.client.update_secret( SecretId=secret_name, SecretString=secret_value diff --git a/pr_agent/servers/serverless.py b/pr_agent/servers/serverless.py index 8e2ab08a..938be31b 100644 --- a/pr_agent/servers/serverless.py +++ b/pr_agent/servers/serverless.py @@ -5,13 +5,10 @@ from starlette_context.middleware import RawContextMiddleware from pr_agent.servers.github_app import router -# Execute AWS Secrets Manager configuration override at module load time -# Initialize with same pattern as Google Cloud Storage provider try: from pr_agent.config_loader import apply_secrets_manager_config apply_secrets_manager_config() except Exception as e: - # Handle initialization failure silently (fallback to environment variables) try: from pr_agent.log import get_logger get_logger().debug(f"AWS Secrets Manager initialization failed, falling back to environment variables: {e}") diff --git a/pr_agent/settings/.secrets_template.toml b/pr_agent/settings/.secrets_template.toml index f27b6bee..350abe5c 100644 --- a/pr_agent/settings/.secrets_template.toml +++ b/pr_agent/settings/.secrets_template.toml @@ -123,16 +123,6 @@ AWS_ACCESS_KEY_ID = "" AWS_SECRET_ACCESS_KEY = "" AWS_REGION_NAME = "" -# AWS Secrets Manager (for secure secret management in Lambda environments) [aws_secrets_manager] secret_arn = "" # The ARN of the AWS Secrets Manager secret containing PR-Agent configuration region_name = "" # Optional: specific AWS region (defaults to AWS_REGION_NAME or Lambda region) - -# AWS Secrets Manager secret should contain JSON with configuration overrides: -# Example secret value: -# { -# "openai.key": "sk-...", -# "github.webhook_secret": "your-webhook-secret", -# "github.user_token": "ghp_...", -# "gitlab.personal_access_token": "glpat-..." -# } \ No newline at end of file From 5e535a8868c5fb26481012d61e8cc5ead3b211ba Mon Sep 17 00:00:00 2001 From: tomoya-kawaguchi Date: Thu, 29 May 2025 13:05:40 +0900 Subject: [PATCH 42/68] chore: add blank --- pr_agent/secret_providers/aws_secrets_manager_provider.py | 2 +- tests/unittest/test_aws_secrets_manager_provider.py | 2 +- tests/unittest/test_config_loader_secrets.py | 2 +- tests/unittest/test_secret_provider_factory.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pr_agent/secret_providers/aws_secrets_manager_provider.py b/pr_agent/secret_providers/aws_secrets_manager_provider.py index 7368f95b..e14d3d87 100644 --- a/pr_agent/secret_providers/aws_secrets_manager_provider.py +++ b/pr_agent/secret_providers/aws_secrets_manager_provider.py @@ -67,4 +67,4 @@ class AWSSecretsManagerProvider(SecretProvider): raise e except Exception as e: get_logger().error(f"Failed to store secret {secret_name} in AWS Secrets Manager: {e}") - raise e \ No newline at end of file + raise e diff --git a/tests/unittest/test_aws_secrets_manager_provider.py b/tests/unittest/test_aws_secrets_manager_provider.py index 7111189b..a966555c 100644 --- a/tests/unittest/test_aws_secrets_manager_provider.py +++ b/tests/unittest/test_aws_secrets_manager_provider.py @@ -99,4 +99,4 @@ class TestAWSSecretsManagerProvider: mock_client.update_secret.side_effect = Exception("AWS error") with pytest.raises(Exception): - provider.store_secret('test-secret', 'test-value') \ No newline at end of file + provider.store_secret('test-secret', 'test-value') diff --git a/tests/unittest/test_config_loader_secrets.py b/tests/unittest/test_config_loader_secrets.py index b0d11811..d0eb3c62 100644 --- a/tests/unittest/test_config_loader_secrets.py +++ b/tests/unittest/test_config_loader_secrets.py @@ -117,4 +117,4 @@ class TestConfigLoaderSecrets: mock_get_provider.side_effect = Exception("Provider error") # Confirm processing continues even when exception occurs - apply_secrets_manager_config() # Confirm no exception is raised \ No newline at end of file + apply_secrets_manager_config() # Confirm no exception is raised diff --git a/tests/unittest/test_secret_provider_factory.py b/tests/unittest/test_secret_provider_factory.py index e2ce1413..98a1bfed 100644 --- a/tests/unittest/test_secret_provider_factory.py +++ b/tests/unittest/test_secret_provider_factory.py @@ -66,4 +66,4 @@ class TestSecretProviderFactory: MockProvider.side_effect = Exception("Initialization failed") with pytest.raises(ValueError, match="Failed to initialize aws_secrets_manager secret provider"): - get_secret_provider() \ No newline at end of file + get_secret_provider() From 53e232d7f4e2da6fbdbc5ba74745a029d895af37 Mon Sep 17 00:00:00 2001 From: tomoya-kawaguchi Date: Thu, 29 May 2025 13:09:58 +0900 Subject: [PATCH 43/68] docs: update document for Secrets Manager --- docs/docs/installation/github.md | 2 +- docs/docs/usage-guide/additional_configurations.md | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/docs/docs/installation/github.md b/docs/docs/installation/github.md index 9ed1effa..d95565f2 100644 --- a/docs/docs/installation/github.md +++ b/docs/docs/installation/github.md @@ -203,7 +203,7 @@ For example: `GITHUB.WEBHOOK_SECRET` --> `GITHUB__WEBHOOK_SECRET` 7. Go back to steps 8-9 of [Method 5](#run-as-a-github-app) with the function url as your Webhook URL. The Webhook URL would look like `https:///api/v1/github_webhooks` -### Using AWS Secrets Manager (Recommended) +### Using AWS Secrets Manager For production Lambda deployments, use AWS Secrets Manager instead of environment variables: diff --git a/docs/docs/usage-guide/additional_configurations.md b/docs/docs/usage-guide/additional_configurations.md index 1967453d..8d205865 100644 --- a/docs/docs/usage-guide/additional_configurations.md +++ b/docs/docs/usage-guide/additional_configurations.md @@ -250,14 +250,3 @@ Where the `ignore_pr_authors` is a list of usernames that you want to ignore. !!! note There is one specific case where bots will receive an automatic response - when they generated a PR with a _failed test_. In that case, the [`ci_feedback`](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) tool will be invoked. - -## Secret Management - -For production deployments, consider using external secret management: - -- **AWS Secrets Manager**: Recommended for AWS Lambda deployments -- **Google Cloud Storage**: For Google Cloud environments - -External secret providers automatically override environment variables at startup, providing enhanced security for sensitive information like API keys and webhook secrets. - -See [Configuration Options](configuration_options.md#secret-providers) for setup details. From b932b96e0c2308d989b71871131f9d600872af24 Mon Sep 17 00:00:00 2001 From: tomoya-kawaguchi Date: Thu, 29 May 2025 13:10:18 +0900 Subject: [PATCH 44/68] docs: rm detailed document for AWS Secrets Manager --- docs/docs/usage-guide/aws_secrets_manager.md | 111 ------------------- 1 file changed, 111 deletions(-) delete mode 100644 docs/docs/usage-guide/aws_secrets_manager.md diff --git a/docs/docs/usage-guide/aws_secrets_manager.md b/docs/docs/usage-guide/aws_secrets_manager.md deleted file mode 100644 index f97774ad..00000000 --- a/docs/docs/usage-guide/aws_secrets_manager.md +++ /dev/null @@ -1,111 +0,0 @@ -# AWS Secrets Manager Integration - -Securely manage sensitive information such as API keys and webhook secrets when running PR-Agent in AWS Lambda environments. - -## Overview - -AWS Secrets Manager integration allows you to: - -- Store sensitive configuration in AWS Secrets Manager instead of environment variables -- Automatically retrieve and apply secrets at application startup -- Improve security for Lambda deployments -- Centrally manage secrets across multiple environments - -## Prerequisites - -- AWS Lambda deployment of PR-Agent -- AWS Secrets Manager access permissions -- Boto3 library (already included in PR-Agent dependencies) - -## Configuration - -### Step 1: Create Secret in AWS Secrets Manager - -Create a secret in AWS Secrets Manager with JSON format: - -```json -{ - "openai.key": "sk-...", - "github.webhook_secret": "your-webhook-secret", - "github.user_token": "ghp_...", - "gitlab.personal_access_token": "glpat-..." -} -``` - -### Step 2: Configure PR-Agent - -Add the following to your configuration: - -```toml -# configuration.toml -[config] -secret_provider = "aws_secrets_manager" - -# .secrets.toml or environment variables -[aws_secrets_manager] -secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:pr-agent-secrets-AbCdEf" -region_name = "" # Optional: specific region (defaults to Lambda's region) -``` - -### Step 3: Set IAM Permissions - -Your Lambda execution role needs the following permissions: - -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": ["secretsmanager:GetSecretValue"], - "Resource": "arn:aws:secretsmanager:region:account:secret:pr-agent/*" - } - ] -} -``` - -## Environment Variable Mapping - -Secrets Manager keys should use dot notation that maps to configuration sections: - -| Secret Key | Configuration Section | Environment Variable | -| ----------------------- | --------------------- | ------------------------ | -| `openai.key` | `[openai]` | `OPENAI__KEY` | -| `github.webhook_secret` | `[github]` | `GITHUB__WEBHOOK_SECRET` | -| `github.user_token` | `[github]` | `GITHUB__USER_TOKEN` | - -## Fallback Behavior - -If AWS Secrets Manager is unavailable or misconfigured: - -- PR-Agent will fall back to environment variables -- A debug log message will be recorded -- No service interruption occurs - -## Troubleshooting - -### Common Issues - -1. **Permission Denied**: Ensure Lambda execution role has `secretsmanager:GetSecretValue` permission -2. **Secret Not Found**: Verify the secret ARN is correct and exists in the specified region -3. **JSON Parse Error**: Ensure the secret value is valid JSON format -4. **Connection Issues**: Check network connectivity and AWS region settings - -### Debug Logging - -Enable debug logging to troubleshoot: - -```toml -[config] -log_level = "DEBUG" -``` - -Check CloudWatch logs for warning/error messages related to AWS Secrets Manager access. - -## Security Best Practices - -1. Use least-privilege IAM policies -2. Rotate secrets regularly -3. Use separate secrets for different environments -4. Monitor CloudTrail for secret access -5. Enable secret versioning for rollback capability From 32b1fb91c3d566edfadb7d801be6f06e06a493d1 Mon Sep 17 00:00:00 2001 From: tomoya-kawaguchi Date: Thu, 29 May 2025 13:17:31 +0900 Subject: [PATCH 45/68] chore: add comments --- pr_agent/config_loader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pr_agent/config_loader.py b/pr_agent/config_loader.py index a4fbb130..f525d893 100644 --- a/pr_agent/config_loader.py +++ b/pr_agent/config_loader.py @@ -88,6 +88,7 @@ def apply_secrets_manager_config(): Retrieve configuration from AWS Secrets Manager and override existing settings """ try: + # Dynamic imports to avoid circular dependency (secret_providers imports config_loader) from pr_agent.secret_providers import get_secret_provider from pr_agent.log import get_logger @@ -118,6 +119,7 @@ def apply_secrets_to_config(secrets: dict): Apply secret dictionary to configuration """ try: + # Dynamic import to avoid potential circular dependency from pr_agent.log import get_logger except: def get_logger(): From d1e8d267f679bee50f9a15770064cf5a95791cec Mon Sep 17 00:00:00 2001 From: tomoya-kawaguchi Date: Thu, 29 May 2025 13:44:33 +0900 Subject: [PATCH 46/68] docs: detailed description --- docs/docs/installation/github.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/docs/installation/github.md b/docs/docs/installation/github.md index d95565f2..69b34b8a 100644 --- a/docs/docs/installation/github.md +++ b/docs/docs/installation/github.md @@ -207,16 +207,23 @@ For example: `GITHUB.WEBHOOK_SECRET` --> `GITHUB__WEBHOOK_SECRET` For production Lambda deployments, use AWS Secrets Manager instead of environment variables: -1. Create a secret in AWS Secrets Manager with your configuration -2. Add IAM permissions for `secretsmanager:GetSecretValue` -3. Set the secret ARN in your Lambda environment: +1. Create a secret in AWS Secrets Manager with JSON format like this: -```bash -AWS_SECRETS_MANAGER__SECRET_ARN=arn:aws:secretsmanager:region:account:secret:name -CONFIG__SECRET_PROVIDER=aws_secrets_manager +```json +{ + "openai.key": "sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "github.webhook_secret": "your-webhook-secret-from-step-2", + "github.private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA...\n-----END RSA PRIVATE KEY-----" +} ``` -For detailed setup instructions, see [AWS Secrets Manager Integration](../usage-guide/aws_secrets_manager.md). +2. Add IAM permission `secretsmanager:GetSecretValue` to your Lambda execution role +3. Set these environment variables in your Lambda: + +```bash +AWS_SECRETS_MANAGER__SECRET_ARN=arn:aws:secretsmanager:us-east-1:123456789012:secret:pr-agent-secrets-AbCdEf +CONFIG__SECRET_PROVIDER=aws_secrets_manager +``` --- From 984d62730088ed52228ed31681f98249a7e9aac4 Mon Sep 17 00:00:00 2001 From: tomoya-kawaguchi Date: Thu, 29 May 2025 14:03:38 +0900 Subject: [PATCH 47/68] fix: rm invalid error handling --- .../aws_secrets_manager_provider.py | 14 -------------- .../unittest/test_aws_secrets_manager_provider.py | 13 ------------- 2 files changed, 27 deletions(-) diff --git a/pr_agent/secret_providers/aws_secrets_manager_provider.py b/pr_agent/secret_providers/aws_secrets_manager_provider.py index e14d3d87..dbb3f044 100644 --- a/pr_agent/secret_providers/aws_secrets_manager_provider.py +++ b/pr_agent/secret_providers/aws_secrets_manager_provider.py @@ -51,20 +51,6 @@ class AWSSecretsManagerProvider(SecretProvider): SecretId=secret_name, SecretString=secret_value ) - except ClientError as e: - if e.response['Error']['Code'] == 'ResourceNotFoundException': - # Create new secret if it doesn't exist - try: - self.client.create_secret( - Name=secret_name, - SecretString=secret_value - ) - except Exception as create_error: - get_logger().error(f"Failed to store secret {secret_name} in AWS Secrets Manager: {create_error}") - raise create_error - else: - get_logger().error(f"Failed to store secret {secret_name} in AWS Secrets Manager: {e}") - raise e except Exception as e: get_logger().error(f"Failed to store secret {secret_name} in AWS Secrets Manager: {e}") raise e diff --git a/tests/unittest/test_aws_secrets_manager_provider.py b/tests/unittest/test_aws_secrets_manager_provider.py index a966555c..f84743ca 100644 --- a/tests/unittest/test_aws_secrets_manager_provider.py +++ b/tests/unittest/test_aws_secrets_manager_provider.py @@ -72,19 +72,6 @@ class TestAWSSecretsManagerProvider: SecretString='test-value' ) - def test_store_secret_create_new(self): - provider, mock_client = self._provider() - mock_client.update_secret.side_effect = ClientError( - {'Error': {'Code': 'ResourceNotFoundException'}}, 'UpdateSecret' - ) - mock_client.create_secret.return_value = {} - - provider.store_secret('new-secret', 'test-value') - mock_client.create_secret.assert_called_once_with( - Name='new-secret', - SecretString='test-value' - ) - def test_init_failure_invalid_config(self): with patch('pr_agent.secret_providers.aws_secrets_manager_provider.get_settings') as mock_get_settings: settings = MagicMock() From cc1b1871d0b295daa97ae1c76358b524dd135131 Mon Sep 17 00:00:00 2001 From: tomoya-kawaguchi Date: Thu, 29 May 2025 14:06:21 +0900 Subject: [PATCH 48/68] fix: raise Exception when arn does not exist --- pr_agent/secret_providers/aws_secrets_manager_provider.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pr_agent/secret_providers/aws_secrets_manager_provider.py b/pr_agent/secret_providers/aws_secrets_manager_provider.py index dbb3f044..bfd1468d 100644 --- a/pr_agent/secret_providers/aws_secrets_manager_provider.py +++ b/pr_agent/secret_providers/aws_secrets_manager_provider.py @@ -18,7 +18,8 @@ class AWSSecretsManagerProvider(SecretProvider): self.client = boto3.client('secretsmanager') self.secret_arn = get_settings().aws_secrets_manager.secret_arn - + if not self.secret_arn: + raise ValueError("AWS Secrets Manager ARN is not configured") except Exception as e: get_logger().error(f"Failed to initialize AWS Secrets Manager Provider: {e}") raise e From 2e75fa31bd550469444212202316842df8ec6c01 Mon Sep 17 00:00:00 2001 From: "Hussam.lawen" Date: Thu, 29 May 2025 08:29:41 +0300 Subject: [PATCH 49/68] docs: add hyperlinks to ticket system integrations in fetching_ticket_context.md --- docs/docs/core-abilities/fetching_ticket_context.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/core-abilities/fetching_ticket_context.md b/docs/docs/core-abilities/fetching_ticket_context.md index db6e72e1..05e19c00 100644 --- a/docs/docs/core-abilities/fetching_ticket_context.md +++ b/docs/docs/core-abilities/fetching_ticket_context.md @@ -9,9 +9,9 @@ This integration enriches the review process by automatically surfacing relevant **Ticket systems supported**: -- GitHub -- Jira (💎) -- Linear (💎) +- [GitHub](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/#github-issues-integration) +- [Jira (💎)](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/#jira-integration) +- [Linear (💎)](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/#linear-integration) **Ticket data fetched:** From c57f8aff9bfb83f0236f98227b1d1fecdb10da7d Mon Sep 17 00:00:00 2001 From: ofir-frd Date: Fri, 30 May 2025 13:35:58 +0300 Subject: [PATCH 50/68] docs: add Help Docs to the Tools list and reorganize tools documentation in alphabetical order --- docs/docs/tools/index.md | 17 +++++++++-------- docs/mkdocs.yml | 36 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/docs/docs/tools/index.md b/docs/docs/tools/index.md index e422b856..36e3caef 100644 --- a/docs/docs/tools/index.md +++ b/docs/docs/tools/index.md @@ -3,22 +3,23 @@ Here is a list of Qodo Merge tools, each with a dedicated page that explains how to use it: | Tool | Description | -| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +|------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| | **[PR Description (`/describe`](./describe.md))** | Automatically generating PR description - title, type, summary, code walkthrough and labels | | **[PR Review (`/review`](./review.md))** | Adjustable feedback about the PR, possible issues, security concerns, review effort and more | | **[Code Suggestions (`/improve`](./improve.md))** | Code suggestions for improving the PR | | **[Question Answering (`/ask ...`](./ask.md))** | Answering free-text questions about the PR, or on specific code lines | -| **[Update Changelog (`/update_changelog`](./update_changelog.md))** | Automatically updating the CHANGELOG.md file with the PR changes | | **[Help (`/help`](./help.md))** | Provides a list of all the available tools. Also enables to trigger them interactively (💎) | +| **[Help Docs (`/help_docs`](./help_docs.md))** | Answer a free-text question based on a git documentation folder. | +| **[Update Changelog (`/update_changelog`](./update_changelog.md))** | Automatically updating the CHANGELOG.md file with the PR changes | | **💎 [Add Documentation (`/add_docs`](./documentation.md))** | Generates documentation to methods/functions/classes that changed in the PR | -| **💎 [Generate Custom Labels (`/generate_labels`](./custom_labels.md))** | Generates custom labels for the PR, based on specific guidelines defined by the user | | **💎 [Analyze (`/analyze`](./analyze.md))** | Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component | -| **💎 [Test (`/test`](./test.md))** | generate tests for a selected component, based on the PR code changes | -| **💎 [Custom Prompt (`/custom_prompt`](./custom_prompt.md))** | Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user | -| **💎 [Generate Tests (`/test component_name`](./test.md))** | Automatically generates unit tests for a selected component, based on the PR code changes | -| **💎 [Improve Component (`/improve_component component_name`](./improve_component.md))** | Generates code suggestions for a specific code component that changed in the PR | | **💎 [CI Feedback (`/checks ci_job`](./ci_feedback.md))** | Automatically generates feedback and analysis for a failed CI job | +| **💎 [Custom Prompt (`/custom_prompt`](./custom_prompt.md))** | Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user | +| **💎 [Generate Custom Labels (`/generate_labels`](./custom_labels.md))** | Generates custom labels for the PR, based on specific guidelines defined by the user | +| **💎 [Generate Tests (`/test component_name`](./test.md))** | Automatically generates unit tests for a selected component, based on the PR code changes | | **💎 [Implement (`/implement`](./implement.md))** | Generates implementation code from review suggestions | +| **💎 [Improve Component (`/improve_component component_name`](./improve_component.md))** | Generates code suggestions for a specific code component that changed in the PR | | **💎 [Scan Repo Discussions (`/scan_repo_discussions`](./scan_repo_discussions.md))** | Generates `best_practices.md` file based on previous discussions in the repository | +| **💎 [Test (`/test`](./test.md))** | generate tests for a selected component, based on the PR code changes | -Note that the tools marked with 💎 are available only for Qodo Merge users. +Note that the tools marked with 💎 are available only for Qodo Merge users. \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 740488ad..c45e4616 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -23,24 +23,24 @@ nav: - Frequently Asked Questions: 'faq/index.md' - 💎 Qodo Merge Models: 'usage-guide/qodo_merge_models.md' - Tools: - - 'tools/index.md' - - Describe: 'tools/describe.md' - - Review: 'tools/review.md' - - Improve: 'tools/improve.md' - - Ask: 'tools/ask.md' - - Update Changelog: 'tools/update_changelog.md' - - Help Docs: 'tools/help_docs.md' - - Help: 'tools/help.md' - - 💎 Analyze: 'tools/analyze.md' - - 💎 Test: 'tools/test.md' - - 💎 Improve Component: 'tools/improve_component.md' - - 💎 Documentation: 'tools/documentation.md' - - 💎 Custom Labels: 'tools/custom_labels.md' - - 💎 Custom Prompt: 'tools/custom_prompt.md' - - 💎 CI Feedback: 'tools/ci_feedback.md' - - 💎 Similar Code: 'tools/similar_code.md' - - 💎 Implement: 'tools/implement.md' - - 💎 Scan Repo Discussions: 'tools/scan_repo_discussions.md' + - 'tools/index.md' + - Describe: 'tools/describe.md' + - Review: 'tools/review.md' + - Improve: 'tools/improve.md' + - Ask: 'tools/ask.md' + - Help: 'tools/help.md' + - Help Docs: 'tools/help_docs.md' + - Update Changelog: 'tools/update_changelog.md' + - 💎 Analyze: 'tools/analyze.md' + - 💎 CI Feedback: 'tools/ci_feedback.md' + - 💎 Custom Labels: 'tools/custom_labels.md' + - 💎 Custom Prompt: 'tools/custom_prompt.md' + - 💎 Documentation: 'tools/documentation.md' + - 💎 Implement: 'tools/implement.md' + - 💎 Improve Component: 'tools/improve_component.md' + - 💎 Scan Repo Discussions: 'tools/scan_repo_discussions.md' + - 💎 Similar Code: 'tools/similar_code.md' + - 💎 Test: 'tools/test.md' - Core Abilities: - 'core-abilities/index.md' - Auto best practices: 'core-abilities/auto_best_practices.md' From e7f85cf858324920c905f6c91b457017b63fa047 Mon Sep 17 00:00:00 2001 From: ofir-frd Date: Fri, 30 May 2025 13:58:48 +0300 Subject: [PATCH 51/68] docs: reorganize tools documentation in alphabetical order and fix inconsistencies --- docs/docs/tools/index.md | 3 +-- docs/mkdocs.yml | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/docs/tools/index.md b/docs/docs/tools/index.md index 36e3caef..d44a988b 100644 --- a/docs/docs/tools/index.md +++ b/docs/docs/tools/index.md @@ -16,10 +16,9 @@ Here is a list of Qodo Merge tools, each with a dedicated page that explains how | **💎 [CI Feedback (`/checks ci_job`](./ci_feedback.md))** | Automatically generates feedback and analysis for a failed CI job | | **💎 [Custom Prompt (`/custom_prompt`](./custom_prompt.md))** | Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user | | **💎 [Generate Custom Labels (`/generate_labels`](./custom_labels.md))** | Generates custom labels for the PR, based on specific guidelines defined by the user | -| **💎 [Generate Tests (`/test component_name`](./test.md))** | Automatically generates unit tests for a selected component, based on the PR code changes | +| **💎 [Generate Tests (`/test`](./test.md))** | Automatically generates unit tests for a selected component, based on the PR code changes | | **💎 [Implement (`/implement`](./implement.md))** | Generates implementation code from review suggestions | | **💎 [Improve Component (`/improve_component component_name`](./improve_component.md))** | Generates code suggestions for a specific code component that changed in the PR | | **💎 [Scan Repo Discussions (`/scan_repo_discussions`](./scan_repo_discussions.md))** | Generates `best_practices.md` file based on previous discussions in the repository | -| **💎 [Test (`/test`](./test.md))** | generate tests for a selected component, based on the PR code changes | Note that the tools marked with 💎 are available only for Qodo Merge users. \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index c45e4616..d8e09a7f 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -31,16 +31,15 @@ nav: - Help: 'tools/help.md' - Help Docs: 'tools/help_docs.md' - Update Changelog: 'tools/update_changelog.md' + - 💎 Add Documentation: 'tools/documentation.md' - 💎 Analyze: 'tools/analyze.md' - 💎 CI Feedback: 'tools/ci_feedback.md' - - 💎 Custom Labels: 'tools/custom_labels.md' - 💎 Custom Prompt: 'tools/custom_prompt.md' - - 💎 Documentation: 'tools/documentation.md' + - 💎 Generate Labels: 'tools/custom_labels.md' + - 💎 Generate Tests: 'tools/test.md' - 💎 Implement: 'tools/implement.md' - - 💎 Improve Component: 'tools/improve_component.md' + - 💎 Improve Components: 'tools/improve_component.md' - 💎 Scan Repo Discussions: 'tools/scan_repo_discussions.md' - - 💎 Similar Code: 'tools/similar_code.md' - - 💎 Test: 'tools/test.md' - Core Abilities: - 'core-abilities/index.md' - Auto best practices: 'core-abilities/auto_best_practices.md' From d857132d1d9073e6a9aececd9841292ea4e911fd Mon Sep 17 00:00:00 2001 From: ofir-frd Date: Fri, 30 May 2025 14:04:45 +0300 Subject: [PATCH 52/68] docs: add Similar Code tool to documentation index and navigation --- docs/docs/tools/index.md | 1 + docs/mkdocs.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/docs/tools/index.md b/docs/docs/tools/index.md index d44a988b..e50e0785 100644 --- a/docs/docs/tools/index.md +++ b/docs/docs/tools/index.md @@ -20,5 +20,6 @@ Here is a list of Qodo Merge tools, each with a dedicated page that explains how | **💎 [Implement (`/implement`](./implement.md))** | Generates implementation code from review suggestions | | **💎 [Improve Component (`/improve_component component_name`](./improve_component.md))** | Generates code suggestions for a specific code component that changed in the PR | | **💎 [Scan Repo Discussions (`/scan_repo_discussions`](./scan_repo_discussions.md))** | Generates `best_practices.md` file based on previous discussions in the repository | +| **💎 [Similar Code (`/similar_code`](./similar_code.md))** | Retrieves the most similar code components from inside the organization's codebase, or from open-source code. | Note that the tools marked with 💎 are available only for Qodo Merge users. \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index d8e09a7f..6210ed1c 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -40,6 +40,7 @@ nav: - 💎 Implement: 'tools/implement.md' - 💎 Improve Components: 'tools/improve_component.md' - 💎 Scan Repo Discussions: 'tools/scan_repo_discussions.md' + - 💎 Similar Code: 'tools/similar_code.md' - Core Abilities: - 'core-abilities/index.md' - Auto best practices: 'core-abilities/auto_best_practices.md' From 403efcae226a6c3c4914a4cc466e5d8b7ca51b90 Mon Sep 17 00:00:00 2001 From: ofir-frd Date: Fri, 30 May 2025 14:16:09 +0300 Subject: [PATCH 53/68] docs: reorganize tools list in alphabetical order in README.md --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8fcfb0d0..e16e771f 100644 --- a/README.md +++ b/README.md @@ -72,27 +72,27 @@ Supported commands per platform: | | | GitHub | GitLab | Bitbucket | Azure DevOps | Gitea | | ----- |---------------------------------------------------------------------------------------------------------|:------:|:------:|:---------:|:------------:|:-----:| -| TOOLS | [Review](https://qodo-merge-docs.qodo.ai/tools/review/) | ✅ | ✅ | ✅ | ✅ | ✅ | -| | [Describe](https://qodo-merge-docs.qodo.ai/tools/describe/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| TOOLS | [Describe](https://qodo-merge-docs.qodo.ai/tools/describe/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [Review](https://qodo-merge-docs.qodo.ai/tools/review/) | ✅ | ✅ | ✅ | ✅ | ✅ | | | [Improve](https://qodo-merge-docs.qodo.ai/tools/improve/) | ✅ | ✅ | ✅ | ✅ | ✅ | | | [Ask](https://qodo-merge-docs.qodo.ai/tools/ask/) | ✅ | ✅ | ✅ | ✅ | | | | ⮑ [Ask on code lines](https://qodo-merge-docs.qodo.ai/tools/ask/#ask-lines) | ✅ | ✅ | | | | -| | [Update CHANGELOG](https://qodo-merge-docs.qodo.ai/tools/update_changelog/) | ✅ | ✅ | ✅ | ✅ | | | | [Help Docs](https://qodo-merge-docs.qodo.ai/tools/help_docs/?h=auto#auto-approval) | ✅ | ✅ | ✅ | | | +| | [Update CHANGELOG](https://qodo-merge-docs.qodo.ai/tools/update_changelog/) | ✅ | ✅ | ✅ | ✅ | | +| | [PR Documentation](https://qodo-merge-docs.qodo.ai/tools/documentation/) 💎 | ✅ | ✅ | | | | +| | [Analyze](https://qodo-merge-docs.qodo.ai/tools/analyze/) 💎 | ✅ | ✅ | | | | +| | [Auto-Approve](https://qodo-merge-docs.qodo.ai/tools/improve/?h=auto#auto-approval) 💎 | ✅ | ✅ | ✅ | | | +| | [CI Feedback](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) 💎 | ✅ | | | | | +| | [Custom Prompt](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | | | +| | [Custom Labels](https://qodo-merge-docs.qodo.ai/tools/custom_labels/) 💎 | ✅ | ✅ | | | | +| | [Test](https://qodo-merge-docs.qodo.ai/tools/test/) 💎 | ✅ | ✅ | | | | +| | [Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | | | +| | [Scan Repo Discussions](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/) 💎 | ✅ | | | | | +| | [Similar Code](https://qodo-merge-docs.qodo.ai/tools/similar_code/) 💎 | ✅ | | | | | | | [Ticket Context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) 💎 | ✅ | ✅ | ✅ | | | | | [Utilizing Best Practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) 💎 | ✅ | ✅ | ✅ | | | | | [PR Chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat) 💎 | ✅ | | | | | | | [Suggestion Tracking](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking) 💎 | ✅ | ✅ | | | | -| | [CI Feedback](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) 💎 | ✅ | | | | | -| | [PR Documentation](https://qodo-merge-docs.qodo.ai/tools/documentation/) 💎 | ✅ | ✅ | | | | -| | [Custom Labels](https://qodo-merge-docs.qodo.ai/tools/custom_labels/) 💎 | ✅ | ✅ | | | | -| | [Analyze](https://qodo-merge-docs.qodo.ai/tools/analyze/) 💎 | ✅ | ✅ | | | | -| | [Similar Code](https://qodo-merge-docs.qodo.ai/tools/similar_code/) 💎 | ✅ | | | | | -| | [Custom Prompt](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | | | -| | [Test](https://qodo-merge-docs.qodo.ai/tools/test/) 💎 | ✅ | ✅ | | | | -| | [Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | | | -| | [Scan Repo Discussions](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/) 💎 | ✅ | | | | | -| | [Auto-Approve](https://qodo-merge-docs.qodo.ai/tools/improve/?h=auto#auto-approval) 💎 | ✅ | ✅ | ✅ | | | | | | | | | | | | USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ | ✅ | | | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ | ✅ | From 8baf6dba935a797d4a39124796d728a0f2d05b67 Mon Sep 17 00:00:00 2001 From: ofir-frd Date: Fri, 30 May 2025 14:27:04 +0300 Subject: [PATCH 54/68] docs: reorganize core abilities in alphabetical order in README.md --- README.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e16e771f..432ff4b7 100644 --- a/README.md +++ b/README.md @@ -99,18 +99,23 @@ Supported commands per platform: | | [Tagging bot](https://github.com/Codium-ai/pr-agent#try-it-now) | ✅ | | | | | | | [Actions](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) | ✅ | ✅ | ✅ | ✅ | | | | | | | | | | -| CORE | [PR compression](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ | | -| | Adaptive and token-aware file patch fitting | ✅ | ✅ | ✅ | ✅ | | -| | [Multiple models support](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/) | ✅ | ✅ | ✅ | ✅ | | -| | [Local and global metadata](https://qodo-merge-docs.qodo.ai/core-abilities/metadata/) | ✅ | ✅ | ✅ | ✅ | | +| CORE | [Adaptive and token-aware file patch fitting](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ | | +| | [Auto Best Practices 💎](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/) | ✅ | | | | | +| | [Chat on code suggestions](https://qodo-merge-docs.qodo.ai/core-abilities/chat_on_code_suggestions/) | ✅ | ✅ | | | | +| | [Code Validation 💎](https://qodo-merge-docs.qodo.ai/core-abilities/code_validation/) | ✅ | ✅ | ✅ | ✅ | | | | [Dynamic context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/) | ✅ | ✅ | ✅ | ✅ | | +| | [Fetching ticket context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) | ✅ | ✅ | ✅ | | | +| | [Global and wiki configurations](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) 💎 | ✅ | ✅ | ✅ | | | +| | [Impact Evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) 💎 | ✅ | ✅ | | | | +| | [Incremental Update](https://qodo-merge-docs.qodo.ai/core-abilities/incremental_update/) | ✅ | | | | | +| | [Interactivity](https://qodo-merge-docs.qodo.ai/core-abilities/interactivity/) | ✅ | ✅ | | | | +| | [Local and global metadata](https://qodo-merge-docs.qodo.ai/core-abilities/metadata/) | ✅ | ✅ | ✅ | ✅ | | +| | [Multiple models support](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/) | ✅ | ✅ | ✅ | ✅ | | +| | [PR compression](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ | | +| | [PR interactive actions](https://www.qodo.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | ✅ | | | | +| | [RAG context enrichment](https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/) | ✅ | | ✅ | | | | | [Self reflection](https://qodo-merge-docs.qodo.ai/core-abilities/self_reflection/) | ✅ | ✅ | ✅ | ✅ | | | | [Static code analysis](https://qodo-merge-docs.qodo.ai/core-abilities/static_code_analysis/) 💎 | ✅ | ✅ | | | | -| | [Global and wiki configurations](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) 💎 | ✅ | ✅ | ✅ | | | -| | [PR interactive actions](https://www.qodo.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | ✅ | | | | -| | [Impact Evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) 💎 | ✅ | ✅ | | | | -| | [Code Validation 💎](https://qodo-merge-docs.qodo.ai/core-abilities/code_validation/) | ✅ | ✅ | ✅ | ✅ | | -| | [Auto Best Practices 💎](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/) | ✅ | | | | | - 💎 means this feature is available only in [Qodo Merge](https://www.qodo.ai/pricing/) [//]: # (- Support for additional git providers is described in [here](./docs/Full_environments.md)) From 9c1f5ad49761048ad53e6dc4a210219b2e5cecfc Mon Sep 17 00:00:00 2001 From: ofir-frd Date: Fri, 30 May 2025 14:31:46 +0300 Subject: [PATCH 55/68] docs: rename "Test" to "Generate Tests" and improve tool name consistency in README --- README.md | 92 +++++++++++++++++++++++++++---------------------------- 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 432ff4b7..e778719b 100644 --- a/README.md +++ b/README.md @@ -70,52 +70,52 @@ Read more about it [here](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discus Supported commands per platform: -| | | GitHub | GitLab | Bitbucket | Azure DevOps | Gitea | -| ----- |---------------------------------------------------------------------------------------------------------|:------:|:------:|:---------:|:------------:|:-----:| -| TOOLS | [Describe](https://qodo-merge-docs.qodo.ai/tools/describe/) | ✅ | ✅ | ✅ | ✅ | ✅ | -| | [Review](https://qodo-merge-docs.qodo.ai/tools/review/) | ✅ | ✅ | ✅ | ✅ | ✅ | -| | [Improve](https://qodo-merge-docs.qodo.ai/tools/improve/) | ✅ | ✅ | ✅ | ✅ | ✅ | -| | [Ask](https://qodo-merge-docs.qodo.ai/tools/ask/) | ✅ | ✅ | ✅ | ✅ | | -| | ⮑ [Ask on code lines](https://qodo-merge-docs.qodo.ai/tools/ask/#ask-lines) | ✅ | ✅ | | | | -| | [Help Docs](https://qodo-merge-docs.qodo.ai/tools/help_docs/?h=auto#auto-approval) | ✅ | ✅ | ✅ | | | -| | [Update CHANGELOG](https://qodo-merge-docs.qodo.ai/tools/update_changelog/) | ✅ | ✅ | ✅ | ✅ | | -| | [PR Documentation](https://qodo-merge-docs.qodo.ai/tools/documentation/) 💎 | ✅ | ✅ | | | | -| | [Analyze](https://qodo-merge-docs.qodo.ai/tools/analyze/) 💎 | ✅ | ✅ | | | | -| | [Auto-Approve](https://qodo-merge-docs.qodo.ai/tools/improve/?h=auto#auto-approval) 💎 | ✅ | ✅ | ✅ | | | -| | [CI Feedback](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) 💎 | ✅ | | | | | -| | [Custom Prompt](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | | | -| | [Custom Labels](https://qodo-merge-docs.qodo.ai/tools/custom_labels/) 💎 | ✅ | ✅ | | | | -| | [Test](https://qodo-merge-docs.qodo.ai/tools/test/) 💎 | ✅ | ✅ | | | | -| | [Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | | | -| | [Scan Repo Discussions](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/) 💎 | ✅ | | | | | -| | [Similar Code](https://qodo-merge-docs.qodo.ai/tools/similar_code/) 💎 | ✅ | | | | | -| | [Ticket Context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) 💎 | ✅ | ✅ | ✅ | | | -| | [Utilizing Best Practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) 💎 | ✅ | ✅ | ✅ | | | -| | [PR Chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat) 💎 | ✅ | | | | | -| | [Suggestion Tracking](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking) 💎 | ✅ | ✅ | | | | -| | | | | | | | -| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ | ✅ | -| | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ | ✅ | -| | [Tagging bot](https://github.com/Codium-ai/pr-agent#try-it-now) | ✅ | | | | | -| | [Actions](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) | ✅ | ✅ | ✅ | ✅ | | -| | | | | | | | -| CORE | [Adaptive and token-aware file patch fitting](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ | | -| | [Auto Best Practices 💎](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/) | ✅ | | | | | +| | | GitHub | GitLab | Bitbucket | Azure DevOps | Gitea | +| ----- |---------------------------------------------------------------------------------------------------------------------|:------:|:------:|:---------:|:------------:|:-----:| +| TOOLS | [Describe](https://qodo-merge-docs.qodo.ai/tools/describe/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [Review](https://qodo-merge-docs.qodo.ai/tools/review/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [Improve](https://qodo-merge-docs.qodo.ai/tools/improve/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [Ask](https://qodo-merge-docs.qodo.ai/tools/ask/) | ✅ | ✅ | ✅ | ✅ | | +| | ⮑ [Ask on code lines](https://qodo-merge-docs.qodo.ai/tools/ask/#ask-lines) | ✅ | ✅ | | | | +| | [Help Docs](https://qodo-merge-docs.qodo.ai/tools/help_docs/?h=auto#auto-approval) | ✅ | ✅ | ✅ | | | +| | [Update CHANGELOG](https://qodo-merge-docs.qodo.ai/tools/update_changelog/) | ✅ | ✅ | ✅ | ✅ | | +| | [PR Documentation](https://qodo-merge-docs.qodo.ai/tools/documentation/) 💎 | ✅ | ✅ | | | | +| | [Analyze](https://qodo-merge-docs.qodo.ai/tools/analyze/) 💎 | ✅ | ✅ | | | | +| | [Auto-Approve](https://qodo-merge-docs.qodo.ai/tools/improve/?h=auto#auto-approval) 💎 | ✅ | ✅ | ✅ | | | +| | [CI Feedback](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) 💎 | ✅ | | | | | +| | [Custom Prompt](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | | | +| | [Generate Custom Labels](https://qodo-merge-docs.qodo.ai/tools/custom_labels/) 💎 | ✅ | ✅ | | | | +| | [Generate Tests](https://qodo-merge-docs.qodo.ai/tools/test/) 💎 | ✅ | ✅ | | | | +| | [Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | | | +| | [Scan Repo Discussions](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/) 💎 | ✅ | | | | | +| | [Similar Code](https://qodo-merge-docs.qodo.ai/tools/similar_code/) 💎 | ✅ | | | | | +| | [Ticket Context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) 💎 | ✅ | ✅ | ✅ | | | +| | [Utilizing Best Practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) 💎 | ✅ | ✅ | ✅ | | | +| | [PR Chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat) 💎 | ✅ | | | | | +| | [Suggestion Tracking](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking) 💎 | ✅ | ✅ | | | | +| | | | | | | | +| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [Tagging bot](https://github.com/Codium-ai/pr-agent#try-it-now) | ✅ | | | | | +| | [Actions](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) | ✅ | ✅ | ✅ | ✅ | | +| | | | | | | | +| CORE | [Adaptive and token-aware file patch fitting](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ | | +| | [Auto Best Practices 💎](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/) | ✅ | | | | | | | [Chat on code suggestions](https://qodo-merge-docs.qodo.ai/core-abilities/chat_on_code_suggestions/) | ✅ | ✅ | | | | -| | [Code Validation 💎](https://qodo-merge-docs.qodo.ai/core-abilities/code_validation/) | ✅ | ✅ | ✅ | ✅ | | -| | [Dynamic context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/) | ✅ | ✅ | ✅ | ✅ | | -| | [Fetching ticket context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) | ✅ | ✅ | ✅ | | | -| | [Global and wiki configurations](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) 💎 | ✅ | ✅ | ✅ | | | -| | [Impact Evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) 💎 | ✅ | ✅ | | | | -| | [Incremental Update](https://qodo-merge-docs.qodo.ai/core-abilities/incremental_update/) | ✅ | | | | | -| | [Interactivity](https://qodo-merge-docs.qodo.ai/core-abilities/interactivity/) | ✅ | ✅ | | | | -| | [Local and global metadata](https://qodo-merge-docs.qodo.ai/core-abilities/metadata/) | ✅ | ✅ | ✅ | ✅ | | -| | [Multiple models support](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/) | ✅ | ✅ | ✅ | ✅ | | -| | [PR compression](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ | | -| | [PR interactive actions](https://www.qodo.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | ✅ | | | | -| | [RAG context enrichment](https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/) | ✅ | | ✅ | | | -| | [Self reflection](https://qodo-merge-docs.qodo.ai/core-abilities/self_reflection/) | ✅ | ✅ | ✅ | ✅ | | -| | [Static code analysis](https://qodo-merge-docs.qodo.ai/core-abilities/static_code_analysis/) 💎 | ✅ | ✅ | | | | +| | [Code Validation 💎](https://qodo-merge-docs.qodo.ai/core-abilities/code_validation/) | ✅ | ✅ | ✅ | ✅ | | +| | [Dynamic context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/) | ✅ | ✅ | ✅ | ✅ | | +| | [Fetching ticket context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) | ✅ | ✅ | ✅ | | | +| | [Global and wiki configurations](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) 💎 | ✅ | ✅ | ✅ | | | +| | [Impact Evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) 💎 | ✅ | ✅ | | | | +| | [Incremental Update](https://qodo-merge-docs.qodo.ai/core-abilities/incremental_update/) | ✅ | | | | | +| | [Interactivity](https://qodo-merge-docs.qodo.ai/core-abilities/interactivity/) | ✅ | ✅ | | | | +| | [Local and global metadata](https://qodo-merge-docs.qodo.ai/core-abilities/metadata/) | ✅ | ✅ | ✅ | ✅ | | +| | [Multiple models support](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/) | ✅ | ✅ | ✅ | ✅ | | +| | [PR compression](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ | | +| | [PR interactive actions](https://www.qodo.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | ✅ | | | | +| | [RAG context enrichment](https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/) | ✅ | | ✅ | | | +| | [Self reflection](https://qodo-merge-docs.qodo.ai/core-abilities/self_reflection/) | ✅ | ✅ | ✅ | ✅ | | +| | [Static code analysis](https://qodo-merge-docs.qodo.ai/core-abilities/static_code_analysis/) 💎 | ✅ | ✅ | | | | - 💎 means this feature is available only in [Qodo Merge](https://www.qodo.ai/pricing/) [//]: # (- Support for additional git providers is described in [here](./docs/Full_environments.md)) @@ -139,8 +139,6 @@ ___ \ ‣ **Analyze 💎 ([`/analyze`](https://qodo-merge-docs.qodo.ai/tools/analyze/))**: Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component. \ -‣ **Test 💎 ([`/test`](https://qodo-merge-docs.qodo.ai/tools/test/))**: Generate tests for a selected component, based on the PR code changes. -\ ‣ **Custom Prompt 💎 ([`/custom_prompt`](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/))**: Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user. \ ‣ **Generate Tests 💎 ([`/test component_name`](https://qodo-merge-docs.qodo.ai/tools/test/))**: Generates unit tests for a selected component, based on the PR code changes. From ac8aa9c2efc34f5c40743a5816c15989ef9e0c29 Mon Sep 17 00:00:00 2001 From: ofir-frd Date: Fri, 30 May 2025 14:36:28 +0300 Subject: [PATCH 56/68] docs: reorganize tools list with consistent formatting and command names in README --- README.md | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e778719b..d1d8cb7d 100644 --- a/README.md +++ b/README.md @@ -121,33 +121,39 @@ Supported commands per platform: [//]: # (- Support for additional git providers is described in [here](./docs/Full_environments.md)) ___ -‣ **Auto Description ([`/describe`](https://qodo-merge-docs.qodo.ai/tools/describe/))**: Automatically generating PR description - title, type, summary, code walkthrough and labels. +‣ **PR Description ([`/describe`](https://qodo-merge-docs.qodo.ai/tools/describe/))**: Automatically generating PR description - title, type, summary, code walkthrough and labels. \ -‣ **Auto Review ([`/review`](https://qodo-merge-docs.qodo.ai/tools/review/))**: Adjustable feedback about the PR, possible issues, security concerns, review effort and more. +‣ **PR Review ([`/review`](https://qodo-merge-docs.qodo.ai/tools/review/))**: Adjustable feedback about the PR, possible issues, security concerns, review effort and more. \ ‣ **Code Suggestions ([`/improve`](https://qodo-merge-docs.qodo.ai/tools/improve/))**: Code suggestions for improving the PR. \ -‣ **Question Answering ([`/ask ...`](https://qodo-merge-docs.qodo.ai/tools/ask/))**: Answering free-text questions about the PR. +‣ **Question Answering ([`/ask ...`](https://qodo-merge-docs.qodo.ai/tools/ask/))**: Answering free-text questions about the PR, or on specific code lines. +\ +‣ **Help ([`/help`](https://qodo-merge-docs.qodo.ai/tools/help/))**: Provides a list of all the available tools. Also enables to trigger them interactively (💎). +\ +‣ **Help Docs ([`/help_docs`](https://qodo-merge-docs.qodo.ai/tools/help_docs/))**: Answer a free-text question based on a git documentation folder. \ ‣ **Update Changelog ([`/update_changelog`](https://qodo-merge-docs.qodo.ai/tools/update_changelog/))**: Automatically updating the CHANGELOG.md file with the PR changes. \ -‣ **Help Docs ([`/help_docs`](https://qodo-merge-docs.qodo.ai/tools/help_docs/))**: Answers a question on any repository by utilizing given documentation. +‣ **💎 Add Documentation ([`/add_docs`](https://qodo-merge-docs.qodo.ai/tools/documentation/))**: Generates documentation to methods/functions/classes that changed in the PR. \ -‣ **Add Documentation 💎 ([`/add_docs`](https://qodo-merge-docs.qodo.ai/tools/documentation/))**: Generates documentation to methods/functions/classes that changed in the PR. +‣ **💎 Analyze ([`/analyze`](https://qodo-merge-docs.qodo.ai/tools/analyze/))**: Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component. \ -‣ **Generate Custom Labels 💎 ([`/generate_labels`](https://qodo-merge-docs.qodo.ai/tools/custom_labels/))**: Generates custom labels for the PR, based on specific guidelines defined by the user. +‣ **💎 CI Feedback ([`/checks ci_job`](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/))**: Automatically generates feedback and analysis for a failed CI job. \ -‣ **Analyze 💎 ([`/analyze`](https://qodo-merge-docs.qodo.ai/tools/analyze/))**: Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component. +‣ **💎 Custom Prompt ([`/custom_prompt`](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/))**: Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user. \ -‣ **Custom Prompt 💎 ([`/custom_prompt`](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/))**: Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user. +‣ **💎 Generate Custom Labels ([`/generate_labels`](https://qodo-merge-docs.qodo.ai/tools/custom_labels/))**: Generates custom labels for the PR, based on specific guidelines defined by the user. \ -‣ **Generate Tests 💎 ([`/test component_name`](https://qodo-merge-docs.qodo.ai/tools/test/))**: Generates unit tests for a selected component, based on the PR code changes. +‣ **💎 Generate Tests ([`/test`](https://qodo-merge-docs.qodo.ai/tools/test/))**: Automatically generates unit tests for a selected component, based on the PR code changes. \ -‣ **CI Feedback 💎 ([`/checks ci_job`](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/))**: Automatically generates feedback and analysis for a failed CI job. +‣ **💎 Implement ([`/implement`](https://qodo-merge-docs.qodo.ai/tools/implement/))**: Generates implementation code from review suggestions. \ -‣ **Similar Code 💎 ([`/find_similar_component`](https://qodo-merge-docs.qodo.ai/tools/similar_code/))**: Retrieves the most similar code components from inside the organization's codebase, or from open-source code. +‣ **💎 Improve Component ([`/improve_component component_name`](https://qodo-merge-docs.qodo.ai/tools/improve_component/))**: Generates code suggestions for a specific code component that changed in the PR. \ -‣ **Implement 💎 ([`/implement`](https://qodo-merge-docs.qodo.ai/tools/implement/))**: Generates implementation code from review suggestions. +‣ **💎 Scan Repo Discussions ([`/scan_repo_discussions`](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/))**: Generates best_practices.md file based on previous discussions in the repository. +\ +‣ **💎 Similar Code ([`/similar_code`](https://qodo-merge-docs.qodo.ai/tools/similar_code/))**: Retrieves the most similar code components from inside the organization's codebase, or from open-source code. ___ ## Example results From cc06da3b7fcee26251102576ea0a69a2fe4a8cce Mon Sep 17 00:00:00 2001 From: ofir-frd Date: Fri, 30 May 2025 15:32:01 +0300 Subject: [PATCH 57/68] docs: update README with improved navigation, Discord link, and table formatting --- README.md | 164 +++++++++++++++++++++++------------------------------- 1 file changed, 69 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index d1d8cb7d..ed43f1b0 100644 --- a/README.md +++ b/README.md @@ -21,24 +21,33 @@ PR-Agent aims to help efficiently review and handle pull requests, by providing [![Static Badge](https://img.shields.io/badge/Chrome-Extension-violet)](https://chromewebstore.google.com/detail/qodo-merge-ai-powered-cod/ephlnjeghhogofkifjloamocljapahnl) [![Static Badge](https://img.shields.io/badge/Pro-App-blue)](https://github.com/apps/qodo-merge-pro/) [![Static Badge](https://img.shields.io/badge/OpenSource-App-red)](https://github.com/apps/qodo-merge-pro-for-open-source/) -[![Discord](https://badgen.net/badge/icon/discord?icon=discord&label&color=purple)](https://discord.com/invite/SgSxuQ65GF) +[![Discord](https://badgen.net/badge/icon/discord?icon=discord&label&color=purple)](https://discord.gg/kG35uSHDBc) GitHub -## Table of Contents +## Getting Started + +### Try it now +Test PR-Agent on any public GitHub repository by commenting `@CodiumAI-Agent /review` + +### GitHub Action +Add automated PR reviews to your repository with a simple workflow file using [GitHub Action setup guide](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) + +#### Other Platforms +- [GitLab webhook setup](https://qodo-merge-docs.qodo.ai/installation/gitlab/) +- [BitBucket app installation](https://qodo-merge-docs.qodo.ai/installation/bitbucket/) +- [Azure DevOps setup](https://qodo-merge-docs.qodo.ai/installation/azure/) + +### CLI Usage +Run PR-Agent locally on your repository via command line: [Local CLI setup guide](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) + +### [Qodo Merge 💎](https://github.com/qodo-ai/pr-agent?tab=readme-ov-file#qodo-merge-) +Zero-setup hosted solution with advanced features and priority support +- [Intro and Installation guide](https://qodo-merge-docs.qodo.ai/installation/qodo_merge/) +- [Plans & Pricing](https://www.qodo.ai/pricing/) -- [News and Updates](#news-and-updates) -- [Overview](#overview) -- [Example results](#example-results) -- [Try it now](#try-it-now) -- [Qodo Merge](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/) -- [How it works](#how-it-works) -- [Why use PR-Agent?](#why-use-pr-agent) -- [Data privacy](#data-privacy) -- [Contributing](#contributing) -- [Links](#links) ## News and Updates @@ -70,93 +79,58 @@ Read more about it [here](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discus Supported commands per platform: -| | | GitHub | GitLab | Bitbucket | Azure DevOps | Gitea | -| ----- |---------------------------------------------------------------------------------------------------------------------|:------:|:------:|:---------:|:------------:|:-----:| -| TOOLS | [Describe](https://qodo-merge-docs.qodo.ai/tools/describe/) | ✅ | ✅ | ✅ | ✅ | ✅ | -| | [Review](https://qodo-merge-docs.qodo.ai/tools/review/) | ✅ | ✅ | ✅ | ✅ | ✅ | -| | [Improve](https://qodo-merge-docs.qodo.ai/tools/improve/) | ✅ | ✅ | ✅ | ✅ | ✅ | -| | [Ask](https://qodo-merge-docs.qodo.ai/tools/ask/) | ✅ | ✅ | ✅ | ✅ | | -| | ⮑ [Ask on code lines](https://qodo-merge-docs.qodo.ai/tools/ask/#ask-lines) | ✅ | ✅ | | | | -| | [Help Docs](https://qodo-merge-docs.qodo.ai/tools/help_docs/?h=auto#auto-approval) | ✅ | ✅ | ✅ | | | -| | [Update CHANGELOG](https://qodo-merge-docs.qodo.ai/tools/update_changelog/) | ✅ | ✅ | ✅ | ✅ | | -| | [PR Documentation](https://qodo-merge-docs.qodo.ai/tools/documentation/) 💎 | ✅ | ✅ | | | | -| | [Analyze](https://qodo-merge-docs.qodo.ai/tools/analyze/) 💎 | ✅ | ✅ | | | | -| | [Auto-Approve](https://qodo-merge-docs.qodo.ai/tools/improve/?h=auto#auto-approval) 💎 | ✅ | ✅ | ✅ | | | -| | [CI Feedback](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) 💎 | ✅ | | | | | -| | [Custom Prompt](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | | | -| | [Generate Custom Labels](https://qodo-merge-docs.qodo.ai/tools/custom_labels/) 💎 | ✅ | ✅ | | | | -| | [Generate Tests](https://qodo-merge-docs.qodo.ai/tools/test/) 💎 | ✅ | ✅ | | | | -| | [Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | | | -| | [Scan Repo Discussions](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/) 💎 | ✅ | | | | | -| | [Similar Code](https://qodo-merge-docs.qodo.ai/tools/similar_code/) 💎 | ✅ | | | | | -| | [Ticket Context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) 💎 | ✅ | ✅ | ✅ | | | -| | [Utilizing Best Practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) 💎 | ✅ | ✅ | ✅ | | | -| | [PR Chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat) 💎 | ✅ | | | | | -| | [Suggestion Tracking](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking) 💎 | ✅ | ✅ | | | | -| | | | | | | | -| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ | ✅ | -| | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ | ✅ | -| | [Tagging bot](https://github.com/Codium-ai/pr-agent#try-it-now) | ✅ | | | | | -| | [Actions](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) | ✅ | ✅ | ✅ | ✅ | | -| | | | | | | | -| CORE | [Adaptive and token-aware file patch fitting](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ | | -| | [Auto Best Practices 💎](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/) | ✅ | | | | | -| | [Chat on code suggestions](https://qodo-merge-docs.qodo.ai/core-abilities/chat_on_code_suggestions/) | ✅ | ✅ | | | | -| | [Code Validation 💎](https://qodo-merge-docs.qodo.ai/core-abilities/code_validation/) | ✅ | ✅ | ✅ | ✅ | | -| | [Dynamic context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/) | ✅ | ✅ | ✅ | ✅ | | -| | [Fetching ticket context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) | ✅ | ✅ | ✅ | | | -| | [Global and wiki configurations](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) 💎 | ✅ | ✅ | ✅ | | | -| | [Impact Evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) 💎 | ✅ | ✅ | | | | -| | [Incremental Update](https://qodo-merge-docs.qodo.ai/core-abilities/incremental_update/) | ✅ | | | | | -| | [Interactivity](https://qodo-merge-docs.qodo.ai/core-abilities/interactivity/) | ✅ | ✅ | | | | -| | [Local and global metadata](https://qodo-merge-docs.qodo.ai/core-abilities/metadata/) | ✅ | ✅ | ✅ | ✅ | | -| | [Multiple models support](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/) | ✅ | ✅ | ✅ | ✅ | | -| | [PR compression](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ | | -| | [PR interactive actions](https://www.qodo.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | ✅ | | | | -| | [RAG context enrichment](https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/) | ✅ | | ✅ | | | -| | [Self reflection](https://qodo-merge-docs.qodo.ai/core-abilities/self_reflection/) | ✅ | ✅ | ✅ | ✅ | | -| | [Static code analysis](https://qodo-merge-docs.qodo.ai/core-abilities/static_code_analysis/) 💎 | ✅ | ✅ | | | | +| | | GitHub | GitLab | Bitbucket | Azure DevOps | Gitea | +|---------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|:------:|:------:|:---------:|:------------:|:-----:| +| [TOOLS](https://qodo-merge-docs.qodo.ai/tools/) | [Describe](https://qodo-merge-docs.qodo.ai/tools/describe/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [Review](https://qodo-merge-docs.qodo.ai/tools/review/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [Improve](https://qodo-merge-docs.qodo.ai/tools/improve/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [Ask](https://qodo-merge-docs.qodo.ai/tools/ask/) | ✅ | ✅ | ✅ | ✅ | | +| | ⮑ [Ask on code lines](https://qodo-merge-docs.qodo.ai/tools/ask/#ask-lines) | ✅ | ✅ | | | | +| | [Help Docs](https://qodo-merge-docs.qodo.ai/tools/help_docs/?h=auto#auto-approval) | ✅ | ✅ | ✅ | | | +| | [Update CHANGELOG](https://qodo-merge-docs.qodo.ai/tools/update_changelog/) | ✅ | ✅ | ✅ | ✅ | | +| | [PR Documentation](https://qodo-merge-docs.qodo.ai/tools/documentation/) 💎 | ✅ | ✅ | | | | +| | [Analyze](https://qodo-merge-docs.qodo.ai/tools/analyze/) 💎 | ✅ | ✅ | | | | +| | [Auto-Approve](https://qodo-merge-docs.qodo.ai/tools/improve/?h=auto#auto-approval) 💎 | ✅ | ✅ | ✅ | | | +| | [CI Feedback](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) 💎 | ✅ | | | | | +| | [Custom Prompt](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | | | +| | [Generate Custom Labels](https://qodo-merge-docs.qodo.ai/tools/custom_labels/) 💎 | ✅ | ✅ | | | | +| | [Generate Tests](https://qodo-merge-docs.qodo.ai/tools/test/) 💎 | ✅ | ✅ | | | | +| | [Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | | | +| | [Scan Repo Discussions](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/) 💎 | ✅ | | | | | +| | [Similar Code](https://qodo-merge-docs.qodo.ai/tools/similar_code/) 💎 | ✅ | | | | | +| | [Ticket Context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) 💎 | ✅ | ✅ | ✅ | | | +| | [Utilizing Best Practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) 💎 | ✅ | ✅ | ✅ | | | +| | [PR Chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat) 💎 | ✅ | | | | | +| | [Suggestion Tracking](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking) 💎 | ✅ | ✅ | | | | +| | | | | | | | +| [USAGE](https://qodo-merge-docs.qodo.ai/usage-guide/) | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ | ✅ | +| | [Tagging bot](https://github.com/Codium-ai/pr-agent#try-it-now) | ✅ | | | | | +| | [Actions](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) | ✅ | ✅ | ✅ | ✅ | | +| | | | | | | | +| [CORE](https://qodo-merge-docs.qodo.ai/core-abilities/) | [Adaptive and token-aware file patch fitting](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ | | +| | [Auto Best Practices 💎](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/) | ✅ | | | | | +| | [Chat on code suggestions](https://qodo-merge-docs.qodo.ai/core-abilities/chat_on_code_suggestions/) | ✅ | ✅ | | | | +| | [Code Validation 💎](https://qodo-merge-docs.qodo.ai/core-abilities/code_validation/) | ✅ | ✅ | ✅ | ✅ | | +| | [Dynamic context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/) | ✅ | ✅ | ✅ | ✅ | | +| | [Fetching ticket context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) | ✅ | ✅ | ✅ | | | +| | [Global and wiki configurations](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) 💎 | ✅ | ✅ | ✅ | | | +| | [Impact Evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) 💎 | ✅ | ✅ | | | | +| | [Incremental Update](https://qodo-merge-docs.qodo.ai/core-abilities/incremental_update/) | ✅ | | | | | +| | [Interactivity](https://qodo-merge-docs.qodo.ai/core-abilities/interactivity/) | ✅ | ✅ | | | | +| | [Local and global metadata](https://qodo-merge-docs.qodo.ai/core-abilities/metadata/) | ✅ | ✅ | ✅ | ✅ | | +| | [Multiple models support](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/) | ✅ | ✅ | ✅ | ✅ | | +| | [PR compression](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ | | +| | [PR interactive actions](https://www.qodo.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | ✅ | | | | +| | [RAG context enrichment](https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/) | ✅ | | ✅ | | | +| | [Self reflection](https://qodo-merge-docs.qodo.ai/core-abilities/self_reflection/) | ✅ | ✅ | ✅ | ✅ | | +| | [Static code analysis](https://qodo-merge-docs.qodo.ai/core-abilities/static_code_analysis/) 💎 | ✅ | ✅ | | | | - 💎 means this feature is available only in [Qodo Merge](https://www.qodo.ai/pricing/) [//]: # (- Support for additional git providers is described in [here](./docs/Full_environments.md)) ___ -‣ **PR Description ([`/describe`](https://qodo-merge-docs.qodo.ai/tools/describe/))**: Automatically generating PR description - title, type, summary, code walkthrough and labels. -\ -‣ **PR Review ([`/review`](https://qodo-merge-docs.qodo.ai/tools/review/))**: Adjustable feedback about the PR, possible issues, security concerns, review effort and more. -\ -‣ **Code Suggestions ([`/improve`](https://qodo-merge-docs.qodo.ai/tools/improve/))**: Code suggestions for improving the PR. -\ -‣ **Question Answering ([`/ask ...`](https://qodo-merge-docs.qodo.ai/tools/ask/))**: Answering free-text questions about the PR, or on specific code lines. -\ -‣ **Help ([`/help`](https://qodo-merge-docs.qodo.ai/tools/help/))**: Provides a list of all the available tools. Also enables to trigger them interactively (💎). -\ -‣ **Help Docs ([`/help_docs`](https://qodo-merge-docs.qodo.ai/tools/help_docs/))**: Answer a free-text question based on a git documentation folder. -\ -‣ **Update Changelog ([`/update_changelog`](https://qodo-merge-docs.qodo.ai/tools/update_changelog/))**: Automatically updating the CHANGELOG.md file with the PR changes. -\ -‣ **💎 Add Documentation ([`/add_docs`](https://qodo-merge-docs.qodo.ai/tools/documentation/))**: Generates documentation to methods/functions/classes that changed in the PR. -\ -‣ **💎 Analyze ([`/analyze`](https://qodo-merge-docs.qodo.ai/tools/analyze/))**: Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component. -\ -‣ **💎 CI Feedback ([`/checks ci_job`](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/))**: Automatically generates feedback and analysis for a failed CI job. -\ -‣ **💎 Custom Prompt ([`/custom_prompt`](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/))**: Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user. -\ -‣ **💎 Generate Custom Labels ([`/generate_labels`](https://qodo-merge-docs.qodo.ai/tools/custom_labels/))**: Generates custom labels for the PR, based on specific guidelines defined by the user. -\ -‣ **💎 Generate Tests ([`/test`](https://qodo-merge-docs.qodo.ai/tools/test/))**: Automatically generates unit tests for a selected component, based on the PR code changes. -\ -‣ **💎 Implement ([`/implement`](https://qodo-merge-docs.qodo.ai/tools/implement/))**: Generates implementation code from review suggestions. -\ -‣ **💎 Improve Component ([`/improve_component component_name`](https://qodo-merge-docs.qodo.ai/tools/improve_component/))**: Generates code suggestions for a specific code component that changed in the PR. -\ -‣ **💎 Scan Repo Discussions ([`/scan_repo_discussions`](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/))**: Generates best_practices.md file based on previous discussions in the repository. -\ -‣ **💎 Similar Code ([`/similar_code`](https://qodo-merge-docs.qodo.ai/tools/similar_code/))**: Retrieves the most similar code components from inside the organization's codebase, or from open-source code. -___ - -## Example results +## See it in Action

/describe

@@ -233,7 +207,7 @@ Here are some advantages of PR-Agent: - We emphasize **real-life practical usage**. Each tool (review, improve, ask, ...) has a single LLM call, no more. We feel that this is critical for realistic team usage - obtaining an answer quickly (~30 seconds) and affordably. - Our [PR Compression strategy](https://qodo-merge-docs.qodo.ai/core-abilities/#pr-compression-strategy) is a core ability that enables to effectively tackle both short and long PRs. -- Our JSON prompting strategy enables to have **modular, customizable tools**. For example, the '/review' tool categories can be controlled via the [configuration](pr_agent/settings/configuration.toml) file. Adding additional categories is easy and accessible. +- Our JSON prompting strategy enables us to have **modular, customizable tools**. For example, the '/review' tool categories can be controlled via the [configuration](pr_agent/settings/configuration.toml) file. Adding additional categories is easy and accessible. - We support **multiple git providers** (GitHub, GitLab, BitBucket), **multiple ways** to use the tool (CLI, GitHub Action, GitHub App, Docker, ...), and **multiple models** (GPT, Claude, Deepseek, ...) ## Data privacy From d5080a35f6ecbf55d0c426f2848ae2e957c5febc Mon Sep 17 00:00:00 2001 From: ofir-frd Date: Fri, 30 May 2025 15:39:24 +0300 Subject: [PATCH 58/68] docs: update Discord invitation link in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ed43f1b0..4a387e9c 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ PR-Agent aims to help efficiently review and handle pull requests, by providing [![Static Badge](https://img.shields.io/badge/Chrome-Extension-violet)](https://chromewebstore.google.com/detail/qodo-merge-ai-powered-cod/ephlnjeghhogofkifjloamocljapahnl) [![Static Badge](https://img.shields.io/badge/Pro-App-blue)](https://github.com/apps/qodo-merge-pro/) [![Static Badge](https://img.shields.io/badge/OpenSource-App-red)](https://github.com/apps/qodo-merge-pro-for-open-source/) -[![Discord](https://badgen.net/badge/icon/discord?icon=discord&label&color=purple)](https://discord.gg/kG35uSHDBc) +[![Discord](https://badgen.net/badge/icon/discord?icon=discord&label&color=purple)](https://discord.com/invite/SgSxuQ65GF) GitHub @@ -235,7 +235,7 @@ To contribute to the project, get started by reading our [Contributing Guide](ht ## Links -- Discord community: https://discord.gg/kG35uSHDBc +- Discord community: https://discord.com/invite/SgSxuQ65GF - Qodo site: https://www.qodo.ai/ - Blog: https://www.qodo.ai/blog/ - Troubleshooting: https://www.qodo.ai/blog/technical-faq-and-troubleshooting/ From 76172bd3ec945380114511673b1936ac31e1381c Mon Sep 17 00:00:00 2001 From: ofir-frd Date: Sun, 1 Jun 2025 09:59:47 +0300 Subject: [PATCH 59/68] docs: add table of contents to README for improved navigation --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 4a387e9c..aa7eac11 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,20 @@ PR-Agent aims to help efficiently review and handle pull requests, by providing +## Table of Contents + +- [Getting Started](#getting-started) +- [News and Updates](#news-and-updates) +- [Overview](#overview) +- [Example results](#example-results) +- [Try it now](#try-it-now) +- [Qodo Merge](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/) +- [How it works](#how-it-works) +- [Why use PR-Agent?](#why-use-pr-agent) +- [Data privacy](#data-privacy) +- [Contributing](#contributing) +- [Links](#links) + ## Getting Started ### Try it now From 27479d87b7c8ee444d6eace61f2291bb611dc3c3 Mon Sep 17 00:00:00 2001 From: ofir-frd Date: Sun, 1 Jun 2025 10:09:37 +0300 Subject: [PATCH 60/68] docs: standardize capitalization in README table of contents and headings --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index aa7eac11..d9abe175 100644 --- a/README.md +++ b/README.md @@ -32,18 +32,18 @@ PR-Agent aims to help efficiently review and handle pull requests, by providing - [Getting Started](#getting-started) - [News and Updates](#news-and-updates) - [Overview](#overview) -- [Example results](#example-results) -- [Try it now](#try-it-now) -- [Qodo Merge](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/) -- [How it works](#how-it-works) -- [Why use PR-Agent?](#why-use-pr-agent) -- [Data privacy](#data-privacy) +- [See It in Action](#see-it-in-action) +- [Try It Now](#try-it-now) +- [Qodo Merge 💎](#qodo-merge-) +- [How It Works](#how-it-works) +- [Why Use PR-Agent?](#why-use-pr-agent) +- [Data Privacy](#data-privacy) - [Contributing](#contributing) - [Links](#links) ## Getting Started -### Try it now +### Try it now Test PR-Agent on any public GitHub repository by commenting `@CodiumAI-Agent /review` ### GitHub Action @@ -57,7 +57,7 @@ Add automated PR reviews to your repository with a simple workflow file using [G ### CLI Usage Run PR-Agent locally on your repository via command line: [Local CLI setup guide](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) -### [Qodo Merge 💎](https://github.com/qodo-ai/pr-agent?tab=readme-ov-file#qodo-merge-) +### Qodo Merge 💎 Zero-setup hosted solution with advanced features and priority support - [Intro and Installation guide](https://qodo-merge-docs.qodo.ai/installation/qodo_merge/) - [Plans & Pricing](https://www.qodo.ai/pricing/) @@ -144,7 +144,7 @@ Supported commands per platform: [//]: # (- Support for additional git providers is described in [here](./docs/Full_environments.md)) ___ -## See it in Action +## See It in Action

/describe

@@ -179,7 +179,7 @@ ___
-## Try it now +## Try It Now Try the Claude Sonnet powered PR-Agent instantly on _your public GitHub repository_. Just mention `@CodiumAI-Agent` and add the desired command in any PR comment. The agent will generate a response based on your command. For example, add a comment to any pull request with the following text: @@ -205,7 +205,7 @@ It does not have 'edit' access to your repo, for example, so it cannot update th 4. **Extra features** - In addition to the benefits listed above, Qodo Merge will emphasize more customization, and the usage of static code analysis, in addition to LLM logic, to improve results. See [here](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/) for a list of features available in Qodo Merge. -## How it works +## How It Works The following diagram illustrates PR-Agent tools and their flow: @@ -213,7 +213,7 @@ The following diagram illustrates PR-Agent tools and their flow: Check out the [PR Compression strategy](https://qodo-merge-docs.qodo.ai/core-abilities/#pr-compression-strategy) page for more details on how we convert a code diff to a manageable LLM prompt -## Why use PR-Agent? +## Why Use PR-Agent? A reasonable question that can be asked is: `"Why use PR-Agent? What makes it stand out from existing tools?"` @@ -224,7 +224,7 @@ Here are some advantages of PR-Agent: - Our JSON prompting strategy enables us to have **modular, customizable tools**. For example, the '/review' tool categories can be controlled via the [configuration](pr_agent/settings/configuration.toml) file. Adding additional categories is easy and accessible. - We support **multiple git providers** (GitHub, GitLab, BitBucket), **multiple ways** to use the tool (CLI, GitHub Action, GitHub App, Docker, ...), and **multiple models** (GPT, Claude, Deepseek, ...) -## Data privacy +## Data Privacy ### Self-hosted PR-Agent From 8be2e43a0f5bbebea502dd0e5ae97027a542e857 Mon Sep 17 00:00:00 2001 From: Tal Date: Sun, 1 Jun 2025 11:15:42 +0300 Subject: [PATCH 61/68] Update README.md Co-authored-by: qodo-merge-pro-for-open-source[bot] <189517486+qodo-merge-pro-for-open-source[bot]@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d9abe175..fcdb7949 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ PR-Agent aims to help efficiently review and handle pull requests, by providing ## Getting Started ### Try it now -Test PR-Agent on any public GitHub repository by commenting `@CodiumAI-Agent /review` +Test PR-Agent on any public GitHub repository by commenting `@CodiumAI-Agent /improve` ### GitHub Action Add automated PR reviews to your repository with a simple workflow file using [GitHub Action setup guide](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) From 0dba39566f10f64bbba225e36f72616fd1761bc4 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Sun, 1 Jun 2025 11:40:17 +0300 Subject: [PATCH 62/68] docs: fix indentation in Linear base URL note in ticket context documentation --- .../core-abilities/fetching_ticket_context.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/docs/core-abilities/fetching_ticket_context.md b/docs/docs/core-abilities/fetching_ticket_context.md index 05e19c00..73096040 100644 --- a/docs/docs/core-abilities/fetching_ticket_context.md +++ b/docs/docs/core-abilities/fetching_ticket_context.md @@ -339,11 +339,12 @@ Include a ticket reference in your PR description using either: Name your branch with the ticket ID as a prefix (e.g., `ABC-123-feature-description` or `feature/ABC-123/feature-description`). !!! note "Linear Base URL" - For shortened ticket IDs or branch detection (method 2), you must configure the Linear base URL in your configuration file under the [linear] section: - ```toml - [linear] - linear_base_url = "https://linear.app/[ORG_ID]" - ``` - - Replace `[ORG_ID]` with your Linear organization identifier. \ No newline at end of file + For shortened ticket IDs or branch detection (method 2), you must configure the Linear base URL in your configuration file under the [linear] section: + + ```toml + [linear] + linear_base_url = "https://linear.app/[ORG_ID]" + ``` + + Replace `[ORG_ID]` with your Linear organization identifier. \ No newline at end of file From 75d24791a4553c88e792fb9b8671480505e41019 Mon Sep 17 00:00:00 2001 From: ofir-frd Date: Sun, 1 Jun 2025 11:48:30 +0300 Subject: [PATCH 63/68] docs: improve section headings in README for better clarity --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fcdb7949..304297b0 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ PR-Agent aims to help efficiently review and handle pull requests, by providing ## Getting Started -### Try it now +### Try it Instantly Test PR-Agent on any public GitHub repository by commenting `@CodiumAI-Agent /improve` ### GitHub Action @@ -57,7 +57,7 @@ Add automated PR reviews to your repository with a simple workflow file using [G ### CLI Usage Run PR-Agent locally on your repository via command line: [Local CLI setup guide](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) -### Qodo Merge 💎 +### Discover Qodo Merge 💎 Zero-setup hosted solution with advanced features and priority support - [Intro and Installation guide](https://qodo-merge-docs.qodo.ai/installation/qodo_merge/) - [Plans & Pricing](https://www.qodo.ai/pricing/) From 9b06f46563506213582dfbe34ceb934bf2ab0365 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Sun, 1 Jun 2025 12:20:21 +0300 Subject: [PATCH 64/68] docs: update recent updates page with new features and roadmap changes --- docs/docs/recent_updates/index.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/docs/recent_updates/index.md b/docs/docs/recent_updates/index.md index 85f21c07..354f3ec4 100644 --- a/docs/docs/recent_updates/index.md +++ b/docs/docs/recent_updates/index.md @@ -1,22 +1,23 @@ # Recent Updates and Future Roadmap -`Page last updated: 2025-05-11` +`Page last updated: 2025-06-01` 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" + - **CLI Endpoint**: A new Qodo Merge endpoint will accept lists of before/after code changes, execute 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, enabling users to link PRs to Linear issues and receive feedback on ticket compliance ([Learn more](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/#linear-integration)) + - **Smart Update**: Upon PR updates, Qodo Merge will offer tailored code suggestions, addressing both the entire PR and the specific incremental changes since the last feedback ([Learn more](https://qodo-merge-docs.qodo.ai/core-abilities/incremental_update//)) - **Qodo Merge Pull Request Benchmark** - evaluating the performance of LLMs in analyzing pull request code ([Learn more](https://qodo-merge-docs.qodo.ai/pr_benchmark/)) - **Chat on Suggestions**: Users can now chat with Qodo Merge code suggestions ([Learn more](https://qodo-merge-docs.qodo.ai/tools/improve/#chat-on-code-suggestions)) - **Scan Repo Discussions Tool**: A new tool that analyzes past code discussions to generate a `best_practices.md` file, distilling key insights and recommendations. ([Learn more](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/)) - - **Enhanced Models**: Qodo Merge now defaults to a combination of top models (Claude Sonnet 3.7 and Gemini 2.5 Pro) and incorporates dedicated code validation logic for improved results. ([Details 1](https://qodo-merge-docs.qodo.ai/usage-guide/qodo_merge_models/), [Details 2](https://qodo-merge-docs.qodo.ai/core-abilities/code_validation/)) - - **Chrome Extension Update**: Qodo Merge Chrome extension now supports single-tenant users. ([Learn more](https://qodo-merge-docs.qodo.ai/chrome-extension/options/#configuration-options/)) - - **Installation Metrics**: Upon installation, Qodo Merge analyzes past PRs for key metrics (e.g., time to merge, time to first reviewer feedback), enabling pre/post-installation comparison to calculate ROI. === "Future Roadmap" - - **Smart Update**: Upon PR updates, Qodo Merge will offer tailored code suggestions, addressing both the entire PR and the specific incremental changes since the last feedback. - - **CLI Endpoint**: A new Qodo Merge endpoint will accept lists of before/after code changes, execute Qodo Merge commands, and return the results. - **Simplified Free Tier**: We plan to transition from a two-week free trial to a free tier offering a limited number of suggestions per month per organization. - **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. From 5856a9e548810409c9d7f683151355f8296635bf Mon Sep 17 00:00:00 2001 From: mrT23 Date: Sun, 1 Jun 2025 12:24:25 +0300 Subject: [PATCH 65/68] docs: refine descriptions in recent updates page for clarity and consistency --- docs/docs/recent_updates/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/recent_updates/index.md b/docs/docs/recent_updates/index.md index 354f3ec4..84b362f0 100644 --- a/docs/docs/recent_updates/index.md +++ b/docs/docs/recent_updates/index.md @@ -7,11 +7,11 @@ 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" - - **CLI Endpoint**: A new Qodo Merge endpoint will accept lists of before/after code changes, execute 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, enabling users to link PRs to Linear issues and receive feedback on ticket compliance ([Learn more](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/#linear-integration)) + - **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)) - **Smart Update**: Upon PR updates, Qodo Merge will offer tailored code suggestions, addressing both the entire PR and the specific incremental changes since the last feedback ([Learn more](https://qodo-merge-docs.qodo.ai/core-abilities/incremental_update//)) - **Qodo Merge Pull Request Benchmark** - evaluating the performance of LLMs in analyzing pull request code ([Learn more](https://qodo-merge-docs.qodo.ai/pr_benchmark/)) - - **Chat on Suggestions**: Users can now chat with Qodo Merge code suggestions ([Learn more](https://qodo-merge-docs.qodo.ai/tools/improve/#chat-on-code-suggestions)) + - **Chat on Suggestions**: Users can now chat with code suggestions ([Learn more](https://qodo-merge-docs.qodo.ai/tools/improve/#chat-on-code-suggestions)) - **Scan Repo Discussions Tool**: A new tool that analyzes past code discussions to generate a `best_practices.md` file, distilling key insights and recommendations. ([Learn more](https://qodo-merge-docs.qodo.ai/tools/scan_repo_discussions/)) From e2867f3a19e5e6a1331f73429866578df479a5fb Mon Sep 17 00:00:00 2001 From: Tomoya Kawaguchi <68677002+yamoyamoto@users.noreply.github.com> Date: Mon, 2 Jun 2025 13:48:16 +0900 Subject: [PATCH 66/68] chore: get settings more safer Co-authored-by: qodo-merge-pro-for-open-source[bot] <189517486+qodo-merge-pro-for-open-source[bot]@users.noreply.github.com> --- pr_agent/secret_providers/aws_secrets_manager_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pr_agent/secret_providers/aws_secrets_manager_provider.py b/pr_agent/secret_providers/aws_secrets_manager_provider.py index bfd1468d..81a2896e 100644 --- a/pr_agent/secret_providers/aws_secrets_manager_provider.py +++ b/pr_agent/secret_providers/aws_secrets_manager_provider.py @@ -17,7 +17,7 @@ class AWSSecretsManagerProvider(SecretProvider): else: self.client = boto3.client('secretsmanager') - self.secret_arn = get_settings().aws_secrets_manager.secret_arn + self.secret_arn = get_settings().get("aws_secrets_manager.secret_arn") if not self.secret_arn: raise ValueError("AWS Secrets Manager ARN is not configured") except Exception as e: From c520a8658f3fb98289e2668c63e9e575459e7404 Mon Sep 17 00:00:00 2001 From: Tomoya Kawaguchi <68677002+yamoyamoto@users.noreply.github.com> Date: Mon, 2 Jun 2025 13:48:47 +0900 Subject: [PATCH 67/68] chore: update secret more robustly Co-authored-by: qodo-merge-pro-for-open-source[bot] <189517486+qodo-merge-pro-for-open-source[bot]@users.noreply.github.com> --- pr_agent/secret_providers/aws_secrets_manager_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pr_agent/secret_providers/aws_secrets_manager_provider.py b/pr_agent/secret_providers/aws_secrets_manager_provider.py index 81a2896e..599369db 100644 --- a/pr_agent/secret_providers/aws_secrets_manager_provider.py +++ b/pr_agent/secret_providers/aws_secrets_manager_provider.py @@ -48,7 +48,7 @@ class AWSSecretsManagerProvider(SecretProvider): def store_secret(self, secret_name: str, secret_value: str): try: - self.client.update_secret( + self.client.put_secret_value( SecretId=secret_name, SecretString=secret_value ) From ecb39856ee666ec2bf05922da1ba07c00c8fe3d7 Mon Sep 17 00:00:00 2001 From: mrT23 Date: Mon, 2 Jun 2025 18:04:44 +0300 Subject: [PATCH 68/68] fix tests --- tests/unittest/test_aws_secrets_manager_provider.py | 4 ++-- tests/unittest/test_config_loader_secrets.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unittest/test_aws_secrets_manager_provider.py b/tests/unittest/test_aws_secrets_manager_provider.py index f84743ca..e5972ac1 100644 --- a/tests/unittest/test_aws_secrets_manager_provider.py +++ b/tests/unittest/test_aws_secrets_manager_provider.py @@ -67,7 +67,7 @@ class TestAWSSecretsManagerProvider: mock_client.update_secret.return_value = {} provider.store_secret('test-secret', 'test-value') - mock_client.update_secret.assert_called_once_with( + mock_client.put_secret_value.assert_called_once_with( SecretId='test-secret', SecretString='test-value' ) @@ -83,7 +83,7 @@ class TestAWSSecretsManagerProvider: def test_store_secret_failure(self): provider, mock_client = self._provider() - mock_client.update_secret.side_effect = Exception("AWS error") + mock_client.put_secret_value.side_effect = Exception("AWS error") with pytest.raises(Exception): provider.store_secret('test-secret', 'test-value') diff --git a/tests/unittest/test_config_loader_secrets.py b/tests/unittest/test_config_loader_secrets.py index d0eb3c62..36752ef0 100644 --- a/tests/unittest/test_config_loader_secrets.py +++ b/tests/unittest/test_config_loader_secrets.py @@ -7,7 +7,7 @@ from pr_agent.config_loader import apply_secrets_manager_config, apply_secrets_t class TestConfigLoaderSecrets: def test_apply_secrets_manager_config_success(self): - with patch('pr_agent.config_loader.get_secret_provider') as mock_get_provider, \ + with patch('pr_agent.secret_providers.get_secret_provider') as mock_get_provider, \ patch('pr_agent.config_loader.apply_secrets_to_config') as mock_apply_secrets, \ patch('pr_agent.config_loader.get_settings') as mock_get_settings: @@ -26,14 +26,14 @@ class TestConfigLoaderSecrets: mock_apply_secrets.assert_called_once_with({'openai.key': 'sk-test'}) def test_apply_secrets_manager_config_no_provider(self): - with patch('pr_agent.config_loader.get_secret_provider') as mock_get_provider: + with patch('pr_agent.secret_providers.get_secret_provider') as mock_get_provider: mock_get_provider.return_value = None # Confirm no exception is raised apply_secrets_manager_config() def test_apply_secrets_manager_config_not_aws(self): - with patch('pr_agent.config_loader.get_secret_provider') as mock_get_provider, \ + with patch('pr_agent.secret_providers.get_secret_provider') as mock_get_provider, \ patch('pr_agent.config_loader.get_settings') as mock_get_settings: # Mock Google Cloud Storage provider @@ -113,7 +113,7 @@ class TestConfigLoaderSecrets: settings.set.assert_not_called() def test_apply_secrets_manager_config_exception_handling(self): - with patch('pr_agent.config_loader.get_secret_provider') as mock_get_provider: + with patch('pr_agent.secret_providers.get_secret_provider') as mock_get_provider: mock_get_provider.side_effect = Exception("Provider error") # Confirm processing continues even when exception occurs