From 24a9faa43641462b353b7ca55bce1b7370278aa4 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Wed, 26 Feb 2025 18:37:13 +0000 Subject: [PATCH 001/146] feat: Add URL scraping and extraction functionality to bot --- src/agents.py | 13 +++++++ src/bot_tools.py | 53 ++++++++++++++++++++++++++++ src/response_agent.py | 82 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 147 insertions(+), 1 deletion(-) diff --git a/src/agents.py b/src/agents.py index 4934e47..a0e3d18 100644 --- a/src/agents.py +++ b/src/agents.py @@ -27,6 +27,7 @@ NEVER ask for user input and NEVER expect it. Return file names that are relevant, and if possible, specific lines where changes can be made. Instead of listing the whole dir, use read_merged_summary or read_merged_docstrings + If URLs are present in the issue, use scrape_text_from_url to extract content and analyze it. Reply "TERMINATE" in the end when everything is done. """, "edit_assistant": """You are a helpful GitHub bot that reviews issues and generates appropriate responses. @@ -37,11 +38,13 @@ NEVER ask for user input and NEVER expect it. If possible, suggest concrete code changes or additions that can be made. Be specific about what files and what lines. Include file paths, line numbers, and exact code changes where possible. + If URLs are present in the issue, use scrape_text_from_url to extract content and analyze it. Format the command in a way that can be parsed by automated tools. Reply "TERMINATE" in the end when everything is done. """, "summary_assistant": """You are a helpful GitHub bot that reviews issues and generates appropriate responses. Analyze the issue details carefully and summarize the suggestions and changes made by other agents. + If URLs are present in the issue, use the scraped content to provide more context in your summary. """, "feedback_assistant": """You are a helpful GitHub bot that processes user feedback on previous bot responses. Analyze the user's feedback carefully and suggest improvements to the original response. @@ -171,6 +174,15 @@ def generate_prompt( """Generate prompt for the agent""" last_comment_str, comments_str = parse_comments( repo_name, repo_path, details, issue) + + # Add URL content information if available + url_content_str = "" + if 'url_contents' in details and details['url_contents']: + url_content_str = "\nURLs found in issue:\n" + for url, content in details['url_contents'].items(): + # Truncate content preview to avoid extremely long prompts + content_preview = content[:500] + "..." if len(content) > 500 else content + url_content_str += f"\n- URL: {url}\n- Content preview: {content_preview}\n" boilerplate_text = f""" Repository: {repo_name} @@ -178,6 +190,7 @@ def generate_prompt( Title: {details['title']} Body: {details['body']} {last_comment_str} + {url_content_str} """ if agent_name == "file_assistant": diff --git a/src/bot_tools.py b/src/bot_tools.py index 04fa140..86d1b1d 100644 --- a/src/bot_tools.py +++ b/src/bot_tools.py @@ -4,6 +4,8 @@ import os import sys +import requests +from bs4 import BeautifulSoup src_dir = os.path.dirname(os.path.abspath(__file__)) base_dir = os.path.dirname(src_dir) @@ -565,3 +567,54 @@ def get_func_code( # Get code for function code = "".join(lines[start_line:end_line]) return code + + +def scrape_text_from_url(url: str) -> str: + """Scrape text content from a given URL. + + Args: + url: The URL to scrape text from. + + Returns: + The scraped text content. + """ + try: + response = requests.get(url, timeout=10) + response.raise_for_status() # Raise an error for bad responses + soup = BeautifulSoup(response.text, 'html.parser') + + # Remove script and style elements + for script in soup(["script", "style"]): + script.extract() + + # Get text + text = soup.get_text() + + # Break into lines and remove leading and trailing space on each + lines = (line.strip() for line in text.splitlines()) + # Break multi-headlines into a line each + chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) + # Remove blank lines + text = '\n'.join(chunk for chunk in chunks if chunk) + + return text + except requests.RequestException as e: + print(f"Error fetching URL {url}: {e}") + return f"Error fetching URL {url}: {str(e)}" + + +def summarize_text(text: str, max_length: int = 1000) -> str: + """Summarize text to a maximum length. + + Args: + text: The text to summarize. + max_length: Maximum length of the summary. + + Returns: + The summarized text. + """ + if len(text) <= max_length: + return text + + # Simple truncation with ellipsis for now + return text[:max_length] + "...\n[Text truncated due to length]" diff --git a/src/response_agent.py b/src/response_agent.py index 74bdcc9..8c0e6a2 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -1,7 +1,7 @@ """ Agent for generating responses to GitHub issues using pyautogen """ -from typing import Optional, Tuple +from typing import Optional, Tuple, List from dotenv import load_dotenv import string @@ -11,6 +11,7 @@ create_agent, generate_prompt, ) +from urlextract import URLExtract import agents from autogen import AssistantAgent import bot_tools @@ -93,6 +94,22 @@ def generate_feedback_response( print('===============================') repo_path = bot_tools.get_local_repo_path(repo_name) details = get_issue_details(issue) + + # Extract URLs from issue and scrape content + urls = extract_urls_from_issue(issue) + url_contents = {} + + if urls: + print(f"Found {len(urls)} URLs in issue") + for url in urls: + print(f"Scraping content from {url}") + content = bot_tools.scrape_text_from_url(url) + # Summarize content to avoid token limits + summarized_content = bot_tools.summarize_text(content) + url_contents[url] = summarized_content + + # Add URL contents to issue details + details['url_contents'] = url_contents prompt_kwargs = { "repo_name": repo_name, @@ -140,6 +157,37 @@ def generate_feedback_response( return updated_response, all_content +def extract_urls_from_issue(issue: Issue) -> List[str]: + """ + Extract URLs from issue body and comments + + Args: + issue: The GitHub issue to extract URLs from + + Returns: + List of URLs found in the issue + """ + extractor = URLExtract() + urls = [] + + # Extract from issue body + issue_body = issue.body or "" + urls.extend(extractor.find_urls(issue_body)) + + # Extract from comments + for comment in get_issue_comments(issue): + comment_body = comment.body or "" + urls.extend(extractor.find_urls(comment_body)) + + # Remove duplicates while preserving order + unique_urls = [] + for url in urls: + if url not in unique_urls: + unique_urls.append(url) + + return unique_urls + + def generate_new_response( issue: Issue, repo_name: str, @@ -160,6 +208,22 @@ def generate_new_response( # Get path to repository and issue details repo_path = bot_tools.get_local_repo_path(repo_name) details = get_issue_details(issue) + + # Extract URLs from issue and scrape content + urls = extract_urls_from_issue(issue) + url_contents = {} + + if urls: + print(f"Found {len(urls)} URLs in issue") + for url in urls: + print(f"Scraping content from {url}") + content = bot_tools.scrape_text_from_url(url) + # Summarize content to avoid token limits + summarized_content = bot_tools.summarize_text(content) + url_contents[url] = summarized_content + + # Add URL contents to issue details + details['url_contents'] = url_contents # Create base agents user = create_user_agent() @@ -247,6 +311,22 @@ def generate_edit_command_response( # Get path to repository and issue details repo_path = bot_tools.get_local_repo_path(repo_name) details = get_issue_details(issue) + + # Extract URLs from issue and scrape content + urls = extract_urls_from_issue(issue) + url_contents = {} + + if urls: + print(f"Found {len(urls)} URLs in issue") + for url in urls: + print(f"Scraping content from {url}") + content = bot_tools.scrape_text_from_url(url) + # Summarize content to avoid token limits + summarized_content = bot_tools.summarize_text(content) + url_contents[url] = summarized_content + + # Add URL contents to issue details + details['url_contents'] = url_contents user = create_user_agent() generate_edit_command_assistant = create_agent( From 8be72e128f9df49447905eb10d8f729cfc393b83 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Wed, 26 Feb 2025 18:37:21 +0000 Subject: [PATCH 002/146] fix: Correct undefined variable name from `filepath` to `file_path` --- src/bot_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bot_tools.py b/src/bot_tools.py index 86d1b1d..14bbc7c 100644 --- a/src/bot_tools.py +++ b/src/bot_tools.py @@ -345,7 +345,7 @@ def readlines( warning = (f"Selected lines exceed token threshold of {token_threshold}. " f"Showing lines {start_line} to {start_line + n_included} " f"({current_tokens}/{total_tokens} tokens). " - f"Use readlines({filepath}, start_line, end_line) " + f"Use readlines({file_path}, start_line, end_line) " f"to read specific ranges.") data = "".join(included_lines) From 0ae83788f2035ac22605ef68d7fbd6cc8a849bb6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 26 Feb 2025 18:37:43 +0000 Subject: [PATCH 003/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/agents.py | 5 +++-- src/bot_tools.py | 13 +++++++------ src/response_agent.py | 30 +++++++++++++++--------------- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/agents.py b/src/agents.py index a0e3d18..8f31ddd 100644 --- a/src/agents.py +++ b/src/agents.py @@ -174,14 +174,15 @@ def generate_prompt( """Generate prompt for the agent""" last_comment_str, comments_str = parse_comments( repo_name, repo_path, details, issue) - + # Add URL content information if available url_content_str = "" if 'url_contents' in details and details['url_contents']: url_content_str = "\nURLs found in issue:\n" for url, content in details['url_contents'].items(): # Truncate content preview to avoid extremely long prompts - content_preview = content[:500] + "..." if len(content) > 500 else content + content_preview = content[:500] + \ + "..." if len(content) > 500 else content url_content_str += f"\n- URL: {url}\n- Content preview: {content_preview}\n" boilerplate_text = f""" diff --git a/src/bot_tools.py b/src/bot_tools.py index 14bbc7c..818c48b 100644 --- a/src/bot_tools.py +++ b/src/bot_tools.py @@ -582,21 +582,22 @@ def scrape_text_from_url(url: str) -> str: response = requests.get(url, timeout=10) response.raise_for_status() # Raise an error for bad responses soup = BeautifulSoup(response.text, 'html.parser') - + # Remove script and style elements for script in soup(["script", "style"]): script.extract() - + # Get text text = soup.get_text() - + # Break into lines and remove leading and trailing space on each lines = (line.strip() for line in text.splitlines()) # Break multi-headlines into a line each - chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) + chunks = (phrase.strip() + for line in lines for phrase in line.split(" ")) # Remove blank lines text = '\n'.join(chunk for chunk in chunks if chunk) - + return text except requests.RequestException as e: print(f"Error fetching URL {url}: {e}") @@ -615,6 +616,6 @@ def summarize_text(text: str, max_length: int = 1000) -> str: """ if len(text) <= max_length: return text - + # Simple truncation with ellipsis for now return text[:max_length] + "...\n[Text truncated due to length]" diff --git a/src/response_agent.py b/src/response_agent.py index 8c0e6a2..e8eee5b 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -94,11 +94,11 @@ def generate_feedback_response( print('===============================') repo_path = bot_tools.get_local_repo_path(repo_name) details = get_issue_details(issue) - + # Extract URLs from issue and scrape content urls = extract_urls_from_issue(issue) url_contents = {} - + if urls: print(f"Found {len(urls)} URLs in issue") for url in urls: @@ -107,7 +107,7 @@ def generate_feedback_response( # Summarize content to avoid token limits summarized_content = bot_tools.summarize_text(content) url_contents[url] = summarized_content - + # Add URL contents to issue details details['url_contents'] = url_contents @@ -160,31 +160,31 @@ def generate_feedback_response( def extract_urls_from_issue(issue: Issue) -> List[str]: """ Extract URLs from issue body and comments - + Args: issue: The GitHub issue to extract URLs from - + Returns: List of URLs found in the issue """ extractor = URLExtract() urls = [] - + # Extract from issue body issue_body = issue.body or "" urls.extend(extractor.find_urls(issue_body)) - + # Extract from comments for comment in get_issue_comments(issue): comment_body = comment.body or "" urls.extend(extractor.find_urls(comment_body)) - + # Remove duplicates while preserving order unique_urls = [] for url in urls: if url not in unique_urls: unique_urls.append(url) - + return unique_urls @@ -208,11 +208,11 @@ def generate_new_response( # Get path to repository and issue details repo_path = bot_tools.get_local_repo_path(repo_name) details = get_issue_details(issue) - + # Extract URLs from issue and scrape content urls = extract_urls_from_issue(issue) url_contents = {} - + if urls: print(f"Found {len(urls)} URLs in issue") for url in urls: @@ -221,7 +221,7 @@ def generate_new_response( # Summarize content to avoid token limits summarized_content = bot_tools.summarize_text(content) url_contents[url] = summarized_content - + # Add URL contents to issue details details['url_contents'] = url_contents @@ -311,11 +311,11 @@ def generate_edit_command_response( # Get path to repository and issue details repo_path = bot_tools.get_local_repo_path(repo_name) details = get_issue_details(issue) - + # Extract URLs from issue and scrape content urls = extract_urls_from_issue(issue) url_contents = {} - + if urls: print(f"Found {len(urls)} URLs in issue") for url in urls: @@ -324,7 +324,7 @@ def generate_edit_command_response( # Summarize content to avoid token limits summarized_content = bot_tools.summarize_text(content) url_contents[url] = summarized_content - + # Add URL contents to issue details details['url_contents'] = url_contents From d2140eaee0fb2cd00117f8041f77f4d3d99be442 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Sun, 9 Mar 2025 10:22:18 +0000 Subject: [PATCH 004/146] feat: Implement auto-update feature for bot with config file preservation --- src/git_utils.py | 63 +++++++++++++++++++++++++++++++++++++++++++ src/response_agent.py | 18 +++++++++++++ 2 files changed, 81 insertions(+) diff --git a/src/git_utils.py b/src/git_utils.py index fbd024f..58fcbbc 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -402,6 +402,69 @@ def get_linked_pr(issue: Issue) -> PullRequest: return None +def update_self_repo(repo_path: str) -> None: + """ + Pull latest changes for the bot's own repository, handling tracked config files. + + Args: + repo_path: Path to the bot's local git repository + """ + import git + import os + import shutil + + git_repo = git.Repo(repo_path) + origin = git_repo.remotes.origin + + # Backup config/repos.txt + config_repos_path = os.path.join(repo_path, 'config', 'repos.txt') + backup_path = os.path.join(repo_path, 'config', 'repos.txt.backup') + + has_backup = False + if os.path.exists(config_repos_path): + print(f"Backing up {config_repos_path}") + shutil.copy2(config_repos_path, backup_path) + has_backup = True + + # Fetch latest changes + print("Fetching latest changes for self-repo") + origin.fetch() + + # Check if the remote is ahead + local_commit = git_repo.head.commit + remote_commit = None + try: + remote_commit = origin.refs.master.commit + except AttributeError: + try: + remote_commit = origin.refs.main.commit + except AttributeError: + print("Could not find master or main branch on remote") + + if remote_commit and local_commit != remote_commit: + print("Remote is ahead. Force pulling latest changes for self-repo.") + # Get the default branch name + default_branch = None + for ref in git_repo.references: + if ref.name == 'HEAD': + default_branch = ref.reference.name.replace('refs/heads/', '') + break + + if not default_branch: + default_branch = 'master' # Fallback + + # Hard reset to remote branch + git_repo.git.reset('--hard', f'origin/{default_branch}') + else: + print("Self-repo is up-to-date.") + + # Restore config/repos.txt + if has_backup: + print(f"Restoring {config_repos_path}") + shutil.copy2(backup_path, config_repos_path) + os.remove(backup_path) + + if __name__ == '__main__': client = get_github_client() repo = get_repository(client, 'katzlabbrandeis/blech_clust') diff --git a/src/response_agent.py b/src/response_agent.py index 5ac0968..5b5543c 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -15,6 +15,7 @@ import agents from autogen import AssistantAgent import bot_tools +import os from git_utils import ( get_github_client, @@ -704,7 +705,24 @@ def process_repository( print(f"Skipped issue #{issue.number}: {error}") +def initialize_bot() -> None: + """ + Initialize the bot and ensure it is up-to-date. + """ + # Path to the bot's own repository + self_repo_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + # Update the bot's own repository + from git_utils import update_self_repo + print(f"Updating bot repository at {self_repo_path}") + update_self_repo(self_repo_path) + print("Bot repository update complete") + + if __name__ == '__main__': + # Initialize the bot (self-update) + initialize_bot() + # Get list of repositories to process tracked_repos = bot_tools.get_tracked_repos() print(f'Found {len(tracked_repos)} tracked repositories') From 148a3aee46dbdccd807a4d290184dc07148ac161 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 9 Mar 2025 10:22:29 +0000 Subject: [PATCH 005/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/git_utils.py | 8 ++++---- src/response_agent.py | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 58fcbbc..3237720 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -419,7 +419,7 @@ def update_self_repo(repo_path: str) -> None: # Backup config/repos.txt config_repos_path = os.path.join(repo_path, 'config', 'repos.txt') backup_path = os.path.join(repo_path, 'config', 'repos.txt.backup') - + has_backup = False if os.path.exists(config_repos_path): print(f"Backing up {config_repos_path}") @@ -440,7 +440,7 @@ def update_self_repo(repo_path: str) -> None: remote_commit = origin.refs.main.commit except AttributeError: print("Could not find master or main branch on remote") - + if remote_commit and local_commit != remote_commit: print("Remote is ahead. Force pulling latest changes for self-repo.") # Get the default branch name @@ -449,10 +449,10 @@ def update_self_repo(repo_path: str) -> None: if ref.name == 'HEAD': default_branch = ref.reference.name.replace('refs/heads/', '') break - + if not default_branch: default_branch = 'master' # Fallback - + # Hard reset to remote branch git_repo.git.reset('--hard', f'origin/{default_branch}') else: diff --git a/src/response_agent.py b/src/response_agent.py index 5b5543c..57b0bb4 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -710,8 +710,9 @@ def initialize_bot() -> None: Initialize the bot and ensure it is up-to-date. """ # Path to the bot's own repository - self_repo_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - + self_repo_path = os.path.dirname( + os.path.dirname(os.path.abspath(__file__))) + # Update the bot's own repository from git_utils import update_self_repo print(f"Updating bot repository at {self_repo_path}") @@ -722,7 +723,7 @@ def initialize_bot() -> None: if __name__ == '__main__': # Initialize the bot (self-update) initialize_bot() - + # Get list of repositories to process tracked_repos = bot_tools.get_tracked_repos() print(f'Found {len(tracked_repos)} tracked repositories') From 1e3e3f5bc815d7df0e58937485358430f8d7a9bb Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Sun, 9 Mar 2025 10:27:07 +0000 Subject: [PATCH 006/146] refactor: Externalize configuration and update repository tracking --- src/agents.py | 3 ++- src/config.py | 32 ++++++++++++++++++++++++++++++++ src/response_agent.py | 30 +++++++++--------------------- 3 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 src/config.py diff --git a/src/agents.py b/src/agents.py index 331cbfe..6ce8b55 100644 --- a/src/agents.py +++ b/src/agents.py @@ -132,8 +132,9 @@ def create_user_agent(): return user -def create_agent(agent_name: str, llm_config: dict) -> AssistantAgent: +def create_agent(agent_name: str) -> AssistantAgent: """Create and configure the autogen agents""" + from config import llm_config agent = AssistantAgent( name=agent_name, diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..8c59509 --- /dev/null +++ b/src/config.py @@ -0,0 +1,32 @@ +""" +Configuration file for the GitHub bot +""" +import os +import random +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +# Define the LLM configuration +llm_config = { + "model": "gpt-4o", + "api_key": os.getenv('OPENAI_API_KEY'), + "temperature": random.uniform(0, 0.2), +} + +# Define repository names as a dictionary +repo_names = { + "repo1": "owner/repo1", + "repo2": "owner/repo2", + # Add more repositories as needed +} + +def get_tracked_repos(): + """ + Get list of tracked repositories from the repo_names dictionary + + Returns: + List of repository names in format owner/repo + """ + return list(repo_names.values()) diff --git a/src/response_agent.py b/src/response_agent.py index 5ac0968..5fbcce0 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -3,7 +3,6 @@ """ from typing import Optional, Tuple -from dotenv import load_dotenv import string import triggers from agents import ( @@ -41,23 +40,11 @@ import os from pprint import pprint from collections.abc import Callable -import random import traceback import json import re from urlextract import URLExtract - -load_dotenv() - -api_key = os.getenv('OPENAI_API_KEY') -if not api_key: - raise ValueError("OpenAI API key not found in environment variables") - -llm_config = { - "model": "gpt-4o", - "api_key": api_key, - "temperature": random.uniform(0, 0.2), -} +from config import llm_config ############################################################ # Response patterns ############################################################ @@ -104,7 +91,7 @@ def summarize_relevant_comments( } comment_summary_assistant = create_agent( - "comment_summary_assistant", llm_config) + "comment_summary_assistant") summarized_comments = [] for comment in comment_list[:-1]: summary_prompt = generate_prompt( @@ -171,7 +158,7 @@ def generate_feedback_response( "issue": issue, } user = create_user_agent() - feedback_assistant = create_agent("feedback_assistant", llm_config) + feedback_assistant = create_agent("feedback_assistant") comments = get_issue_comments(issue) for comment in reversed(comments): @@ -234,9 +221,9 @@ def generate_new_response( # Create base agents user = create_user_agent() - file_assistant = create_agent("file_assistant", llm_config) - edit_assistant = create_agent("edit_assistant", llm_config) - summary_assistant = create_agent("summary_assistant", llm_config) + file_assistant = create_agent("file_assistant") + edit_assistant = create_agent("edit_assistant") + summary_assistant = create_agent("summary_assistant") # user, file_assistant, edit_assistant = create_agents() # Get prompts and run agents @@ -323,7 +310,7 @@ def generate_edit_command_response( user = create_user_agent() generate_edit_command_assistant = create_agent( - "generate_edit_command_assistant", llm_config) + "generate_edit_command_assistant") if summarized_comments: generate_edit_command_prompt = generate_prompt( "generate_edit_command_assistant", @@ -706,7 +693,8 @@ def process_repository( if __name__ == '__main__': # Get list of repositories to process - tracked_repos = bot_tools.get_tracked_repos() + from config import get_tracked_repos + tracked_repos = get_tracked_repos() print(f'Found {len(tracked_repos)} tracked repositories') pprint(tracked_repos) From 5e8f16cf2e338182e0d6a2bdad9996d72401e950 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 9 Mar 2025 10:27:16 +0000 Subject: [PATCH 007/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/config.py b/src/config.py index 8c59509..3aeae0f 100644 --- a/src/config.py +++ b/src/config.py @@ -22,10 +22,11 @@ # Add more repositories as needed } + def get_tracked_repos(): """ Get list of tracked repositories from the repo_names dictionary - + Returns: List of repository names in format owner/repo """ From 202e3bf835eb20275df7b15eac06f421913266ed Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Sun, 9 Mar 2025 11:13:21 +0000 Subject: [PATCH 008/146] refactor: Import llm_config from config.py in response_agent.py --- src/response_agent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/response_agent.py b/src/response_agent.py index 5fbcce0..d198f0d 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -28,6 +28,7 @@ has_linked_pr, push_changes_with_authentication, ) +from config import llm_config from github.Repository import Repository from github.Issue import Issue from branch_handler import ( From 799e0f6793d4c0168306c546637dd4810e859657 Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Mon, 17 Mar 2025 10:52:55 -0400 Subject: [PATCH 009/146] feat: Add urlextract, beautifulsoup4, and aider-chat dependencies --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index 4d8c79f..b29158c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,6 @@ pyyaml>=5.4.1 pyautogen>=0.2.0 gitpython>=3.1.40 pre-commit>=3.5.0 +urlextract>=1.0.0 +beautifulsoup4>=4.9.3 +aider-chat>=0.18.0 From 77b873152ac4f1535da148138e13b892d139ffcd Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 17 Mar 2025 11:36:26 -0400 Subject: [PATCH 010/146] refactor(bot_tools): Remove unused functions and migrate required functions to response_agent - Removed unused functions related to file modification and git operations in `bot_tools.py`. - Moved essential functions `get_tracked_repos` and `is_tool_related` from `bot_tools.py` to `response_agent.py`. - Updated `response_agent.py` to use the functions internally, removing dependency on `bot_tools.py` for these operations. - Added a cautionary comment in `bot_tools.py` for safe usage by agents. --- src/agents.py | 1 + src/bot_tools.py | 320 +----------------------------------------- src/response_agent.py | 26 +++- 3 files changed, 27 insertions(+), 320 deletions(-) diff --git a/src/agents.py b/src/agents.py index 36cedca..c8ffac9 100644 --- a/src/agents.py +++ b/src/agents.py @@ -17,6 +17,7 @@ import triggers from urlextract import URLExtract + # Get callable tool functions tool_funcs = [] for func in dir(bot_tools): diff --git a/src/bot_tools.py b/src/bot_tools.py index 818c48b..f02d305 100644 --- a/src/bot_tools.py +++ b/src/bot_tools.py @@ -1,5 +1,7 @@ """ Tools for the agents to use. + +DO NOT PUT ANYTHING HERE THAT IS NOT SAFE FOR THE AGENT TO USE. """ import os @@ -12,14 +14,6 @@ token_threshold = 100_000 -# Keep everything but tool calls - - -def is_tool_related( - x: dict,) -> bool: - if 'tool_calls' in x.keys() or x['role'] == 'tool': - return True - def get_local_repo_path(repo_name: str) -> str: """ @@ -41,20 +35,6 @@ def get_local_repo_path(repo_name: str) -> str: return f"Repository {repo_name} not found @ {repo_path}" -def get_tracked_repos() -> str: - """ - Get the tracked repositories - - Returns: - - List of tracked repositories - """ - tracked_repos_path = os.path.join(base_dir, 'config', 'repos.txt') - with open(tracked_repos_path, 'r') as file: - tracked_repos = file.readlines() - tracked_repos = [repo.strip() for repo in tracked_repos] - return tracked_repos - - def search_for_pattern( search_dir: str, pattern: str, @@ -77,129 +57,6 @@ def search_for_pattern( return out -def search_and_replace( - file_path: str, - search_text: str, - replace_text: str, -) -> bool: - """ - Search and replace text in a file - - Inputs: - - file_path : Path to file - - search_text : Text to search for - - replace_text : Text to replace with - - Returns: - - True if successful, False if search_text not found - """ - # make backup - import shutil - import ast - - shutil.copy2(file_path, file_path + '.bak') - - with open(file_path, 'r') as file: - file_data = file.read() - - # Check for exact match - if search_text not in file_data: - print(f"Search text not found in file: {file_path}") - return False - - new_data = file_data.replace(search_text, replace_text) - - with open(file_path, 'w') as file: - file.write(new_data) - - # Check that file is valid - try: - ast.parse(new_data) - except SyntaxError as e: - print('Editing file created a syntax error') - print(f"Syntax error in file: {file_path}") - print(e) - # Restore backup - shutil.copy2(file_path + '.bak', file_path) - os.remove(file_path + '.bak') - return False - - print('Search and replace successful') - return True - - -def modify_lines( - file_path: str, - start_line: int, - end_line: int, - new_lines: str, -) -> bool: - """ - Modify lines in a file - Don't use escape characters - - - Can delete lines by setting new_lines to empty string - - Can add lines by setting start_line = end_line - - Can modify lines by setting start_line != end_line - - Inputs: - - file_path : Path to file - - start_line : Start line - - end_line : End line (inclusive) - - new_lines : New lines to replace with - - Returns: - - True if successful, False otherwise - """ - - assert start_line <= end_line, "Start line must be less than or equal to end line" - - # make backup - import shutil - import ast - - shutil.copy2(file_path, file_path + '.bak') - - with open(file_path, 'r') as file: - lines = file.readlines() - - # Check for exact match - if len(lines) < end_line: - print(f"End line greater than number of lines in file: {file_path}") - return False - - init_lines = lines[:start_line] - end_lines = lines[end_line+1:] - mod_lines = new_lines.split('\n') - mod_lines = [line + '\n' for line in mod_lines] - - lines = init_lines + mod_lines + end_lines - - with open(file_path, 'w') as file: - # file.write("".join(lines)) - file.writelines(lines) - - # Check that file is valid - try: - ast.parse("".join(lines)) - except SyntaxError as e: - print('Editing file created a syntax error') - print('View around modified lines') - print_start = max(0, start_line - 5) - print_end = min(start_line + len(mod_lines) + 5, len(lines)) - print("".join(f"{i:03}: {line}" for i, - line in enumerate(lines[print_start:print_end]))) - print(f"Syntax error in file: {file_path}") - print(e) - # Restore backup - shutil.copy2(file_path + '.bak', file_path) - os.remove(file_path + '.bak') - return False - - print('Modify lines successful') - return True - - def search_for_file( directory: str, filename: str, @@ -354,179 +211,6 @@ def readlines( return data -def git_fetch( - blech_clust_path: str, -) -> str: - """Fetch from git - - Inputs: - - blech_clust_path : Path to blech_clust - - Returns: - - Output from git fetch - """ - cmd_str = f"git -C {blech_clust_path} fetch" - out = os.popen(cmd_str).read() - return out - - -def get_commit_history( - blech_clust_path: str, - max_num: int = 10, -) -> str: - """Get the commit history - - Inputs: - - blech_clust_path : Path to blech_clust - - max_num : Maximum number of commits to show - - Returns: - - Commit history - """ - cmd_str = \ - f"git -C {blech_clust_path} log --graph --pretty=format:'%C(auto)%h%d (%cr) %s' --abbrev-commit" - out = os.popen(cmd_str).read() - out = "\n".join(out.split("\n")[:max_num]) - return out - - -def get_current_git_commit( - blech_clust_path: str, -) -> str: - """Get the current git commit - - Inputs: - - blech_clust_path : Path to blech_clust - - Returns: - - Current commit - """ - cmd_str = f"git -C {blech_clust_path} rev-parse HEAD" - out = os.popen(cmd_str).read() - return out - - -def change_git_commit( - blech_clust_path: str, - commit: str) -> str: - """Change the current git commit - Inputs: - - Commit - """ - # os.system(f"git checkout {commit}") - git_fetch(blech_clust_path) - cmd_str = f"git -C {blech_clust_path} checkout {commit}" - out = os.popen(cmd_str).read() - return out - - -def create_file( - file_path: str, - data: str, -) -> bool: - """Create a file with given data - - Inputs: - - file_path : Path to file - - data : Data to write - - Returns: - - True if successful, False otherwise - """ - try: - with open(file_path, 'w') as file: - file.write(data) - print(f"Data written to file: {file_path}") - return True - except Exception as e: - print(f"Error writing to file: {file_path}") - print(e) - return False - - -def run_python_script( - script_path: str, -) -> str: - """Run a script - - Inputs: - - script_path : Path to script - - Returns: - - Output from script - """ - out = os.popen(f"python {script_path}").read() - return out - - -def run_bash_script( - script_path: str, -) -> str: - """Run a bash script - - Inputs: - - script_path : Path to script - - Returns: - - Output from script - """ - out = os.popen(f"bash {script_path}").read() - return out - - -# def read_merged_docstrings(repo_name: str) -> str: -# """Read the merged docstrings JSON file from a repository -# -# Inputs: -# - repo_name : Name of repository (owner/repo) -# -# Returns: -# - Text containing merged docstrings data -# """ -# # import json -# repo_path = get_local_repo_path(repo_name) -# docstrings_path = os.path.join(repo_path, 'file_index', 'data', 'merged_docstrings.json') -# -# try: -# with open(docstrings_path, 'r') as f: -# return f.read() -# # return json.load(f) -# except FileNotFoundError: -# print(f"Docstrings file not found at: {docstrings_path}") -# return '' -# # return {} -# # except json.JSONDecodeError: -# # print(f"Error decoding JSON from: {docstrings_path}") -# # return {} - -def read_merged_summary(repo_name: str) -> str: - """Read the merged summary JSON file from a repository - - Inputs: - - repo_name : Name of repository (owner/repo) - - Returns: - - Text containing merged summary data - """ - # import json - repo_path = get_local_repo_path(repo_name) - summary_path = os.path.join( - repo_path, 'file_index', 'data', 'merged_summary.json') - - try: - with open(summary_path, 'r') as f: - return f.read() - # return json.load(f) - - except FileNotFoundError: - print(f"Summary file not found at: {summary_path}") - return '' - # return {} - # except json.JSONDecodeError: - # print(f"Error decoding JSON from: {summary_path}") - # return {} - - def get_func_code( module_path: str, func_name: str, diff --git a/src/response_agent.py b/src/response_agent.py index 30c3393..bcb683c 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -64,6 +64,28 @@ ############################################################ +def get_tracked_repos() -> str: + """ + Get the tracked repositories + + Returns: + - List of tracked repositories + """ + tracked_repos_path = os.path.join(base_dir, 'config', 'repos.txt') + with open(tracked_repos_path, 'r') as file: + tracked_repos = file.readlines() + tracked_repos = [repo.strip() for repo in tracked_repos] + return tracked_repos + +# Keep everything but tool calls + + +def is_tool_related( + x: dict,) -> bool: + if 'tool_calls' in x.keys() or x['role'] == 'tool': + return True + + def check_not_empty(data: str) -> bool: """ Check that given data is not empty and is not a TERMINATE message @@ -331,7 +353,7 @@ def generate_new_response( ) results_to_summarize = [ - [x for x in this_result.chat_history if not bot_tools.is_tool_related( + [x for x in this_result.chat_history if not is_tool_related( x)] for this_result in chat_results ] @@ -786,7 +808,7 @@ def process_repository( if __name__ == '__main__': # Get list of repositories to process - tracked_repos = bot_tools.get_tracked_repos() + tracked_repos = get_tracked_repos() print(f'Found {len(tracked_repos)} tracked repositories') pprint(tracked_repos) From 49cf14374ec09956b74e4b711eda666c7125e360 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 17 Mar 2025 11:36:55 -0400 Subject: [PATCH 011/146] refactor(branch_handler): enhance branch name detection and improve efficiency - Sanitized issue titles by removing punctuation and spaces, improving branch name prediction. - Replaced iteration over remote references with a more efficient `ls-remote` command to fetch heads. - Commented out the old remote branch checking logic for potential future reference. --- src/branch_handler.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/branch_handler.py b/src/branch_handler.py index badb9d4..9e7f7fb 100644 --- a/src/branch_handler.py +++ b/src/branch_handler.py @@ -41,23 +41,33 @@ def get_issue_related_branches( if len(related_branches) == 0: repo = git.Repo(repo_path) - possible_branch_name = f"{issue.number}-{'-'.join(issue.title.lower().split(' '))}" + issue_title_cleaned = issue.title.replace(' ', '-').lower() + # Remove any punctuation from the title + issue_title_cleaned = ''.join( + char for char in issue_title_cleaned if char.isalnum() or char == '-') + possible_branch_name = f"{issue.number}-{issue_title_cleaned}" + + fetched_heads = repo.git.ls_remote('--heads', 'origin').splitlines() # Check local branches for branch in repo.heads: if possible_branch_name in branch.name: related_branches.append((branch.name, False)) + for this_head in fetched_heads: + if possible_branch_name in this_head: + related_branches.append((this_head.split('heads/')[1], True)) + # Check remote branches - for remote in repo.remotes: - for ref in remote.refs: - # Skip HEAD ref - if ref.name.endswith('/HEAD'): - continue - # Remove remote name prefix for comparison - branch_name = ref.name.split('/', 1)[1] - if possible_branch_name in branch_name: - related_branches.append((branch_name, True)) + # for remote in repo.remotes: + # for ref in remote.refs: + # # Skip HEAD ref + # if ref.name.endswith('/HEAD'): + # continue + # # Remove remote name prefix for comparison + # branch_name = ref.name.split('/', 1)[1] + # if possible_branch_name in branch_name: + # related_branches.append((branch_name, True)) os.chdir(orig_dir) return related_branches From cc0ed6a9ca89543bc7b6398c0f81dc027b1e37f0 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 17 Mar 2025 11:42:41 -0400 Subject: [PATCH 012/146] fix(imports): update import statement and standardize directory paths - Modified import of BeautifulSoup: switched from `from bs4 import BeautifulSoup` to `import bs4` to address module usage issue. - Introduced `src_dir` and `base_dir` in `response_agent.py` to standardize path handling after recent project reorganization. This commit resolves import errors and enhances directory path management post-reorganization. --- src/bot_tools.py | 5 +++-- src/response_agent.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/bot_tools.py b/src/bot_tools.py index f02d305..bd8f875 100644 --- a/src/bot_tools.py +++ b/src/bot_tools.py @@ -2,12 +2,13 @@ Tools for the agents to use. DO NOT PUT ANYTHING HERE THAT IS NOT SAFE FOR THE AGENT TO USE. +ALSO CAN'T HAVE CALLABLE MODULES. """ import os import sys import requests -from bs4 import BeautifulSoup +import bs4 src_dir = os.path.dirname(os.path.abspath(__file__)) base_dir = os.path.dirname(src_dir) @@ -265,7 +266,7 @@ def scrape_text_from_url(url: str) -> str: try: response = requests.get(url, timeout=10) response.raise_for_status() # Raise an error for bad responses - soup = BeautifulSoup(response.text, 'html.parser') + soup = bs4.BeautifulSoup(response.text, 'html.parser') # Remove script and style elements for script in soup(["script", "style"]): diff --git a/src/response_agent.py b/src/response_agent.py index bcb683c..b2f2dba 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -49,6 +49,8 @@ from urlextract import URLExtract load_dotenv() +src_dir = os.path.dirname(os.path.abspath(__file__)) +base_dir = os.path.dirname(src_dir) api_key = os.getenv('OPENAI_API_KEY') if not api_key: From 145163669a0253946834f5ac80b5a6a3b1742cac Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Mon, 17 Mar 2025 11:45:40 -0400 Subject: [PATCH 013/146] feat: Add URL scraping and summarization capabilities to GitHub bot --- src/agents.py | 1 + src/bot_tools.py | 54 +++++++++++++++++++++++++++++++++++++++++++ src/response_agent.py | 32 +++++++++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/src/agents.py b/src/agents.py index 36cedca..fac3efd 100644 --- a/src/agents.py +++ b/src/agents.py @@ -16,6 +16,7 @@ import string import triggers from urlextract import URLExtract +from urlextract import URLExtract # Get callable tool functions tool_funcs = [] diff --git a/src/bot_tools.py b/src/bot_tools.py index 818c48b..78b8647 100644 --- a/src/bot_tools.py +++ b/src/bot_tools.py @@ -6,6 +6,8 @@ import sys import requests from bs4 import BeautifulSoup +import requests +from bs4 import BeautifulSoup src_dir = os.path.dirname(os.path.abspath(__file__)) base_dir = os.path.dirname(src_dir) @@ -474,6 +476,58 @@ def run_bash_script( return out +def scrape_text_from_url(url: str) -> str: + """Scrape text content from a given URL. + + Args: + url: The URL to scrape text from. + + Returns: + The scraped text content. + """ + try: + response = requests.get(url, timeout=10) + response.raise_for_status() # Raise an error for bad responses + soup = BeautifulSoup(response.text, 'html.parser') + + # Remove script and style elements + for script in soup(["script", "style"]): + script.extract() + + # Get text + text = soup.get_text() + + # Break into lines and remove leading and trailing space on each + lines = (line.strip() for line in text.splitlines()) + # Break multi-headlines into a line each + chunks = (phrase.strip() + for line in lines for phrase in line.split(" ")) + # Remove blank lines + text = '\n'.join(chunk for chunk in chunks if chunk) + + return text + except requests.RequestException as e: + print(f"Error fetching URL {url}: {e}") + return f"Error fetching URL {url}: {str(e)}" + + +def summarize_text(text: str, max_length: int = 1000) -> str: + """Summarize text to a maximum length. + + Args: + text: The text to summarize. + max_length: Maximum length of the summary. + + Returns: + The summarized text. + """ + if len(text) <= max_length: + return text + + # Simple truncation with ellipsis for now + return text[:max_length] + "...\n[Text truncated due to length]" + + # def read_merged_docstrings(repo_name: str) -> str: # """Read the merged docstrings JSON file from a repository # diff --git a/src/response_agent.py b/src/response_agent.py index 30c3393..bd3a32e 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -47,6 +47,7 @@ import json import re from urlextract import URLExtract +from urlextract import URLExtract load_dotenv() @@ -76,6 +77,37 @@ def check_not_empty(data: str) -> bool: return False +def extract_urls_from_issue(issue: Issue) -> List[str]: + """ + Extract URLs from issue body and comments + + Args: + issue: The GitHub issue to extract URLs from + + Returns: + List of URLs found in the issue + """ + extractor = URLExtract() + urls = [] + + # Extract from issue body + issue_body = issue.body or "" + urls.extend(extractor.find_urls(issue_body)) + + # Extract from comments + for comment in get_issue_comments(issue): + comment_body = comment.body or "" + urls.extend(extractor.find_urls(comment_body)) + + # Remove duplicates while preserving order + unique_urls = [] + for url in urls: + if url not in unique_urls: + unique_urls.append(url) + + return unique_urls + + def summarize_relevant_comments( issue: Issue, repo_name: str, From 91ffce35e22f0ca3ea46cbcef657528245593c9c Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Mon, 17 Mar 2025 11:45:57 -0400 Subject: [PATCH 014/146] feat: Remove duplicate imports and functions --- src/agents.py | 1 - src/bot_tools.py | 52 ------------------------------------------- src/response_agent.py | 30 ------------------------- 3 files changed, 83 deletions(-) diff --git a/src/agents.py b/src/agents.py index fac3efd..36cedca 100644 --- a/src/agents.py +++ b/src/agents.py @@ -16,7 +16,6 @@ import string import triggers from urlextract import URLExtract -from urlextract import URLExtract # Get callable tool functions tool_funcs = [] diff --git a/src/bot_tools.py b/src/bot_tools.py index 78b8647..a03c7b0 100644 --- a/src/bot_tools.py +++ b/src/bot_tools.py @@ -6,8 +6,6 @@ import sys import requests from bs4 import BeautifulSoup -import requests -from bs4 import BeautifulSoup src_dir = os.path.dirname(os.path.abspath(__file__)) base_dir = os.path.dirname(src_dir) @@ -476,56 +474,6 @@ def run_bash_script( return out -def scrape_text_from_url(url: str) -> str: - """Scrape text content from a given URL. - - Args: - url: The URL to scrape text from. - - Returns: - The scraped text content. - """ - try: - response = requests.get(url, timeout=10) - response.raise_for_status() # Raise an error for bad responses - soup = BeautifulSoup(response.text, 'html.parser') - - # Remove script and style elements - for script in soup(["script", "style"]): - script.extract() - - # Get text - text = soup.get_text() - - # Break into lines and remove leading and trailing space on each - lines = (line.strip() for line in text.splitlines()) - # Break multi-headlines into a line each - chunks = (phrase.strip() - for line in lines for phrase in line.split(" ")) - # Remove blank lines - text = '\n'.join(chunk for chunk in chunks if chunk) - - return text - except requests.RequestException as e: - print(f"Error fetching URL {url}: {e}") - return f"Error fetching URL {url}: {str(e)}" - - -def summarize_text(text: str, max_length: int = 1000) -> str: - """Summarize text to a maximum length. - - Args: - text: The text to summarize. - max_length: Maximum length of the summary. - - Returns: - The summarized text. - """ - if len(text) <= max_length: - return text - - # Simple truncation with ellipsis for now - return text[:max_length] + "...\n[Text truncated due to length]" # def read_merged_docstrings(repo_name: str) -> str: diff --git a/src/response_agent.py b/src/response_agent.py index bd3a32e..f1a4d3a 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -47,7 +47,6 @@ import json import re from urlextract import URLExtract -from urlextract import URLExtract load_dotenv() @@ -77,35 +76,6 @@ def check_not_empty(data: str) -> bool: return False -def extract_urls_from_issue(issue: Issue) -> List[str]: - """ - Extract URLs from issue body and comments - - Args: - issue: The GitHub issue to extract URLs from - - Returns: - List of URLs found in the issue - """ - extractor = URLExtract() - urls = [] - - # Extract from issue body - issue_body = issue.body or "" - urls.extend(extractor.find_urls(issue_body)) - - # Extract from comments - for comment in get_issue_comments(issue): - comment_body = comment.body or "" - urls.extend(extractor.find_urls(comment_body)) - - # Remove duplicates while preserving order - unique_urls = [] - for url in urls: - if url not in unique_urls: - unique_urls.append(url) - - return unique_urls def summarize_relevant_comments( From d48d8df428093ef6a31ced3b5eefea90835402f6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 15:46:18 +0000 Subject: [PATCH 015/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/bot_tools.py | 2 -- src/response_agent.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/bot_tools.py b/src/bot_tools.py index a03c7b0..818c48b 100644 --- a/src/bot_tools.py +++ b/src/bot_tools.py @@ -474,8 +474,6 @@ def run_bash_script( return out - - # def read_merged_docstrings(repo_name: str) -> str: # """Read the merged docstrings JSON file from a repository # diff --git a/src/response_agent.py b/src/response_agent.py index f1a4d3a..30c3393 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -76,8 +76,6 @@ def check_not_empty(data: str) -> bool: return False - - def summarize_relevant_comments( issue: Issue, repo_name: str, From c6e91148d3cd95eb5c0ab6930fbc4a0b940e833b Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 17 Mar 2025 11:53:50 -0400 Subject: [PATCH 016/146] refactor(scraping): restructure logic for improved organization - Removed `scrape_text_from_url` and `summarize_text` from `bot_tools.py` to streamline module responsibilities. - Transferred these functions to `response_agent.py` to consolidate scraping capabilities. - Updated function calls in `response_agent.py` to use the relocated local versions, ensuring seamless operation. - Cleaned up `agents.py` by removing outdated system messages related to URL scraping. --- src/agents.py | 3 -- src/bot_tools.py | 52 ----------------------------------- src/response_agent.py | 64 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 58 insertions(+), 61 deletions(-) diff --git a/src/agents.py b/src/agents.py index c8ffac9..eeecdbc 100644 --- a/src/agents.py +++ b/src/agents.py @@ -33,7 +33,6 @@ NEVER ask for user input and NEVER expect it. Return file names that are relevant, and if possible, specific lines where changes can be made. Instead of listing the whole dir, use read_merged_summary or read_merged_docstrings - If URLs are present in the issue, use scrape_text_from_url to extract content and analyze it. Reply "TERMINATE" in the end when everything is done. """, "edit_assistant": """You are a helpful GitHub bot that reviews issues and generates appropriate responses. @@ -44,13 +43,11 @@ NEVER ask for user input and NEVER expect it. If possible, suggest concrete code changes or additions that can be made. Be specific about what files and what lines. Include file paths, line numbers, and exact code changes where possible. - If URLs are present in the issue, use scrape_text_from_url to extract content and analyze it. Format the command in a way that can be parsed by automated tools. Reply "TERMINATE" in the end when everything is done. """, "summary_assistant": """You are a helpful GitHub bot that reviews issues and generates appropriate responses. Analyze the issue details carefully and summarize the suggestions and changes made by other agents. - If URLs are present in the issue, use the scraped content to provide more context in your summary. """, "feedback_assistant": """You are a helpful GitHub bot that processes user feedback on previous bot responses. Analyze the user's feedback carefully and suggest improvements to the original response. diff --git a/src/bot_tools.py b/src/bot_tools.py index bd8f875..fe652c5 100644 --- a/src/bot_tools.py +++ b/src/bot_tools.py @@ -252,55 +252,3 @@ def get_func_code( # Get code for function code = "".join(lines[start_line:end_line]) return code - - -def scrape_text_from_url(url: str) -> str: - """Scrape text content from a given URL. - - Args: - url: The URL to scrape text from. - - Returns: - The scraped text content. - """ - try: - response = requests.get(url, timeout=10) - response.raise_for_status() # Raise an error for bad responses - soup = bs4.BeautifulSoup(response.text, 'html.parser') - - # Remove script and style elements - for script in soup(["script", "style"]): - script.extract() - - # Get text - text = soup.get_text() - - # Break into lines and remove leading and trailing space on each - lines = (line.strip() for line in text.splitlines()) - # Break multi-headlines into a line each - chunks = (phrase.strip() - for line in lines for phrase in line.split(" ")) - # Remove blank lines - text = '\n'.join(chunk for chunk in chunks if chunk) - - return text - except requests.RequestException as e: - print(f"Error fetching URL {url}: {e}") - return f"Error fetching URL {url}: {str(e)}" - - -def summarize_text(text: str, max_length: int = 1000) -> str: - """Summarize text to a maximum length. - - Args: - text: The text to summarize. - max_length: Maximum length of the summary. - - Returns: - The summarized text. - """ - if len(text) <= max_length: - return text - - # Simple truncation with ellipsis for now - return text[:max_length] + "...\n[Text truncated due to length]" diff --git a/src/response_agent.py b/src/response_agent.py index b2f2dba..dab5de8 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -66,6 +66,58 @@ ############################################################ +def scrape_text_from_url(url: str) -> str: + """Scrape text content from a given URL. + + Args: + url: The URL to scrape text from. + + Returns: + The scraped text content. + """ + try: + response = requests.get(url, timeout=10) + response.raise_for_status() # Raise an error for bad responses + soup = bs4.BeautifulSoup(response.text, 'html.parser') + + # Remove script and style elements + for script in soup(["script", "style"]): + script.extract() + + # Get text + text = soup.get_text() + + # Break into lines and remove leading and trailing space on each + lines = (line.strip() for line in text.splitlines()) + # Break multi-headlines into a line each + chunks = (phrase.strip() + for line in lines for phrase in line.split(" ")) + # Remove blank lines + text = '\n'.join(chunk for chunk in chunks if chunk) + + return text + except requests.RequestException as e: + print(f"Error fetching URL {url}: {e}") + return f"Error fetching URL {url}: {str(e)}" + + +def summarize_text(text: str, max_length: int = 1000) -> str: + """Summarize text to a maximum length. + + Args: + text: The text to summarize. + max_length: Maximum length of the summary. + + Returns: + The summarized text. + """ + if len(text) <= max_length: + return text + + # Simple truncation with ellipsis for now + return text[:max_length] + "...\n[Text truncated due to length]" + + def get_tracked_repos() -> str: """ Get the tracked repositories @@ -197,9 +249,9 @@ def generate_feedback_response( print(f"Found {len(urls)} URLs in issue") for url in urls: print(f"Scraping content from {url}") - content = bot_tools.scrape_text_from_url(url) + content = scrape_text_from_url(url) # Summarize content to avoid token limits - summarized_content = bot_tools.summarize_text(content) + summarized_content = summarize_text(content) url_contents[url] = summarized_content # Add URL contents to issue details @@ -312,9 +364,9 @@ def generate_new_response( print(f"Found {len(urls)} URLs in issue") for url in urls: print(f"Scraping content from {url}") - content = bot_tools.scrape_text_from_url(url) + content = scrape_text_from_url(url) # Summarize content to avoid token limits - summarized_content = bot_tools.summarize_text(content) + summarized_content = summarize_text(content) url_contents[url] = summarized_content # Add URL contents to issue details @@ -417,9 +469,9 @@ def generate_edit_command_response( print(f"Found {len(urls)} URLs in issue") for url in urls: print(f"Scraping content from {url}") - content = bot_tools.scrape_text_from_url(url) + content = scrape_text_from_url(url) # Summarize content to avoid token limits - summarized_content = bot_tools.summarize_text(content) + summarized_content = summarize_text(content) url_contents[url] = summarized_content # Add URL contents to issue details From 5be93801f01dd505b9e13cc2f27c7e1b4fed34cb Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 17 Mar 2025 12:04:05 -0400 Subject: [PATCH 017/146] refactor: clean up imports and remove duplicate function - Removed unused imports `requests` and `bs4` from `src/bot_tools.py`. - Added necessary imports `requests` and `bs4` to `src/response_agent.py`. - Removed duplicate definition of `extract_urls_from_issue` function in `src/response_agent.py`. - Cleaned up comments in `src/agents.py` by removing redundant lines about `read_merged_summary` and `read_merged_docstrings`. --- src/agents.py | 3 -- src/bot_tools.py | 2 -- src/response_agent.py | 64 ++++++++++++++++++++++--------------------- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/agents.py b/src/agents.py index eeecdbc..7bd9b14 100644 --- a/src/agents.py +++ b/src/agents.py @@ -32,7 +32,6 @@ DO NOT MAKE ANY CHANGES TO THE FILES OR CREATE NEW FILES. Only provide information or suggestions. NEVER ask for user input and NEVER expect it. Return file names that are relevant, and if possible, specific lines where changes can be made. - Instead of listing the whole dir, use read_merged_summary or read_merged_docstrings Reply "TERMINATE" in the end when everything is done. """, "edit_assistant": """You are a helpful GitHub bot that reviews issues and generates appropriate responses. @@ -225,7 +224,6 @@ def generate_prompt( Generate a helpful and specific response addressing the issue contents. Use the tools you have. Do not ask for user input or expect it. - To find details of files use read_merged_summary or read_merged_docstrings If those are not functioning, use tools like search_for_file to search for .py files, or other tools you have. Return response in format: @@ -283,7 +281,6 @@ def generate_prompt( Use the tools you have. Do not ask for user input or expect it. DO NOT SUGGEST CODE EXECUTIONS. Only make code editing suggestions. - To find details of files use read_merged_summary or read_merged_docstrings If those are not functioning, use tools like search_for_file to search for .py files, or other tools you have. Try to read the whole file (readfile) to understand context where possible. If file is too large, search for specific functions or classes (get_func_code). If you can't find functions to classes, try reading sets of lines repeatedly (readlines). Finish the job by suggesting specific lines in specific files where changes can be made. diff --git a/src/bot_tools.py b/src/bot_tools.py index fe652c5..6af116a 100644 --- a/src/bot_tools.py +++ b/src/bot_tools.py @@ -7,8 +7,6 @@ import os import sys -import requests -import bs4 src_dir = os.path.dirname(os.path.abspath(__file__)) base_dir = os.path.dirname(src_dir) diff --git a/src/response_agent.py b/src/response_agent.py index dab5de8..9781a3f 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -47,6 +47,8 @@ import json import re from urlextract import URLExtract +import requests +import bs4 load_dotenv() src_dir = os.path.dirname(os.path.abspath(__file__)) @@ -66,6 +68,37 @@ ############################################################ +def extract_urls_from_issue(issue: Issue) -> List[str]: + """ + Extract URLs from issue body and comments + + Args: + issue: The GitHub issue to extract URLs from + + Returns: + List of URLs found in the issue + """ + extractor = URLExtract() + urls = [] + + # Extract from issue body + issue_body = issue.body or "" + urls.extend(extractor.find_urls(issue_body)) + + # Extract from comments + for comment in get_issue_comments(issue): + comment_body = comment.body or "" + urls.extend(extractor.find_urls(comment_body)) + + # Remove duplicates while preserving order + unique_urls = [] + for url in urls: + if url not in unique_urls: + unique_urls.append(url) + + return unique_urls + + def scrape_text_from_url(url: str) -> str: """Scrape text content from a given URL. @@ -304,37 +337,6 @@ def generate_feedback_response( return updated_response + signature, all_content -def extract_urls_from_issue(issue: Issue) -> List[str]: - """ - Extract URLs from issue body and comments - - Args: - issue: The GitHub issue to extract URLs from - - Returns: - List of URLs found in the issue - """ - extractor = URLExtract() - urls = [] - - # Extract from issue body - issue_body = issue.body or "" - urls.extend(extractor.find_urls(issue_body)) - - # Extract from comments - for comment in get_issue_comments(issue): - comment_body = comment.body or "" - urls.extend(extractor.find_urls(comment_body)) - - # Remove duplicates while preserving order - unique_urls = [] - for url in urls: - if url not in unique_urls: - unique_urls.append(url) - - return unique_urls - - def generate_new_response( issue: Issue, repo_name: str, From f1e3380a29a1c7a4534923e405a79201a1bb3481 Mon Sep 17 00:00:00 2001 From: abuzarmahmood Date: Mon, 17 Mar 2025 16:06:52 +0000 Subject: [PATCH 018/146] Update max number of turns to 20 --- src/response_agent.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index 9781a3f..51968e0 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -60,6 +60,7 @@ llm_config = { "model": "gpt-4o", + # "model": "o3-mini-2025-01-31", "api_key": api_key, "temperature": random.uniform(0, 0.2), } @@ -257,7 +258,7 @@ def summarize_relevant_comments( def generate_feedback_response( issue: Issue, repo_name: str, - max_turns: int = 10, + max_turns: int = 20, ) -> Tuple[str, list]: """Generate an improved response based on user feedback @@ -396,13 +397,13 @@ def generate_new_response( { "recipient": file_assistant, "message": file_prompt, - "max_turns": 10, + "max_turns": 20, "summary_method": "last_msg", }, { "recipient": edit_assistant, "message": edit_prompt, - "max_turns": 10, + "max_turns": 20, "summary_method": "reflection_with_llm", }, ] @@ -499,7 +500,7 @@ def generate_edit_command_response( { "recipient": generate_edit_command_assistant, "message": generate_edit_command_prompt, - "max_turns": 10, + "max_turns": 20, "summary_method": "reflection_with_llm", }, ] From 9244505a698a3e060aeaee44b02766ed0198f226 Mon Sep 17 00:00:00 2001 From: abuzarmahmood Date: Mon, 17 Mar 2025 16:07:11 +0000 Subject: [PATCH 019/146] Checkout local changes before making any other changes --- src/branch_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/branch_handler.py b/src/branch_handler.py index 9e7f7fb..ec43c18 100644 --- a/src/branch_handler.py +++ b/src/branch_handler.py @@ -129,6 +129,8 @@ def checkout_branch(repo_path: str, branch_name: str, create: bool = False) -> N create: If True, create branch if it doesn't exist """ repo = git.Repo(repo_path) + # Get rid of uncommited local changes + repo.git.checkout(repo_path) if create and branch_name not in repo.heads: repo.create_head(branch_name) print(f"Created branch {branch_name}") From b445d98fc94b1f1f84f4df1c3240f6999d1017b4 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 17 Mar 2025 12:31:56 -0400 Subject: [PATCH 020/146] feat(update): enhance repository update process - Initialize GitHub client and determine the default branch using the GitHub API. - Remove fallback mechanism for determining the default branch locally. - Introduce reading configuration from `params.json` to control auto-update behavior. - Modify bot initialization to conditionally update the repository based on `auto_update` parameter. --- src/git_utils.py | 15 ++++++--------- src/response_agent.py | 26 +++++++++++++++++--------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 3237720..8317d62 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -416,6 +416,12 @@ def update_self_repo(repo_path: str) -> None: git_repo = git.Repo(repo_path) origin = git_repo.remotes.origin + # Initialize GitHub client + client = get_github_client() + github_repo = get_repository(client, repo_name) + # Determine the default branch + default_branch = github_repo.default_branch + # Backup config/repos.txt config_repos_path = os.path.join(repo_path, 'config', 'repos.txt') backup_path = os.path.join(repo_path, 'config', 'repos.txt.backup') @@ -443,15 +449,6 @@ def update_self_repo(repo_path: str) -> None: if remote_commit and local_commit != remote_commit: print("Remote is ahead. Force pulling latest changes for self-repo.") - # Get the default branch name - default_branch = None - for ref in git_repo.references: - if ref.name == 'HEAD': - default_branch = ref.reference.name.replace('refs/heads/', '') - break - - if not default_branch: - default_branch = 'master' # Fallback # Hard reset to remote branch git_repo.git.reset('--hard', f'origin/{default_branch}') diff --git a/src/response_agent.py b/src/response_agent.py index 290786c..3040e5e 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -55,6 +55,10 @@ src_dir = os.path.dirname(os.path.abspath(__file__)) base_dir = os.path.dirname(src_dir) +# Read config/params.json +with open(os.path.join(base_dir, 'config', 'params.json')) as f: + params = json.load(f) + api_key = os.getenv('OPENAI_API_KEY') if not api_key: raise ValueError("OpenAI API key not found in environment variables") @@ -868,15 +872,19 @@ def initialize_bot() -> None: """ Initialize the bot and ensure it is up-to-date. """ - # Path to the bot's own repository - self_repo_path = os.path.dirname( - os.path.dirname(os.path.abspath(__file__))) - - # Update the bot's own repository - from git_utils import update_self_repo - print(f"Updating bot repository at {self_repo_path}") - update_self_repo(self_repo_path) - print("Bot repository update complete") + if params['auto_update']: + print("Updating bot repository...") + # Path to the bot's own repository + self_repo_path = os.path.dirname( + os.path.dirname(os.path.abspath(__file__))) + + # Update the bot's own repository + from git_utils import update_self_repo + print(f"Updating bot repository at {self_repo_path}") + update_self_repo(self_repo_path) + print("Bot repository update complete") + else: + print("Auto-update is disabled. Skipping bot repository update.") if __name__ == '__main__': From 81b253102a3bc33a4066b1f7867c742ff465f02e Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 17 Mar 2025 12:33:32 -0400 Subject: [PATCH 021/146] feat(config): add auto-update parameter - Introduced a `params.json` file to configure parameters. - Added an `auto_update` option to streamline updates. --- config/params.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 config/params.json diff --git a/config/params.json b/config/params.json new file mode 100644 index 0000000..4c534dd --- /dev/null +++ b/config/params.json @@ -0,0 +1,3 @@ +{ + "auto_update": true +} From b6c3d354cb08589570c61f06593a5eec812c8cfa Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 17 Mar 2025 12:36:28 -0400 Subject: [PATCH 022/146] refactor(git_utils): format function definition and add logging statement - Reformatted `update_self_repo` function definition for better readability. - Added a print statement to log the repository name being updated. --- src/git_utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/git_utils.py b/src/git_utils.py index 8317d62..4e2edbb 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -402,7 +402,9 @@ def get_linked_pr(issue: Issue) -> PullRequest: return None -def update_self_repo(repo_path: str) -> None: +def update_self_repo( + repo_path: str, +) -> None: """ Pull latest changes for the bot's own repository, handling tracked config files. @@ -421,6 +423,9 @@ def update_self_repo(repo_path: str) -> None: github_repo = get_repository(client, repo_name) # Determine the default branch default_branch = github_repo.default_branch + repo_name = github_repo.full_name + + print(f"Updating self-repo {repo_name}...") # Backup config/repos.txt config_repos_path = os.path.join(repo_path, 'config', 'repos.txt') From 387a8280256267333b98f0600294ca5893a2f575 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 17 Mar 2025 12:44:17 -0400 Subject: [PATCH 023/146] refactor(git_utils): improve repository name extraction logic - Extracted the logic to determine the repository's name from the remote URL before initializing the GitHub client. - Removed the redundant assignment of `repo_name` using `github_repo.full_name`. - Improved the clarity of the approach by breaking down the URL parsing into discrete steps. --- src/git_utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/git_utils.py b/src/git_utils.py index 4e2edbb..bbae0d3 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -417,13 +417,16 @@ def update_self_repo( git_repo = git.Repo(repo_path) origin = git_repo.remotes.origin + # Get repo username/repo_name + url_splits = git_repo.remotes.origin.url.split('/')[-2:] + repo_basename = url_splits[-1].split('.')[0] + repo_name = url_splits[-2] + '/' + repo_basename # Initialize GitHub client client = get_github_client() github_repo = get_repository(client, repo_name) # Determine the default branch default_branch = github_repo.default_branch - repo_name = github_repo.full_name print(f"Updating self-repo {repo_name}...") From 8364c12b93fe93d6f43363ee2bf31316dc20e8ed Mon Sep 17 00:00:00 2001 From: abuzarmahmood Date: Mon, 17 Mar 2025 19:09:55 +0000 Subject: [PATCH 024/146] Fix logic for cleaning repo --- src/branch_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/branch_handler.py b/src/branch_handler.py index ec43c18..4e3dd6c 100644 --- a/src/branch_handler.py +++ b/src/branch_handler.py @@ -130,7 +130,7 @@ def checkout_branch(repo_path: str, branch_name: str, create: bool = False) -> N """ repo = git.Repo(repo_path) # Get rid of uncommited local changes - repo.git.checkout(repo_path) + repo.git.clean('-f') if create and branch_name not in repo.heads: repo.create_head(branch_name) print(f"Created branch {branch_name}") From 03d13ea052520ce6c8dda5a1173be45ae122439f Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 17 Mar 2025 15:10:50 -0400 Subject: [PATCH 025/146] refactor(git-utils): remove unused import and update push error handling - Removed the unused `IssueComment` import. - Updated the type hint in `push_changes_with_authentication` function from `IssueComment | PullRequest` to `Issue | PullRequest`. - Improved error message handling to check and prevent duplicate comments for failed push operations. --- src/git_utils.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index bbae0d3..64931af 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -14,7 +14,6 @@ from github import Github from github.Issue import Issue from github.Repository import Repository -from github.IssueComment import IssueComment from github.PullRequest import PullRequest from dotenv import load_dotenv import re @@ -301,7 +300,7 @@ def create_pull_request_from_issue(issue: Issue, repo_path: str) -> str: def push_changes_with_authentication( repo_path: str, # pull_request: PullRequest, - out_thread: IssueComment | PullRequest, + out_thread: Issue | PullRequest, branch_name: Optional[str] = None ) -> Tuple[bool, Optional[str]]: """ @@ -334,8 +333,10 @@ def push_changes_with_authentication( success_bool = True except git.GitCommandError as e: error_msg = f"Failed to push changes: {e.stderr.strip()}" - if isinstance(out_thread, IssueComment): - write_issue_response(out_thread, error_msg) + if isinstance(out_thread, Issue): + issue_comments = list(out_thread.get_comments()) + if 'Failed to push changes' not in issue_comments[-1].body: + write_issue_response(out_thread, error_msg) elif isinstance(out_thread, PullRequest): pr_comments = list(out_thread.get_issue_comments()) if 'Failed to push changes' not in pr_comments[-1].body: From 5403af17f46592d10616c8ba6c615ee4231af05d Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 17 Mar 2025 15:24:35 -0400 Subject: [PATCH 026/146] feat(agent): add change detection after Aider execution - Imported `IssueComment` from the GitHub module in `git_utils.py`. - Updated `run_aider` function in `response_agent.py`: - Stored the current commit hash before running Aider. - Introduced a conditional to check if Aider made any changes by comparing commit hashes. - Raised a `RuntimeError` if no changes were detected after Aider execution. --- src/git_utils.py | 1 + src/response_agent.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/git_utils.py b/src/git_utils.py index 64931af..7c1695a 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -14,6 +14,7 @@ from github import Github from github.Issue import Issue from github.Repository import Repository +from github.IssueComment import IssueComment from github.PullRequest import PullRequest from dotenv import load_dotenv import re diff --git a/src/response_agent.py b/src/response_agent.py index 3040e5e..c35cce8 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -792,10 +792,14 @@ def run_aider(message: str, repo_path: str) -> str: FileNotFoundError: If aider is not installed """ try: + # Change to repo directory original_dir = os.getcwd() os.chdir(repo_path) + # Current commit + current_commit = git.Repo(repo_path).head.object.hexsha + # Run aider with the message result = subprocess.run( ['aider', '--sonnet', '--yes-always', '--message', message], @@ -811,6 +815,11 @@ def run_aider(message: str, repo_path: str) -> str: text=True ) + # Check if there are any changes + updated_commit = git.Repo(repo_path).head.object.hexsha + if current_commit == updated_commit: + raise RuntimeError("No changes made by Aider") + # Return to original directory os.chdir(original_dir) From 5f590bbf98c7f713387dc1c1aa68ce5b530957d8 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 17 Mar 2025 15:37:29 -0400 Subject: [PATCH 027/146] fix(branch_handler): allow underscores in branch names - Modified the `get_issue_related_branches` function to include underscores (`_`) in valid branch names. - This change ensures that branch names derived from issue titles do not exclude underscores, providing more flexibility in naming conventions. --- src/branch_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/branch_handler.py b/src/branch_handler.py index 4e3dd6c..6541aeb 100644 --- a/src/branch_handler.py +++ b/src/branch_handler.py @@ -44,7 +44,7 @@ def get_issue_related_branches( issue_title_cleaned = issue.title.replace(' ', '-').lower() # Remove any punctuation from the title issue_title_cleaned = ''.join( - char for char in issue_title_cleaned if char.isalnum() or char == '-') + char for char in issue_title_cleaned if char.isalnum() or char == '-' or char == '_') possible_branch_name = f"{issue.number}-{issue_title_cleaned}" fetched_heads = repo.git.ls_remote('--heads', 'origin').splitlines() From a156e5e55add0f1ce1980909441680533e1ce1f0 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 17 Mar 2025 15:49:24 -0400 Subject: [PATCH 028/146] chore(response_agent): add git module import - Import the `git` module to expand functionality in `response_agent.py`. - This import might be intended for future use, as no new usage is shown in this diff. --- src/response_agent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/response_agent.py b/src/response_agent.py index c35cce8..14559a5 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -50,6 +50,7 @@ from urlextract import URLExtract import requests import bs4 +import git load_dotenv() src_dir = os.path.dirname(os.path.abspath(__file__)) From 7c0e40739cdb3c8105eeb92b83631e756e9c4854 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Mon, 17 Mar 2025 21:18:04 +0000 Subject: [PATCH 029/146] fix: Prevent double signatures in bot responses --- src/git_utils.py | 27 +++++++++++++++++++++------ src/response_agent.py | 13 ++++++++++--- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 7c1695a..dea8eaf 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -25,9 +25,13 @@ def clean_response(response: str) -> str: # Remove TERMINATE flags response = re.sub(r'\bTERMINATE\b', '', response, flags=re.IGNORECASE) - # Remove existing signatures - response = re.sub( - r'\n\n---\n\*This response was automatically generated by blech_bot\*\s*$', '', response) + # Don't remove signatures with model info, as they should be preserved + # Only remove duplicate basic signatures if they exist + basic_signature = r'\n\n---\n\*This response was automatically generated by blech_bot\*\s*$' + if response.count(basic_signature) > 1: + # Keep only the first occurrence + parts = re.split(basic_signature, response) + response = parts[0] + basic_signature + ''.join(parts[1:]) return response.strip() @@ -101,9 +105,20 @@ def write_issue_response(issue: Issue, response_text: str) -> IssueComment: The created comment """ response_text = clean_response(response_text) - signature = "\n\n---\n*This response was automatically generated by blech_bot*" - full_response = response_text + signature - return create_issue_comment(issue, full_response) + + # Check if response already has a signature with model info + model_signature_pattern = r"\n\n---\n\*This response was automatically generated by blech_bot using model .+\*" + has_model_signature = bool(re.search(model_signature_pattern, response_text)) + + # Check if response has the basic signature + basic_signature = "\n\n---\n*This response was automatically generated by blech_bot*" + has_basic_signature = basic_signature in response_text + + # Only add signature if no signature exists + if not has_model_signature and not has_basic_signature: + response_text += basic_signature + + return create_issue_comment(issue, response_text) def iterate_issues(repo: Repository): diff --git a/src/response_agent.py b/src/response_agent.py index 14559a5..98231cd 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -341,7 +341,9 @@ def generate_feedback_response( break all_content = [original_response, feedback_text, updated_response] signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" - return updated_response + signature, all_content + if signature not in updated_response: + updated_response += signature + return updated_response, all_content def generate_new_response( @@ -520,7 +522,9 @@ def generate_edit_command_response( break all_content = [response] signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" - return response + signature, all_content + if signature not in response: + response += signature + return response, all_content ############################################################ # Processing logic @@ -745,7 +749,10 @@ def process_issue( write_str = f"Generated edit command:\n---\n{response}\n\n" + \ f"Aider output:\n
View Aider Output\n\n```{aider_output}```\n
" signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" - full_response = write_str + signature + if signature not in write_str: + full_response = write_str + signature + else: + full_response = write_str pull.create_issue_comment(full_response) # Switch back to main branch From e8f87e17dc11e9999e5de21404e75c0b18aa09f2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 21:18:29 +0000 Subject: [PATCH 030/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/git_utils.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index dea8eaf..324b8e8 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -105,19 +105,20 @@ def write_issue_response(issue: Issue, response_text: str) -> IssueComment: The created comment """ response_text = clean_response(response_text) - + # Check if response already has a signature with model info model_signature_pattern = r"\n\n---\n\*This response was automatically generated by blech_bot using model .+\*" - has_model_signature = bool(re.search(model_signature_pattern, response_text)) - + has_model_signature = bool( + re.search(model_signature_pattern, response_text)) + # Check if response has the basic signature basic_signature = "\n\n---\n*This response was automatically generated by blech_bot*" has_basic_signature = basic_signature in response_text - + # Only add signature if no signature exists if not has_model_signature and not has_basic_signature: response_text += basic_signature - + return create_issue_comment(issue, response_text) From 507beb2d7f67ab00b960daac214aef4818d496db Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Mon, 17 Mar 2025 22:53:35 +0000 Subject: [PATCH 031/146] fix: Prevent double signatures by cleaning responses before adding model-specific signature --- src/git_utils.py | 8 +++++++- src/response_agent.py | 24 ++++++++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 324b8e8..61886da 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -117,7 +117,13 @@ def write_issue_response(issue: Issue, response_text: str) -> IssueComment: # Only add signature if no signature exists if not has_model_signature and not has_basic_signature: - response_text += basic_signature + # Import the model info from response_agent if available + try: + from response_agent import llm_config + signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" + except (ImportError, KeyError): + signature = basic_signature + response_text += signature return create_issue_comment(issue, response_text) diff --git a/src/response_agent.py b/src/response_agent.py index 98231cd..2f596ca 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -340,6 +340,8 @@ def generate_feedback_response( updated_response = this_content break all_content = [original_response, feedback_text, updated_response] + # Clean the response first to remove any existing signatures + updated_response = bot_tools.clean_response(updated_response) signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" if signature not in updated_response: updated_response += signature @@ -445,8 +447,12 @@ def generate_new_response( response = summary_results.chat_history[-1]['content'] all_content = results_to_summarize + [response] + # Clean the response first to remove any existing signatures + response = bot_tools.clean_response(response) signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" - return response + signature, all_content + if signature not in response: + response += signature + return response, all_content def generate_edit_command_response( @@ -521,6 +527,8 @@ def generate_edit_command_response( response = this_content break all_content = [response] + # Clean the response first to remove any existing signatures + response = bot_tools.clean_response(response) signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" if signature not in response: response += signature @@ -669,7 +677,11 @@ def process_issue( # Write response write_str = f"Applied changes based on comment:\n
View Aider Output\n\n```\n{aider_output}\n```\n
" signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" - pr.create_issue_comment(write_str+signature) + # Clean the response first to remove any existing signatures + write_str = bot_tools.clean_response(write_str) + if signature not in write_str: + write_str += signature + pr.create_issue_comment(write_str) # Clean up back_to_master_branch(repo_path) @@ -748,12 +760,12 @@ def process_issue( # write_issue_response(issue, "Generated edit command:\n" + response) write_str = f"Generated edit command:\n---\n{response}\n\n" + \ f"Aider output:\n
View Aider Output\n\n```{aider_output}```\n
" + # Clean the response first to remove any existing signatures + write_str = bot_tools.clean_response(write_str) signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" if signature not in write_str: - full_response = write_str + signature - else: - full_response = write_str - pull.create_issue_comment(full_response) + write_str += signature + pull.create_issue_comment(write_str) # Switch back to main branch back_to_master_branch(repo_path) From a1433f32e3b02a3b88ada6c9b885c00dd845576d Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Mon, 17 Mar 2025 22:53:45 +0000 Subject: [PATCH 032/146] feat: Add clean_response function to remove signatures and TERMINATE flags --- src/response_agent.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/response_agent.py b/src/response_agent.py index 2f596ca..19cc8c5 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -192,6 +192,30 @@ def check_not_empty(data: str) -> bool: return False +def clean_response(response: str) -> str: + """ + Remove any existing signatures or TERMINATE flags from response text + + Args: + response: The response text to clean + + Returns: + Cleaned response text without signatures or TERMINATE flags + """ + # Remove TERMINATE flags + response = re.sub(r'\bTERMINATE\b', '', response, flags=re.IGNORECASE) + + # Remove model-specific signatures + model_signature_pattern = r'\n\n---\n\*This response was automatically generated by blech_bot using model .+\*\s*$' + response = re.sub(model_signature_pattern, '', response) + + # Remove basic signatures + basic_signature = r'\n\n---\n\*This response was automatically generated by blech_bot\*\s*$' + response = re.sub(basic_signature, '', response) + + return response.strip() + + def summarize_relevant_comments( issue: Issue, repo_name: str, From 58e0e43e570a93c0b456bb312ce0f2af1cd7e98d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 22:54:38 +0000 Subject: [PATCH 033/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/response_agent.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index 19cc8c5..526fcb7 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -195,24 +195,24 @@ def check_not_empty(data: str) -> bool: def clean_response(response: str) -> str: """ Remove any existing signatures or TERMINATE flags from response text - + Args: response: The response text to clean - + Returns: Cleaned response text without signatures or TERMINATE flags """ # Remove TERMINATE flags response = re.sub(r'\bTERMINATE\b', '', response, flags=re.IGNORECASE) - + # Remove model-specific signatures model_signature_pattern = r'\n\n---\n\*This response was automatically generated by blech_bot using model .+\*\s*$' response = re.sub(model_signature_pattern, '', response) - + # Remove basic signatures basic_signature = r'\n\n---\n\*This response was automatically generated by blech_bot\*\s*$' response = re.sub(basic_signature, '', response) - + return response.strip() From 6ffea3327d2c001770684e9bf19ab0258418fe68 Mon Sep 17 00:00:00 2001 From: abuzarmahmood Date: Wed, 19 Mar 2025 09:51:58 +0000 Subject: [PATCH 034/146] Fix clean_response usage --- src/response_agent.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index 526fcb7..83cd58e 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -365,7 +365,7 @@ def generate_feedback_response( break all_content = [original_response, feedback_text, updated_response] # Clean the response first to remove any existing signatures - updated_response = bot_tools.clean_response(updated_response) + updated_response = clean_response(updated_response) signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" if signature not in updated_response: updated_response += signature @@ -472,7 +472,7 @@ def generate_new_response( all_content = results_to_summarize + [response] # Clean the response first to remove any existing signatures - response = bot_tools.clean_response(response) + response = clean_response(response) signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" if signature not in response: response += signature @@ -552,7 +552,7 @@ def generate_edit_command_response( break all_content = [response] # Clean the response first to remove any existing signatures - response = bot_tools.clean_response(response) + response = clean_response(response) signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" if signature not in response: response += signature @@ -702,7 +702,7 @@ def process_issue( write_str = f"Applied changes based on comment:\n
View Aider Output\n\n```\n{aider_output}\n```\n
" signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" # Clean the response first to remove any existing signatures - write_str = bot_tools.clean_response(write_str) + write_str = clean_response(write_str) if signature not in write_str: write_str += signature pr.create_issue_comment(write_str) @@ -785,7 +785,7 @@ def process_issue( write_str = f"Generated edit command:\n---\n{response}\n\n" + \ f"Aider output:\n
View Aider Output\n\n```{aider_output}```\n
" # Clean the response first to remove any existing signatures - write_str = bot_tools.clean_response(write_str) + write_str = clean_response(write_str) signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" if signature not in write_str: write_str += signature From d3c43a5e0c377c4f511bf0d95fda408865485961 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Wed, 19 Mar 2025 16:16:15 +0000 Subject: [PATCH 035/146] feat: Update README.md with new features, agents, and usage instructions --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 27951f6..b823e19 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,25 @@ A Python bot that monitors GitHub repositories and automatically responds to iss ## Features - Monitors configured GitHub repositories from `config/repos.txt` -- Automatically responds to issues with the `blech_bot` label -- Analyzes issue content using GPT-4 +- Automatically responds to issues with the `blech_bot` label or title mention +- Analyzes issue content using GPT-4o - Suggests relevant files and code changes - Clones and updates local repository copies - Provides detailed code review and suggestions - Tracks response history to avoid duplicates +- Creates development branches and pull requests from issues +- Implements changes automatically using Aider +- Processes user feedback on responses and pull requests +- Extracts and analyzes content from URLs in issues +- Self-updates while preserving configuration ## Requirements - Python 3.8+ - OpenAI API key - GitHub API access token +- GitHub CLI (gh) +- Aider CLI - Required Python packages (see requirements.txt): - pyautogen - PyGithub @@ -26,6 +33,9 @@ A Python bot that monitors GitHub repositories and automatically responds to iss - gitpython - requests - pyyaml + - urlextract + - beautifulsoup4 + - aider-chat ## Setup @@ -71,17 +81,25 @@ The bot will: 1. Connect to GitHub using your API token 2. Clone/update configured repositories locally 3. Process open issues that: - - Have the `blech_bot` label - - Don't already have a bot response - - Don't have associated branches/PRs -4. Generate and post responses using GPT-4 + - Have the `blech_bot` label or "[ blech_bot ]" in the title + - Don't already have a bot response (for new issues) + - Have user feedback (for follow-up responses) + - Have development commands (for creating PRs) +4. Generate and post responses using GPT-4o +5. Create branches and PRs when requested with "[ develop_issue ]" command +6. Apply changes automatically using Aider when feedback is provided on PRs ## Code Structure - `src/response_agent.py`: Main bot logic and Autogen agents - `src/git_utils.py`: GitHub API interaction utilities - `src/bot_tools.py`: Helper functions for file operations +- `src/agents.py`: Agent definitions and prompt generation +- `src/branch_handler.py`: Git branch management utilities +- `src/triggers.py`: Issue trigger detection functions +- `src/run_response_agent.sh`: Script for continuous bot operation - `config/repos.txt`: List of repositories to monitor +- `config/params.json`: Bot configuration parameters ## Get Started @@ -95,19 +113,30 @@ source venv/bin/activate pip install -r requirements.txt ``` -2. Configure your environment variables in `.env`: +2. Install required CLI tools: +```bash +# Install GitHub CLI +# For Linux: https://github.com/cli/cli/blob/trunk/docs/install_linux.md +# For macOS: brew install gh +# For Windows: winget install GitHub.cli + +# Install Aider +pip install aider-chat +``` + +3. Configure your environment variables in `.env`: ``` GITHUB_TOKEN=your_github_token OPENAI_API_KEY=your_openai_key ``` -3. Add repositories to monitor in `config/repos.txt`: +4. Add repositories to monitor in `config/repos.txt`: ``` owner/repo1 owner/repo2 ``` -4. Run the bot: +5. Run the bot: ```bash # Run once python src/response_agent.py @@ -118,7 +147,7 @@ python src/response_agent.py ## AI Agent Architecture -The bot uses specialized GPT-4 agents working together through Autogen: +The bot uses specialized GPT-4o agents working together through Autogen: - **File Assistant**: Analyzes repository structure - Reviews issue content and codebase @@ -144,11 +173,23 @@ The bot uses specialized GPT-4 agents working together through Autogen: - Maintains context from original response - Generates updated recommendations +- **Comment Summary Assistant**: Summarizes issue comments + - Extracts relevant information from comment threads + - Identifies key points and requirements + - Provides concise summaries for other agents + +- **Generate Edit Command Assistant**: Creates Aider commands + - Converts discussion into actionable edit instructions + - Generates precise commands for automated implementation + - Formats instructions for Aider compatibility + The agents work together to: 1. Analyze issues and identify affected files 2. Propose specific code changes 3. Generate comprehensive responses 4. Process user feedback and improve suggestions +5. Automatically implement changes via Aider +6. Create and manage pull requests ## Contributing From a503ec7a306f7a337d7af1481426aacbf3118728 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Wed, 19 Mar 2025 16:47:16 +0000 Subject: [PATCH 036/146] docs: Add documentation for `auto_update` parameter in README --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index b823e19..79c76c8 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,14 @@ owner/repo1 owner/repo2 ``` +6. Configure bot behavior in `config/params.json`: +```json +{ + "auto_update": true +} +``` +- `auto_update`: Controls whether the bot automatically updates itself with the latest changes from its repository. Set to `true` to enable auto-updates or `false` to disable them. + ## Usage Run the bot: @@ -136,6 +144,13 @@ owner/repo1 owner/repo2 ``` +5. Configure bot behavior in `config/params.json`: +```json +{ + "auto_update": true # Enable/disable bot self-updates +} +``` + 5. Run the bot: ```bash # Run once From cff659b226c43da3bba0c37f3d413b0e8d45f1bb Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 19 Mar 2025 12:57:13 -0400 Subject: [PATCH 037/146] Update README.md --- README.md | 62 +++++++++---------------------------------------------- 1 file changed, 10 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 79c76c8..491382c 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ A Python bot that monitors GitHub repositories and automatically responds to iss - beautifulsoup4 - aider-chat -## Setup +## Get Started 1. Clone the repository: ```bash @@ -78,6 +78,15 @@ owner/repo2 ``` - `auto_update`: Controls whether the bot automatically updates itself with the latest changes from its repository. Set to `true` to enable auto-updates or `false` to disable them. +7. Run the bot: +```bash +# Run once +python src/response_agent.py + +# Or run continuously with the shell script +./src/run_response_agent.sh --delay 300 # Check every 5 minutes +``` + ## Usage Run the bot: @@ -109,57 +118,6 @@ The bot will: - `config/repos.txt`: List of repositories to monitor - `config/params.json`: Bot configuration parameters -## Get Started - -1. Set up your environment: -```bash -# Create and activate virtual environment -python -m venv venv -source venv/bin/activate - -# Install dependencies -pip install -r requirements.txt -``` - -2. Install required CLI tools: -```bash -# Install GitHub CLI -# For Linux: https://github.com/cli/cli/blob/trunk/docs/install_linux.md -# For macOS: brew install gh -# For Windows: winget install GitHub.cli - -# Install Aider -pip install aider-chat -``` - -3. Configure your environment variables in `.env`: -``` -GITHUB_TOKEN=your_github_token -OPENAI_API_KEY=your_openai_key -``` - -4. Add repositories to monitor in `config/repos.txt`: -``` -owner/repo1 -owner/repo2 -``` - -5. Configure bot behavior in `config/params.json`: -```json -{ - "auto_update": true # Enable/disable bot self-updates -} -``` - -5. Run the bot: -```bash -# Run once -python src/response_agent.py - -# Or run continuously with the shell script -./src/run_response_agent.sh --delay 300 # Check every 5 minutes -``` - ## AI Agent Architecture The bot uses specialized GPT-4o agents working together through Autogen: From cb8bf7a7b91382ddbbe154be98aa9cd956ccbd13 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Fri, 21 Mar 2025 05:59:09 +0000 Subject: [PATCH 038/146] The changes look good! I see you've added the `search_github` function to `bot_tools.py` and the `perform_github_search` function to `git_utils.py`. However, I noticed a couple of small things that need to be addressed: 1. In `bot_tools.py`, you'll need to import `sys` and `src_dir`. Add these imports at the top of the file: ```python import sys import os # Assuming this is how src_dir is typically defined src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) ``` 2. Make sure the GitHub API client is properly imported in `git_utils.py`. The function uses `get_github_client()`, so ensure that function is defined and imported correctly. These changes will enable the bot to perform GitHub code searches with the following capabilities: - Search GitHub for code snippets - Limit results to 5 items to avoid rate limiting - Fetch and display repository, file path, and URL for each result - Include code snippets (truncated for large files) - Handle errors gracefully You can test the functionality with: ```python from src.bot_tools import search_github print(search_github('language:python pandas dataframe example')) ``` Would you like me to generate a commit message for these changes? --- src/bot_tools.py | 17 +++++++++++++ src/git_utils.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/bot_tools.py b/src/bot_tools.py index 6af116a..8bc00a2 100644 --- a/src/bot_tools.py +++ b/src/bot_tools.py @@ -250,3 +250,20 @@ def get_func_code( # Get code for function code = "".join(lines[start_line:end_line]) return code + + +def search_github(query: str) -> str: + """ + Search GitHub for a given query and return code snippets. + + Args: + query: The search query string. + + Returns: + A string containing search results with code snippets. + """ + # Import here to avoid circular imports + sys.path.append(src_dir) + from git_utils import perform_github_search + + return perform_github_search(query) diff --git a/src/git_utils.py b/src/git_utils.py index 61886da..6e710a5 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -494,6 +494,71 @@ def update_self_repo( os.remove(backup_path) +def perform_github_search(query: str) -> str: + """ + Perform a search on GitHub using the provided query and extract code URLs. + + Args: + query: The search query string. + + Returns: + A string containing search results with code snippets. + """ + client = get_github_client() + + try: + # Search for code with the given query + search_results = client.search_code(query=query) + + # Limit results to avoid rate limiting (GitHub API has limits) + max_results = 5 + results_str = f"GitHub search results for query: '{query}'\n\n" + count = 0 + + for file in search_results: + if count >= max_results: + break + + count += 1 + code_url = file.html_url + repo_name = file.repository.full_name + file_path = file.path + + results_str += f"Result {count}:\n" + results_str += f"Repository: {repo_name}\n" + results_str += f"File: {file_path}\n" + results_str += f"URL: {code_url}\n" + + try: + # Fetch the file content using the API + repo = client.get_repo(repo_name) + file_content = repo.get_contents(file_path, ref=file.repository.default_branch) + + # Decode content - handle binary files gracefully + try: + code_snippet = file_content.decoded_content.decode('utf-8') + + # Truncate very large files + if len(code_snippet) > 2000: + code_snippet = code_snippet[:2000] + "\n... (content truncated, see full file at URL) ..." + + results_str += f"Code snippet:\n```\n{code_snippet}\n```\n\n" + except UnicodeDecodeError: + results_str += "(Binary file, content not displayed)\n\n" + except Exception as e: + results_str += f"(Error fetching file content: {str(e)})\n\n" + + if count == 0: + results_str += "No results found for this query." + elif count == max_results: + results_str += f"Note: Results limited to {max_results} items. Refine your query for more specific results." + + return results_str + + except Exception as e: + return f"Error performing GitHub search: {str(e)}" + + if __name__ == '__main__': client = get_github_client() repo = get_repository(client, 'katzlabbrandeis/blech_clust') From caf334d08069da9edd3810cc9869ceffd75b08dc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 05:59:18 +0000 Subject: [PATCH 039/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/bot_tools.py | 2 +- src/git_utils.py | 30 ++++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/bot_tools.py b/src/bot_tools.py index 8bc00a2..ec89bf2 100644 --- a/src/bot_tools.py +++ b/src/bot_tools.py @@ -265,5 +265,5 @@ def search_github(query: str) -> str: # Import here to avoid circular imports sys.path.append(src_dir) from git_utils import perform_github_search - + return perform_github_search(query) diff --git a/src/git_utils.py b/src/git_utils.py index 6e710a5..44d313f 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -505,56 +505,58 @@ def perform_github_search(query: str) -> str: A string containing search results with code snippets. """ client = get_github_client() - + try: # Search for code with the given query search_results = client.search_code(query=query) - + # Limit results to avoid rate limiting (GitHub API has limits) max_results = 5 results_str = f"GitHub search results for query: '{query}'\n\n" count = 0 - + for file in search_results: if count >= max_results: break - + count += 1 code_url = file.html_url repo_name = file.repository.full_name file_path = file.path - + results_str += f"Result {count}:\n" results_str += f"Repository: {repo_name}\n" results_str += f"File: {file_path}\n" results_str += f"URL: {code_url}\n" - + try: # Fetch the file content using the API repo = client.get_repo(repo_name) - file_content = repo.get_contents(file_path, ref=file.repository.default_branch) - + file_content = repo.get_contents( + file_path, ref=file.repository.default_branch) + # Decode content - handle binary files gracefully try: code_snippet = file_content.decoded_content.decode('utf-8') - + # Truncate very large files if len(code_snippet) > 2000: - code_snippet = code_snippet[:2000] + "\n... (content truncated, see full file at URL) ..." - + code_snippet = code_snippet[:2000] + \ + "\n... (content truncated, see full file at URL) ..." + results_str += f"Code snippet:\n```\n{code_snippet}\n```\n\n" except UnicodeDecodeError: results_str += "(Binary file, content not displayed)\n\n" except Exception as e: results_str += f"(Error fetching file content: {str(e)})\n\n" - + if count == 0: results_str += "No results found for this query." elif count == max_results: results_str += f"Note: Results limited to {max_results} items. Refine your query for more specific results." - + return results_str - + except Exception as e: return f"Error performing GitHub search: {str(e)}" From 66733bd5b7a111faf132fb594a82c07758c1338f Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 24 Mar 2025 10:16:42 -0400 Subject: [PATCH 040/146] refactor(git-utils): simplify GitHub search function and add max snippet length parameter - Added `max_snippet_length` parameter to `perform_github_search` function to control snippet truncation. - Modified the search to include a language filter for Python. - Simplified the code by removing redundant API fetch operations. - Removed exception handling for content fetching, assuming simpler execution flow based on new API capabilities. - Revised code to clarify and avoid unnecessary repetition or complexity. --- src/git_utils.py | 56 +++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 44d313f..220b0c9 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -494,7 +494,10 @@ def update_self_repo( os.remove(backup_path) -def perform_github_search(query: str) -> str: +def perform_github_search( + query: str, + max_snippet_length: int = 2000, # lines +) -> str: """ Perform a search on GitHub using the provided query and extract code URLs. @@ -508,18 +511,14 @@ def perform_github_search(query: str) -> str: try: # Search for code with the given query - search_results = client.search_code(query=query) + search_results = client.search_code(query=query, language='Python') # Limit results to avoid rate limiting (GitHub API has limits) max_results = 5 results_str = f"GitHub search results for query: '{query}'\n\n" - count = 0 - for file in search_results: - if count >= max_results: - break + for count, file in enumerate(search_results[:max_results]): - count += 1 code_url = file.html_url repo_name = file.repository.full_name file_path = file.path @@ -529,26 +528,29 @@ def perform_github_search(query: str) -> str: results_str += f"File: {file_path}\n" results_str += f"URL: {code_url}\n" - try: - # Fetch the file content using the API - repo = client.get_repo(repo_name) - file_content = repo.get_contents( - file_path, ref=file.repository.default_branch) - - # Decode content - handle binary files gracefully - try: - code_snippet = file_content.decoded_content.decode('utf-8') - - # Truncate very large files - if len(code_snippet) > 2000: - code_snippet = code_snippet[:2000] + \ - "\n... (content truncated, see full file at URL) ..." - - results_str += f"Code snippet:\n```\n{code_snippet}\n```\n\n" - except UnicodeDecodeError: - results_str += "(Binary file, content not displayed)\n\n" - except Exception as e: - results_str += f"(Error fetching file content: {str(e)})\n\n" + code_snippet = file.decoded_content.decode('utf-8') + + # try: + # # Fetch the file content using the API + # repo = client.get_repo(repo_name) + # file_content = repo.get_contents( + # file_path, ref=file.repository.default_branch) + + # # Decode content - handle binary files gracefully + # try: + # code_snippet = file_content.decoded_content.decode('utf-8') + + # Truncate very large files + if len(code_snippet) > max_snippet_length: + code_snippet = code_snippet[:max_snippet_length] + \ + "\n... (content truncated, see full file at URL) ..." + + results_str += f"Code snippet:\n```\n{code_snippet}\n```\n\n" + + # except UnicodeDecodeError: + # results_str += "(Binary file, content not displayed)\n\n" + # except Exception as e: + # results_str += f"(Error fetching file content: {str(e)})\n\n" if count == 0: results_str += "No results found for this query." From 593eea56a7e4b9b8e5591ba28156dbc0f0127052 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 24 Mar 2025 10:17:18 -0400 Subject: [PATCH 041/146] refactor(git_utils): remove commented-out code - Removed legacy code that fetched file contents using the API. - Simplified the function by eliminating inactive `try-except` blocks. - Improved readability and maintainability by cleaning up unused code paths. --- src/git_utils.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 220b0c9..b4f357a 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -530,16 +530,6 @@ def perform_github_search( code_snippet = file.decoded_content.decode('utf-8') - # try: - # # Fetch the file content using the API - # repo = client.get_repo(repo_name) - # file_content = repo.get_contents( - # file_path, ref=file.repository.default_branch) - - # # Decode content - handle binary files gracefully - # try: - # code_snippet = file_content.decoded_content.decode('utf-8') - # Truncate very large files if len(code_snippet) > max_snippet_length: code_snippet = code_snippet[:max_snippet_length] + \ @@ -547,11 +537,6 @@ def perform_github_search( results_str += f"Code snippet:\n```\n{code_snippet}\n```\n\n" - # except UnicodeDecodeError: - # results_str += "(Binary file, content not displayed)\n\n" - # except Exception as e: - # results_str += f"(Error fetching file content: {str(e)})\n\n" - if count == 0: results_str += "No results found for this query." elif count == max_results: From 5ac573d7601301aa3021e07482f9896cbf75938f Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 24 Mar 2025 10:41:45 -0400 Subject: [PATCH 042/146] feat(agents): enhance prompt guidance with search_github suggestions - Added instructions to use `search_github` to find similar code snippets in the repository if unsure about a suggested change. - Improved the contextual understanding guidance within the `generate_prompt` function. - Ensured consistency in advice for handling large files and uncertainty in code changes. --- src/agents.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/agents.py b/src/agents.py index 7bd9b14..396cdf9 100644 --- a/src/agents.py +++ b/src/agents.py @@ -248,6 +248,7 @@ def generate_prompt( Do not look for files again. Use the files suggested by the previous agent. Provide code blocks which will address the issue where you can and suggest specific lines in specific files where changes can be made. Try to read the whole file (readfile) to understand context where possible. If file is too large, search for specific functions or classes (get_func_code). If you can't find functions to classes, try reading sets of lines repeatedly (readlines). + If you're unsure about a suggested change, use search_github to find similar code snippets in the repository. Format your output with the following structure: - Summary of user's issues and requests @@ -283,6 +284,7 @@ def generate_prompt( DO NOT SUGGEST CODE EXECUTIONS. Only make code editing suggestions. If those are not functioning, use tools like search_for_file to search for .py files, or other tools you have. Try to read the whole file (readfile) to understand context where possible. If file is too large, search for specific functions or classes (get_func_code). If you can't find functions to classes, try reading sets of lines repeatedly (readlines). + If you're unsure about a suggested change, use search_github to find similar code snippets in the repository. Finish the job by suggesting specific lines in specific files where changes can be made. Previous Response: @@ -313,6 +315,7 @@ def generate_prompt( Include file paths, line numbers, and exact code changes where possible. Format the command in a way that can be parsed by automated tools. First try searching for files to get paths. + If you're unsure about a suggested change, use search_github to find similar code snippets in the repository. Format your output with the following structure: - Summary of user's issues and requests From 58e17b2459098aca1ca8f6268326534d7b835824 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Mon, 31 Mar 2025 15:41:36 +0000 Subject: [PATCH 043/146] refactor: Update issue and PR processing logic to handle blech_bot labels and associated issues --- src/git_utils.py | 65 +++++++++++++++++++++-- src/response_agent.py | 116 ++++++++++++++++++++++++++++-------------- 2 files changed, 138 insertions(+), 43 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index b4f357a..e968ac6 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -1,7 +1,7 @@ """ Utility functions for interacting with GitHub API """ -from typing import List, Dict, Optional, Tuple +from typing import List, Dict, Optional, Tuple, Union import os import subprocess import git @@ -54,8 +54,8 @@ def get_repository(client: Github, repo_name: str) -> Repository: def get_open_issues(repo: Repository) -> List[Issue]: - """Get all open issues from repository""" - return list(repo.get_issues(state='open')) + """Get all open issues and pull requests from repository""" + return list(repo.get_issues(state='open', sort='created', direction='asc')) def get_issue_comments(issue: Issue) -> List[IssueComment]: @@ -401,7 +401,7 @@ def has_linked_pr(issue: Issue) -> bool: return False -def get_linked_pr(issue: Issue) -> PullRequest: +def get_linked_pr(issue: Issue) -> Optional[PullRequest]: """ Get the linked pull request for an issue @@ -424,6 +424,63 @@ def get_linked_pr(issue: Issue) -> PullRequest: return repo.get_pull(pr_number) return None + + +def get_associated_issue(pr: PullRequest) -> Optional[Issue]: + """ + Get the associated issue for a pull request + + Args: + pr: The GitHub pull request to check + + Returns: + The associated Issue object or None if not found + """ + # Check if PR body contains "Fixes #X" or "Closes #X" or similar + if not pr.body: + return None + + # Look for common issue reference patterns + issue_ref_patterns = [ + r"(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+#(\d+)", + r"(?:issue|issues)\s+#(\d+)", + r"#(\d+)" + ] + + for pattern in issue_ref_patterns: + matches = re.findall(pattern, pr.body, re.IGNORECASE) + if matches: + try: + issue_number = int(matches[0]) + return pr.repository.get_issue(issue_number) + except Exception: + continue + + # If no match found in body, check PR title + if pr.title: + for pattern in issue_ref_patterns: + matches = re.findall(pattern, pr.title, re.IGNORECASE) + if matches: + try: + issue_number = int(matches[0]) + return pr.repository.get_issue(issue_number) + except Exception: + continue + + return None + + +def is_pull_request(issue_or_pr: Union[Issue, PullRequest]) -> bool: + """ + Check if an object is a pull request + + Args: + issue_or_pr: The GitHub issue or pull request to check + + Returns: + True if the object is a pull request, False otherwise + """ + return hasattr(issue_or_pr, 'merge_commit_sha') def update_self_repo( diff --git a/src/response_agent.py b/src/response_agent.py index 83cd58e..c6d6b58 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -1,7 +1,7 @@ """ Agent for generating responses to GitHub issues using pyautogen """ -from typing import Optional, Tuple, List +from typing import Optional, Tuple, List, Union from dotenv import load_dotenv import string @@ -29,7 +29,10 @@ create_pull_request_from_issue, get_development_branch, has_linked_pr, + get_linked_pr, push_changes_with_authentication, + get_associated_issue, + is_pull_request, ) from github.Repository import Repository from github.Issue import Issue @@ -606,30 +609,52 @@ def response_selector(trigger: str) -> Callable: def process_issue( - issue: Issue, + issue_or_pr: Union[Issue, PullRequest], repo_name: str, ) -> Tuple[bool, Optional[str]]: """ - Process a single issue - check if it needs response and generate one + Process a single issue or PR - check if it needs response and generate one Args: - issue: The GitHub issue to process + issue_or_pr: The GitHub issue or PR to process Returns: Tuple of (whether response was posted, optional error message) """ - print(f"Processing issue #{issue.number}") + is_pr = is_pull_request(issue_or_pr) + entity_type = "PR" if is_pr else "issue" + print(f"Processing {entity_type} #{issue_or_pr.number}") + try: - # Check if issue has blech_bot tag or blech_bot in title, and no existing response - has_bot_mention = triggers.has_blech_bot_tag( - issue) or "[ blech_bot ]" in issue.title.lower() - if not has_bot_mention: - return False, "Issue does not have blech_bot tag or mention in title" + # Handle PR differently + if is_pr: + pr = issue_or_pr + # Check if PR has blech_bot label + has_bot_mention = triggers.has_blech_bot_tag(pr) + + # If PR doesn't have blech_bot label, check if it has an associated issue with the label + if not has_bot_mention: + associated_issue = get_associated_issue(pr) + if associated_issue and triggers.has_blech_bot_tag(associated_issue): + # Use the associated issue for processing + print(f"PR #{pr.number} has associated issue #{associated_issue.number} with blech_bot tag") + has_bot_mention = True + else: + return False, f"PR #{pr.number} does not have blech_bot label and no associated issue with blech_bot tag" + else: + # Regular issue processing + # Check if issue has blech_bot tag or blech_bot in title + has_bot_mention = triggers.has_blech_bot_tag( + issue_or_pr) or "[ blech_bot ]" in issue_or_pr.title.lower() + if not has_bot_mention: + return False, "Issue does not have blech_bot tag or mention in title" + + # Check if already responded without user feedback already_responded = triggers.has_bot_response( - issue) and not triggers.has_user_feedback(issue) - pr_comment_bool, pr_comment = triggers.has_pr_creation_comment(issue) + issue_or_pr) and not triggers.has_user_feedback(issue_or_pr) + pr_comment_bool, pr_comment = triggers.has_pr_creation_comment(issue_or_pr) if already_responded and not pr_comment_bool: - return False, "Issue already has a bot response without feedback from user" + return False, f"{entity_type} already has a bot response without feedback from user" # Check for user comments on PR first if pr_comment_bool: @@ -655,7 +680,7 @@ def process_issue( comments) - 1 branch_name = get_development_branch( - issue, repo_path, create=False) + issue_or_pr, repo_path, create=False) # Only run if branch exists and user comment is found on PR if branch_name and user_feedback_bool: @@ -677,14 +702,14 @@ def process_issue( if user_comment: # Summarize relevant comments summarized_comments, comment_list, summary_comment_str = summarize_relevant_comments( - issue, repo_name) + issue_or_pr, repo_name) if summary_comment_str == '': summary_comment_str = 'No relevant comments found' # Pass to generate_edit_command agent first response, _ = generate_edit_command_response( - issue, repo_name, summary_comment_str) + issue_or_pr, repo_name, summary_comment_str) # Then run aider with the generated command aider_output = run_aider(response, repo_path) @@ -724,28 +749,32 @@ def process_issue( f"Failed to process PR comment: {str(e)}") # Check for develop_issue trigger next - elif triggers.has_develop_issue_trigger(issue): + elif triggers.has_develop_issue_trigger(issue_or_pr): + # Only issues can be developed, not PRs + if is_pr: + return False, "Cannot develop a PR, only issues can be developed" + print('Triggered by [ develop_issue ] command') repo_path = bot_tools.get_local_repo_path(repo_name) # Check for existing branches branch_name = get_development_branch( - issue, repo_path, create=False) + issue_or_pr, repo_path, create=False) # if branch_name is not None: - # return False, f"Branch {branch_name} already exists for issue #{issue.number}" + # return False, f"Branch {branch_name} already exists for issue #{issue_or_pr.number}" # Check for linked PRs - if has_linked_pr(issue): - return False, f"Issue #{issue.number} already has a linked pull request" + if has_linked_pr(issue_or_pr): + return False, f"Issue #{issue_or_pr.number} already has a linked pull request" # Check if issue has label "under_development" - if "under_development" in [label.name for label in issue.labels]: - return False, f"Issue #{issue.number} is already under development" + if "under_development" in [label.name for label in issue_or_pr.labels]: + return False, f"Issue #{issue_or_pr.number} is already under development" # First generate edit command from previous discussion - response, _ = generate_edit_command_response(issue, repo_name) + response, _ = generate_edit_command_response(issue_or_pr, repo_name) - branch_name = get_development_branch(issue, repo_path, create=True) + branch_name = get_development_branch(issue_or_pr, repo_path, create=True) original_dir = os.getcwd() os.chdir(repo_path) checkout_branch(repo_path, branch_name, create=False) @@ -761,22 +790,22 @@ def process_issue( # Push changes with authentication push_success, err_msg = push_changes_with_authentication( repo_path, - issue, + issue_or_pr, branch_name ) - pr_url = create_pull_request_from_issue(issue, repo_path) + pr_url = create_pull_request_from_issue(issue_or_pr, repo_path) pr_number = pr_url.split('/')[-1] pull = repo.get_pull(int(pr_number)) # Create pull request write_issue_response( - issue, + issue_or_pr, f"Created pull request: {pr_url}\nContinue discussion there." ) # Mark issue with label "under_development" - issue.add_to_labels("under_development") + issue_or_pr.add_to_labels("under_development") if not push_success: return False, f"Failed to push changes: {err_msg}" @@ -807,13 +836,21 @@ def process_issue( return True, None + # Handle linked PR for issues + if not is_pr and has_linked_pr(issue_or_pr): + linked_pr = get_linked_pr(issue_or_pr) + if linked_pr and triggers.has_user_feedback(linked_pr): + print(f"Issue #{issue_or_pr.number} has linked PR #{linked_pr.number} with user feedback") + # Process the PR instead of the issue + return process_issue(linked_pr, repo_name) + # Generate and post response - trigger = check_triggers(issue) + trigger = check_triggers(issue_or_pr) response_func = response_selector(trigger) if response_func is None: - return False, f"No trigger found for issue #{issue.number}" - response, all_content = response_func(issue, repo_name) - write_issue_response(issue, response) + return False, f"No trigger found for {entity_type} #{issue_or_pr.number}" + response, all_content = response_func(issue_or_pr, repo_name) + write_issue_response(issue_or_pr, response) return True, None except Exception as e: @@ -880,7 +917,7 @@ def process_repository( repo_name: str, ) -> None: """ - Process all open issues in a repository + Process all open issues and PRs in a repository Args: repo_name: Full name of repository (owner/repo) @@ -912,13 +949,14 @@ def process_repository( # Get open issues open_issues = repo.get_issues(state='open') - # Process each issue - for issue in open_issues: - success, error = process_issue(issue, repo_name) + # Process each issue and PR + for item in open_issues: + entity_type = "PR" if is_pull_request(item) else "issue" + success, error = process_issue(item, repo_name) if success: - print(f"Successfully processed issue #{issue.number}") + print(f"Successfully processed {entity_type} #{item.number}") else: - print(f"Skipped issue #{issue.number}: {error}") + print(f"Skipped {entity_type} #{item.number}: {error}") def initialize_bot() -> None: From 2ea45da3f639ef42482fd77ef3f909683cc680bc Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Mon, 31 Mar 2025 15:41:43 +0000 Subject: [PATCH 044/146] fix: Import PullRequest to resolve undefined name error --- src/response_agent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/response_agent.py b/src/response_agent.py index c6d6b58..064f769 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -36,6 +36,7 @@ ) from github.Repository import Repository from github.Issue import Issue +from github.PullRequest import PullRequest from branch_handler import ( checkout_branch, back_to_master_branch, From 090caf7b2e7924914d3ad5dce7f268d7205cb74f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 15:41:51 +0000 Subject: [PATCH 045/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/git_utils.py | 20 ++++++++++---------- src/response_agent.py | 25 +++++++++++++++---------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index e968ac6..b6191c6 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -424,29 +424,29 @@ def get_linked_pr(issue: Issue) -> Optional[PullRequest]: return repo.get_pull(pr_number) return None - - + + def get_associated_issue(pr: PullRequest) -> Optional[Issue]: """ Get the associated issue for a pull request - + Args: pr: The GitHub pull request to check - + Returns: The associated Issue object or None if not found """ # Check if PR body contains "Fixes #X" or "Closes #X" or similar if not pr.body: return None - + # Look for common issue reference patterns issue_ref_patterns = [ r"(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+#(\d+)", r"(?:issue|issues)\s+#(\d+)", r"#(\d+)" ] - + for pattern in issue_ref_patterns: matches = re.findall(pattern, pr.body, re.IGNORECASE) if matches: @@ -455,7 +455,7 @@ def get_associated_issue(pr: PullRequest) -> Optional[Issue]: return pr.repository.get_issue(issue_number) except Exception: continue - + # If no match found in body, check PR title if pr.title: for pattern in issue_ref_patterns: @@ -466,17 +466,17 @@ def get_associated_issue(pr: PullRequest) -> Optional[Issue]: return pr.repository.get_issue(issue_number) except Exception: continue - + return None def is_pull_request(issue_or_pr: Union[Issue, PullRequest]) -> bool: """ Check if an object is a pull request - + Args: issue_or_pr: The GitHub issue or pull request to check - + Returns: True if the object is a pull request, False otherwise """ diff --git a/src/response_agent.py b/src/response_agent.py index 064f769..3c67f91 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -625,20 +625,21 @@ def process_issue( is_pr = is_pull_request(issue_or_pr) entity_type = "PR" if is_pr else "issue" print(f"Processing {entity_type} #{issue_or_pr.number}") - + try: # Handle PR differently if is_pr: pr = issue_or_pr # Check if PR has blech_bot label has_bot_mention = triggers.has_blech_bot_tag(pr) - + # If PR doesn't have blech_bot label, check if it has an associated issue with the label if not has_bot_mention: associated_issue = get_associated_issue(pr) if associated_issue and triggers.has_blech_bot_tag(associated_issue): # Use the associated issue for processing - print(f"PR #{pr.number} has associated issue #{associated_issue.number} with blech_bot tag") + print( + f"PR #{pr.number} has associated issue #{associated_issue.number} with blech_bot tag") has_bot_mention = True else: return False, f"PR #{pr.number} does not have blech_bot label and no associated issue with blech_bot tag" @@ -649,11 +650,12 @@ def process_issue( issue_or_pr) or "[ blech_bot ]" in issue_or_pr.title.lower() if not has_bot_mention: return False, "Issue does not have blech_bot tag or mention in title" - + # Check if already responded without user feedback already_responded = triggers.has_bot_response( issue_or_pr) and not triggers.has_user_feedback(issue_or_pr) - pr_comment_bool, pr_comment = triggers.has_pr_creation_comment(issue_or_pr) + pr_comment_bool, pr_comment = triggers.has_pr_creation_comment( + issue_or_pr) if already_responded and not pr_comment_bool: return False, f"{entity_type} already has a bot response without feedback from user" @@ -754,7 +756,7 @@ def process_issue( # Only issues can be developed, not PRs if is_pr: return False, "Cannot develop a PR, only issues can be developed" - + print('Triggered by [ develop_issue ] command') repo_path = bot_tools.get_local_repo_path(repo_name) @@ -773,9 +775,11 @@ def process_issue( return False, f"Issue #{issue_or_pr.number} is already under development" # First generate edit command from previous discussion - response, _ = generate_edit_command_response(issue_or_pr, repo_name) + response, _ = generate_edit_command_response( + issue_or_pr, repo_name) - branch_name = get_development_branch(issue_or_pr, repo_path, create=True) + branch_name = get_development_branch( + issue_or_pr, repo_path, create=True) original_dir = os.getcwd() os.chdir(repo_path) checkout_branch(repo_path, branch_name, create=False) @@ -841,10 +845,11 @@ def process_issue( if not is_pr and has_linked_pr(issue_or_pr): linked_pr = get_linked_pr(issue_or_pr) if linked_pr and triggers.has_user_feedback(linked_pr): - print(f"Issue #{issue_or_pr.number} has linked PR #{linked_pr.number} with user feedback") + print( + f"Issue #{issue_or_pr.number} has linked PR #{linked_pr.number} with user feedback") # Process the PR instead of the issue return process_issue(linked_pr, repo_name) - + # Generate and post response trigger = check_triggers(issue_or_pr) response_func = response_selector(trigger) From f283242fb0a73ab15a0bad5053f841691f2e3ccf Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 31 Mar 2025 13:22:17 -0400 Subject: [PATCH 046/146] feat(git_utils): enhance branch and issue association logic for PRs - Added `get_pr_branch` function to retrieve the branch name from a pull request. - Improved `get_associated_issue` to use PR timeline events for identifying associated issues. - Commented out the previous logic for extracting issue references from PR bodies and titles. - Modified `is_pull_request` to determine PR status using the URL pattern instead of `merge_commit_sha`. --- src/git_utils.py | 89 +++++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 31 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index b6191c6..0f83ca5 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -189,6 +189,19 @@ def update_repository(repo_path: str) -> None: origin.pull() +def get_pr_branch(pr: PullRequest) -> str: + """ + Get the branch name for a pull request + + Args: + pr: The GitHub pull request to check + + Returns: + The branch name of the pull request + """ + return pr.head.ref + + def get_development_branch(issue: Issue, repo_path: str, create: bool = False) -> str: """ Gets or creates a development branch for an issue @@ -436,37 +449,50 @@ def get_associated_issue(pr: PullRequest) -> Optional[Issue]: Returns: The associated Issue object or None if not found """ - # Check if PR body contains "Fixes #X" or "Closes #X" or similar - if not pr.body: - return None - - # Look for common issue reference patterns - issue_ref_patterns = [ - r"(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+#(\d+)", - r"(?:issue|issues)\s+#(\d+)", - r"#(\d+)" - ] - - for pattern in issue_ref_patterns: - matches = re.findall(pattern, pr.body, re.IGNORECASE) - if matches: - try: - issue_number = int(matches[0]) - return pr.repository.get_issue(issue_number) - except Exception: - continue - - # If no match found in body, check PR title - if pr.title: - for pattern in issue_ref_patterns: - matches = re.findall(pattern, pr.title, re.IGNORECASE) - if matches: - try: - issue_number = int(matches[0]) - return pr.repository.get_issue(issue_number) - except Exception: - continue + pr_timeline_events = list(pr.get_timeline()) + # Check if any timeline event is a cross-reference to an issue + for event in pr_timeline_events: + if event.event == "cross-referenced": + # Check if the reference is to an issue + if event.source: + for key, val in event.source.raw_data.items(): + if isinstance(val, dict) and 'issue' in val['html_url']: + issue_number = val['number'] + repo = pr.repository + return repo.get_issue(issue_number) + + # # Check if PR body contains "Fixes #X" or "Closes #X" or similar + # if not pr.body: + # return None + # + # # Look for common issue reference patterns + # issue_ref_patterns = [ + # r"(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+#(\d+)", + # r"(?:issue|issues)\s+#(\d+)", + # r"#(\d+)" + # ] + # + # for pattern in issue_ref_patterns: + # matches = re.findall(pattern, pr.body, re.IGNORECASE) + # if matches: + # try: + # issue_number = int(matches[0]) + # return pr.repository.get_issue(issue_number) + # except Exception: + # continue + # + # # If no match found in body, check PR title + # if pr.title: + # for pattern in issue_ref_patterns: + # matches = re.findall(pattern, pr.title, re.IGNORECASE) + # if matches: + # try: + # issue_number = int(matches[0]) + # return pr.repository.get_issue(issue_number) + # except Exception: + # continue + # return None @@ -480,7 +506,8 @@ def is_pull_request(issue_or_pr: Union[Issue, PullRequest]) -> bool: Returns: True if the object is a pull request, False otherwise """ - return hasattr(issue_or_pr, 'merge_commit_sha') + # return hasattr(issue_or_pr, 'merge_commit_sha') + return 'pull' in issue.html_url def update_self_repo( From 9bf9e8472abbb011bb0fc8837109926abe1d63c9 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 31 Mar 2025 13:22:32 -0400 Subject: [PATCH 047/146] refactor(response_agent): streamline PR handling logic - Added `get_pr_branch` to imports for enhanced functionality. - Optimized code by moving `associated_issue` logic and merging conditionals for clarity. - Developed a process for creating and managing pull request comments and branches automatically. - Improved error handling and directory navigation during the process execution. - Enhanced the structured response generation when dealing with pull requests, ensuring signatures are appended correctly. --- src/response_agent.py | 81 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/src/response_agent.py b/src/response_agent.py index 3c67f91..c9c674f 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -33,6 +33,7 @@ push_changes_with_authentication, get_associated_issue, is_pull_request, + get_pr_branch, ) from github.Repository import Repository from github.Issue import Issue @@ -632,10 +633,10 @@ def process_issue( pr = issue_or_pr # Check if PR has blech_bot label has_bot_mention = triggers.has_blech_bot_tag(pr) + associated_issue = get_associated_issue(pr) # If PR doesn't have blech_bot label, check if it has an associated issue with the label if not has_bot_mention: - associated_issue = get_associated_issue(pr) if associated_issue and triggers.has_blech_bot_tag(associated_issue): # Use the associated issue for processing print( @@ -659,6 +660,7 @@ def process_issue( if already_responded and not pr_comment_bool: return False, f"{entity_type} already has a bot response without feedback from user" + # Process PR Already created from issue # Check for user comments on PR first if pr_comment_bool: @@ -751,6 +753,7 @@ def process_issue( raise RuntimeError( f"Failed to process PR comment: {str(e)}") + # Developing pull request from issue # Check for develop_issue trigger next elif triggers.has_develop_issue_trigger(issue_or_pr): # Only issues can be developed, not PRs @@ -841,6 +844,82 @@ def process_issue( return True, None + # Process a PR with no associated issue and blech_bot_tag + elif is_pr and has_bot_mention and not associated_issue: + pr_obj = repo.get_pull(issue_or_pr.number) + branch_name = get_pr_branch(pr_obj) + # branch_name = get_development_branch( + # issue_or_pr, repo_path, create=False) + # if branch_name is not None: + # return False, f"Branch {branch_name} already exists for issue #{issue_or_pr.number}" + + # First generate edit command from previous discussion + response, _ = generate_edit_command_response( + issue_or_pr, repo_name) + + # branch_name = get_development_branch( + # issue_or_pr, repo_path, create=True) + original_dir = os.getcwd() + os.chdir(repo_path) + checkout_branch(repo_path, branch_name, create=False) + + try: + # Run aider with the generated command + aider_output = run_aider(response, repo_path) + + # Get repo object and pull request + client = get_github_client() + repo = get_repository(client, repo_name) + + # Push changes with authentication + push_success, err_msg = push_changes_with_authentication( + repo_path, + issue_or_pr, + branch_name + ) + + # pr_url = create_pull_request_from_issue(issue_or_pr, repo_path) + # pr_number = pr_url.split('/')[-1] + # pull = repo.get_pull(int(pr_number)) + + # Create pull request + # write_issue_response( + # issue_or_pr, + # f"Created pull request: {pr_url}\nContinue discussion there." + # ) + + # Mark issue with label "under_development" + # issue_or_pr.add_to_labels("under_development") + + # if not push_success: + # return False, f"Failed to push changes: {err_msg}" + + # write_issue_response(issue, "Generated edit command:\n" + response) + write_str = f"Generated edit command:\n---\n{response}\n\n" + \ + f"Aider output:\n
View Aider Output\n\n```{aider_output}```\n
" + # Clean the response first to remove any existing signatures + write_str = clean_response(write_str) + signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" + if signature not in write_str: + write_str += signature + pr_obj.create_issue_comment(write_str) + + # Switch back to main branch + back_to_master_branch(repo_path) + # Return to original directory + os.chdir(original_dir) + + except Exception as e: + # Clean up on error + back_to_master_branch(repo_path) + delete_branch(repo_path, branch_name, force=True) + # Return to original directory + os.chdir(original_dir) + raise RuntimeError( + f"Failed to process develop issue: {str(e)}") + + return True, None + # Handle linked PR for issues if not is_pr and has_linked_pr(issue_or_pr): linked_pr = get_linked_pr(issue_or_pr) From f4d4175aec68ea43b06ca75450286768134d8f6c Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 31 Mar 2025 13:51:24 -0400 Subject: [PATCH 048/146] fix(git_utils): correct attribute access for PR determination - Updated the `is_pull_request` function to correctly reference `issue_or_pr.html_url`. refactor(response_agent): enhance PR processing with early repo handling - Moved repository and client setup earlier in the `process_issue` function for clarity. - Ensured changing to the correct repo directory before performing branch operations. - Improved comments for better understanding of PR processing without an associated issue. --- src/git_utils.py | 2 +- src/response_agent.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 0f83ca5..6891964 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -507,7 +507,7 @@ def is_pull_request(issue_or_pr: Union[Issue, PullRequest]) -> bool: True if the object is a pull request, False otherwise """ # return hasattr(issue_or_pr, 'merge_commit_sha') - return 'pull' in issue.html_url + return 'pull' in issue_or_pr.html_url def update_self_repo( diff --git a/src/response_agent.py b/src/response_agent.py index c9c674f..f0ff69a 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -846,31 +846,32 @@ def process_issue( # Process a PR with no associated issue and blech_bot_tag elif is_pr and has_bot_mention and not associated_issue: + # Get repo object and pull request + client = get_github_client() + repo = get_repository(client, repo_name) pr_obj = repo.get_pull(issue_or_pr.number) branch_name = get_pr_branch(pr_obj) + repo_path = bot_tools.get_local_repo_path(repo_name) # branch_name = get_development_branch( # issue_or_pr, repo_path, create=False) # if branch_name is not None: # return False, f"Branch {branch_name} already exists for issue #{issue_or_pr.number}" + original_dir = os.getcwd() + os.chdir(repo_path) + checkout_branch(repo_path, branch_name, create=False) + # First generate edit command from previous discussion response, _ = generate_edit_command_response( issue_or_pr, repo_name) # branch_name = get_development_branch( # issue_or_pr, repo_path, create=True) - original_dir = os.getcwd() - os.chdir(repo_path) - checkout_branch(repo_path, branch_name, create=False) try: # Run aider with the generated command aider_output = run_aider(response, repo_path) - # Get repo object and pull request - client = get_github_client() - repo = get_repository(client, repo_name) - # Push changes with authentication push_success, err_msg = push_changes_with_authentication( repo_path, From 292da08f784c2f4245d137e6730d61fd79466547 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 31 Mar 2025 14:33:50 -0400 Subject: [PATCH 049/146] feat(response_agent): enhance issue processing with comment summarization - Added a function call to summarize relevant comments from issues or PRs. - Introduced a condition to handle cases where no relevant comments are found, assigning a default string. - Modified `generate_edit_command_response` to include summarized comments in its parameters. --- src/response_agent.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/response_agent.py b/src/response_agent.py index f0ff69a..33b5d8d 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -861,9 +861,14 @@ def process_issue( os.chdir(repo_path) checkout_branch(repo_path, branch_name, create=False) + summarized_comments, comment_list, summary_comment_str = summarize_relevant_comments( + issue_or_pr, repo_name) + if summary_comment_str == '': + summary_comment_str = 'No relevant comments found' + # First generate edit command from previous discussion response, _ = generate_edit_command_response( - issue_or_pr, repo_name) + issue_or_pr, repo_name, summary_comment_str) # branch_name = get_development_branch( # issue_or_pr, repo_path, create=True) From b62fbd9ee0d43e033bcc14d31075eb37e7b46172 Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Mon, 31 Mar 2025 14:41:53 -0400 Subject: [PATCH 050/146] feat: Enhance GitHub bot to process PRs and issues with improved context handling --- src/git_utils.py | 1 + src/response_agent.py | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 6891964..74cfb1e 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -55,6 +55,7 @@ def get_repository(client: Github, repo_name: str) -> Repository: def get_open_issues(repo: Repository) -> List[Issue]: """Get all open issues and pull requests from repository""" + # This already returns both issues and PRs with the GitHub API return list(repo.get_issues(state='open', sort='created', direction='asc')) diff --git a/src/response_agent.py b/src/response_agent.py index 33b5d8d..5acf4fd 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -844,31 +844,34 @@ def process_issue( return True, None - # Process a PR with no associated issue and blech_bot_tag - elif is_pr and has_bot_mention and not associated_issue: + # Process a PR with blech_bot_tag (with or without associated issue) + elif is_pr and has_bot_mention: # Get repo object and pull request client = get_github_client() repo = get_repository(client, repo_name) pr_obj = repo.get_pull(issue_or_pr.number) branch_name = get_pr_branch(pr_obj) repo_path = bot_tools.get_local_repo_path(repo_name) - # branch_name = get_development_branch( - # issue_or_pr, repo_path, create=False) - # if branch_name is not None: - # return False, f"Branch {branch_name} already exists for issue #{issue_or_pr.number}" + + # If we have an associated issue, use it for context in our response + issue_context = associated_issue if associated_issue else issue_or_pr original_dir = os.getcwd() os.chdir(repo_path) checkout_branch(repo_path, branch_name, create=False) + # Use the associated issue for context if available, otherwise use the PR + context_item = associated_issue if associated_issue else issue_or_pr summarized_comments, comment_list, summary_comment_str = summarize_relevant_comments( - issue_or_pr, repo_name) + context_item, repo_name) if summary_comment_str == '': summary_comment_str = 'No relevant comments found' # First generate edit command from previous discussion + # Use the associated issue for context if available + context_item = associated_issue if associated_issue else issue_or_pr response, _ = generate_edit_command_response( - issue_or_pr, repo_name, summary_comment_str) + context_item, repo_name, summary_comment_str) # branch_name = get_development_branch( # issue_or_pr, repo_path, create=True) @@ -929,10 +932,10 @@ def process_issue( # Handle linked PR for issues if not is_pr and has_linked_pr(issue_or_pr): linked_pr = get_linked_pr(issue_or_pr) - if linked_pr and triggers.has_user_feedback(linked_pr): + if linked_pr: print( - f"Issue #{issue_or_pr.number} has linked PR #{linked_pr.number} with user feedback") - # Process the PR instead of the issue + f"Issue #{issue_or_pr.number} has linked PR #{linked_pr.number}") + # Always process the PR instead of the issue when a linked PR exists return process_issue(linked_pr, repo_name) # Generate and post response From 0bf877a9ec48bf2969472789a5dafd3c5d1a6157 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:42:00 +0000 Subject: [PATCH 051/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/response_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/response_agent.py b/src/response_agent.py index 5acf4fd..5dd8fa1 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -852,7 +852,7 @@ def process_issue( pr_obj = repo.get_pull(issue_or_pr.number) branch_name = get_pr_branch(pr_obj) repo_path = bot_tools.get_local_repo_path(repo_name) - + # If we have an associated issue, use it for context in our response issue_context = associated_issue if associated_issue else issue_or_pr From 56cd333891e89db1984ab13ffbe5cefe28b5a78b Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 13:14:09 -0400 Subject: [PATCH 052/146] refactor(response_agent): modularize and streamline flow functions - Added `develop_issue_flow` function to handle issue development, including branch creation, PR linking, and label management. - Introduced `respond_pr_comment_flow` to manage PR comments processing. - Implemented `standalone_pr_flow` for handling standalone PRs. - Refactored `process_issue` to utilize the new modular functions for clean execution and reduced redundancy. --- src/response_agent.py | 523 ++++++++++++++++++++++-------------------- 1 file changed, 270 insertions(+), 253 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index 33b5d8d..6fefc52 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -610,6 +610,259 @@ def response_selector(trigger: str) -> Callable: return None +def develop_issue_flow( + issue_or_pr: Union[Issue, PullRequest], + repo_name: str, + is_pr: bool = False, +) -> Tuple[bool, Optional[str]]: + # Only issues can be developed, not PRs + if is_pr: + return False, "Cannot develop a PR, only issues can be developed" + + print('Triggered by [ develop_issue ] command') + repo_path = bot_tools.get_local_repo_path(repo_name) + + # Check for existing branches + branch_name = get_development_branch( + issue_or_pr, repo_path, create=False) + # if branch_name is not None: + # return False, f"Branch {branch_name} already exists for issue #{issue_or_pr.number}" + + # Check for linked PRs + if has_linked_pr(issue_or_pr): + return False, f"Issue #{issue_or_pr.number} already has a linked pull request" + + # Check if issue has label "under_development" + if "under_development" in [label.name for label in issue_or_pr.labels]: + return False, f"Issue #{issue_or_pr.number} is already under development" + + # First generate edit command from previous discussion + response, _ = generate_edit_command_response( + issue_or_pr, repo_name) + + branch_name = get_development_branch( + issue_or_pr, repo_path, create=True) + original_dir = os.getcwd() + os.chdir(repo_path) + checkout_branch(repo_path, branch_name, create=False) + + try: + # Run aider with the generated command + aider_output = run_aider(response, repo_path) + + # Get repo object and pull request + client = get_github_client() + repo = get_repository(client, repo_name) + + # Push changes with authentication + push_success, err_msg = push_changes_with_authentication( + repo_path, + issue_or_pr, + branch_name + ) + + pr_url = create_pull_request_from_issue(issue_or_pr, repo_path) + pr_number = pr_url.split('/')[-1] + pull = repo.get_pull(int(pr_number)) + + # Create pull request + write_issue_response( + issue_or_pr, + f"Created pull request: {pr_url}\nContinue discussion there." + ) + + # Mark issue with label "under_development" + issue_or_pr.add_to_labels("under_development") + + if not push_success: + return False, f"Failed to push changes: {err_msg}" + + # write_issue_response(issue, "Generated edit command:\n" + response) + write_str = f"Generated edit command:\n---\n{response}\n\n" + \ + f"Aider output:\n
View Aider Output\n\n```{aider_output}```\n
" + # Clean the response first to remove any existing signatures + write_str = clean_response(write_str) + signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" + if signature not in write_str: + write_str += signature + pull.create_issue_comment(write_str) + + # Switch back to main branch + back_to_master_branch(repo_path) + # Return to original directory + os.chdir(original_dir) + + except Exception as e: + # Clean up on error + back_to_master_branch(repo_path) + delete_branch(repo_path, branch_name, force=True) + # Return to original directory + os.chdir(original_dir) + raise RuntimeError( + f"Failed to process develop issue: {str(e)}") + + return True, None + + +def respond_pr_comment_flow( + issue_or_pr: Union[Issue, PullRequest], + repo_name: str, + pr_comment: str, +) -> Tuple[bool, Optional[str]]: + + repo_path = bot_tools.get_local_repo_path(repo_name) + repo = get_repository(get_github_client(), repo_name) + + # Get latest user comment + extractor = URLExtract() + urls = extractor.find_urls(pr_comment)[0] + pr_number = int(urls.split('/')[-1]) + pr = repo.get_pull(pr_number) + + comments = list(pr.get_issue_comments()) + # find the latest bot comment + latest_bot_idx = -1 + for i, comment in enumerate(comments): + if "generated by blech_bot" in comment.body: + latest_bot_idx = i + + # check if there are any comments after the latest bot comment + user_feedback_bool = latest_bot_idx >= 0 and latest_bot_idx < len( + comments) - 1 + + branch_name = get_development_branch( + issue_or_pr, repo_path, create=False) + + # Only run if branch exists and user comment is found on PR + if branch_name and user_feedback_bool: + user_comment = comments[-1].body + print('Triggered by user comment on PR') + + try: + original_dir = os.getcwd() + os.chdir(repo_path) + # Switch to development branch + checkout_branch(repo_path, branch_name, create=False) + + # Might have to pull and merge here if pre-commit hooks are enabled + # and made changes + remote_branch = f"origin/{branch_name}" + subprocess.run( + ['git', 'pull', 'origin', branch_name], check=True) + + if user_comment: + # Summarize relevant comments + summarized_comments, comment_list, summary_comment_str = summarize_relevant_comments( + issue_or_pr, repo_name) + + if summary_comment_str == '': + summary_comment_str = 'No relevant comments found' + + # Pass to generate_edit_command agent first + response, _ = generate_edit_command_response( + issue_or_pr, repo_name, summary_comment_str) + + # Then run aider with the generated command + aider_output = run_aider(response, repo_path) + + # Push changes + push_success, err_msg = push_changes_with_authentication( + repo_path, + pr, + branch_name) + + if not push_success: + return False, f"Failed to push changes: {err_msg}" + + # Write response + write_str = f"Applied changes based on comment:\n
View Aider Output\n\n```\n{aider_output}\n```\n
" + signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" + # Clean the response first to remove any existing signatures + write_str = clean_response(write_str) + if signature not in write_str: + write_str += signature + pr.create_issue_comment(write_str) + + # Clean up + back_to_master_branch(repo_path) + + # Return to original directory + os.chdir(original_dir) + + return True, None + + except Exception as e: + # Clean up on error + back_to_master_branch(repo_path) + # Return to original directory + os.chdir(original_dir) + raise RuntimeError( + f"Failed to process PR comment: {str(e)}") + + +def standalone_pr_flow( + issue_or_pr: Union[Issue, PullRequest], + repo_name: str, +) -> Tuple[bool, Optional[str]]: + + # Get repo object and pull request + client = get_github_client() + repo = get_repository(client, repo_name) + pr_obj = repo.get_pull(issue_or_pr.number) + branch_name = get_pr_branch(pr_obj) + repo_path = bot_tools.get_local_repo_path(repo_name) + + original_dir = os.getcwd() + os.chdir(repo_path) + checkout_branch(repo_path, branch_name, create=False) + + summarized_comments, comment_list, summary_comment_str = summarize_relevant_comments( + issue_or_pr, repo_name) + if summary_comment_str == '': + summary_comment_str = 'No relevant comments found' + + # First generate edit command from previous discussion + response, _ = generate_edit_command_response( + issue_or_pr, repo_name, summary_comment_str) + + try: + # Run aider with the generated command + aider_output = run_aider(response, repo_path) + + # Push changes with authentication + push_success, err_msg = push_changes_with_authentication( + repo_path, + issue_or_pr, + branch_name + ) + + # write_issue_response(issue, "Generated edit command:\n" + response) + write_str = f"Generated edit command:\n---\n{response}\n\n" + \ + f"Aider output:\n
View Aider Output\n\n```{aider_output}```\n
" + # Clean the response first to remove any existing signatures + write_str = clean_response(write_str) + signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" + if signature not in write_str: + write_str += signature + pr_obj.create_issue_comment(write_str) + + # Switch back to main branch + back_to_master_branch(repo_path) + # Return to original directory + os.chdir(original_dir) + + except Exception as e: + # Clean up on error + back_to_master_branch(repo_path) + delete_branch(repo_path, branch_name, force=True) + # Return to original directory + os.chdir(original_dir) + raise RuntimeError( + f"Failed to process develop issue: {str(e)}") + + return True, None + + def process_issue( issue_or_pr: Union[Issue, PullRequest], repo_name: str, @@ -663,268 +916,32 @@ def process_issue( # Process PR Already created from issue # Check for user comments on PR first if pr_comment_bool: - - repo_path = bot_tools.get_local_repo_path(repo_name) - repo = get_repository(get_github_client(), repo_name) - - # Get latest user comment - extractor = URLExtract() - urls = extractor.find_urls(pr_comment)[0] - pr_number = int(urls.split('/')[-1]) - pr = repo.get_pull(pr_number) - - comments = list(pr.get_issue_comments()) - # find the latest bot comment - latest_bot_idx = -1 - for i, comment in enumerate(comments): - if "generated by blech_bot" in comment.body: - latest_bot_idx = i - - # check if there are any comments after the latest bot comment - user_feedback_bool = latest_bot_idx >= 0 and latest_bot_idx < len( - comments) - 1 - - branch_name = get_development_branch( - issue_or_pr, repo_path, create=False) - - # Only run if branch exists and user comment is found on PR - if branch_name and user_feedback_bool: - user_comment = comments[-1].body - print('Triggered by user comment on PR') - - try: - original_dir = os.getcwd() - os.chdir(repo_path) - # Switch to development branch - checkout_branch(repo_path, branch_name, create=False) - - # Might have to pull and merge here if pre-commit hooks are enabled - # and made changes - remote_branch = f"origin/{branch_name}" - subprocess.run( - ['git', 'pull', 'origin', branch_name], check=True) - - if user_comment: - # Summarize relevant comments - summarized_comments, comment_list, summary_comment_str = summarize_relevant_comments( - issue_or_pr, repo_name) - - if summary_comment_str == '': - summary_comment_str = 'No relevant comments found' - - # Pass to generate_edit_command agent first - response, _ = generate_edit_command_response( - issue_or_pr, repo_name, summary_comment_str) - - # Then run aider with the generated command - aider_output = run_aider(response, repo_path) - - # Push changes - push_success, err_msg = push_changes_with_authentication( - repo_path, - pr, - branch_name) - - if not push_success: - return False, f"Failed to push changes: {err_msg}" - - # Write response - write_str = f"Applied changes based on comment:\n
View Aider Output\n\n```\n{aider_output}\n```\n
" - signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" - # Clean the response first to remove any existing signatures - write_str = clean_response(write_str) - if signature not in write_str: - write_str += signature - pr.create_issue_comment(write_str) - - # Clean up - back_to_master_branch(repo_path) - - # Return to original directory - os.chdir(original_dir) - - return True, None - - except Exception as e: - # Clean up on error - back_to_master_branch(repo_path) - # Return to original directory - os.chdir(original_dir) - raise RuntimeError( - f"Failed to process PR comment: {str(e)}") + result, err_msg = respond_pr_comment_flow( + issue_or_pr, + repo_name, + pr_comment + ) + return result, err_msg # Developing pull request from issue # Check for develop_issue trigger next elif triggers.has_develop_issue_trigger(issue_or_pr): - # Only issues can be developed, not PRs - if is_pr: - return False, "Cannot develop a PR, only issues can be developed" - - print('Triggered by [ develop_issue ] command') - repo_path = bot_tools.get_local_repo_path(repo_name) - - # Check for existing branches - branch_name = get_development_branch( - issue_or_pr, repo_path, create=False) - # if branch_name is not None: - # return False, f"Branch {branch_name} already exists for issue #{issue_or_pr.number}" - - # Check for linked PRs - if has_linked_pr(issue_or_pr): - return False, f"Issue #{issue_or_pr.number} already has a linked pull request" - - # Check if issue has label "under_development" - if "under_development" in [label.name for label in issue_or_pr.labels]: - return False, f"Issue #{issue_or_pr.number} is already under development" - - # First generate edit command from previous discussion - response, _ = generate_edit_command_response( - issue_or_pr, repo_name) - - branch_name = get_development_branch( - issue_or_pr, repo_path, create=True) - original_dir = os.getcwd() - os.chdir(repo_path) - checkout_branch(repo_path, branch_name, create=False) - - try: - # Run aider with the generated command - aider_output = run_aider(response, repo_path) - - # Get repo object and pull request - client = get_github_client() - repo = get_repository(client, repo_name) - - # Push changes with authentication - push_success, err_msg = push_changes_with_authentication( - repo_path, - issue_or_pr, - branch_name - ) - - pr_url = create_pull_request_from_issue(issue_or_pr, repo_path) - pr_number = pr_url.split('/')[-1] - pull = repo.get_pull(int(pr_number)) - - # Create pull request - write_issue_response( - issue_or_pr, - f"Created pull request: {pr_url}\nContinue discussion there." - ) - - # Mark issue with label "under_development" - issue_or_pr.add_to_labels("under_development") - - if not push_success: - return False, f"Failed to push changes: {err_msg}" - - # write_issue_response(issue, "Generated edit command:\n" + response) - write_str = f"Generated edit command:\n---\n{response}\n\n" + \ - f"Aider output:\n
View Aider Output\n\n```{aider_output}```\n
" - # Clean the response first to remove any existing signatures - write_str = clean_response(write_str) - signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" - if signature not in write_str: - write_str += signature - pull.create_issue_comment(write_str) - # Switch back to main branch - back_to_master_branch(repo_path) - # Return to original directory - os.chdir(original_dir) - - except Exception as e: - # Clean up on error - back_to_master_branch(repo_path) - delete_branch(repo_path, branch_name, force=True) - # Return to original directory - os.chdir(original_dir) - raise RuntimeError( - f"Failed to process develop issue: {str(e)}") - - return True, None + result, err_msg = develop_issue_flow( + issue_or_pr, + repo_name, + is_pr=is_pr + ) + return result, err_msg # Process a PR with no associated issue and blech_bot_tag elif is_pr and has_bot_mention and not associated_issue: - # Get repo object and pull request - client = get_github_client() - repo = get_repository(client, repo_name) - pr_obj = repo.get_pull(issue_or_pr.number) - branch_name = get_pr_branch(pr_obj) - repo_path = bot_tools.get_local_repo_path(repo_name) - # branch_name = get_development_branch( - # issue_or_pr, repo_path, create=False) - # if branch_name is not None: - # return False, f"Branch {branch_name} already exists for issue #{issue_or_pr.number}" - - original_dir = os.getcwd() - os.chdir(repo_path) - checkout_branch(repo_path, branch_name, create=False) - - summarized_comments, comment_list, summary_comment_str = summarize_relevant_comments( - issue_or_pr, repo_name) - if summary_comment_str == '': - summary_comment_str = 'No relevant comments found' - - # First generate edit command from previous discussion - response, _ = generate_edit_command_response( - issue_or_pr, repo_name, summary_comment_str) - - # branch_name = get_development_branch( - # issue_or_pr, repo_path, create=True) - - try: - # Run aider with the generated command - aider_output = run_aider(response, repo_path) - - # Push changes with authentication - push_success, err_msg = push_changes_with_authentication( - repo_path, - issue_or_pr, - branch_name - ) - - # pr_url = create_pull_request_from_issue(issue_or_pr, repo_path) - # pr_number = pr_url.split('/')[-1] - # pull = repo.get_pull(int(pr_number)) - - # Create pull request - # write_issue_response( - # issue_or_pr, - # f"Created pull request: {pr_url}\nContinue discussion there." - # ) - - # Mark issue with label "under_development" - # issue_or_pr.add_to_labels("under_development") - - # if not push_success: - # return False, f"Failed to push changes: {err_msg}" - - # write_issue_response(issue, "Generated edit command:\n" + response) - write_str = f"Generated edit command:\n---\n{response}\n\n" + \ - f"Aider output:\n
View Aider Output\n\n```{aider_output}```\n
" - # Clean the response first to remove any existing signatures - write_str = clean_response(write_str) - signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" - if signature not in write_str: - write_str += signature - pr_obj.create_issue_comment(write_str) - - # Switch back to main branch - back_to_master_branch(repo_path) - # Return to original directory - os.chdir(original_dir) - except Exception as e: - # Clean up on error - back_to_master_branch(repo_path) - delete_branch(repo_path, branch_name, force=True) - # Return to original directory - os.chdir(original_dir) - raise RuntimeError( - f"Failed to process develop issue: {str(e)}") - - return True, None + result, err_msg = standalone_pr_flow( + issue_or_pr, + repo_name + ) + return result, err_msg # Handle linked PR for issues if not is_pr and has_linked_pr(issue_or_pr): From 368a7e190724b87f220b4255d85e63123622fc92 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 13:24:54 -0400 Subject: [PATCH 053/146] refactor(response): encapsulate PR comment writing logic - Introduced a new function `write_pr_comment` to handle the creation of comments on pull requests. - Replaced repeated code across multiple functions (`develop_issue_flow`, `respond_pr_comment_flow`, `standalone_pr_flow`) with a call to `write_pr_comment`. - Improved code maintainability and readability by centralizing the comment-writing logic. - Removed commented-out and redundant code for branch existence checks. --- src/response_agent.py | 61 +++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index 684544d..7c7f6c2 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -610,6 +610,29 @@ def response_selector(trigger: str) -> Callable: return None +def write_pr_comment( + pr_obj: PullRequest, + response: str, + aider_output: str, + llm_config: dict, +) -> None: + """ + Write a comment on the pull request with the generated response and aider output + + Args: + pr_obj: The PullRequest object to write the comment on + response: The generated response text + aider_output: The output from the aider command + llm_config: The configuration for the LLM used to generate the response + """ + # Clean the response first to remove any existing signatures + write_str = clean_response(write_str) + signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" + if signature not in write_str: + write_str += signature + pr_obj.create_issue_comment(write_str) + + def develop_issue_flow( issue_or_pr: Union[Issue, PullRequest], repo_name: str, @@ -625,8 +648,6 @@ def develop_issue_flow( # Check for existing branches branch_name = get_development_branch( issue_or_pr, repo_path, create=False) - # if branch_name is not None: - # return False, f"Branch {branch_name} already exists for issue #{issue_or_pr.number}" # Check for linked PRs if has_linked_pr(issue_or_pr): @@ -680,12 +701,12 @@ def develop_issue_flow( # write_issue_response(issue, "Generated edit command:\n" + response) write_str = f"Generated edit command:\n---\n{response}\n\n" + \ f"Aider output:\n
View Aider Output\n\n```{aider_output}```\n
" - # Clean the response first to remove any existing signatures - write_str = clean_response(write_str) - signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" - if signature not in write_str: - write_str += signature - pull.create_issue_comment(write_str) + write_pr_comment( + pull, + write_str, + aider_output=aider_output, + llm_config=llm_config + ) # Switch back to main branch back_to_master_branch(repo_path) @@ -776,12 +797,12 @@ def respond_pr_comment_flow( # Write response write_str = f"Applied changes based on comment:\n
View Aider Output\n\n```\n{aider_output}\n```\n
" - signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" - # Clean the response first to remove any existing signatures - write_str = clean_response(write_str) - if signature not in write_str: - write_str += signature - pr.create_issue_comment(write_str) + write_pr_comment( + pr, + write_str, + aider_output=aider_output, + llm_config=llm_config + ) # Clean up back_to_master_branch(repo_path) @@ -839,12 +860,12 @@ def standalone_pr_flow( # write_issue_response(issue, "Generated edit command:\n" + response) write_str = f"Generated edit command:\n---\n{response}\n\n" + \ f"Aider output:\n
View Aider Output\n\n```{aider_output}```\n
" - # Clean the response first to remove any existing signatures - write_str = clean_response(write_str) - signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" - if signature not in write_str: - write_str += signature - pr_obj.create_issue_comment(write_str) + write_pr_comment( + pr_obj, + write_str, + aider_output=aider_output, + llm_config=llm_config + ) # Switch back to main branch back_to_master_branch(repo_path) From 43fd805aff9d64029163f651ab253b26f91ba22f Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 13:39:41 -0400 Subject: [PATCH 054/146] refactor(response_agent): simplify process_issue logic and remove redundant functions - Removed `has_linked_pr` and `get_linked_pr` functions from `git_utils.py` as they were not utilized. - Refactored the `process_issue` function in `response_agent.py` to improve readability and flow control. - Eliminated redundant exception handling code at the higher level in `process_repository` for a cleaner try-except approach. - Simplified checks for bot tag association and processing logic for both issues and PRs. --- src/git_utils.py | 79 ------------------------ src/response_agent.py | 136 ++++++++++++++++++++---------------------- 2 files changed, 64 insertions(+), 151 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 74cfb1e..a7072d5 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -392,54 +392,6 @@ def push_changes_with_authentication( return success_bool, error_msg -def has_linked_pr(issue: Issue) -> bool: - """ - Check if an issue has a linked pull request - - Args: - issue: The GitHub issue to check - - Returns: - True if the issue has a linked PR, False otherwise - """ - # Get timeline events to check for PR links - timeline = list(issue.get_timeline()) - - # Check if any timeline event is a cross-reference to a PR - for event in timeline: - if event.event == "cross-referenced": - # Check if the reference is to a PR - if event.source and event.source.type == "PullRequest": - return True - - return False - - -def get_linked_pr(issue: Issue) -> Optional[PullRequest]: - """ - Get the linked pull request for an issue - - Args: - issue: The GitHub issue to check - - Returns: - The linked PullRequest object or None if not found - """ - # Get timeline events to check for PR links - timeline = list(issue.get_timeline()) - - # Check if any timeline event is a cross-reference to a PR - for event in timeline: - if event.event == "cross-referenced": - # Check if the reference is to a PR - if event.source and event.source.type == "PullRequest": - pr_number = event.source.issue.number - repo = issue.repository - return repo.get_pull(pr_number) - - return None - - def get_associated_issue(pr: PullRequest) -> Optional[Issue]: """ Get the associated issue for a pull request @@ -463,37 +415,6 @@ def get_associated_issue(pr: PullRequest) -> Optional[Issue]: repo = pr.repository return repo.get_issue(issue_number) - # # Check if PR body contains "Fixes #X" or "Closes #X" or similar - # if not pr.body: - # return None - # - # # Look for common issue reference patterns - # issue_ref_patterns = [ - # r"(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+#(\d+)", - # r"(?:issue|issues)\s+#(\d+)", - # r"#(\d+)" - # ] - # - # for pattern in issue_ref_patterns: - # matches = re.findall(pattern, pr.body, re.IGNORECASE) - # if matches: - # try: - # issue_number = int(matches[0]) - # return pr.repository.get_issue(issue_number) - # except Exception: - # continue - # - # # If no match found in body, check PR title - # if pr.title: - # for pattern in issue_ref_patterns: - # matches = re.findall(pattern, pr.title, re.IGNORECASE) - # if matches: - # try: - # issue_number = int(matches[0]) - # return pr.repository.get_issue(issue_number) - # except Exception: - # continue - # return None diff --git a/src/response_agent.py b/src/response_agent.py index 7c7f6c2..f81dd9d 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -900,78 +900,31 @@ def process_issue( is_pr = is_pull_request(issue_or_pr) entity_type = "PR" if is_pr else "issue" print(f"Processing {entity_type} #{issue_or_pr.number}") + has_bot_mention = triggers.has_blech_bot_tag(issue_or_pr) \ + or '[ blech_bot ]' in (issue_or_pr.title or '').lower() - try: - # Handle PR differently - if is_pr: - pr = issue_or_pr - # Check if PR has blech_bot label - has_bot_mention = triggers.has_blech_bot_tag(pr) - associated_issue = get_associated_issue(pr) - - # If PR doesn't have blech_bot label, check if it has an associated issue with the label - if not has_bot_mention: - if associated_issue and triggers.has_blech_bot_tag(associated_issue): - # Use the associated issue for processing - print( - f"PR #{pr.number} has associated issue #{associated_issue.number} with blech_bot tag") - has_bot_mention = True - else: - return False, f"PR #{pr.number} does not have blech_bot label and no associated issue with blech_bot tag" - else: - # Regular issue processing - # Check if issue has blech_bot tag or blech_bot in title - has_bot_mention = triggers.has_blech_bot_tag( - issue_or_pr) or "[ blech_bot ]" in issue_or_pr.title.lower() - if not has_bot_mention: - return False, "Issue does not have blech_bot tag or mention in title" - - # Check if already responded without user feedback - already_responded = triggers.has_bot_response( - issue_or_pr) and not triggers.has_user_feedback(issue_or_pr) - pr_comment_bool, pr_comment = triggers.has_pr_creation_comment( - issue_or_pr) - if already_responded and not pr_comment_bool: - return False, f"{entity_type} already has a bot response without feedback from user" - - # Process PR Already created from issue - # Check for user comments on PR first - if pr_comment_bool: - result, err_msg = respond_pr_comment_flow( - issue_or_pr, - repo_name, - pr_comment - ) - return result, err_msg - - # Developing pull request from issue - # Check for develop_issue trigger next - elif triggers.has_develop_issue_trigger(issue_or_pr): - - result, err_msg = develop_issue_flow( - issue_or_pr, - repo_name, - is_pr=is_pr - ) - return result, err_msg - - # Process a PR with no associated issue and blech_bot_tag - elif is_pr and has_bot_mention and not associated_issue: - - result, err_msg = standalone_pr_flow( - issue_or_pr, - repo_name - ) - return result, err_msg + # Handle PR differently + if is_pr: + # Check if PR has blech_bot label + associated_issue = get_associated_issue(pr_or_issue) - # Handle linked PR for issues - if not is_pr and has_linked_pr(issue_or_pr): - linked_pr = get_linked_pr(issue_or_pr) - if linked_pr: + # If PR doesn't have blech_bot label, check if it has an associated issue with the label + if not has_bot_mention: + if associated_issue and triggers.has_blech_bot_tag(associated_issue): + # Use the associated issue for processing print( - f"Issue #{issue_or_pr.number} has linked PR #{linked_pr.number}") - # Always process the PR instead of the issue when a linked PR exists - return process_issue(linked_pr, repo_name) + f"PR #{pr.number} has associated issue #{associated_issue.number} with blech_bot tag") + # Overwrite has_bot_mention to True to process the PR based on the associated issue + has_bot_mention = True + else: + return False, f"PR #{pr.number} does not have blech_bot label and no associated issue with blech_bot tag" + else: # It's an issue + if not has_bot_mention: + return False, "Issue does not have blech_bot tag or mention in title" + + # Check if a pr_creation comment exists for the issue + pr_creation_comment_bool, pr_creation_comment = triggers.has_pr_creation_comment( + issue_or_pr) # Generate and post response trigger = check_triggers(issue_or_pr) @@ -982,8 +935,40 @@ def process_issue( write_issue_response(issue_or_pr, response) return True, None - except Exception as e: - return False, f"Error processing issue: {traceback.format_exc()}" + # Check if already responded without user feedback + already_responded = triggers.has_bot_response( + issue_or_pr) and not triggers.has_user_feedback(issue_or_pr) + if already_responded and not pr_creation_comment_bool: + return False, f"{entity_type} already has a bot response without feedback from user" + + # Process PR Already created from issue + if pr_creation_comment_bool: # If PR has been created, respond if it has an unresponded comment + result, err_msg = respond_pr_comment_flow( + issue_or_pr, + repo_name, + pr_comment + ) + return result, err_msg + + # Developing pull request from issue + # Check for develop_issue trigger next + elif triggers.has_develop_issue_trigger(issue_or_pr): + + result, err_msg = develop_issue_flow( + issue_or_pr, + repo_name, + is_pr=is_pr + ) + return result, err_msg + + # Process a PR with no associated issue and blech_bot_tag + elif is_pr and has_bot_mention and not associated_issue: + + result, err_msg = standalone_pr_flow( + issue_or_pr, + repo_name + ) + return result, err_msg def run_aider(message: str, repo_path: str) -> str: @@ -1081,7 +1066,14 @@ def process_repository( # Process each issue and PR for item in open_issues: entity_type = "PR" if is_pull_request(item) else "issue" - success, error = process_issue(item, repo_name) + try: + process_issue(item, repo_name) + success = True + error = None + except Exception as e: + success = False + # Capture the error and print the traceback for debugging + error = str(e) if success: print(f"Successfully processed {entity_type} #{item.number}") else: From f214d02c6d0b96b2eeaae60d811b91f95c575958 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 14:09:01 -0400 Subject: [PATCH 055/146] feat(git-integration): enhance issue and PR processing - Added `has_linked_pr` and `get_linked_pr` functions to check and fetch linked PRs from issues in `git_utils.py`. - Updated logic in `response_agent.py` to handle scenarios without directly using the `has_linked_pr` and `get_linked_pr` functions. - Improved checks for handling issues vs PRs, including processing standalone PR flows. - Added print statements for better visibility into the PR processing flow. - Refactored response generation and issue development flow to ensure clean handling of different triggers and states. --- src/git_utils.py | 43 +++++++++++++++++++ src/response_agent.py | 96 ++++++++++++++++++++++--------------------- 2 files changed, 93 insertions(+), 46 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index a7072d5..85dc7e9 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -554,6 +554,49 @@ def perform_github_search( return f"Error performing GitHub search: {str(e)}" +def has_linked_pr(issue: Issue) -> bool: + """ + Check if an issue has a linked pull request + Args: + issue: The GitHub issue to check + Returns: + True if the issue has a linked PR, False otherwise + """ + # Get timeline events to check for PR links + timeline = list(issue.get_timeline()) + + # Check if any timeline event is a cross-reference to a PR + for event in timeline: + if event.event == "cross-referenced": + # Check if the reference is to a PR + if event.source and event.source.type == "PullRequest": + return True + return False + + +def get_linked_pr(issue: Issue) -> PullRequest: + """ + Get the linked pull request for an issue + Args: + issue: The GitHub issue to check + Returns: + The linked PullRequest object or None if not found + """ + # Get timeline events to check for PR links + timeline = list(issue.get_timeline()) + + # Check if any timeline event is a cross-reference to a PR + for event in timeline: + if event.event == "cross-referenced": + # Check if the reference is to a PR + if event.source and event.source.type == "PullRequest": + pr_number = event.source.issue.number + repo = issue.repository + return repo.get_pull(pr_number) + + return None + + if __name__ == '__main__': client = get_github_client() repo = get_repository(client, 'katzlabbrandeis/blech_clust') diff --git a/src/response_agent.py b/src/response_agent.py index f81dd9d..2417261 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -28,8 +28,6 @@ get_issue_comments, create_pull_request_from_issue, get_development_branch, - has_linked_pr, - get_linked_pr, push_changes_with_authentication, get_associated_issue, is_pull_request, @@ -649,9 +647,9 @@ def develop_issue_flow( branch_name = get_development_branch( issue_or_pr, repo_path, create=False) - # Check for linked PRs - if has_linked_pr(issue_or_pr): - return False, f"Issue #{issue_or_pr.number} already has a linked pull request" + # # Check for linked PRs + # if has_linked_pr(issue_or_pr): + # return False, f"Issue #{issue_or_pr.number} already has a linked pull request" # Check if issue has label "under_development" if "under_development" in [label.name for label in issue_or_pr.labels]: @@ -905,11 +903,13 @@ def process_issue( # Handle PR differently if is_pr: + print('Detected as a Pull Request (PR)') # Check if PR has blech_bot label - associated_issue = get_associated_issue(pr_or_issue) + associated_issue = get_associated_issue(issue_or_pr) # If PR doesn't have blech_bot label, check if it has an associated issue with the label if not has_bot_mention: + print('PR does not have blech_bot label, checking associated issue') if associated_issue and triggers.has_blech_bot_tag(associated_issue): # Use the associated issue for processing print( @@ -918,6 +918,17 @@ def process_issue( has_bot_mention = True else: return False, f"PR #{pr.number} does not have blech_bot label and no associated issue with blech_bot tag" + + # Process a PR with no associated issue and blech_bot_tag + elif is_pr and has_bot_mention and not associated_issue: + print('Processing standalone PR flow since no associated issue found') + + result, err_msg = standalone_pr_flow( + issue_or_pr, + repo_name + ) + return result, err_msg + else: # It's an issue if not has_bot_mention: return False, "Issue does not have blech_bot tag or mention in title" @@ -926,49 +937,42 @@ def process_issue( pr_creation_comment_bool, pr_creation_comment = triggers.has_pr_creation_comment( issue_or_pr) - # Generate and post response - trigger = check_triggers(issue_or_pr) - response_func = response_selector(trigger) - if response_func is None: - return False, f"No trigger found for {entity_type} #{issue_or_pr.number}" - response, all_content = response_func(issue_or_pr, repo_name) - write_issue_response(issue_or_pr, response) - return True, None - - # Check if already responded without user feedback - already_responded = triggers.has_bot_response( - issue_or_pr) and not triggers.has_user_feedback(issue_or_pr) - if already_responded and not pr_creation_comment_bool: - return False, f"{entity_type} already has a bot response without feedback from user" - - # Process PR Already created from issue - if pr_creation_comment_bool: # If PR has been created, respond if it has an unresponded comment - result, err_msg = respond_pr_comment_flow( - issue_or_pr, - repo_name, - pr_comment - ) - return result, err_msg - - # Developing pull request from issue - # Check for develop_issue trigger next - elif triggers.has_develop_issue_trigger(issue_or_pr): + # Check if already responded without user feedback + already_responded = triggers.has_bot_response( + issue_or_pr) and not triggers.has_user_feedback(issue_or_pr) + if already_responded and not pr_creation_comment_bool: + return False, f"{entity_type} already has a bot response without feedback from user" + + # Process PR Already created from issue + if pr_creation_comment_bool: # If PR has been created, respond if it has an unresponded comment + result, err_msg = respond_pr_comment_flow( + issue_or_pr, + repo_name, + pr_comment + ) + return result, err_msg - result, err_msg = develop_issue_flow( - issue_or_pr, - repo_name, - is_pr=is_pr - ) - return result, err_msg + # Developing pull request from issue + # Check for develop_issue trigger next + elif triggers.has_develop_issue_trigger(issue_or_pr): - # Process a PR with no associated issue and blech_bot_tag - elif is_pr and has_bot_mention and not associated_issue: + result, err_msg = develop_issue_flow( + issue_or_pr, + repo_name, + is_pr=is_pr + ) + return result, err_msg - result, err_msg = standalone_pr_flow( - issue_or_pr, - repo_name - ) - return result, err_msg + # Process as new issue + else: + # Generate and post response + trigger = check_triggers(issue_or_pr) + response_func = response_selector(trigger) + if response_func is None: + return False, f"No trigger found for {entity_type} #{issue_or_pr.number}" + response, all_content = response_func(issue_or_pr, repo_name) + write_issue_response(issue_or_pr, response) + return True, None def run_aider(message: str, repo_path: str) -> str: From 6cf5730bff6a387ede469219a02bfac7ccb4686d Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 14:39:29 -0400 Subject: [PATCH 056/146] fix(response_agent): handle PR comment flow exceptions and improve logging - Added exception handling around the PR comment flow to manage errors gracefully. - Improved logging with additional print statements to trace the process and potential issues. - Fixed a variable substitution error in the process issue method to ensure correct PR number referencing. --- src/response_agent.py | 61 +++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index 2417261..d2e5b79 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -729,28 +729,38 @@ def respond_pr_comment_flow( pr_comment: str, ) -> Tuple[bool, Optional[str]]: - repo_path = bot_tools.get_local_repo_path(repo_name) - repo = get_repository(get_github_client(), repo_name) - - # Get latest user comment - extractor = URLExtract() - urls = extractor.find_urls(pr_comment)[0] - pr_number = int(urls.split('/')[-1]) - pr = repo.get_pull(pr_number) - - comments = list(pr.get_issue_comments()) - # find the latest bot comment - latest_bot_idx = -1 - for i, comment in enumerate(comments): - if "generated by blech_bot" in comment.body: - latest_bot_idx = i - - # check if there are any comments after the latest bot comment - user_feedback_bool = latest_bot_idx >= 0 and latest_bot_idx < len( - comments) - 1 - - branch_name = get_development_branch( - issue_or_pr, repo_path, create=False) + try: + print("Attempting to get PR branch details") + repo_path = bot_tools.get_local_repo_path(repo_name) + repo = get_repository(get_github_client(), repo_name) + + # Get latest user comment + extractor = URLExtract() + urls = extractor.find_urls(pr_comment)[0] + pr_number = int(urls.split('/')[-1]) + pr = repo.get_pull(pr_number) + + comments = list(pr.get_issue_comments()) + + if not comments: + print("No comments found on the PR") + print("If PR was generated using `develop_issue`, something went wrong.") + + # find the latest bot comment + latest_bot_idx = -1 + for i, comment in enumerate(comments): + if "generated by blech_bot" in comment.body: + latest_bot_idx = i + + # check if there are any comments after the latest bot comment + user_feedback_bool = latest_bot_idx >= 0 and latest_bot_idx < len( + comments) - 1 + + branch_name = get_development_branch( + issue_or_pr, repo_path, create=False) + except Exception as e: + pr_msg = f"Failed to process PR comment flow: {str(e)}" + print(pr_msg) # Only run if branch exists and user comment is found on PR if branch_name and user_feedback_bool: @@ -913,11 +923,11 @@ def process_issue( if associated_issue and triggers.has_blech_bot_tag(associated_issue): # Use the associated issue for processing print( - f"PR #{pr.number} has associated issue #{associated_issue.number} with blech_bot tag") + f"PR #{issue_or_pr.number} has associated issue #{associated_issue.number} with blech_bot tag") # Overwrite has_bot_mention to True to process the PR based on the associated issue has_bot_mention = True else: - return False, f"PR #{pr.number} does not have blech_bot label and no associated issue with blech_bot tag" + return False, f"PR #{issue_or_pr.number} does not have blech_bot label and no associated issue with blech_bot tag" # Process a PR with no associated issue and blech_bot_tag elif is_pr and has_bot_mention and not associated_issue: @@ -945,10 +955,11 @@ def process_issue( # Process PR Already created from issue if pr_creation_comment_bool: # If PR has been created, respond if it has an unresponded comment + print('Checking for comments on PR generated by this issue') result, err_msg = respond_pr_comment_flow( issue_or_pr, repo_name, - pr_comment + pr_creation_comment ) return result, err_msg From 02d93def62dd2815aa1c6e31ef4bbd6bfdea202d Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Tue, 1 Apr 2025 15:23:47 -0400 Subject: [PATCH 057/146] refactor: Improve PR and issue processing logic in response agent --- src/git_utils.py | 2 +- src/response_agent.py | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 85dc7e9..f910b85 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -574,7 +574,7 @@ def has_linked_pr(issue: Issue) -> bool: return False -def get_linked_pr(issue: Issue) -> PullRequest: +def get_linked_pr(issue: Issue) -> Optional[PullRequest]: """ Get the linked pull request for an issue Args: diff --git a/src/response_agent.py b/src/response_agent.py index d2e5b79..957eef8 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -624,7 +624,7 @@ def write_pr_comment( llm_config: The configuration for the LLM used to generate the response """ # Clean the response first to remove any existing signatures - write_str = clean_response(write_str) + write_str = clean_response(response) signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {llm_config['model']}*" if signature not in write_str: write_str += signature @@ -929,15 +929,28 @@ def process_issue( else: return False, f"PR #{issue_or_pr.number} does not have blech_bot label and no associated issue with blech_bot tag" - # Process a PR with no associated issue and blech_bot_tag - elif is_pr and has_bot_mention and not associated_issue: + # Process a PR with no associated issue but has blech_bot_tag + if is_pr and has_bot_mention and not associated_issue: print('Processing standalone PR flow since no associated issue found') - result, err_msg = standalone_pr_flow( issue_or_pr, repo_name ) return result, err_msg + + # Process a PR with an associated issue that has blech_bot_tag + elif is_pr and has_bot_mention and associated_issue: + print(f'Processing PR #{issue_or_pr.number} with associated issue #{associated_issue.number}') + # Check if there are user comments on the PR that need to be addressed + if triggers.has_user_comment_on_pr(issue_or_pr): + print('Found user comment on PR, processing') + result, err_msg = standalone_pr_flow( + issue_or_pr, + repo_name + ) + return result, err_msg + else: + return False, f"PR #{issue_or_pr.number} has no new user comments to process" else: # It's an issue if not has_bot_mention: @@ -966,7 +979,6 @@ def process_issue( # Developing pull request from issue # Check for develop_issue trigger next elif triggers.has_develop_issue_trigger(issue_or_pr): - result, err_msg = develop_issue_flow( issue_or_pr, repo_name, From 62088ab57e9f63e546a7f14a9ca710f85423dc39 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 19:23:54 +0000 Subject: [PATCH 058/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/response_agent.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index 957eef8..13865ea 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -937,10 +937,11 @@ def process_issue( repo_name ) return result, err_msg - + # Process a PR with an associated issue that has blech_bot_tag elif is_pr and has_bot_mention and associated_issue: - print(f'Processing PR #{issue_or_pr.number} with associated issue #{associated_issue.number}') + print( + f'Processing PR #{issue_or_pr.number} with associated issue #{associated_issue.number}') # Check if there are user comments on the PR that need to be addressed if triggers.has_user_comment_on_pr(issue_or_pr): print('Found user comment on PR, processing') From aed3c8837673cf58f574764ed5f6394f382e501a Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 15:30:50 -0400 Subject: [PATCH 059/146] refactor(response_agent): remove redundant PR processing logic - Removed code block that processed pull requests with associated issues when they had no new user comments to address. - Simplified the logic and flow by eliminating unnecessary checks and method calls. --- src/response_agent.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index 13865ea..c586084 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -938,21 +938,6 @@ def process_issue( ) return result, err_msg - # Process a PR with an associated issue that has blech_bot_tag - elif is_pr and has_bot_mention and associated_issue: - print( - f'Processing PR #{issue_or_pr.number} with associated issue #{associated_issue.number}') - # Check if there are user comments on the PR that need to be addressed - if triggers.has_user_comment_on_pr(issue_or_pr): - print('Found user comment on PR, processing') - result, err_msg = standalone_pr_flow( - issue_or_pr, - repo_name - ) - return result, err_msg - else: - return False, f"PR #{issue_or_pr.number} has no new user comments to process" - else: # It's an issue if not has_bot_mention: return False, "Issue does not have blech_bot tag or mention in title" From 18bd1564cf63395973bf3c20c8e7db42a8a7a225 Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Tue, 1 Apr 2025 14:14:09 -0400 Subject: [PATCH 060/146] feat: Add content type check to prevent scraping non-text URLs --- src/response_agent.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/response_agent.py b/src/response_agent.py index c586084..d911264 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -116,11 +116,17 @@ def scrape_text_from_url(url: str) -> str: url: The URL to scrape text from. Returns: - The scraped text content. + The scraped text content or a message if non-text content is detected. """ try: response = requests.get(url, timeout=10) response.raise_for_status() # Raise an error for bad responses + + # Check if the content type is text-based + content_type = response.headers.get('Content-Type', '') + if 'text' not in content_type and 'html' not in content_type and 'json' not in content_type: + return f"Non-text content detected at URL {url}: {content_type}" + soup = bs4.BeautifulSoup(response.text, 'html.parser') # Remove script and style elements From 00060d79856ed3015c1e86eba94f89f239adf5f2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 18:16:41 +0000 Subject: [PATCH 061/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/response_agent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index d911264..9d3a985 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -121,12 +121,12 @@ def scrape_text_from_url(url: str) -> str: try: response = requests.get(url, timeout=10) response.raise_for_status() # Raise an error for bad responses - + # Check if the content type is text-based content_type = response.headers.get('Content-Type', '') if 'text' not in content_type and 'html' not in content_type and 'json' not in content_type: return f"Non-text content detected at URL {url}: {content_type}" - + soup = bs4.BeautifulSoup(response.text, 'html.parser') # Remove script and style elements From fbdeeefcebb5bc36ac8e53dd179bcbc0c1dfeee3 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 15:06:18 -0400 Subject: [PATCH 062/146] refactor(logging): streamline output with `tab_print` and fix minor bugs - Introduced `tab_print` function to replace standard print statements, ensuring tab-indented logging for improved readability. - Updated multiple functions to leverage `tab_print`, promoting consistent output formatting. - Enhanced error handling and inserted missing return statements to guarantee proper function termination. - Fixed minor bugs encountered during the refactoring process. --- .gitignore | 2 + src/response_agent.py | 121 ++++++++++++++++++++++++++++-------------- 2 files changed, 83 insertions(+), 40 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5ac763 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.aider* +.env diff --git a/src/response_agent.py b/src/response_agent.py index 9d3a985..e3abe24 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -78,6 +78,25 @@ ############################################################ +def tab_print(x): + """ + Print with tab indentation for readability + """ + """ + Print with tab indentation for readability + :param x: The object to print + """ + if isinstance(x, str): + print('\t' + x) + elif isinstance(x, dict): + pprint(x) + elif isinstance(x, list): + for item in x: + print('\t' + str(item)) + else: + print('\t' + str(x)) + + def extract_urls_from_issue(issue: Issue) -> List[str]: """ Extract URLs from issue body and comments @@ -146,7 +165,7 @@ def scrape_text_from_url(url: str) -> str: return text except requests.RequestException as e: - print(f"Error fetching URL {url}: {e}") + tab_print(f"Error fetching URL {url}: {e}") return f"Error fetching URL {url}: {str(e)}" @@ -308,9 +327,9 @@ def generate_feedback_response( Returns: Tuple of (updated response text, full conversation history) """ - print('===============================') - print('Generating feedback response') - print('===============================') + tab_print('===============================') + tab_print('Generating feedback response') + tab_print('===============================') repo_path = bot_tools.get_local_repo_path(repo_name) details = get_issue_details(issue) @@ -319,9 +338,9 @@ def generate_feedback_response( url_contents = {} if urls: - print(f"Found {len(urls)} URLs in issue") + tab_print(f"Found {len(urls)} URLs in issue") for url in urls: - print(f"Scraping content from {url}") + tab_print(f"Scraping content from {url}") content = scrape_text_from_url(url) # Summarize content to avoid token limits summarized_content = summarize_text(content) @@ -395,9 +414,9 @@ def generate_new_response( Returns: Tuple of (response text, conversation history) """ - print('===============================') - print('Generating new response') - print('===============================') + tab_print('===============================') + tab_print('Generating new response') + tab_print('===============================') # Get path to repository and issue details repo_path = bot_tools.get_local_repo_path(repo_name) details = get_issue_details(issue) @@ -407,9 +426,9 @@ def generate_new_response( url_contents = {} if urls: - print(f"Found {len(urls)} URLs in issue") + tab_print(f"Found {len(urls)} URLs in issue") for url in urls: - print(f"Scraping content from {url}") + tab_print(f"Scraping content from {url}") content = scrape_text_from_url(url) # Summarize content to avoid token limits summarized_content = summarize_text(content) @@ -503,9 +522,9 @@ def generate_edit_command_response( Returns: Tuple of (response text, conversation history) """ - print('===============================') - print('Generating edit command response') - print('===============================') + tab_print('===============================') + tab_print('Generating edit command response') + tab_print('===============================') # Get path to repository and issue details repo_path = bot_tools.get_local_repo_path(repo_name) @@ -516,9 +535,9 @@ def generate_edit_command_response( url_contents = {} if urls: - print(f"Found {len(urls)} URLs in issue") + tab_print(f"Found {len(urls)} URLs in issue") for url in urls: - print(f"Scraping content from {url}") + tab_print(f"Scraping content from {url}") content = scrape_text_from_url(url) # Summarize content to avoid token limits summarized_content = summarize_text(content) @@ -583,13 +602,13 @@ def check_triggers(issue: Issue) -> str: The trigger phrase found in the issue """ if triggers.has_generate_edit_command_trigger(issue): - print('Triggered by generate_edit_command') + tab_print('Triggered by generate_edit_command') return "generate_edit_command" elif triggers.has_user_feedback(issue): - print('Triggered by user feedback') + tab_print('Triggered by user feedback') return "feedback" elif not triggers.has_bot_response(issue): - print('Triggered by new issue') + tab_print('Triggered by new issue') return "new_response" else: return None @@ -619,6 +638,7 @@ def write_pr_comment( response: str, aider_output: str, llm_config: dict, + write_str: str = None, ) -> None: """ Write a comment on the pull request with the generated response and aider output @@ -646,7 +666,7 @@ def develop_issue_flow( if is_pr: return False, "Cannot develop a PR, only issues can be developed" - print('Triggered by [ develop_issue ] command') + tab_print('Triggered by [ develop_issue ] command') repo_path = bot_tools.get_local_repo_path(repo_name) # Check for existing branches @@ -709,7 +729,8 @@ def develop_issue_flow( pull, write_str, aider_output=aider_output, - llm_config=llm_config + llm_config=llm_config, + write_str=write_str ) # Switch back to main branch @@ -725,6 +746,7 @@ def develop_issue_flow( os.chdir(original_dir) raise RuntimeError( f"Failed to process develop issue: {str(e)}") + return False, f"Failed to process develop issue: {str(e)}" return True, None @@ -736,7 +758,7 @@ def respond_pr_comment_flow( ) -> Tuple[bool, Optional[str]]: try: - print("Attempting to get PR branch details") + tab_print("Attempting to get PR branch details") repo_path = bot_tools.get_local_repo_path(repo_name) repo = get_repository(get_github_client(), repo_name) @@ -749,8 +771,9 @@ def respond_pr_comment_flow( comments = list(pr.get_issue_comments()) if not comments: - print("No comments found on the PR") - print("If PR was generated using `develop_issue`, something went wrong.") + tab_print("No comments found on the PR") + tab_print( + "If PR was generated using `develop_issue`, something went wrong.") # find the latest bot comment latest_bot_idx = -1 @@ -764,14 +787,16 @@ def respond_pr_comment_flow( branch_name = get_development_branch( issue_or_pr, repo_path, create=False) + except Exception as e: pr_msg = f"Failed to process PR comment flow: {str(e)}" - print(pr_msg) + tab_print(pr_msg) + return False, pr_msg # Only run if branch exists and user comment is found on PR if branch_name and user_feedback_bool: user_comment = comments[-1].body - print('Triggered by user comment on PR') + tab_print('Triggered by user comment on PR') try: original_dir = os.getcwd() @@ -815,7 +840,13 @@ def respond_pr_comment_flow( pr, write_str, aider_output=aider_output, - llm_config=llm_config + llm_config=llm_config, + write_str=write_str + else: + # Handle case where there are no user comments + pr_msg="No user feedback found to process on the PR." + print(pr_msg) + return True, pr_msg ) # Clean up @@ -833,6 +864,11 @@ def respond_pr_comment_flow( os.chdir(original_dir) raise RuntimeError( f"Failed to process PR comment: {str(e)}") + else: + # Handle case where there are no user comments + pr_msg = "No user feedback found to process on the PR." + tab_print(pr_msg) + return True, pr_msg def standalone_pr_flow( @@ -878,7 +914,8 @@ def standalone_pr_flow( pr_obj, write_str, aider_output=aider_output, - llm_config=llm_config + llm_config=llm_config, + write_str=write_str ) # Switch back to main branch @@ -894,6 +931,7 @@ def standalone_pr_flow( os.chdir(original_dir) raise RuntimeError( f"Failed to process develop issue: {str(e)}") + return False, f"Failed to process standalone PR flow: {str(e)}" return True, None @@ -919,25 +957,28 @@ def process_issue( # Handle PR differently if is_pr: - print('Detected as a Pull Request (PR)') + tab_print('Detected as a Pull Request (PR)') # Check if PR has blech_bot label associated_issue = get_associated_issue(issue_or_pr) # If PR doesn't have blech_bot label, check if it has an associated issue with the label if not has_bot_mention: - print('PR does not have blech_bot label, checking associated issue') + tab_print( + 'PR does not have blech_bot label, checking associated issue') if associated_issue and triggers.has_blech_bot_tag(associated_issue): # Use the associated issue for processing - print( + tab_print( f"PR #{issue_or_pr.number} has associated issue #{associated_issue.number} with blech_bot tag") # Overwrite has_bot_mention to True to process the PR based on the associated issue has_bot_mention = True else: return False, f"PR #{issue_or_pr.number} does not have blech_bot label and no associated issue with blech_bot tag" - # Process a PR with no associated issue but has blech_bot_tag - if is_pr and has_bot_mention and not associated_issue: - print('Processing standalone PR flow since no associated issue found') + # Process a PR with no associated issue and blech_bot_tag + elif is_pr and has_bot_mention and not associated_issue: + tab_print( + 'Processing standalone PR flow since no associated issue found') + result, err_msg = standalone_pr_flow( issue_or_pr, repo_name @@ -960,7 +1001,7 @@ def process_issue( # Process PR Already created from issue if pr_creation_comment_bool: # If PR has been created, respond if it has an unresponded comment - print('Checking for comments on PR generated by this issue') + tab_print('Checking for comments on PR generated by this issue') result, err_msg = respond_pr_comment_flow( issue_or_pr, repo_name, @@ -1073,7 +1114,7 @@ def process_repository( try: checkout_branch(repo_dir, default_branch) except Exception as e: - print( + tab_print( f"Error switching to default branch '{default_branch}': {str(e)}") return # Update repository @@ -1094,9 +1135,9 @@ def process_repository( # Capture the error and print the traceback for debugging error = str(e) if success: - print(f"Successfully processed {entity_type} #{item.number}") + tab_print(f"Successfully processed {entity_type} #{item.number}") else: - print(f"Skipped {entity_type} #{item.number}: {error}") + tab_print(f"Skipped {entity_type} #{item.number}: {error}") def initialize_bot() -> None: @@ -1132,9 +1173,9 @@ def initialize_bot() -> None: print(f'\n=== Processing repository: {repo_name} ===') try: process_repository(repo_name) - print(f'Completed processing {repo_name}') + tab_print(f'Completed processing {repo_name}') except Exception as e: - print(f'Error processing {repo_name}: {str(e)}') + tab_print(f'Error processing {repo_name}: {str(e)}') continue print('\nCompleted processing all repositories') From ec5b7e423f734b63221e4120ad0517c002f58253 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 15:08:00 -0400 Subject: [PATCH 063/146] fix(response_agent): remove unreachable code in respond_pr_comment_flow - Removed an unreachable code block that incorrectly handled the absence of user feedback in PR comments. - Ensured the flow proceeds correctly without premature returns, improving overall function logic and addressing a bug. --- src/response_agent.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index e3abe24..83c32c6 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -842,11 +842,6 @@ def respond_pr_comment_flow( aider_output=aider_output, llm_config=llm_config, write_str=write_str - else: - # Handle case where there are no user comments - pr_msg="No user feedback found to process on the PR." - print(pr_msg) - return True, pr_msg ) # Clean up From 48c60b13ef994901a89e9665a29059b8273ebf09 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 15:11:58 -0400 Subject: [PATCH 064/146] fix(response_agent): correct print function for process completion - Replaced `tab_print` with `print` when indicating the completion of repository processing. - Ensures consistent output format in the script's console logs. --- src/response_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/response_agent.py b/src/response_agent.py index 83c32c6..6946c93 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -1168,7 +1168,7 @@ def initialize_bot() -> None: print(f'\n=== Processing repository: {repo_name} ===') try: process_repository(repo_name) - tab_print(f'Completed processing {repo_name}') + print(f'Completed processing {repo_name}') except Exception as e: tab_print(f'Error processing {repo_name}: {str(e)}') continue From d73e677141ffe4e75c650a02298d38183980e6f3 Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Tue, 1 Apr 2025 14:14:09 -0400 Subject: [PATCH 065/146] feat: Add content type check to prevent scraping non-text URLs --- src/git_utils.py | 16 +++++++++++++--- src/response_agent.py | 4 +++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index f910b85..9a34736 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -60,10 +60,20 @@ def get_open_issues(repo: Repository) -> List[Issue]: def get_issue_comments(issue: Issue) -> List[IssueComment]: - """Get all comments for a specific issue or pull request""" + """Get all comments for a specific issue or pull request, ignoring Graphite-related comments""" + # Text to identify Graphite-related comments + ignore_text = "This stack of pull requests is managed by" + if isinstance(issue, PullRequest): - return list(issue.get_issue_comments()) - return list(issue.get_comments()) + comments = issue.get_issue_comments() + else: + comments = issue.get_comments() + + # Filter out comments containing the ignore_text + filtered_comments = [ + comment for comment in comments if ignore_text not in comment.body] + + return list(filtered_comments) def create_issue_comment( diff --git a/src/response_agent.py b/src/response_agent.py index 6946c93..85f1914 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -768,7 +768,9 @@ def respond_pr_comment_flow( pr_number = int(urls.split('/')[-1]) pr = repo.get_pull(pr_number) - comments = list(pr.get_issue_comments()) + # comments = list(pr.get_issue_comments()) + # Use the helper function to get comments to filter graphite comments + comments = get_issue_comments(pr) if not comments: tab_print("No comments found on the PR") From 3986d09bb3a63a68124d7b7bc42ca71e4155bb8f Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 16:41:42 -0400 Subject: [PATCH 066/146] fix(git_utils): update text to identify Graphite-related comments - Changed the identifier for Graphite-related comments from "This stack of pull requests is managed by" to "app.grapite.dev". - Commented out the old ignore text for clarity. --- src/git_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/git_utils.py b/src/git_utils.py index 9a34736..3fed3c5 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -62,7 +62,8 @@ def get_open_issues(repo: Repository) -> List[Issue]: def get_issue_comments(issue: Issue) -> List[IssueComment]: """Get all comments for a specific issue or pull request, ignoring Graphite-related comments""" # Text to identify Graphite-related comments - ignore_text = "This stack of pull requests is managed by" + # ignore_text = "This stack of pull requests is managed by" + ignore_text = "app.grapite.dev" if isinstance(issue, PullRequest): comments = issue.get_issue_comments() From 9effbbfb1e86655233e6c6afb8ddc6b0cdee2f64 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Tue, 1 Apr 2025 21:13:46 +0000 Subject: [PATCH 067/146] feat: Add process termination after bot repository self-update --- src/response_agent.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/response_agent.py b/src/response_agent.py index 85f1914..97d83ac 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -1152,6 +1152,8 @@ def initialize_bot() -> None: print(f"Updating bot repository at {self_repo_path}") update_self_repo(self_repo_path) print("Bot repository update complete") + print("Exiting to apply updates. Please restart the bot.") + os._exit(0) # Terminate process to allow restart with updates else: print("Auto-update is disabled. Skipping bot repository update.") From 62cb737c46d3378e9b308c2d3e6680c7fc5ad2d0 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 17:40:51 -0400 Subject: [PATCH 068/146] feat(config, response_agent): add silent mode for LLM output control - Introduced a new configuration option `print_llm_output` to control the verbosity of LLM operations. - Updated `response_agent.py` functions to respect the new `silent` parameter, reducing console output when `print_llm_output` is set to `false`. --- config/params.json | 3 ++- src/response_agent.py | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/config/params.json b/config/params.json index 4c534dd..0872b51 100644 --- a/config/params.json +++ b/config/params.json @@ -1,3 +1,4 @@ { - "auto_update": true + "auto_update": true, + "print_llm_output": false } diff --git a/src/response_agent.py b/src/response_agent.py index 97d83ac..869fb30 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -288,6 +288,7 @@ def summarize_relevant_comments( comment_summary_assistant, message=summary_prompt, max_turns=1, + silent=params['print_llm_output'] ) response = comment_summary_results.chat_history[-1]['content'] @@ -382,6 +383,7 @@ def generate_feedback_response( "message": feedback_prompt, "max_turns": max_turns, "summary_method": "reflection_with_llm", + "silent": params['print_llm_output'] } ] ) @@ -461,12 +463,14 @@ def generate_new_response( "message": file_prompt, "max_turns": 20, "summary_method": "last_msg", + "silent": params['print_llm_output'] }, { "recipient": edit_assistant, "message": edit_prompt, "max_turns": 20, "summary_method": "reflection_with_llm", + "silent": params['print_llm_output'] }, ] ) @@ -494,6 +498,7 @@ def generate_new_response( summary_assistant, message=summary_prompt, max_turns=1, + silent=params['print_llm_output'] ) response = summary_results.chat_history[-1]['content'] @@ -568,6 +573,7 @@ def generate_edit_command_response( "message": generate_edit_command_prompt, "max_turns": 20, "summary_method": "reflection_with_llm", + "silent": params['print_llm_output'] }, ] ) From f598284cb296eda464d0a4d7c8276385b3aa389c Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 18:24:46 -0400 Subject: [PATCH 069/146] refactor(chat-config): refactor chat configuration for consistency - Moved chat configuration to separate dictionaries for improved readability and maintainability. - Consolidated repeated chat initiation logic into a consistent format using lists and dictionaries. - Updated log messages to include clearer separators for visual clarity in console outputs. - Altered maximum turns for comment_summary_assistant to 20 for detailed responses. - Added log to inform if LLM output printing is disabled, improving user awareness. --- src/response_agent.py | 91 +++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index 869fb30..a8484bf 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -284,12 +284,15 @@ def summarize_relevant_comments( results_to_summarize=[comment], ) - comment_summary_results = comment_summary_assistant.initiate_chat( - comment_summary_assistant, + chat_config = dict( + recipient=comment_summary_assistant, message=summary_prompt, - max_turns=1, + max_turns=20, + summary_method="reflection_with_llm", silent=params['print_llm_output'] ) + comment_summary_results = comment_summary_assistant.initiate_chat( + chat_config) response = comment_summary_results.chat_history[-1]['content'] summarized_comments.append(response) @@ -376,17 +379,14 @@ def generate_feedback_response( feedback_text=feedback_text, ) - feedback_results = user.initiate_chats( - [ - { - "recipient": feedback_assistant, - "message": feedback_prompt, - "max_turns": max_turns, - "summary_method": "reflection_with_llm", - "silent": params['print_llm_output'] - } - ] + chat_config = dict( + recipient=feedback_assistant, + message=feedback_prompt, + max_turns=max_turns, + summary_method="reflection_with_llm", + silent=params['print_llm_output'] ) + feedback_results = user.initiate_chats([chat_config]) for this_chat in feedback_results[0].chat_history[::-1]: this_content = this_chat['content'] @@ -456,24 +456,24 @@ def generate_new_response( file_prompt = generate_prompt("file_assistant", **prompt_kwargs) edit_prompt = generate_prompt("edit_assistant", **prompt_kwargs) - chat_results = user.initiate_chats( - [ - { - "recipient": file_assistant, - "message": file_prompt, - "max_turns": 20, - "summary_method": "last_msg", - "silent": params['print_llm_output'] - }, - { - "recipient": edit_assistant, - "message": edit_prompt, - "max_turns": 20, - "summary_method": "reflection_with_llm", - "silent": params['print_llm_output'] - }, - ] - ) + chat_configs = [ + dict( + recipient=file_assistant, + message=file_prompt, + max_turns=20, + summary_method="last_msg", + silent=params['print_llm_output'] + ), + dict( + recipient=edit_assistant, + message=edit_prompt, + max_turns=20, + summary_method="reflection_with_llm", + silent=params['print_llm_output'] + ), + ] + + chat_results = user.initiate_chats(chat_configs) results_to_summarize = [ [x for x in this_result.chat_history if not is_tool_related( @@ -566,19 +566,15 @@ def generate_edit_command_response( repo_name, repo_path, details, issue ) - chat_results = user.initiate_chats( - [ - { - "recipient": generate_edit_command_assistant, - "message": generate_edit_command_prompt, - "max_turns": 20, - "summary_method": "reflection_with_llm", - "silent": params['print_llm_output'] - }, - ] + chat_config = dict( + silent=params['print_llm_output'], + recipient=generate_edit_command_assistant, + message=generate_edit_command_prompt, + max_turns=20, + summary_method="reflection_with_llm", ) + chat_results = user.initiate_chats([chat_config]) - # response = chat_results[0].chat_history[-1]['content'] for this_chat in chat_results[0].chat_history[::-1]: this_content = this_chat['content'] if check_not_empty(this_content): @@ -1148,7 +1144,8 @@ def initialize_bot() -> None: Initialize the bot and ensure it is up-to-date. """ if params['auto_update']: - print("Updating bot repository...") + print('===============================') + print("== Updating bot repository...") # Path to the bot's own repository self_repo_path = os.path.dirname( os.path.dirname(os.path.abspath(__file__))) @@ -1159,9 +1156,17 @@ def initialize_bot() -> None: update_self_repo(self_repo_path) print("Bot repository update complete") print("Exiting to apply updates. Please restart the bot.") + print('===============================') os._exit(0) # Terminate process to allow restart with updates else: + print('===============================') print("Auto-update is disabled. Skipping bot repository update.") + print('===============================') + + if not params['print_llm_output']: + print('===============================') + print("LLM output printing is disabled. Set 'print_llm_output' to true in the parameters to enable.") + print('===============================') if __name__ == '__main__': From c6f51d2885bada80e655b28863405fd7ac786138 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 18:27:35 -0400 Subject: [PATCH 070/146] refactor(response_agent): remove redundant parameters from chat configuration - Removed `max_turns` and `summary_method` parameters from `chat_config` as they are not used. --- src/response_agent.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index a8484bf..bc75d77 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -287,8 +287,6 @@ def summarize_relevant_comments( chat_config = dict( recipient=comment_summary_assistant, message=summary_prompt, - max_turns=20, - summary_method="reflection_with_llm", silent=params['print_llm_output'] ) comment_summary_results = comment_summary_assistant.initiate_chat( From 1cf0ad8dabc11cfe212b151db92cadcc745c1d73 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 18:29:33 -0400 Subject: [PATCH 071/146] fix(response_agent): add max_turns parameter to chat_config - Added `max_turns=1` to the `chat_config` dictionary to limit the number of turns in the chat. - Ensures consistent behavior in chat interactions initiated by `comment_summary_assistant`. --- src/response_agent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/response_agent.py b/src/response_agent.py index bc75d77..ea73773 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -287,6 +287,7 @@ def summarize_relevant_comments( chat_config = dict( recipient=comment_summary_assistant, message=summary_prompt, + max_turns=1, silent=params['print_llm_output'] ) comment_summary_results = comment_summary_assistant.initiate_chat( From 81165e3162a7337b6c893f0ecfaf2141acbf37ec Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Tue, 1 Apr 2025 18:43:42 -0400 Subject: [PATCH 072/146] chore: Add src/repos to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d5ac763..2c4d0a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .aider* .env +src/repos From 15b43cdd93a98cb73777308e15412bc51f8481b9 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 17:58:15 -0400 Subject: [PATCH 073/146] refactor(codebase): remove unused function and streamline PR processing - Removed the `get_associated_issue` function from `git_utils.py` as it was no longer needed. - Updated the import statements in `response_agent.py` to remove the unused `get_associated_issue`. - Refactored `process_issue` logic in `response_agent.py` to streamline the handling of bot mentions and associated PRs. - Simplified the processing flow by removing redundant checks for bot mentions and associated issues in PR handling. --- src/git_utils.py | 26 -------------------- src/response_agent.py | 56 +++++++++++-------------------------------- 2 files changed, 14 insertions(+), 68 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 3fed3c5..e619a33 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -403,32 +403,6 @@ def push_changes_with_authentication( return success_bool, error_msg -def get_associated_issue(pr: PullRequest) -> Optional[Issue]: - """ - Get the associated issue for a pull request - - Args: - pr: The GitHub pull request to check - - Returns: - The associated Issue object or None if not found - """ - - pr_timeline_events = list(pr.get_timeline()) - # Check if any timeline event is a cross-reference to an issue - for event in pr_timeline_events: - if event.event == "cross-referenced": - # Check if the reference is to an issue - if event.source: - for key, val in event.source.raw_data.items(): - if isinstance(val, dict) and 'issue' in val['html_url']: - issue_number = val['number'] - repo = pr.repository - return repo.get_issue(issue_number) - - return None - - def is_pull_request(issue_or_pr: Union[Issue, PullRequest]) -> bool: """ Check if an object is a pull request diff --git a/src/response_agent.py b/src/response_agent.py index ea73773..a50fc09 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -29,7 +29,6 @@ create_pull_request_from_issue, get_development_branch, push_changes_with_authentication, - get_associated_issue, is_pull_request, get_pr_branch, ) @@ -952,51 +951,31 @@ def process_issue( print(f"Processing {entity_type} #{issue_or_pr.number}") has_bot_mention = triggers.has_blech_bot_tag(issue_or_pr) \ or '[ blech_bot ]' in (issue_or_pr.title or '').lower() + if not has_bot_mention: + return False, f"{entity_type} #{issue_or_pr.number} does not have blech_bot label" + # Check if already responded without user feedback + already_responded = triggers.has_bot_response( + issue_or_pr) and not triggers.has_user_feedback(issue_or_pr) + if already_responded: + return False, f"{entity_type} already has a bot response without feedback from user" # Handle PR differently if is_pr: tab_print('Detected as a Pull Request (PR)') - # Check if PR has blech_bot label - associated_issue = get_associated_issue(issue_or_pr) + tab_print('Processing standalone PR flow') - # If PR doesn't have blech_bot label, check if it has an associated issue with the label - if not has_bot_mention: - tab_print( - 'PR does not have blech_bot label, checking associated issue') - if associated_issue and triggers.has_blech_bot_tag(associated_issue): - # Use the associated issue for processing - tab_print( - f"PR #{issue_or_pr.number} has associated issue #{associated_issue.number} with blech_bot tag") - # Overwrite has_bot_mention to True to process the PR based on the associated issue - has_bot_mention = True - else: - return False, f"PR #{issue_or_pr.number} does not have blech_bot label and no associated issue with blech_bot tag" - - # Process a PR with no associated issue and blech_bot_tag - elif is_pr and has_bot_mention and not associated_issue: - tab_print( - 'Processing standalone PR flow since no associated issue found') - - result, err_msg = standalone_pr_flow( - issue_or_pr, - repo_name - ) - return result, err_msg + result, err_msg = standalone_pr_flow( + issue_or_pr, + repo_name + ) + return result, err_msg else: # It's an issue - if not has_bot_mention: - return False, "Issue does not have blech_bot tag or mention in title" # Check if a pr_creation comment exists for the issue pr_creation_comment_bool, pr_creation_comment = triggers.has_pr_creation_comment( issue_or_pr) - # Check if already responded without user feedback - already_responded = triggers.has_bot_response( - issue_or_pr) and not triggers.has_user_feedback(issue_or_pr) - if already_responded and not pr_creation_comment_bool: - return False, f"{entity_type} already has a bot response without feedback from user" - # Process PR Already created from issue if pr_creation_comment_bool: # If PR has been created, respond if it has an unresponded comment tab_print('Checking for comments on PR generated by this issue') @@ -1124,14 +1103,7 @@ def process_repository( # Process each issue and PR for item in open_issues: entity_type = "PR" if is_pull_request(item) else "issue" - try: - process_issue(item, repo_name) - success = True - error = None - except Exception as e: - success = False - # Capture the error and print the traceback for debugging - error = str(e) + success, error = process_issue(item, repo_name) if success: tab_print(f"Successfully processed {entity_type} #{item.number}") else: From ccb7ec792a2a30b21ccfaa9dd196a6e244f26717 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Wed, 2 Apr 2025 00:28:09 +0000 Subject: [PATCH 074/146] refactor: Improve self-repo update logic with better branch detection and logging --- src/git_utils.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index e619a33..37736ca 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -463,20 +463,28 @@ def update_self_repo( local_commit = git_repo.head.commit remote_commit = None try: - remote_commit = origin.refs.master.commit + remote_commit = origin.refs[default_branch].commit except AttributeError: - try: - remote_commit = origin.refs.main.commit - except AttributeError: - print("Could not find master or main branch on remote") + # Try common branch names if default_branch reference fails + for branch_name in ['master', 'main']: + try: + remote_commit = getattr(origin.refs, branch_name).commit + break + except AttributeError: + continue + + if not remote_commit: + print(f"Could not find {default_branch} or common branches on remote") + return # Exit function if we can't determine remote commit if remote_commit and local_commit != remote_commit: - print("Remote is ahead. Force pulling latest changes for self-repo.") + print(f"Remote is ahead. Local commit: {local_commit.hexsha[:7]}, Remote commit: {remote_commit.hexsha[:7]}") + print(f"Force pulling latest changes for self-repo from {default_branch} branch.") # Hard reset to remote branch git_repo.git.reset('--hard', f'origin/{default_branch}') else: - print("Self-repo is up-to-date.") + print(f"Self-repo is up-to-date. Current commit: {local_commit.hexsha[:7]}") # Restore config/repos.txt if has_backup: From a0dcb6e59d6764c4e1a98c2fdc46c86291984233 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 00:28:18 +0000 Subject: [PATCH 075/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/git_utils.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 37736ca..2fb827f 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -472,19 +472,23 @@ def update_self_repo( break except AttributeError: continue - + if not remote_commit: - print(f"Could not find {default_branch} or common branches on remote") + print( + f"Could not find {default_branch} or common branches on remote") return # Exit function if we can't determine remote commit if remote_commit and local_commit != remote_commit: - print(f"Remote is ahead. Local commit: {local_commit.hexsha[:7]}, Remote commit: {remote_commit.hexsha[:7]}") - print(f"Force pulling latest changes for self-repo from {default_branch} branch.") + print( + f"Remote is ahead. Local commit: {local_commit.hexsha[:7]}, Remote commit: {remote_commit.hexsha[:7]}") + print( + f"Force pulling latest changes for self-repo from {default_branch} branch.") # Hard reset to remote branch git_repo.git.reset('--hard', f'origin/{default_branch}') else: - print(f"Self-repo is up-to-date. Current commit: {local_commit.hexsha[:7]}") + print( + f"Self-repo is up-to-date. Current commit: {local_commit.hexsha[:7]}") # Restore config/repos.txt if has_backup: From 0317c29fcd3c4e9529f5f1946595c1c4aca3b7a4 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 20:37:26 -0400 Subject: [PATCH 076/146] Update git_utils.py self_update func --- src/git_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/git_utils.py b/src/git_utils.py index 2fb827f..ec5715a 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -419,7 +419,7 @@ def is_pull_request(issue_or_pr: Union[Issue, PullRequest]) -> bool: def update_self_repo( repo_path: str, -) -> None: +) -> bool: """ Pull latest changes for the bot's own repository, handling tracked config files. @@ -462,6 +462,7 @@ def update_self_repo( # Check if the remote is ahead local_commit = git_repo.head.commit remote_commit = None + update_performed = False try: remote_commit = origin.refs[default_branch].commit except AttributeError: @@ -486,6 +487,7 @@ def update_self_repo( # Hard reset to remote branch git_repo.git.reset('--hard', f'origin/{default_branch}') + update_performed = True else: print( f"Self-repo is up-to-date. Current commit: {local_commit.hexsha[:7]}") @@ -496,6 +498,8 @@ def update_self_repo( shutil.copy2(backup_path, config_repos_path) os.remove(backup_path) +return update_performed + def perform_github_search( query: str, From 63becce08732893edd58fe636658cdddf5ad622d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 00:37:52 +0000 Subject: [PATCH 077/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/git_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/git_utils.py b/src/git_utils.py index ec5715a..efd6028 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -498,6 +498,7 @@ def update_self_repo( shutil.copy2(backup_path, config_repos_path) os.remove(backup_path) + return update_performed From a115b5094d96c54fc07e6b53e5b7ef5761b3dc26 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 20:39:12 -0400 Subject: [PATCH 078/146] Update response_agent.py --- src/response_agent.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index a50fc09..86cd7c3 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -1124,11 +1124,15 @@ def initialize_bot() -> None: # Update the bot's own repository from git_utils import update_self_repo print(f"Updating bot repository at {self_repo_path}") - update_self_repo(self_repo_path) - print("Bot repository update complete") - print("Exiting to apply updates. Please restart the bot.") - print('===============================') - os._exit(0) # Terminate process to allow restart with updates + update_performed = update_self_repo(self_repo_path) + if update_performed: + print("Bot repository update complete") + print("Exiting to apply updates. Please restart the bot.") + print('===============================') + os._exit(0) # Terminate process to allow restart with updates + else: + print("Bot already up to date") + print('===============================') else: print('===============================') print("Auto-update is disabled. Skipping bot repository update.") From 2e8ea2a02cb3e4bfd80793fc98c148a4903a2994 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 1 Apr 2025 20:45:28 -0400 Subject: [PATCH 079/146] Update git_utils.py --- src/git_utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index efd6028..616bbb3 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -498,8 +498,7 @@ def update_self_repo( shutil.copy2(backup_path, config_repos_path) os.remove(backup_path) - -return update_performed + return update_performed def perform_github_search( From 76667b695a753446fb1bf4a79590c2dd61ee8384 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Wed, 2 Apr 2025 09:32:45 +0000 Subject: [PATCH 080/146] refactor: Add consistent error logging with model signature across bot components --- src/branch_handler.py | 2 ++ src/git_utils.py | 59 +++++++++++++++++++++++++++++++++++++++---- src/response_agent.py | 41 ++++++++++++++++++++---------- 3 files changed, 84 insertions(+), 18 deletions(-) diff --git a/src/branch_handler.py b/src/branch_handler.py index 6541aeb..70275ca 100644 --- a/src/branch_handler.py +++ b/src/branch_handler.py @@ -37,6 +37,8 @@ def get_issue_related_branches( related_branches.append((branch_name, url)) except Exception as e: print(f"Error getting related branches: {str(e)}") + # We can't directly log to the issue here as we don't have access to write_issue_response + # This will be caught by the calling function if len(related_branches) == 0: diff --git a/src/git_utils.py b/src/git_utils.py index 616bbb3..434324e 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -36,6 +36,27 @@ def clean_response(response: str) -> str: return response.strip() +def add_signature_to_comment(comment_text: str, model: str) -> str: + """ + Add a signature with model information to the comment if not already present. + + Args: + comment_text: The text content of the comment + model: The model used for generating the response + + Returns: + Comment text with signature added if it was not present + """ + # Define the signature with model information + signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {model}*" + + # Check if the signature is already present + if signature not in comment_text: + comment_text += signature + + return comment_text + + def get_github_client() -> Github: """Initialize and return authenticated GitHub client""" load_dotenv() @@ -252,8 +273,15 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - ) error_msg = f"Found multiple branches for issue #{issue.number}:\n{branch_list}\n" +\ "Please delete or use existing branches before creating a new one." + # Import the model info from response_agent if available + try: + from response_agent import llm_config + error_msg_with_signature = add_signature_to_comment(error_msg, llm_config['model']) + except (ImportError, KeyError): + error_msg_with_signature = error_msg + "\n\n---\n*This response was automatically generated by blech_bot*" + if "Found multiple branches" not in comments[-1].body: - write_issue_response(issue, error_msg) + write_issue_response(issue, error_msg_with_signature) raise RuntimeError(error_msg) elif len(branch_dict) == 1: return list(branch_dict.keys())[0] @@ -281,13 +309,27 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - except FileNotFoundError: error_msg = "GitHub CLI (gh) not found. Please install it first." + # Import the model info from response_agent if available + try: + from response_agent import llm_config + error_msg_with_signature = add_signature_to_comment(error_msg, llm_config['model']) + except (ImportError, KeyError): + error_msg_with_signature = error_msg + "\n\n---\n*This response was automatically generated by blech_bot*" + if error_msg not in comments[-1].body: - write_issue_response(issue, error_msg) + write_issue_response(issue, error_msg_with_signature) raise ValueError(error_msg) except subprocess.CalledProcessError as e: error_msg = f"Failed to create development branch: {e.stderr.strip()}" + # Import the model info from response_agent if available + try: + from response_agent import llm_config + error_msg_with_signature = add_signature_to_comment(error_msg, llm_config['model']) + except (ImportError, KeyError): + error_msg_with_signature = error_msg + "\n\n---\n*This response was automatically generated by blech_bot*" + if "Failed to create" not in comments[-1].body: - write_issue_response(issue, error_msg) + write_issue_response(issue, error_msg_with_signature) raise RuntimeError(error_msg) else: return None @@ -381,14 +423,21 @@ def push_changes_with_authentication( success_bool = True except git.GitCommandError as e: error_msg = f"Failed to push changes: {e.stderr.strip()}" + # Import the model info from response_agent if available + try: + from response_agent import llm_config + error_msg_with_signature = add_signature_to_comment(error_msg, llm_config['model']) + except (ImportError, KeyError): + error_msg_with_signature = error_msg + "\n\n---\n*This response was automatically generated by blech_bot*" + if isinstance(out_thread, Issue): issue_comments = list(out_thread.get_comments()) if 'Failed to push changes' not in issue_comments[-1].body: - write_issue_response(out_thread, error_msg) + write_issue_response(out_thread, error_msg_with_signature) elif isinstance(out_thread, PullRequest): pr_comments = list(out_thread.get_issue_comments()) if 'Failed to push changes' not in pr_comments[-1].body: - out_thread.create_issue_comment(error_msg) + out_thread.create_issue_comment(error_msg_with_signature) else: raise ValueError( "Invalid output thread type, must be IssueComment or PullRequest") diff --git a/src/response_agent.py b/src/response_agent.py index 86cd7c3..65c9bc6 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -744,9 +744,11 @@ def develop_issue_flow( delete_branch(repo_path, branch_name, force=True) # Return to original directory os.chdir(original_dir) - raise RuntimeError( - f"Failed to process develop issue: {str(e)}") - return False, f"Failed to process develop issue: {str(e)}" + # Log error to the issue with signature + error_msg = f"Failed to process develop issue: {str(e)}" + write_issue_response(issue_or_pr, add_signature_to_comment(error_msg, llm_config['model'])) + raise RuntimeError(error_msg) + return False, error_msg return True, None @@ -859,8 +861,16 @@ def respond_pr_comment_flow( back_to_master_branch(repo_path) # Return to original directory os.chdir(original_dir) - raise RuntimeError( - f"Failed to process PR comment: {str(e)}") + # Log error to the PR with signature + error_msg = f"Failed to process PR comment: {str(e)}" + write_pr_comment( + pr, + error_msg, + aider_output="", + llm_config=llm_config, + write_str=add_signature_to_comment(error_msg, llm_config['model']) + ) + raise RuntimeError(error_msg) else: # Handle case where there are no user comments pr_msg = "No user feedback found to process on the PR." @@ -926,9 +936,11 @@ def standalone_pr_flow( delete_branch(repo_path, branch_name, force=True) # Return to original directory os.chdir(original_dir) - raise RuntimeError( - f"Failed to process develop issue: {str(e)}") - return False, f"Failed to process standalone PR flow: {str(e)}" + # Log error to the PR with signature + error_msg = f"Failed to process standalone PR flow: {str(e)}" + write_issue_response(issue_or_pr, add_signature_to_comment(error_msg, llm_config['model'])) + raise RuntimeError(error_msg) + return False, error_msg return True, None @@ -1058,10 +1070,11 @@ def run_aider(message: str, repo_path: str) -> str: return result.stdout except FileNotFoundError: - raise ValueError( - "Aider not found. Please install it first with 'pip install aider-chat'") + error_msg = "Aider not found. Please install it first with 'pip install aider-chat'" + raise ValueError(error_msg) except subprocess.CalledProcessError as e: - raise RuntimeError(f"Failed to run aider: {e.stderr}") + error_msg = f"Failed to run aider: {e.stderr}" + raise RuntimeError(error_msg) def process_repository( @@ -1091,8 +1104,10 @@ def process_repository( try: checkout_branch(repo_dir, default_branch) except Exception as e: - tab_print( - f"Error switching to default branch '{default_branch}': {str(e)}") + error_msg = f"Error switching to default branch '{default_branch}': {str(e)}" + tab_print(error_msg) + # We can't log this to an issue since we're processing the whole repository + # But we'll print it for logging purposes return # Update repository update_repository(repo_dir) From d64b0fb25fa7edbbae0b7cdcd4158b69266b45a4 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Wed, 2 Apr 2025 09:32:57 +0000 Subject: [PATCH 081/146] fix: Import add_signature_to_comment function in response_agent.py --- src/response_agent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/response_agent.py b/src/response_agent.py index 65c9bc6..f3978e6 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -31,6 +31,7 @@ push_changes_with_authentication, is_pull_request, get_pr_branch, + add_signature_to_comment, ) from github.Repository import Repository from github.Issue import Issue From d90adb26d376af3204eb0de2ca3c393f46a66f8d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 09:33:19 +0000 Subject: [PATCH 082/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/git_utils.py | 32 ++++++++++++++++++++------------ src/response_agent.py | 9 ++++++--- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 434324e..176f24f 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -276,10 +276,12 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - # Import the model info from response_agent if available try: from response_agent import llm_config - error_msg_with_signature = add_signature_to_comment(error_msg, llm_config['model']) + error_msg_with_signature = add_signature_to_comment( + error_msg, llm_config['model']) except (ImportError, KeyError): - error_msg_with_signature = error_msg + "\n\n---\n*This response was automatically generated by blech_bot*" - + error_msg_with_signature = error_msg + \ + "\n\n---\n*This response was automatically generated by blech_bot*" + if "Found multiple branches" not in comments[-1].body: write_issue_response(issue, error_msg_with_signature) raise RuntimeError(error_msg) @@ -312,10 +314,12 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - # Import the model info from response_agent if available try: from response_agent import llm_config - error_msg_with_signature = add_signature_to_comment(error_msg, llm_config['model']) + error_msg_with_signature = add_signature_to_comment( + error_msg, llm_config['model']) except (ImportError, KeyError): - error_msg_with_signature = error_msg + "\n\n---\n*This response was automatically generated by blech_bot*" - + error_msg_with_signature = error_msg + \ + "\n\n---\n*This response was automatically generated by blech_bot*" + if error_msg not in comments[-1].body: write_issue_response(issue, error_msg_with_signature) raise ValueError(error_msg) @@ -324,10 +328,12 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - # Import the model info from response_agent if available try: from response_agent import llm_config - error_msg_with_signature = add_signature_to_comment(error_msg, llm_config['model']) + error_msg_with_signature = add_signature_to_comment( + error_msg, llm_config['model']) except (ImportError, KeyError): - error_msg_with_signature = error_msg + "\n\n---\n*This response was automatically generated by blech_bot*" - + error_msg_with_signature = error_msg + \ + "\n\n---\n*This response was automatically generated by blech_bot*" + if "Failed to create" not in comments[-1].body: write_issue_response(issue, error_msg_with_signature) raise RuntimeError(error_msg) @@ -426,10 +432,12 @@ def push_changes_with_authentication( # Import the model info from response_agent if available try: from response_agent import llm_config - error_msg_with_signature = add_signature_to_comment(error_msg, llm_config['model']) + error_msg_with_signature = add_signature_to_comment( + error_msg, llm_config['model']) except (ImportError, KeyError): - error_msg_with_signature = error_msg + "\n\n---\n*This response was automatically generated by blech_bot*" - + error_msg_with_signature = error_msg + \ + "\n\n---\n*This response was automatically generated by blech_bot*" + if isinstance(out_thread, Issue): issue_comments = list(out_thread.get_comments()) if 'Failed to push changes' not in issue_comments[-1].body: diff --git a/src/response_agent.py b/src/response_agent.py index f3978e6..3462990 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -747,7 +747,8 @@ def develop_issue_flow( os.chdir(original_dir) # Log error to the issue with signature error_msg = f"Failed to process develop issue: {str(e)}" - write_issue_response(issue_or_pr, add_signature_to_comment(error_msg, llm_config['model'])) + write_issue_response(issue_or_pr, add_signature_to_comment( + error_msg, llm_config['model'])) raise RuntimeError(error_msg) return False, error_msg @@ -869,7 +870,8 @@ def respond_pr_comment_flow( error_msg, aider_output="", llm_config=llm_config, - write_str=add_signature_to_comment(error_msg, llm_config['model']) + write_str=add_signature_to_comment( + error_msg, llm_config['model']) ) raise RuntimeError(error_msg) else: @@ -939,7 +941,8 @@ def standalone_pr_flow( os.chdir(original_dir) # Log error to the PR with signature error_msg = f"Failed to process standalone PR flow: {str(e)}" - write_issue_response(issue_or_pr, add_signature_to_comment(error_msg, llm_config['model'])) + write_issue_response(issue_or_pr, add_signature_to_comment( + error_msg, llm_config['model'])) raise RuntimeError(error_msg) return False, error_msg From 92de04578598ce17782b90d1ba661e32216e4f51 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 2 Apr 2025 09:47:05 -0400 Subject: [PATCH 083/146] fix(response_agent): improve PR creation comment check logic - Added a check for `pr_creation_comment` before proceeding with response logic to ensure issues have PR creation comments. - Moved the `pr_creation_comment` logic to avoid redundancy and ensure logical flow. - Improved conditions to handle already responded issues more accurately by integrating new PR comment checks. --- src/response_agent.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index 86cd7c3..6b1591b 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -953,10 +953,13 @@ def process_issue( or '[ blech_bot ]' in (issue_or_pr.title or '').lower() if not has_bot_mention: return False, f"{entity_type} #{issue_or_pr.number} does not have blech_bot label" + # Check if a pr_creation comment exists for the issue + pr_creation_comment_bool, pr_creation_comment = triggers.has_pr_creation_comment( + issue_or_pr) # Check if already responded without user feedback already_responded = triggers.has_bot_response( issue_or_pr) and not triggers.has_user_feedback(issue_or_pr) - if already_responded: + if already_responded and not pr_creation_comment_bool: return False, f"{entity_type} already has a bot response without feedback from user" # Handle PR differently @@ -972,12 +975,9 @@ def process_issue( else: # It's an issue - # Check if a pr_creation comment exists for the issue - pr_creation_comment_bool, pr_creation_comment = triggers.has_pr_creation_comment( - issue_or_pr) - # Process PR Already created from issue if pr_creation_comment_bool: # If PR has been created, respond if it has an unresponded comment + # respond_pr_comment_flow checks for unresolved comments on PR tab_print('Checking for comments on PR generated by this issue') result, err_msg = respond_pr_comment_flow( issue_or_pr, From 0a40f95396bd480e22af580309c0bcfb29f5b36e Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 2 Apr 2025 12:33:26 -0400 Subject: [PATCH 084/146] fix(response_agent): correct function call parameters - Fixed the `initiate_chat` function call to correctly unpack the `chat_config` dictionary using `**chat_config`. --- src/response_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/response_agent.py b/src/response_agent.py index 6b1591b..0c68f88 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -290,7 +290,7 @@ def summarize_relevant_comments( silent=params['print_llm_output'] ) comment_summary_results = comment_summary_assistant.initiate_chat( - chat_config) + **chat_config) response = comment_summary_results.chat_history[-1]['content'] summarized_comments.append(response) From c435ee1967d5610f33ead40302748d11fcdc0b35 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 2 Apr 2025 12:47:41 -0400 Subject: [PATCH 085/146] feat(branch-handler): force align branch with remote after checkout - Added functionality to reset the local branch to match the remote branch after checking out. - Prints a confirmation message once the branch is aligned with the remote. --- src/branch_handler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/branch_handler.py b/src/branch_handler.py index 6541aeb..6bc8c20 100644 --- a/src/branch_handler.py +++ b/src/branch_handler.py @@ -136,6 +136,9 @@ def checkout_branch(repo_path: str, branch_name: str, create: bool = False) -> N print(f"Created branch {branch_name}") repo.git.checkout(branch_name) print(f"Checked out branch {branch_name}") + # Force align branch with remote + repo.git.reset('--hard', f'origin/{branch_name}') + print(f"Branch {branch_name} aligned with remote") def delete_branch(repo_path: str, branch_name: str, force: bool = False) -> None: From d683ae39be899547b5062df366076cc5b8129644 Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Wed, 2 Apr 2025 13:27:22 -0400 Subject: [PATCH 086/146] feat: Improve error handling and logging across bot components This commit enhances error handling and logging in the GitHub bot by: 1. Adding detailed traceback information to error messages 2. Ensuring errors are logged to the relevant GitHub issues or PRs 3. Preventing repeated error logging 4. Improving cleanup and error recovery mechanisms 5. Adding consistent error signatures across different components Key changes include: - Enhanced error logging in `response_agent.py` - Improved error handling in `git_utils.py` - Added traceback imports - Consistent error message formatting - Better directory management during error scenarios The changes ensure that: - Errors are captured with full context - Errors are logged to the appropriate GitHub issue or PR - The bot can recover from most error scenarios - Debugging information is readily available --- src/git_utils.py | 195 +++++++++++++++--------- src/response_agent.py | 343 ++++++++++++++++++++++++------------------ 2 files changed, 320 insertions(+), 218 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 176f24f..621b4b8 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -5,6 +5,7 @@ import os import subprocess import git +import traceback from branch_handler import ( get_issue_related_branches, get_current_branch, @@ -39,6 +40,7 @@ def clean_response(response: str) -> str: def add_signature_to_comment(comment_text: str, model: str) -> str: """ Add a signature with model information to the comment if not already present. + This function is used for both regular responses and error messages. Args: comment_text: The text content of the comment @@ -50,9 +52,11 @@ def add_signature_to_comment(comment_text: str, model: str) -> str: # Define the signature with model information signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {model}*" - # Check if the signature is already present + # Check if the signature is already present (exact match) if signature not in comment_text: - comment_text += signature + # Also check for any other signature format + if "\n\n---\n*This response was automatically generated by blech_bot" not in comment_text: + comment_text += signature return comment_text @@ -252,65 +256,28 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - ValueError: If gh CLI is not installed RuntimeError: If multiple branches exist for the issue """ - # Check for existing branches related to this issue - related_branches = get_issue_related_branches(repo_path, issue) - - unique_branches = set([branch_name for branch_name, _ in related_branches]) - branch_dict = {} - for branch_name in unique_branches: - branch_dict[branch_name] = [] - wanted_inds = [i for i, (name, _) in enumerate( - related_branches) if name == branch_name] - for ind in wanted_inds: - branch_dict[branch_name].append(related_branches[ind][1]) - - comments = get_issue_comments(issue) - - if len(branch_dict) > 1: - branch_list = "\n".join( - [f"- {branch_name} : Remote = {is_remote}" - for branch_name, is_remote in branch_dict.items()] - ) - error_msg = f"Found multiple branches for issue #{issue.number}:\n{branch_list}\n" +\ - "Please delete or use existing branches before creating a new one." - # Import the model info from response_agent if available - try: - from response_agent import llm_config - error_msg_with_signature = add_signature_to_comment( - error_msg, llm_config['model']) - except (ImportError, KeyError): - error_msg_with_signature = error_msg + \ - "\n\n---\n*This response was automatically generated by blech_bot*" - - if "Found multiple branches" not in comments[-1].body: - write_issue_response(issue, error_msg_with_signature) - raise RuntimeError(error_msg) - elif len(branch_dict) == 1: - return list(branch_dict.keys())[0] - elif create: - try: - # Change to repo directory - original_dir = os.getcwd() - os.chdir(repo_path) - - # Create branch from issue - result = subprocess.run( - ['gh', 'issue', 'develop', str(issue.number)], - check=True, - capture_output=True, - text=True - ) - - related_branch = get_issue_related_branches( - repo_path, issue) - - # Return to original directory - os.chdir(original_dir) + try: + # Check for existing branches related to this issue + related_branches = get_issue_related_branches(repo_path, issue) + + unique_branches = set([branch_name for branch_name, _ in related_branches]) + branch_dict = {} + for branch_name in unique_branches: + branch_dict[branch_name] = [] + wanted_inds = [i for i, (name, _) in enumerate( + related_branches) if name == branch_name] + for ind in wanted_inds: + branch_dict[branch_name].append(related_branches[ind][1]) - return related_branch[0][0] + comments = get_issue_comments(issue) - except FileNotFoundError: - error_msg = "GitHub CLI (gh) not found. Please install it first." + if len(branch_dict) > 1: + branch_list = "\n".join( + [f"- {branch_name} : Remote = {is_remote}" + for branch_name, is_remote in branch_dict.items()] + ) + error_msg = f"Found multiple branches for issue #{issue.number}:\n{branch_list}\n" +\ + "Please delete or use existing branches before creating a new one." # Import the model info from response_agent if available try: from response_agent import llm_config @@ -320,25 +287,103 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - error_msg_with_signature = error_msg + \ "\n\n---\n*This response was automatically generated by blech_bot*" - if error_msg not in comments[-1].body: + if len(comments) == 0 or "Found multiple branches" not in comments[-1].body: write_issue_response(issue, error_msg_with_signature) - raise ValueError(error_msg) - except subprocess.CalledProcessError as e: - error_msg = f"Failed to create development branch: {e.stderr.strip()}" - # Import the model info from response_agent if available + raise RuntimeError(error_msg) + elif len(branch_dict) == 1: + return list(branch_dict.keys())[0] + elif create: try: - from response_agent import llm_config - error_msg_with_signature = add_signature_to_comment( - error_msg, llm_config['model']) - except (ImportError, KeyError): - error_msg_with_signature = error_msg + \ - "\n\n---\n*This response was automatically generated by blech_bot*" - - if "Failed to create" not in comments[-1].body: + # Change to repo directory + original_dir = os.getcwd() + os.chdir(repo_path) + + # Create branch from issue + result = subprocess.run( + ['gh', 'issue', 'develop', str(issue.number)], + check=True, + capture_output=True, + text=True + ) + + related_branch = get_issue_related_branches( + repo_path, issue) + + # Return to original directory + os.chdir(original_dir) + + return related_branch[0][0] + + except FileNotFoundError: + error_msg = "GitHub CLI (gh) not found. Please install it first." + # Import the model info from response_agent if available + try: + from response_agent import llm_config + error_msg_with_signature = add_signature_to_comment( + error_msg, llm_config['model']) + except (ImportError, KeyError): + error_msg_with_signature = error_msg + \ + "\n\n---\n*This response was automatically generated by blech_bot*" + + if len(comments) == 0 or error_msg not in comments[-1].body: + write_issue_response(issue, error_msg_with_signature) + + # Make sure to return to original directory before raising exception + if 'original_dir' in locals(): + os.chdir(original_dir) + + raise ValueError(error_msg) + except subprocess.CalledProcessError as e: + error_msg = f"Failed to create development branch: {e.stderr.strip()}" + # Import the model info from response_agent if available + try: + from response_agent import llm_config + error_msg_with_signature = add_signature_to_comment( + error_msg, llm_config['model']) + except (ImportError, KeyError): + error_msg_with_signature = error_msg + \ + "\n\n---\n*This response was automatically generated by blech_bot*" + + if len(comments) == 0 or "Failed to create" not in comments[-1].body: + write_issue_response(issue, error_msg_with_signature) + + # Make sure to return to original directory before raising exception + if 'original_dir' in locals(): + os.chdir(original_dir) + + raise RuntimeError(error_msg) + except Exception as e: + error_msg = f"Unexpected error creating development branch: {str(e)}\n\n```\n{traceback.format_exc()}\n```" + try: + from response_agent import llm_config + error_msg_with_signature = add_signature_to_comment( + error_msg, llm_config['model']) + except (ImportError, KeyError): + error_msg_with_signature = error_msg + \ + "\n\n---\n*This response was automatically generated by blech_bot*" + write_issue_response(issue, error_msg_with_signature) - raise RuntimeError(error_msg) - else: - return None + + # Make sure to return to original directory before raising exception + if 'original_dir' in locals(): + os.chdir(original_dir) + + raise RuntimeError(error_msg) + else: + return None + except Exception as e: + # Catch-all for any unexpected errors + error_msg = f"Error in get_development_branch: {str(e)}\n\n```\n{traceback.format_exc()}\n```" + try: + from response_agent import llm_config + error_msg_with_signature = add_signature_to_comment( + error_msg, llm_config['model']) + except (ImportError, KeyError): + error_msg_with_signature = error_msg + \ + "\n\n---\n*This response was automatically generated by blech_bot*" + + write_issue_response(issue, error_msg_with_signature) + raise RuntimeError(error_msg) def create_pull_request(repo_path: str) -> str: diff --git a/src/response_agent.py b/src/response_agent.py index 1bbd6e1..3379a56 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -6,6 +6,7 @@ from dotenv import load_dotenv import string import triggers +import traceback from agents import ( create_user_agent, create_agent, @@ -741,15 +742,20 @@ def develop_issue_flow( except Exception as e: # Clean up on error - back_to_master_branch(repo_path) - delete_branch(repo_path, branch_name, force=True) + try: + back_to_master_branch(repo_path) + delete_branch(repo_path, branch_name, force=True) + except Exception as cleanup_error: + tab_print(f"Error during cleanup: {str(cleanup_error)}") + # Return to original directory os.chdir(original_dir) - # Log error to the issue with signature - error_msg = f"Failed to process develop issue: {str(e)}" + + # Log detailed error to the issue with signature + error_msg = f"Failed to process develop issue: {str(e)}\n\n```\n{traceback.format_exc()}\n```" write_issue_response(issue_or_pr, add_signature_to_comment( error_msg, llm_config['model'])) - raise RuntimeError(error_msg) + tab_print(f"Error logged to issue: {error_msg}") return False, error_msg return True, None @@ -795,8 +801,11 @@ def respond_pr_comment_flow( issue_or_pr, repo_path, create=False) except Exception as e: - pr_msg = f"Failed to process PR comment flow: {str(e)}" + pr_msg = f"Failed to process PR comment flow: {str(e)}\n\n```\n{traceback.format_exc()}\n```" tab_print(pr_msg) + # Log error to the issue with signature + write_issue_response(issue_or_pr, add_signature_to_comment( + pr_msg, llm_config['model'])) return False, pr_msg # Only run if branch exists and user comment is found on PR @@ -860,11 +869,16 @@ def respond_pr_comment_flow( except Exception as e: # Clean up on error - back_to_master_branch(repo_path) + try: + back_to_master_branch(repo_path) + except Exception as cleanup_error: + tab_print(f"Error during cleanup: {str(cleanup_error)}") + # Return to original directory os.chdir(original_dir) - # Log error to the PR with signature - error_msg = f"Failed to process PR comment: {str(e)}" + + # Log detailed error to the PR with signature + error_msg = f"Failed to process PR comment: {str(e)}\n\n```\n{traceback.format_exc()}\n```" write_pr_comment( pr, error_msg, @@ -873,7 +887,8 @@ def respond_pr_comment_flow( write_str=add_signature_to_comment( error_msg, llm_config['model']) ) - raise RuntimeError(error_msg) + tab_print(f"Error logged to PR: {error_msg}") + return False, error_msg else: # Handle case where there are no user comments pr_msg = "No user feedback found to process on the PR." @@ -886,68 +901,81 @@ def standalone_pr_flow( repo_name: str, ) -> Tuple[bool, Optional[str]]: - # Get repo object and pull request - client = get_github_client() - repo = get_repository(client, repo_name) - pr_obj = repo.get_pull(issue_or_pr.number) - branch_name = get_pr_branch(pr_obj) - repo_path = bot_tools.get_local_repo_path(repo_name) + try: + # Get repo object and pull request + client = get_github_client() + repo = get_repository(client, repo_name) + pr_obj = repo.get_pull(issue_or_pr.number) + branch_name = get_pr_branch(pr_obj) + repo_path = bot_tools.get_local_repo_path(repo_name) - original_dir = os.getcwd() - os.chdir(repo_path) - checkout_branch(repo_path, branch_name, create=False) + original_dir = os.getcwd() + os.chdir(repo_path) + checkout_branch(repo_path, branch_name, create=False) - summarized_comments, comment_list, summary_comment_str = summarize_relevant_comments( - issue_or_pr, repo_name) - if summary_comment_str == '': - summary_comment_str = 'No relevant comments found' + summarized_comments, comment_list, summary_comment_str = summarize_relevant_comments( + issue_or_pr, repo_name) + if summary_comment_str == '': + summary_comment_str = 'No relevant comments found' - # First generate edit command from previous discussion - response, _ = generate_edit_command_response( - issue_or_pr, repo_name, summary_comment_str) + # First generate edit command from previous discussion + response, _ = generate_edit_command_response( + issue_or_pr, repo_name, summary_comment_str) - try: - # Run aider with the generated command - aider_output = run_aider(response, repo_path) + try: + # Run aider with the generated command + aider_output = run_aider(response, repo_path) - # Push changes with authentication - push_success, err_msg = push_changes_with_authentication( - repo_path, - issue_or_pr, - branch_name - ) + # Push changes with authentication + push_success, err_msg = push_changes_with_authentication( + repo_path, + issue_or_pr, + branch_name + ) - # write_issue_response(issue, "Generated edit command:\n" + response) - write_str = f"Generated edit command:\n---\n{response}\n\n" + \ - f"Aider output:\n
View Aider Output\n\n```{aider_output}```\n
" - write_pr_comment( - pr_obj, - write_str, - aider_output=aider_output, - llm_config=llm_config, - write_str=write_str - ) + # write_issue_response(issue, "Generated edit command:\n" + response) + write_str = f"Generated edit command:\n---\n{response}\n\n" + \ + f"Aider output:\n
View Aider Output\n\n```{aider_output}```\n
" + write_pr_comment( + pr_obj, + write_str, + aider_output=aider_output, + llm_config=llm_config, + write_str=write_str + ) - # Switch back to main branch - back_to_master_branch(repo_path) - # Return to original directory - os.chdir(original_dir) + # Switch back to main branch + back_to_master_branch(repo_path) + # Return to original directory + os.chdir(original_dir) + except Exception as e: + # Clean up on error + try: + back_to_master_branch(repo_path) + except Exception as cleanup_error: + tab_print(f"Error during cleanup: {str(cleanup_error)}") + + # Return to original directory + os.chdir(original_dir) + + # Log detailed error to the PR with signature + error_msg = f"Failed to process standalone PR flow: {str(e)}\n\n```\n{traceback.format_exc()}\n```" + write_issue_response(issue_or_pr, add_signature_to_comment( + error_msg, llm_config['model'])) + tab_print(f"Error logged to PR: {error_msg}") + return False, error_msg + + return True, None + except Exception as e: - # Clean up on error - back_to_master_branch(repo_path) - delete_branch(repo_path, branch_name, force=True) - # Return to original directory - os.chdir(original_dir) - # Log error to the PR with signature - error_msg = f"Failed to process standalone PR flow: {str(e)}" + # Handle errors in the initial setup + error_msg = f"Failed to initialize standalone PR flow: {str(e)}\n\n```\n{traceback.format_exc()}\n```" write_issue_response(issue_or_pr, add_signature_to_comment( error_msg, llm_config['model'])) - raise RuntimeError(error_msg) + tab_print(f"Error logged to PR: {error_msg}") return False, error_msg - return True, None - def process_issue( issue_or_pr: Union[Issue, PullRequest], @@ -965,63 +993,72 @@ def process_issue( is_pr = is_pull_request(issue_or_pr) entity_type = "PR" if is_pr else "issue" print(f"Processing {entity_type} #{issue_or_pr.number}") - has_bot_mention = triggers.has_blech_bot_tag(issue_or_pr) \ - or '[ blech_bot ]' in (issue_or_pr.title or '').lower() - if not has_bot_mention: - return False, f"{entity_type} #{issue_or_pr.number} does not have blech_bot label" - # Check if a pr_creation comment exists for the issue - pr_creation_comment_bool, pr_creation_comment = triggers.has_pr_creation_comment( - issue_or_pr) - # Check if already responded without user feedback - already_responded = triggers.has_bot_response( - issue_or_pr) and not triggers.has_user_feedback(issue_or_pr) - if already_responded and not pr_creation_comment_bool: - return False, f"{entity_type} already has a bot response without feedback from user" - - # Handle PR differently - if is_pr: - tab_print('Detected as a Pull Request (PR)') - tab_print('Processing standalone PR flow') - - result, err_msg = standalone_pr_flow( - issue_or_pr, - repo_name - ) - return result, err_msg - - else: # It's an issue - - # Process PR Already created from issue - if pr_creation_comment_bool: # If PR has been created, respond if it has an unresponded comment - # respond_pr_comment_flow checks for unresolved comments on PR - tab_print('Checking for comments on PR generated by this issue') - result, err_msg = respond_pr_comment_flow( + + try: + has_bot_mention = triggers.has_blech_bot_tag(issue_or_pr) \ + or '[ blech_bot ]' in (issue_or_pr.title or '').lower() + if not has_bot_mention: + return False, f"{entity_type} #{issue_or_pr.number} does not have blech_bot label" + + # Check if a pr_creation comment exists for the issue + pr_creation_comment_bool, pr_creation_comment = triggers.has_pr_creation_comment( + issue_or_pr) + # Check if already responded without user feedback + already_responded = triggers.has_bot_response( + issue_or_pr) and not triggers.has_user_feedback(issue_or_pr) + if already_responded and not pr_creation_comment_bool: + return False, f"{entity_type} already has a bot response without feedback from user" + + # Handle PR differently + if is_pr: + tab_print('Detected as a Pull Request (PR)') + tab_print('Processing standalone PR flow') + + result, err_msg = standalone_pr_flow( issue_or_pr, - repo_name, - pr_creation_comment + repo_name ) return result, err_msg - # Developing pull request from issue - # Check for develop_issue trigger next - elif triggers.has_develop_issue_trigger(issue_or_pr): - result, err_msg = develop_issue_flow( - issue_or_pr, - repo_name, - is_pr=is_pr - ) - return result, err_msg + else: # It's an issue - # Process as new issue - else: - # Generate and post response - trigger = check_triggers(issue_or_pr) - response_func = response_selector(trigger) - if response_func is None: - return False, f"No trigger found for {entity_type} #{issue_or_pr.number}" - response, all_content = response_func(issue_or_pr, repo_name) - write_issue_response(issue_or_pr, response) - return True, None + # Process PR Already created from issue + if pr_creation_comment_bool: # If PR has been created, respond if it has an unresponded comment + # respond_pr_comment_flow checks for unresolved comments on PR + tab_print('Checking for comments on PR generated by this issue') + result, err_msg = respond_pr_comment_flow( + issue_or_pr, + repo_name, + pr_creation_comment + ) + return result, err_msg + + # Developing pull request from issue + # Check for develop_issue trigger next + elif triggers.has_develop_issue_trigger(issue_or_pr): + result, err_msg = develop_issue_flow( + issue_or_pr, + repo_name, + is_pr=is_pr + ) + return result, err_msg + + # Process as new issue + else: + # Generate and post response + trigger = check_triggers(issue_or_pr) + response_func = response_selector(trigger) + if response_func is None: + return False, f"No trigger found for {entity_type} #{issue_or_pr.number}" + response, all_content = response_func(issue_or_pr, repo_name) + write_issue_response(issue_or_pr, response) + return True, None + except Exception as e: + # Log the error to the issue/PR with signature + error_msg = f"Error processing {entity_type} #{issue_or_pr.number}: {str(e)}\n\n```\n{traceback.format_exc()}\n```" + write_issue_response(issue_or_pr, add_signature_to_comment(error_msg, llm_config['model'])) + tab_print(f"Error logged to {entity_type}: {error_msg}") + return False, error_msg def run_aider(message: str, repo_path: str) -> str: @@ -1040,7 +1077,6 @@ def run_aider(message: str, repo_path: str) -> str: FileNotFoundError: If aider is not installed """ try: - # Change to repo directory original_dir = os.getcwd() os.chdir(repo_path) @@ -1075,9 +1111,15 @@ def run_aider(message: str, repo_path: str) -> str: except FileNotFoundError: error_msg = "Aider not found. Please install it first with 'pip install aider-chat'" + os.chdir(original_dir) if 'original_dir' in locals() else None raise ValueError(error_msg) except subprocess.CalledProcessError as e: error_msg = f"Failed to run aider: {e.stderr}" + os.chdir(original_dir) if 'original_dir' in locals() else None + raise RuntimeError(error_msg) + except Exception as e: + error_msg = f"Unexpected error running aider: {str(e)}\n\n```\n{traceback.format_exc()}\n```" + os.chdir(original_dir) if 'original_dir' in locals() else None raise RuntimeError(error_msg) @@ -1090,43 +1132,58 @@ def process_repository( Args: repo_name: Full name of repository (owner/repo) """ - # Initialize GitHub client - client = get_github_client() - repo = get_repository(client, repo_name) + try: + # Initialize GitHub client + client = get_github_client() + repo = get_repository(client, repo_name) - # Get local repository path - repo_dir = bot_tools.get_local_repo_path(repo_name) + # Get local repository path + repo_dir = bot_tools.get_local_repo_path(repo_name) - # Clone repository only if not already present - if not os.path.exists(repo_dir): - repo_dir = clone_repository(repo) + # Clone repository only if not already present + if not os.path.exists(repo_dir): + repo_dir = clone_repository(repo) - # Determine the default branch - default_branch = repo.default_branch + # Determine the default branch + default_branch = repo.default_branch - # Ensure repository is on the default branch - try: - checkout_branch(repo_dir, default_branch) + # Ensure repository is on the default branch + try: + checkout_branch(repo_dir, default_branch) + except Exception as e: + error_msg = f"Error switching to default branch '{default_branch}': {str(e)}\n\n```\n{traceback.format_exc()}\n```" + tab_print(error_msg) + # We can't log this to an issue since we're processing the whole repository + # But we'll print it for logging purposes + return + + # Update repository + update_repository(repo_dir) + + # Get open issues + open_issues = repo.get_issues(state='open') + + # Process each issue and PR + for item in open_issues: + entity_type = "PR" if is_pull_request(item) else "issue" + try: + success, error = process_issue(item, repo_name) + if success: + tab_print(f"Successfully processed {entity_type} #{item.number}") + else: + tab_print(f"Skipped {entity_type} #{item.number}: {error}") + except Exception as e: + error_msg = f"Error processing {entity_type} #{item.number}: {str(e)}\n\n```\n{traceback.format_exc()}\n```" + tab_print(error_msg) + # Try to log the error to the issue/PR + try: + write_issue_response(item, add_signature_to_comment(error_msg, llm_config['model'])) + tab_print(f"Error logged to {entity_type}") + except Exception as log_error: + tab_print(f"Failed to log error to {entity_type}: {str(log_error)}") except Exception as e: - error_msg = f"Error switching to default branch '{default_branch}': {str(e)}" + error_msg = f"Error processing repository {repo_name}: {str(e)}\n\n```\n{traceback.format_exc()}\n```" tab_print(error_msg) - # We can't log this to an issue since we're processing the whole repository - # But we'll print it for logging purposes - return - # Update repository - update_repository(repo_dir) - - # Get open issues - open_issues = repo.get_issues(state='open') - - # Process each issue and PR - for item in open_issues: - entity_type = "PR" if is_pull_request(item) else "issue" - success, error = process_issue(item, repo_name) - if success: - tab_print(f"Successfully processed {entity_type} #{item.number}") - else: - tab_print(f"Skipped {entity_type} #{item.number}: {error}") def initialize_bot() -> None: From a07ce9ec9be03b2408e0eefac6a2951c8b815f59 Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Wed, 2 Apr 2025 13:31:00 -0400 Subject: [PATCH 087/146] feat: Add consistent error logging signature across bot components --- src/git_utils.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/git_utils.py b/src/git_utils.py index 621b4b8..a68f7a2 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -61,6 +61,30 @@ def add_signature_to_comment(comment_text: str, model: str) -> str: return comment_text +def add_signature_to_comment(comment_text: str, model: str) -> str: + """ + Add a signature with model information to the comment if not already present. + This function is used for both regular responses and error messages. + + Args: + comment_text: The text content of the comment + model: The model used for generating the response + + Returns: + Comment text with signature added if it was not present + """ + # Define the signature with model information + signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {model}*" + + # Check if the signature is already present (exact match) + if signature not in comment_text: + # Also check for any other signature format + if "\n\n---\n*This response was automatically generated by blech_bot" not in comment_text: + comment_text += signature + + return comment_text + + def get_github_client() -> Github: """Initialize and return authenticated GitHub client""" load_dotenv() From 287ec43bbbb0d368fb4041415bc6d6d8c32d7fb5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 17:32:03 +0000 Subject: [PATCH 088/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/git_utils.py | 21 +++++++++++---------- src/response_agent.py | 32 ++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index a68f7a2..a9a701b 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -284,7 +284,8 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - # Check for existing branches related to this issue related_branches = get_issue_related_branches(repo_path, issue) - unique_branches = set([branch_name for branch_name, _ in related_branches]) + unique_branches = set( + [branch_name for branch_name, _ in related_branches]) branch_dict = {} for branch_name in unique_branches: branch_dict[branch_name] = [] @@ -298,7 +299,7 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - if len(branch_dict) > 1: branch_list = "\n".join( [f"- {branch_name} : Remote = {is_remote}" - for branch_name, is_remote in branch_dict.items()] + for branch_name, is_remote in branch_dict.items()] ) error_msg = f"Found multiple branches for issue #{issue.number}:\n{branch_list}\n" +\ "Please delete or use existing branches before creating a new one." @@ -351,11 +352,11 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - if len(comments) == 0 or error_msg not in comments[-1].body: write_issue_response(issue, error_msg_with_signature) - + # Make sure to return to original directory before raising exception if 'original_dir' in locals(): os.chdir(original_dir) - + raise ValueError(error_msg) except subprocess.CalledProcessError as e: error_msg = f"Failed to create development branch: {e.stderr.strip()}" @@ -370,11 +371,11 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - if len(comments) == 0 or "Failed to create" not in comments[-1].body: write_issue_response(issue, error_msg_with_signature) - + # Make sure to return to original directory before raising exception if 'original_dir' in locals(): os.chdir(original_dir) - + raise RuntimeError(error_msg) except Exception as e: error_msg = f"Unexpected error creating development branch: {str(e)}\n\n```\n{traceback.format_exc()}\n```" @@ -385,13 +386,13 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - except (ImportError, KeyError): error_msg_with_signature = error_msg + \ "\n\n---\n*This response was automatically generated by blech_bot*" - + write_issue_response(issue, error_msg_with_signature) - + # Make sure to return to original directory before raising exception if 'original_dir' in locals(): os.chdir(original_dir) - + raise RuntimeError(error_msg) else: return None @@ -405,7 +406,7 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - except (ImportError, KeyError): error_msg_with_signature = error_msg + \ "\n\n---\n*This response was automatically generated by blech_bot*" - + write_issue_response(issue, error_msg_with_signature) raise RuntimeError(error_msg) diff --git a/src/response_agent.py b/src/response_agent.py index 3379a56..e3e05c3 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -747,10 +747,10 @@ def develop_issue_flow( delete_branch(repo_path, branch_name, force=True) except Exception as cleanup_error: tab_print(f"Error during cleanup: {str(cleanup_error)}") - + # Return to original directory os.chdir(original_dir) - + # Log detailed error to the issue with signature error_msg = f"Failed to process develop issue: {str(e)}\n\n```\n{traceback.format_exc()}\n```" write_issue_response(issue_or_pr, add_signature_to_comment( @@ -873,10 +873,10 @@ def respond_pr_comment_flow( back_to_master_branch(repo_path) except Exception as cleanup_error: tab_print(f"Error during cleanup: {str(cleanup_error)}") - + # Return to original directory os.chdir(original_dir) - + # Log detailed error to the PR with signature error_msg = f"Failed to process PR comment: {str(e)}\n\n```\n{traceback.format_exc()}\n```" write_pr_comment( @@ -955,10 +955,10 @@ def standalone_pr_flow( back_to_master_branch(repo_path) except Exception as cleanup_error: tab_print(f"Error during cleanup: {str(cleanup_error)}") - + # Return to original directory os.chdir(original_dir) - + # Log detailed error to the PR with signature error_msg = f"Failed to process standalone PR flow: {str(e)}\n\n```\n{traceback.format_exc()}\n```" write_issue_response(issue_or_pr, add_signature_to_comment( @@ -967,7 +967,7 @@ def standalone_pr_flow( return False, error_msg return True, None - + except Exception as e: # Handle errors in the initial setup error_msg = f"Failed to initialize standalone PR flow: {str(e)}\n\n```\n{traceback.format_exc()}\n```" @@ -993,13 +993,13 @@ def process_issue( is_pr = is_pull_request(issue_or_pr) entity_type = "PR" if is_pr else "issue" print(f"Processing {entity_type} #{issue_or_pr.number}") - + try: has_bot_mention = triggers.has_blech_bot_tag(issue_or_pr) \ or '[ blech_bot ]' in (issue_or_pr.title or '').lower() if not has_bot_mention: return False, f"{entity_type} #{issue_or_pr.number} does not have blech_bot label" - + # Check if a pr_creation comment exists for the issue pr_creation_comment_bool, pr_creation_comment = triggers.has_pr_creation_comment( issue_or_pr) @@ -1056,7 +1056,8 @@ def process_issue( except Exception as e: # Log the error to the issue/PR with signature error_msg = f"Error processing {entity_type} #{issue_or_pr.number}: {str(e)}\n\n```\n{traceback.format_exc()}\n```" - write_issue_response(issue_or_pr, add_signature_to_comment(error_msg, llm_config['model'])) + write_issue_response(issue_or_pr, add_signature_to_comment( + error_msg, llm_config['model'])) tab_print(f"Error logged to {entity_type}: {error_msg}") return False, error_msg @@ -1156,7 +1157,7 @@ def process_repository( # We can't log this to an issue since we're processing the whole repository # But we'll print it for logging purposes return - + # Update repository update_repository(repo_dir) @@ -1169,7 +1170,8 @@ def process_repository( try: success, error = process_issue(item, repo_name) if success: - tab_print(f"Successfully processed {entity_type} #{item.number}") + tab_print( + f"Successfully processed {entity_type} #{item.number}") else: tab_print(f"Skipped {entity_type} #{item.number}: {error}") except Exception as e: @@ -1177,10 +1179,12 @@ def process_repository( tab_print(error_msg) # Try to log the error to the issue/PR try: - write_issue_response(item, add_signature_to_comment(error_msg, llm_config['model'])) + write_issue_response(item, add_signature_to_comment( + error_msg, llm_config['model'])) tab_print(f"Error logged to {entity_type}") except Exception as log_error: - tab_print(f"Failed to log error to {entity_type}: {str(log_error)}") + tab_print( + f"Failed to log error to {entity_type}: {str(log_error)}") except Exception as e: error_msg = f"Error processing repository {repo_name}: {str(e)}\n\n```\n{traceback.format_exc()}\n```" tab_print(error_msg) From 3a1b213c197dd25f8a6e3f436b219cc21b5ff271 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 2 Apr 2025 13:42:22 -0400 Subject: [PATCH 089/146] refactor(git_utils): remove duplicate add_signature_to_comment function - Removed a duplicate definition of the `add_signature_to_comment` function, streamlining the codebase. - Ensures only one consistent implementation of the `add_signature_to_comment` function is maintained. --- src/git_utils.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index a9a701b..88092ab 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -61,30 +61,6 @@ def add_signature_to_comment(comment_text: str, model: str) -> str: return comment_text -def add_signature_to_comment(comment_text: str, model: str) -> str: - """ - Add a signature with model information to the comment if not already present. - This function is used for both regular responses and error messages. - - Args: - comment_text: The text content of the comment - model: The model used for generating the response - - Returns: - Comment text with signature added if it was not present - """ - # Define the signature with model information - signature = f"\n\n---\n*This response was automatically generated by blech_bot using model {model}*" - - # Check if the signature is already present (exact match) - if signature not in comment_text: - # Also check for any other signature format - if "\n\n---\n*This response was automatically generated by blech_bot" not in comment_text: - comment_text += signature - - return comment_text - - def get_github_client() -> Github: """Initialize and return authenticated GitHub client""" load_dotenv() From 49ec743444dd07d80f770042c8183cf936de4cfc Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 2 Apr 2025 13:47:15 -0400 Subject: [PATCH 090/146] docs(response_agent): document outcome types for issue processing - Added comments to explain the three possible outcomes when processing GitHub issues or PRs. --- src/response_agent.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/response_agent.py b/src/response_agent.py index e3e05c3..0d2137f 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -1,5 +1,10 @@ """ Agent for generating responses to GitHub issues using pyautogen + +3 outcomes types for processing each issue or PR: + 1. Processed successfully and response posted + 2. Skipped because triggers were not met + 3. Error processing the issue or PR """ from typing import Optional, Tuple, List, Union From dd9028e42ae576b2be3055638342ebf9ed75dc85 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 2 Apr 2025 14:15:16 -0400 Subject: [PATCH 091/146] fix(git_utils): correct filter logic and streamline comment handling - Changed the `ignore_text` to accurately reference "app.graphite.dev". - Unified comment body processing by eliminating punctuation and newlines. - Streamlined retrieval of issue comments by updating the `push_changes_with_authentication` function to use the `get_issue_comments` function. --- src/git_utils.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 88092ab..cd8077e 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -19,6 +19,7 @@ from github.PullRequest import PullRequest from dotenv import load_dotenv import re +import string def clean_response(response: str) -> str: @@ -88,16 +89,23 @@ def get_issue_comments(issue: Issue) -> List[IssueComment]: """Get all comments for a specific issue or pull request, ignoring Graphite-related comments""" # Text to identify Graphite-related comments # ignore_text = "This stack of pull requests is managed by" - ignore_text = "app.grapite.dev" + # ignore_text = "app.grapite.dev" + ignore_text = "app.graphite.dev" if isinstance(issue, PullRequest): comments = issue.get_issue_comments() else: comments = issue.get_comments() + # Join comment bodies to deal with puncutation and newlines + comment_bodies = [x.body for x in comments] + # Drop punctuation and newlines + # comments = [x.translate(str.maketrans('', '', string.punctuation + '\n')) for x in comment_bodies] + comments = [" ".join(x.split()) for x in comment_bodies] + # Filter out comments containing the ignore_text filtered_comments = [ - comment for comment in comments if ignore_text not in comment.body] + comment for comment in comments if ignore_text not in comment] return list(filtered_comments) @@ -485,7 +493,8 @@ def push_changes_with_authentication( "\n\n---\n*This response was automatically generated by blech_bot*" if isinstance(out_thread, Issue): - issue_comments = list(out_thread.get_comments()) + # issue_comments = list(out_thread.get_comments()) + issue_comments = get_issue_comments(out_thread) if 'Failed to push changes' not in issue_comments[-1].body: write_issue_response(out_thread, error_msg_with_signature) elif isinstance(out_thread, PullRequest): From 3d6b6c94fb48a43360512f59369c9510375309ac Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 2 Apr 2025 14:28:10 -0400 Subject: [PATCH 092/146] refactor: improve comment filtering and error detection logic - Modified `get_issue_comments` in `git_utils.py` to correctly bind the filtered comments with their cleaned-up bodies. - Introduced `has_error_comment` function in `triggers.py` to check for error indicators within issue comments. - Updated `process_issue` in `response_agent.py` to incorporate error comment detection, improving logic flow when processing issues and PRs. --- src/git_utils.py | 7 ++++--- src/response_agent.py | 5 ++++- src/triggers.py | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index cd8077e..c3cd7e6 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -89,7 +89,6 @@ def get_issue_comments(issue: Issue) -> List[IssueComment]: """Get all comments for a specific issue or pull request, ignoring Graphite-related comments""" # Text to identify Graphite-related comments # ignore_text = "This stack of pull requests is managed by" - # ignore_text = "app.grapite.dev" ignore_text = "app.graphite.dev" if isinstance(issue, PullRequest): @@ -101,11 +100,13 @@ def get_issue_comments(issue: Issue) -> List[IssueComment]: comment_bodies = [x.body for x in comments] # Drop punctuation and newlines # comments = [x.translate(str.maketrans('', '', string.punctuation + '\n')) for x in comment_bodies] - comments = [" ".join(x.split()) for x in comment_bodies] + comment_bodies = [" ".join(x.split()) for x in comment_bodies] # Filter out comments containing the ignore_text filtered_comments = [ - comment for comment in comments if ignore_text not in comment] + comment for comment, comment_body in zip(comments, comment_bodies) + if ignore_text not in comment_body + ] return list(filtered_comments) diff --git a/src/response_agent.py b/src/response_agent.py index 0d2137f..60e3c25 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -1014,6 +1014,8 @@ def process_issue( if already_responded and not pr_creation_comment_bool: return False, f"{entity_type} already has a bot response without feedback from user" + has_error = triggers.has_error_comment(issue_or_pr) + # Handle PR differently if is_pr: tab_print('Detected as a Pull Request (PR)') @@ -1028,7 +1030,8 @@ def process_issue( else: # It's an issue # Process PR Already created from issue - if pr_creation_comment_bool: # If PR has been created, respond if it has an unresponded comment + # If PR has been created, respond if it has an unresponded comment + if pr_creation_comment_bool and not has_error: # respond_pr_comment_flow checks for unresolved comments on PR tab_print('Checking for comments on PR generated by this issue') result, err_msg = respond_pr_comment_flow( diff --git a/src/triggers.py b/src/triggers.py index d5f1da4..ce6c5a3 100644 --- a/src/triggers.py +++ b/src/triggers.py @@ -121,6 +121,20 @@ def has_pr_creation_comment(issue: Issue) -> bool: return False, None +def has_error_comment(issue: Issue) -> bool: + """ + Check if an issue has comments indicating an error + + Args: + issue: The GitHub issue to check + + Returns: + True if the issue has error comments, False otherwise + """ + comments = get_issue_comments(issue) + return 'Error' in comments[-1].body if comments else False + + def has_user_comment_on_pr(issue: Issue) -> bool: """ Check if there is a user comment on a pull request that needs processing From 62da62b9361cbdf263d9cf95a4d9be344fe6f390 Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Wed, 2 Apr 2025 14:16:53 -0400 Subject: [PATCH 093/146] feat: Differentiate between error and skip outcomes in GitHub issue processing --- src/response_agent.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index 60e3c25..5d074e1 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -2,9 +2,9 @@ Agent for generating responses to GitHub issues using pyautogen 3 outcomes types for processing each issue or PR: - 1. Processed successfully and response posted - 2. Skipped because triggers were not met - 3. Error processing the issue or PR + 1. Success: Processed successfully and response posted + 2. Skip: Skipped because triggers were not met (e.g., no bot tag, already responded) + 3. Error: An error occurred during processing (e.g., exception thrown) """ from typing import Optional, Tuple, List, Union @@ -1003,6 +1003,7 @@ def process_issue( has_bot_mention = triggers.has_blech_bot_tag(issue_or_pr) \ or '[ blech_bot ]' in (issue_or_pr.title or '').lower() if not has_bot_mention: + # This is a skip outcome, not an error return False, f"{entity_type} #{issue_or_pr.number} does not have blech_bot label" # Check if a pr_creation comment exists for the issue @@ -1012,6 +1013,7 @@ def process_issue( already_responded = triggers.has_bot_response( issue_or_pr) and not triggers.has_user_feedback(issue_or_pr) if already_responded and not pr_creation_comment_bool: + # This is a skip outcome, not an error return False, f"{entity_type} already has a bot response without feedback from user" has_error = triggers.has_error_comment(issue_or_pr) @@ -1057,13 +1059,15 @@ def process_issue( trigger = check_triggers(issue_or_pr) response_func = response_selector(trigger) if response_func is None: + # This is a skip outcome, not an error return False, f"No trigger found for {entity_type} #{issue_or_pr.number}" response, all_content = response_func(issue_or_pr, repo_name) write_issue_response(issue_or_pr, response) return True, None except Exception as e: - # Log the error to the issue/PR with signature + # This is a true error outcome error_msg = f"Error processing {entity_type} #{issue_or_pr.number}: {str(e)}\n\n```\n{traceback.format_exc()}\n```" + # Log the error to the issue/PR with signature write_issue_response(issue_or_pr, add_signature_to_comment( error_msg, llm_config['model'])) tab_print(f"Error logged to {entity_type}: {error_msg}") @@ -1176,13 +1180,22 @@ def process_repository( for item in open_issues: entity_type = "PR" if is_pull_request(item) else "issue" try: - success, error = process_issue(item, repo_name) + # Process the issue/PR and determine the outcome + success, message = process_issue(item, repo_name) if success: + # Success outcome tab_print( f"Successfully processed {entity_type} #{item.number}") else: - tab_print(f"Skipped {entity_type} #{item.number}: {error}") + # Determine if this is a skip or error outcome based on message content + if "Error" in message or "error" in message or "Exception" in message: + # This is an error outcome + tab_print(f"Error processing {entity_type} #{item.number}: {message}") + else: + # This is a skip outcome + tab_print(f"Skipped {entity_type} #{item.number}: {message}") except Exception as e: + # This is a true error outcome error_msg = f"Error processing {entity_type} #{item.number}: {str(e)}\n\n```\n{traceback.format_exc()}\n```" tab_print(error_msg) # Try to log the error to the issue/PR From ddd381f3f8692aa733d348882ba6e674592ee2de Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 18:17:01 +0000 Subject: [PATCH 094/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/response_agent.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index 5d074e1..8b25937 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -1190,10 +1190,12 @@ def process_repository( # Determine if this is a skip or error outcome based on message content if "Error" in message or "error" in message or "Exception" in message: # This is an error outcome - tab_print(f"Error processing {entity_type} #{item.number}: {message}") + tab_print( + f"Error processing {entity_type} #{item.number}: {message}") else: # This is a skip outcome - tab_print(f"Skipped {entity_type} #{item.number}: {message}") + tab_print( + f"Skipped {entity_type} #{item.number}: {message}") except Exception as e: # This is a true error outcome error_msg = f"Error processing {entity_type} #{item.number}: {str(e)}\n\n```\n{traceback.format_exc()}\n```" From f5f4ce95334054426bd7eb588a277c010463f976 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 2 Apr 2025 15:17:33 -0400 Subject: [PATCH 095/146] fix(response_agent): improve logging with PR number context - Added PR number to log messages for better traceability. - Enhanced error and feedback logging by including the PR number in the output messages. - Ensures more informative logs, facilitating easier troubleshooting. --- src/response_agent.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index 8b25937..4222964 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -788,7 +788,7 @@ def respond_pr_comment_flow( comments = get_issue_comments(pr) if not comments: - tab_print("No comments found on the PR") + tab_print(f"No comments found on the PR# {pr_number}") tab_print( "If PR was generated using `develop_issue`, something went wrong.") @@ -806,7 +806,7 @@ def respond_pr_comment_flow( issue_or_pr, repo_path, create=False) except Exception as e: - pr_msg = f"Failed to process PR comment flow: {str(e)}\n\n```\n{traceback.format_exc()}\n```" + pr_msg = f"Failed to process PR {pr_number} comment flow: {str(e)}\n\n```\n{traceback.format_exc()}\n```" tab_print(pr_msg) # Log error to the issue with signature write_issue_response(issue_or_pr, add_signature_to_comment( @@ -816,7 +816,7 @@ def respond_pr_comment_flow( # Only run if branch exists and user comment is found on PR if branch_name and user_feedback_bool: user_comment = comments[-1].body - tab_print('Triggered by user comment on PR') + tab_print(f'Triggered by user comment on PR #{pr_number}') try: original_dir = os.getcwd() @@ -883,7 +883,7 @@ def respond_pr_comment_flow( os.chdir(original_dir) # Log detailed error to the PR with signature - error_msg = f"Failed to process PR comment: {str(e)}\n\n```\n{traceback.format_exc()}\n```" + error_msg = f"Failed to process PR# {pr_number} comment: {str(e)}\n\n```\n{traceback.format_exc()}\n```" write_pr_comment( pr, error_msg, @@ -896,7 +896,7 @@ def respond_pr_comment_flow( return False, error_msg else: # Handle case where there are no user comments - pr_msg = "No user feedback found to process on the PR." + pr_msg = f"No user feedback found to process on the PR #{pr_number}" tab_print(pr_msg) return True, pr_msg From caa7d5e95f93e1b4220916102380c7a6c51cd7bb Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 2 Apr 2025 15:25:38 -0400 Subject: [PATCH 096/146] feat(logging): add debug prints for branch management in get_development_branch - Added print statements to log when a branch is found, created, or not found, which aids in the debugging process and understanding branch management flow. - Ensured that these logs give clear information during function execution for ease of development and troubleshooting. --- src/git_utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/git_utils.py b/src/git_utils.py index c3cd7e6..62f989c 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -301,6 +301,7 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - write_issue_response(issue, error_msg_with_signature) raise RuntimeError(error_msg) elif len(branch_dict) == 1: + print(f"Found branch: {list(branch_dict.keys())[0]}") return list(branch_dict.keys())[0] elif create: try: @@ -322,6 +323,7 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - # Return to original directory os.chdir(original_dir) + print(f"Created branch: {related_branch[0][0]}") return related_branch[0][0] except FileNotFoundError: @@ -380,6 +382,7 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - raise RuntimeError(error_msg) else: + print("No development branch found") return None except Exception as e: # Catch-all for any unexpected errors From fb78b667d89808ab9b753657b04790e124cc907b Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 2 Apr 2025 15:34:02 -0400 Subject: [PATCH 097/146] fix(branch): improve branch retrieval logic - Added logic to retrieve branch from a linked PR in `git_utils.py`. - Removed redundant code and improved the clarity of branch creation conditions. - Updated `response_agent.py` to directly use `get_pr_branch` for fetching the branch name from a PR. - Added logging for clearer information on which branch is being used. --- src/git_utils.py | 16 ++++++++++++---- src/response_agent.py | 6 ++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index 62f989c..e02ed49 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -303,7 +303,17 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - elif len(branch_dict) == 1: print(f"Found branch: {list(branch_dict.keys())[0]}") return list(branch_dict.keys())[0] - elif create: + elif len(branch_dict) == 0: # Use PR to get branch + pr = get_linked_pr(issue) + if pr: + branch_name = get_pr_branch(pr) + print(f"Found branch from linked PR: {branch_name}") + return branch_name + else: + print("No development branch found") + return None + + if create: try: # Change to repo directory original_dir = os.getcwd() @@ -381,9 +391,7 @@ def get_development_branch(issue: Issue, repo_path: str, create: bool = False) - os.chdir(original_dir) raise RuntimeError(error_msg) - else: - print("No development branch found") - return None + except Exception as e: # Catch-all for any unexpected errors error_msg = f"Error in get_development_branch: {str(e)}\n\n```\n{traceback.format_exc()}\n```" diff --git a/src/response_agent.py b/src/response_agent.py index 4222964..5fdcf7a 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -802,8 +802,10 @@ def respond_pr_comment_flow( user_feedback_bool = latest_bot_idx >= 0 and latest_bot_idx < len( comments) - 1 - branch_name = get_development_branch( - issue_or_pr, repo_path, create=False) + # branch_name = get_development_branch( + # issue_or_pr, repo_path, create=False) + branch_name = get_pr_branch(pr) + tab_print(f"Found branch name: {branch_name}") except Exception as e: pr_msg = f"Failed to process PR {pr_number} comment flow: {str(e)}\n\n```\n{traceback.format_exc()}\n```" From 3fdfd91674a98490bf76efe56b6fce7ce6ab6617 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Thu, 3 Apr 2025 17:36:57 -0400 Subject: [PATCH 098/146] fix(config): disable auto_update in params configuration - Changed "auto_update" parameter from `true` to `false` in `params.json`. --- config/params.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/params.json b/config/params.json index 0872b51..8555d85 100644 --- a/config/params.json +++ b/config/params.json @@ -1,4 +1,4 @@ { - "auto_update": true, + "auto_update": false, "print_llm_output": false } From ec7ee579baedbdb65e0c8b65dd708ccf58c3e9fd Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 2 Apr 2025 18:11:28 -0400 Subject: [PATCH 099/146] refactor(error-handling): streamline error handling and messaging - Removed redundant import and error message signature handling in `push_changes_with_authentication`. - Unified exception handling to raise consistent runtime exceptions across functions. - Simplified control flow by removing unnecessary boolean returns and catching of exceptions. - Enhanced log messaging to differentiate between skip outcomes and error outcomes. --- src/git_utils.py | 42 ++++--------------- src/response_agent.py | 98 ++++++++++++++----------------------------- 2 files changed, 39 insertions(+), 101 deletions(-) diff --git a/src/git_utils.py b/src/git_utils.py index e02ed49..370e4e0 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -484,47 +484,19 @@ def push_changes_with_authentication( remote = repo.remote(name='origin') repo_url = remote.url - if repo_url.startswith('https://'): - repo_suffix = repo_url.split('github.com/')[-1] - repo_url_with_token = f"https://x-access-token:{token}@github.com/{repo_suffix}" - remote.set_url(repo_url_with_token) - try: + if repo_url.startswith('https://'): + repo_suffix = repo_url.split('github.com/')[-1] + repo_url_with_token = f"https://x-access-token:{token}@github.com/{repo_suffix}" + remote.set_url(repo_url_with_token) + remote.push(refspec=f'{branch_name}:{branch_name}') print(f"Successfully pushed changes to {branch_name}") - success_bool = True - except git.GitCommandError as e: - error_msg = f"Failed to push changes: {e.stderr.strip()}" - # Import the model info from response_agent if available - try: - from response_agent import llm_config - error_msg_with_signature = add_signature_to_comment( - error_msg, llm_config['model']) - except (ImportError, KeyError): - error_msg_with_signature = error_msg + \ - "\n\n---\n*This response was automatically generated by blech_bot*" - if isinstance(out_thread, Issue): - # issue_comments = list(out_thread.get_comments()) - issue_comments = get_issue_comments(out_thread) - if 'Failed to push changes' not in issue_comments[-1].body: - write_issue_response(out_thread, error_msg_with_signature) - elif isinstance(out_thread, PullRequest): - pr_comments = list(out_thread.get_issue_comments()) - if 'Failed to push changes' not in pr_comments[-1].body: - out_thread.create_issue_comment(error_msg_with_signature) - else: - raise ValueError( - "Invalid output thread type, must be IssueComment or PullRequest") - print(error_msg) - success_bool = False - finally: + except Exception as e: if repo_url.startswith('https://'): remote.set_url(repo_url) # Reset URL to remove token - if success_bool: - return success_bool, None - else: - return success_bool, error_msg + raise RuntimeError(f"Failed to push changes: {str(e)}") def is_pull_request(issue_or_pr: Union[Issue, PullRequest]) -> bool: diff --git a/src/response_agent.py b/src/response_agent.py index 5fdcf7a..764aeb6 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -680,10 +680,6 @@ def develop_issue_flow( branch_name = get_development_branch( issue_or_pr, repo_path, create=False) - # # Check for linked PRs - # if has_linked_pr(issue_or_pr): - # return False, f"Issue #{issue_or_pr.number} already has a linked pull request" - # Check if issue has label "under_development" if "under_development" in [label.name for label in issue_or_pr.labels]: return False, f"Issue #{issue_or_pr.number} is already under development" @@ -707,7 +703,7 @@ def develop_issue_flow( repo = get_repository(client, repo_name) # Push changes with authentication - push_success, err_msg = push_changes_with_authentication( + push_changes_with_authentication( repo_path, issue_or_pr, branch_name @@ -726,9 +722,6 @@ def develop_issue_flow( # Mark issue with label "under_development" issue_or_pr.add_to_labels("under_development") - if not push_success: - return False, f"Failed to push changes: {err_msg}" - # write_issue_response(issue, "Generated edit command:\n" + response) write_str = f"Generated edit command:\n---\n{response}\n\n" + \ f"Aider output:\n
View Aider Output\n\n```{aider_output}```\n
" @@ -750,18 +743,20 @@ def develop_issue_flow( try: back_to_master_branch(repo_path) delete_branch(repo_path, branch_name, force=True) + clean_error_msg = "" except Exception as cleanup_error: - tab_print(f"Error during cleanup: {str(cleanup_error)}") + clean_error_msg = f"ERROR during cleanup: {str(cleanup_error)}" + tab_print(clean_error_msg) # Return to original directory os.chdir(original_dir) # Log detailed error to the issue with signature - error_msg = f"Failed to process develop issue: {str(e)}\n\n```\n{traceback.format_exc()}\n```" - write_issue_response(issue_or_pr, add_signature_to_comment( - error_msg, llm_config['model'])) + error_msg = f"ERROR: Failed to process develop issue: {str(e)}\n\n```\n{traceback.format_exc()}\n```" + if clean_error_msg: + error_msg += f"\n\n{clean_error_msg}" tab_print(f"Error logged to issue: {error_msg}") - return False, error_msg + raise Exception(error_msg) return True, None @@ -808,12 +803,9 @@ def respond_pr_comment_flow( tab_print(f"Found branch name: {branch_name}") except Exception as e: - pr_msg = f"Failed to process PR {pr_number} comment flow: {str(e)}\n\n```\n{traceback.format_exc()}\n```" + pr_msg = f"ERROR: Failed to process PR {pr_number} comment flow: {str(e)}\n\n```\n{traceback.format_exc()}\n```" tab_print(pr_msg) - # Log error to the issue with signature - write_issue_response(issue_or_pr, add_signature_to_comment( - pr_msg, llm_config['model'])) - return False, pr_msg + raise Exception(pr_msg) # Only run if branch exists and user comment is found on PR if branch_name and user_feedback_bool: @@ -848,14 +840,11 @@ def respond_pr_comment_flow( aider_output = run_aider(response, repo_path) # Push changes - push_success, err_msg = push_changes_with_authentication( + push_changes_with_authentication( repo_path, pr, branch_name) - if not push_success: - return False, f"Failed to push changes: {err_msg}" - # Write response write_str = f"Applied changes based on comment:\n
View Aider Output\n\n```\n{aider_output}\n```\n
" write_pr_comment( @@ -886,21 +875,13 @@ def respond_pr_comment_flow( # Log detailed error to the PR with signature error_msg = f"Failed to process PR# {pr_number} comment: {str(e)}\n\n```\n{traceback.format_exc()}\n```" - write_pr_comment( - pr, - error_msg, - aider_output="", - llm_config=llm_config, - write_str=add_signature_to_comment( - error_msg, llm_config['model']) - ) tab_print(f"Error logged to PR: {error_msg}") - return False, error_msg + raise Exception(error_msg) else: # Handle case where there are no user comments pr_msg = f"No user feedback found to process on the PR #{pr_number}" tab_print(pr_msg) - return True, pr_msg + return False, pr_msg def standalone_pr_flow( @@ -934,7 +915,7 @@ def standalone_pr_flow( aider_output = run_aider(response, repo_path) # Push changes with authentication - push_success, err_msg = push_changes_with_authentication( + push_changes_with_authentication( repo_path, issue_or_pr, branch_name @@ -968,20 +949,16 @@ def standalone_pr_flow( # Log detailed error to the PR with signature error_msg = f"Failed to process standalone PR flow: {str(e)}\n\n```\n{traceback.format_exc()}\n```" - write_issue_response(issue_or_pr, add_signature_to_comment( - error_msg, llm_config['model'])) tab_print(f"Error logged to PR: {error_msg}") - return False, error_msg + raise Exception(error_msg) return True, None except Exception as e: # Handle errors in the initial setup error_msg = f"Failed to initialize standalone PR flow: {str(e)}\n\n```\n{traceback.format_exc()}\n```" - write_issue_response(issue_or_pr, add_signature_to_comment( - error_msg, llm_config['model'])) tab_print(f"Error logged to PR: {error_msg}") - return False, error_msg + raise Exception(error_msg) def process_issue( @@ -1073,7 +1050,7 @@ def process_issue( write_issue_response(issue_or_pr, add_signature_to_comment( error_msg, llm_config['model'])) tab_print(f"Error logged to {entity_type}: {error_msg}") - return False, error_msg + return False, f"ERROR: {error_msg}" def run_aider(message: str, repo_path: str) -> str: @@ -1181,35 +1158,24 @@ def process_repository( # Process each issue and PR for item in open_issues: entity_type = "PR" if is_pull_request(item) else "issue" - try: - # Process the issue/PR and determine the outcome - success, message = process_issue(item, repo_name) - if success: - # Success outcome + + # Process the issue/PR and determine the outcome + success, message = process_issue(item, repo_name) + if success: + # Success outcome + tab_print( + f"Successfully processed {entity_type} #{item.number}") + else: + # Determine if this is a skip or error outcome based on message content + if "ERROR" in message: + # This is an error outcome tab_print( - f"Successfully processed {entity_type} #{item.number}") + f"ERROR processing {entity_type} #{item.number}: {message}") else: - # Determine if this is a skip or error outcome based on message content - if "Error" in message or "error" in message or "Exception" in message: - # This is an error outcome - tab_print( - f"Error processing {entity_type} #{item.number}: {message}") - else: - # This is a skip outcome - tab_print( - f"Skipped {entity_type} #{item.number}: {message}") - except Exception as e: - # This is a true error outcome - error_msg = f"Error processing {entity_type} #{item.number}: {str(e)}\n\n```\n{traceback.format_exc()}\n```" - tab_print(error_msg) - # Try to log the error to the issue/PR - try: - write_issue_response(item, add_signature_to_comment( - error_msg, llm_config['model'])) - tab_print(f"Error logged to {entity_type}") - except Exception as log_error: + # This is a skip outcome tab_print( - f"Failed to log error to {entity_type}: {str(log_error)}") + f"Skipped {entity_type} #{item.number}: {message}") + except Exception as e: error_msg = f"Error processing repository {repo_name}: {str(e)}\n\n```\n{traceback.format_exc()}\n```" tab_print(error_msg) From 031c87c60585886c7c8fdc79f76f81a7f941aed8 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Fri, 4 Apr 2025 14:14:54 +0000 Subject: [PATCH 100/146] feat: Add error handling to skip processing for issues/PRs with reported errors --- src/response_agent.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/response_agent.py b/src/response_agent.py index 764aeb6..633262c 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -996,6 +996,10 @@ def process_issue( return False, f"{entity_type} already has a bot response without feedback from user" has_error = triggers.has_error_comment(issue_or_pr) + + # Skip processing if an error has been reported + if has_error: + return False, f"Error reported in {entity_type} #{issue_or_pr.number}. Skipping further processing." # Handle PR differently if is_pr: From f3d08539848f3cb11d8e45799671ef57a26438ec Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 14:15:13 +0000 Subject: [PATCH 101/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/response_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/response_agent.py b/src/response_agent.py index 633262c..dda280f 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -996,7 +996,7 @@ def process_issue( return False, f"{entity_type} already has a bot response without feedback from user" has_error = triggers.has_error_comment(issue_or_pr) - + # Skip processing if an error has been reported if has_error: return False, f"Error reported in {entity_type} #{issue_or_pr.number}. Skipping further processing." From 1aa696822b5eb4864491c030086cae7e9e14387e Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Fri, 4 Apr 2025 10:19:56 -0400 Subject: [PATCH 102/146] Update triggers.py --- src/triggers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/triggers.py b/src/triggers.py index ce6c5a3..fde55da 100644 --- a/src/triggers.py +++ b/src/triggers.py @@ -132,7 +132,7 @@ def has_error_comment(issue: Issue) -> bool: True if the issue has error comments, False otherwise """ comments = get_issue_comments(issue) - return 'Error' in comments[-1].body if comments else False + return 'Traceback (most recent call last):' in comments[-1].body if comments else False def has_user_comment_on_pr(issue: Issue) -> bool: From f9404916203b9d51c57643ffd351f5a5bd87d8e6 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Sat, 5 Apr 2025 10:19:19 +0000 Subject: [PATCH 103/146] feat: Implement intelligent text summarization using summary agent --- src/response_agent.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index dda280f..c52afe5 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -176,7 +176,7 @@ def scrape_text_from_url(url: str) -> str: def summarize_text(text: str, max_length: int = 1000) -> str: - """Summarize text to a maximum length. + """Summarize text using a summary agent. Args: text: The text to summarize. @@ -188,8 +188,34 @@ def summarize_text(text: str, max_length: int = 1000) -> str: if len(text) <= max_length: return text - # Simple truncation with ellipsis for now - return text[:max_length] + "...\n[Text truncated due to length]" + # Use summary agent to create a contextually relevant summary + summary_agent = create_agent("summary_agent", llm_config) + + # Create a prompt for the summary agent + summary_prompt = f""" + Please summarize the following text, focusing on the most relevant information. + Keep your summary under {max_length} characters. + + TEXT TO SUMMARIZE: + {text} + """ + + # Get summary from the agent + summary_results = summary_agent.initiate_chat( + summary_agent, + message=summary_prompt, + max_turns=1, + silent=params['print_llm_output'] + ) + + # Extract the summary from the response + summary = summary_results.chat_history[-1]['content'] + + # Ensure the summary is within the max length + if len(summary) > max_length: + summary = summary[:max_length] + "...\n[Summary truncated due to length]" + + return summary def get_tracked_repos() -> str: From 824d9cba5aa3479093d694b8fee4a38a493323ff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 5 Apr 2025 10:19:27 +0000 Subject: [PATCH 104/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/response_agent.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index c52afe5..5490ea1 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -190,16 +190,16 @@ def summarize_text(text: str, max_length: int = 1000) -> str: # Use summary agent to create a contextually relevant summary summary_agent = create_agent("summary_agent", llm_config) - + # Create a prompt for the summary agent summary_prompt = f""" Please summarize the following text, focusing on the most relevant information. Keep your summary under {max_length} characters. - + TEXT TO SUMMARIZE: {text} """ - + # Get summary from the agent summary_results = summary_agent.initiate_chat( summary_agent, @@ -207,14 +207,15 @@ def summarize_text(text: str, max_length: int = 1000) -> str: max_turns=1, silent=params['print_llm_output'] ) - + # Extract the summary from the response summary = summary_results.chat_history[-1]['content'] - + # Ensure the summary is within the max length if len(summary) > max_length: - summary = summary[:max_length] + "...\n[Summary truncated due to length]" - + summary = summary[:max_length] + \ + "...\n[Summary truncated due to length]" + return summary From 04ff7fbd9a67475f2a47c46bcfff2a417776a8c2 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Sat, 5 Apr 2025 10:35:20 +0000 Subject: [PATCH 105/146] refactor: Enhance summary agent prompt to prioritize technical details and context --- src/response_agent.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/response_agent.py b/src/response_agent.py index 5490ea1..a3781a6 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -195,6 +195,8 @@ def summarize_text(text: str, max_length: int = 1000) -> str: summary_prompt = f""" Please summarize the following text, focusing on the most relevant information. Keep your summary under {max_length} characters. + Maintain all technical details and important context that would be relevant to the issue. + Prioritize code-related information, error messages, and specific technical requirements. TEXT TO SUMMARIZE: {text} From b1a56e33a01efbd997b5ae9fb1fddc4ffe1f3ca0 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Sun, 6 Apr 2025 00:41:48 +0000 Subject: [PATCH 106/146] refactor: Update summarize_text to use summary agent without length constraint --- src/response_agent.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/response_agent.py b/src/response_agent.py index a3781a6..36fe04a 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -194,7 +194,6 @@ def summarize_text(text: str, max_length: int = 1000) -> str: # Create a prompt for the summary agent summary_prompt = f""" Please summarize the following text, focusing on the most relevant information. - Keep your summary under {max_length} characters. Maintain all technical details and important context that would be relevant to the issue. Prioritize code-related information, error messages, and specific technical requirements. @@ -213,11 +212,6 @@ def summarize_text(text: str, max_length: int = 1000) -> str: # Extract the summary from the response summary = summary_results.chat_history[-1]['content'] - # Ensure the summary is within the max length - if len(summary) > max_length: - summary = summary[:max_length] + \ - "...\n[Summary truncated due to length]" - return summary From 37322175125e1bf70457cfad830b38b2fd5131c7 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Mon, 21 Apr 2025 20:51:12 +0000 Subject: [PATCH 107/146] test: Add comprehensive unit tests for triggers.py module --- tests/test_triggers.py | 255 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 tests/test_triggers.py diff --git a/tests/test_triggers.py b/tests/test_triggers.py new file mode 100644 index 0000000..517a1dd --- /dev/null +++ b/tests/test_triggers.py @@ -0,0 +1,255 @@ +import unittest +from unittest.mock import Mock, patch +import sys +import os + +# Add the src directory to the path so we can import the modules +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) + +from triggers import ( + has_blech_bot_tag, + has_generate_edit_command_trigger, + has_bot_response, + has_user_feedback, + has_develop_issue_trigger, + has_pull_request_trigger, + has_pr_creation_comment, + has_error_comment, + has_user_comment_on_pr +) + + +class TestTriggers(unittest.TestCase): + + def test_has_blech_bot_tag(self): + # Test with the tag present + mock_label = Mock() + mock_label.name = "blech_bot" + issue_with_tag = Mock() + issue_with_tag.labels = [mock_label] + + # Test with the tag absent + mock_other_label = Mock() + mock_other_label.name = "other_tag" + issue_without_tag = Mock() + issue_without_tag.labels = [mock_other_label] + + # Test with empty labels + issue_empty_labels = Mock() + issue_empty_labels.labels = [] + + self.assertTrue(has_blech_bot_tag(issue_with_tag)) + self.assertFalse(has_blech_bot_tag(issue_without_tag)) + self.assertFalse(has_blech_bot_tag(issue_empty_labels)) + + @patch('triggers.get_issue_comments') + def test_has_generate_edit_command_trigger(self, mock_get_comments): + # Test with trigger in comments + mock_comment_with_trigger = Mock() + mock_comment_with_trigger.body = "This is a comment with [ generate_edit_command ]" + mock_get_comments.return_value = [mock_comment_with_trigger] + + self.assertTrue(has_generate_edit_command_trigger(Mock())) + + # Test without trigger in comments + mock_comment_without_trigger = Mock() + mock_comment_without_trigger.body = "This is a regular comment" + mock_get_comments.return_value = [mock_comment_without_trigger] + + self.assertFalse(has_generate_edit_command_trigger(Mock())) + + # Test with empty comments + mock_get_comments.return_value = [] + + self.assertFalse(has_generate_edit_command_trigger(Mock())) + + @patch('triggers.get_issue_comments') + def test_has_bot_response(self, mock_get_comments): + # Test with bot response + mock_bot_comment = Mock() + mock_bot_comment.body = "This comment was generated by blech_bot" + mock_get_comments.return_value = [mock_bot_comment] + + self.assertTrue(has_bot_response(Mock())) + + # Test without bot response + mock_user_comment = Mock() + mock_user_comment.body = "This is a user comment" + mock_get_comments.return_value = [mock_user_comment] + + self.assertFalse(has_bot_response(Mock())) + + # Test with empty comments + mock_get_comments.return_value = [] + + self.assertFalse(has_bot_response(Mock())) + + @patch('triggers.get_issue_comments') + def test_has_user_feedback(self, mock_get_comments): + # Test with user feedback after bot comment + mock_bot_comment = Mock() + mock_bot_comment.body = "This comment was generated by blech_bot" + mock_user_comment = Mock() + mock_user_comment.body = "This is user feedback" + mock_get_comments.return_value = [mock_bot_comment, mock_user_comment] + + self.assertTrue(has_user_feedback(Mock())) + + # Test without user feedback after bot comment + mock_get_comments.return_value = [mock_bot_comment] + + self.assertFalse(has_user_feedback(Mock())) + + # Test with user comment before bot comment + mock_get_comments.return_value = [mock_user_comment, mock_bot_comment] + + self.assertFalse(has_user_feedback(Mock())) + + # Test with empty comments + mock_get_comments.return_value = [] + + self.assertFalse(has_user_feedback(Mock())) + + @patch('triggers.get_issue_comments') + def test_has_develop_issue_trigger(self, mock_get_comments): + # Test with develop_issue trigger in latest comment + mock_comment_with_trigger = Mock() + mock_comment_with_trigger.body = "[ develop_issue ]" + mock_get_comments.return_value = [Mock(), mock_comment_with_trigger] + + self.assertTrue(has_develop_issue_trigger(Mock())) + + # Test with develop_issue trigger not in latest comment + mock_comment_without_trigger = Mock() + mock_comment_without_trigger.body = "Regular comment" + mock_get_comments.return_value = [mock_comment_with_trigger, mock_comment_without_trigger] + + self.assertFalse(has_develop_issue_trigger(Mock())) + + # Test with empty comments + mock_get_comments.return_value = [] + + self.assertFalse(has_develop_issue_trigger(Mock())) + + @patch('triggers.get_issue_comments') + def test_has_pull_request_trigger(self, mock_get_comments): + # Test with pull_request trigger in latest comment + mock_comment_with_trigger = Mock() + mock_comment_with_trigger.body = "Created pull request" + mock_get_comments.return_value = [Mock(), mock_comment_with_trigger] + + self.assertTrue(has_pull_request_trigger(Mock())) + + # Test with pull_request trigger not in latest comment + mock_comment_without_trigger = Mock() + mock_comment_without_trigger.body = "Regular comment" + mock_get_comments.return_value = [mock_comment_with_trigger, mock_comment_without_trigger] + + self.assertFalse(has_pull_request_trigger(Mock())) + + # Test with empty comments + mock_get_comments.return_value = [] + + self.assertFalse(has_pull_request_trigger(Mock())) + + @patch('triggers.get_issue_comments') + def test_has_pr_creation_comment(self, mock_get_comments): + # Test with PR creation comment + mock_pr_comment = Mock() + mock_pr_comment.body = "Created pull request #123" + mock_get_comments.return_value = [mock_pr_comment] + + result, comment = has_pr_creation_comment(Mock()) + self.assertTrue(result) + self.assertEqual(comment, "Created pull request #123") + + # Test without PR creation comment + mock_regular_comment = Mock() + mock_regular_comment.body = "Regular comment" + mock_get_comments.return_value = [mock_regular_comment] + + result, comment = has_pr_creation_comment(Mock()) + self.assertFalse(result) + self.assertIsNone(comment) + + # Test with empty comments + mock_get_comments.return_value = [] + + result, comment = has_pr_creation_comment(Mock()) + self.assertFalse(result) + self.assertIsNone(comment) + + @patch('triggers.get_issue_comments') + def test_has_error_comment(self, mock_get_comments): + # Test with error comment + mock_error_comment = Mock() + mock_error_comment.body = "Traceback (most recent call last): Error occurred" + mock_get_comments.return_value = [mock_error_comment] + + self.assertTrue(has_error_comment(Mock())) + + # Test without error comment + mock_regular_comment = Mock() + mock_regular_comment.body = "Regular comment" + mock_get_comments.return_value = [mock_regular_comment] + + self.assertFalse(has_error_comment(Mock())) + + # Test with empty comments + mock_get_comments.return_value = [] + + self.assertFalse(has_error_comment(Mock())) + + @patch('triggers.get_issue_comments') + @patch('triggers.has_linked_pr') + @patch('triggers.get_linked_pr') + def test_has_user_comment_on_pr(self, mock_get_linked_pr, mock_has_linked_pr, mock_get_comments): + # Test with user comment on issue + mock_user_comment = Mock() + mock_user_comment.body = "User comment" + mock_get_comments.return_value = [mock_user_comment] + + self.assertTrue(has_user_comment_on_pr(Mock())) + + # Test with only bot comment on issue + mock_bot_comment = Mock() + mock_bot_comment.body = "generated by blech_bot" + mock_get_comments.return_value = [mock_bot_comment] + + # Set up for PR comments check + mock_has_linked_pr.return_value = True + mock_pr = Mock() + mock_get_linked_pr.return_value = mock_pr + + # Test with user comment after bot comment on PR + mock_get_comments.side_effect = [ + [mock_bot_comment], # Issue comments + [mock_bot_comment, mock_user_comment] # PR comments + ] + + self.assertTrue(has_user_comment_on_pr(Mock())) + + # Test with only bot comment on PR + mock_get_comments.side_effect = [ + [mock_bot_comment], # Issue comments + [mock_bot_comment] # PR comments + ] + + self.assertFalse(has_user_comment_on_pr(Mock())) + + # Test with no linked PR + mock_has_linked_pr.return_value = False + mock_get_comments.side_effect = None + mock_get_comments.return_value = [mock_bot_comment] + + self.assertFalse(has_user_comment_on_pr(Mock())) + + # Test with exception in PR comments retrieval + mock_has_linked_pr.return_value = True + mock_get_linked_pr.side_effect = Exception("PR retrieval error") + + self.assertFalse(has_user_comment_on_pr(Mock())) + + +if __name__ == '__main__': + unittest.main() From 3755454e5a5be8682755ed0dc940fa05a8655529 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 20:51:20 +0000 Subject: [PATCH 108/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_triggers.py | 142 +++++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 70 deletions(-) diff --git a/tests/test_triggers.py b/tests/test_triggers.py index 517a1dd..090f053 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -1,11 +1,3 @@ -import unittest -from unittest.mock import Mock, patch -import sys -import os - -# Add the src directory to the path so we can import the modules -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) - from triggers import ( has_blech_bot_tag, has_generate_edit_command_trigger, @@ -17,73 +9,81 @@ has_error_comment, has_user_comment_on_pr ) +import unittest +from unittest.mock import Mock, patch +import sys +import os + +# Add the src directory to the path so we can import the modules +sys.path.append(os.path.abspath( + os.path.join(os.path.dirname(__file__), '../src'))) class TestTriggers(unittest.TestCase): - + def test_has_blech_bot_tag(self): # Test with the tag present mock_label = Mock() mock_label.name = "blech_bot" issue_with_tag = Mock() issue_with_tag.labels = [mock_label] - + # Test with the tag absent mock_other_label = Mock() mock_other_label.name = "other_tag" issue_without_tag = Mock() issue_without_tag.labels = [mock_other_label] - + # Test with empty labels issue_empty_labels = Mock() issue_empty_labels.labels = [] - + self.assertTrue(has_blech_bot_tag(issue_with_tag)) self.assertFalse(has_blech_bot_tag(issue_without_tag)) self.assertFalse(has_blech_bot_tag(issue_empty_labels)) - + @patch('triggers.get_issue_comments') def test_has_generate_edit_command_trigger(self, mock_get_comments): # Test with trigger in comments mock_comment_with_trigger = Mock() mock_comment_with_trigger.body = "This is a comment with [ generate_edit_command ]" mock_get_comments.return_value = [mock_comment_with_trigger] - + self.assertTrue(has_generate_edit_command_trigger(Mock())) - + # Test without trigger in comments mock_comment_without_trigger = Mock() mock_comment_without_trigger.body = "This is a regular comment" mock_get_comments.return_value = [mock_comment_without_trigger] - + self.assertFalse(has_generate_edit_command_trigger(Mock())) - + # Test with empty comments mock_get_comments.return_value = [] - + self.assertFalse(has_generate_edit_command_trigger(Mock())) - + @patch('triggers.get_issue_comments') def test_has_bot_response(self, mock_get_comments): # Test with bot response mock_bot_comment = Mock() mock_bot_comment.body = "This comment was generated by blech_bot" mock_get_comments.return_value = [mock_bot_comment] - + self.assertTrue(has_bot_response(Mock())) - + # Test without bot response mock_user_comment = Mock() mock_user_comment.body = "This is a user comment" mock_get_comments.return_value = [mock_user_comment] - + self.assertFalse(has_bot_response(Mock())) - + # Test with empty comments mock_get_comments.return_value = [] - + self.assertFalse(has_bot_response(Mock())) - + @patch('triggers.get_issue_comments') def test_has_user_feedback(self, mock_get_comments): # Test with user feedback after bot comment @@ -92,114 +92,116 @@ def test_has_user_feedback(self, mock_get_comments): mock_user_comment = Mock() mock_user_comment.body = "This is user feedback" mock_get_comments.return_value = [mock_bot_comment, mock_user_comment] - + self.assertTrue(has_user_feedback(Mock())) - + # Test without user feedback after bot comment mock_get_comments.return_value = [mock_bot_comment] - + self.assertFalse(has_user_feedback(Mock())) - + # Test with user comment before bot comment mock_get_comments.return_value = [mock_user_comment, mock_bot_comment] - + self.assertFalse(has_user_feedback(Mock())) - + # Test with empty comments mock_get_comments.return_value = [] - + self.assertFalse(has_user_feedback(Mock())) - + @patch('triggers.get_issue_comments') def test_has_develop_issue_trigger(self, mock_get_comments): # Test with develop_issue trigger in latest comment mock_comment_with_trigger = Mock() mock_comment_with_trigger.body = "[ develop_issue ]" mock_get_comments.return_value = [Mock(), mock_comment_with_trigger] - + self.assertTrue(has_develop_issue_trigger(Mock())) - + # Test with develop_issue trigger not in latest comment mock_comment_without_trigger = Mock() mock_comment_without_trigger.body = "Regular comment" - mock_get_comments.return_value = [mock_comment_with_trigger, mock_comment_without_trigger] - + mock_get_comments.return_value = [ + mock_comment_with_trigger, mock_comment_without_trigger] + self.assertFalse(has_develop_issue_trigger(Mock())) - + # Test with empty comments mock_get_comments.return_value = [] - + self.assertFalse(has_develop_issue_trigger(Mock())) - + @patch('triggers.get_issue_comments') def test_has_pull_request_trigger(self, mock_get_comments): # Test with pull_request trigger in latest comment mock_comment_with_trigger = Mock() mock_comment_with_trigger.body = "Created pull request" mock_get_comments.return_value = [Mock(), mock_comment_with_trigger] - + self.assertTrue(has_pull_request_trigger(Mock())) - + # Test with pull_request trigger not in latest comment mock_comment_without_trigger = Mock() mock_comment_without_trigger.body = "Regular comment" - mock_get_comments.return_value = [mock_comment_with_trigger, mock_comment_without_trigger] - + mock_get_comments.return_value = [ + mock_comment_with_trigger, mock_comment_without_trigger] + self.assertFalse(has_pull_request_trigger(Mock())) - + # Test with empty comments mock_get_comments.return_value = [] - + self.assertFalse(has_pull_request_trigger(Mock())) - + @patch('triggers.get_issue_comments') def test_has_pr_creation_comment(self, mock_get_comments): # Test with PR creation comment mock_pr_comment = Mock() mock_pr_comment.body = "Created pull request #123" mock_get_comments.return_value = [mock_pr_comment] - + result, comment = has_pr_creation_comment(Mock()) self.assertTrue(result) self.assertEqual(comment, "Created pull request #123") - + # Test without PR creation comment mock_regular_comment = Mock() mock_regular_comment.body = "Regular comment" mock_get_comments.return_value = [mock_regular_comment] - + result, comment = has_pr_creation_comment(Mock()) self.assertFalse(result) self.assertIsNone(comment) - + # Test with empty comments mock_get_comments.return_value = [] - + result, comment = has_pr_creation_comment(Mock()) self.assertFalse(result) self.assertIsNone(comment) - + @patch('triggers.get_issue_comments') def test_has_error_comment(self, mock_get_comments): # Test with error comment mock_error_comment = Mock() mock_error_comment.body = "Traceback (most recent call last): Error occurred" mock_get_comments.return_value = [mock_error_comment] - + self.assertTrue(has_error_comment(Mock())) - + # Test without error comment mock_regular_comment = Mock() mock_regular_comment.body = "Regular comment" mock_get_comments.return_value = [mock_regular_comment] - + self.assertFalse(has_error_comment(Mock())) - + # Test with empty comments mock_get_comments.return_value = [] - + self.assertFalse(has_error_comment(Mock())) - + @patch('triggers.get_issue_comments') @patch('triggers.has_linked_pr') @patch('triggers.get_linked_pr') @@ -208,46 +210,46 @@ def test_has_user_comment_on_pr(self, mock_get_linked_pr, mock_has_linked_pr, mo mock_user_comment = Mock() mock_user_comment.body = "User comment" mock_get_comments.return_value = [mock_user_comment] - + self.assertTrue(has_user_comment_on_pr(Mock())) - + # Test with only bot comment on issue mock_bot_comment = Mock() mock_bot_comment.body = "generated by blech_bot" mock_get_comments.return_value = [mock_bot_comment] - + # Set up for PR comments check mock_has_linked_pr.return_value = True mock_pr = Mock() mock_get_linked_pr.return_value = mock_pr - + # Test with user comment after bot comment on PR mock_get_comments.side_effect = [ [mock_bot_comment], # Issue comments [mock_bot_comment, mock_user_comment] # PR comments ] - + self.assertTrue(has_user_comment_on_pr(Mock())) - + # Test with only bot comment on PR mock_get_comments.side_effect = [ [mock_bot_comment], # Issue comments [mock_bot_comment] # PR comments ] - + self.assertFalse(has_user_comment_on_pr(Mock())) - + # Test with no linked PR mock_has_linked_pr.return_value = False mock_get_comments.side_effect = None mock_get_comments.return_value = [mock_bot_comment] - + self.assertFalse(has_user_comment_on_pr(Mock())) - + # Test with exception in PR comments retrieval mock_has_linked_pr.return_value = True mock_get_linked_pr.side_effect = Exception("PR retrieval error") - + self.assertFalse(has_user_comment_on_pr(Mock())) From 27f2555f8b409cd91b71c434e13831ab94ea840d Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Mon, 21 Apr 2025 20:53:50 +0000 Subject: [PATCH 109/146] ci: add GitHub Actions workflow to run tests for triggers.py --- .github/workflows/test.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..df24a82 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,32 @@ +name: Run Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest + + - name: Run tests + run: | + pytest tests/test_triggers.py -v From 5a3b493120b4ca44f7ea1f2aa0436bd4c9b84846 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 21 Apr 2025 16:56:00 -0400 Subject: [PATCH 110/146] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b29158c..94df364 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,4 @@ gitpython>=3.1.40 pre-commit>=3.5.0 urlextract>=1.0.0 beautifulsoup4>=4.9.3 -aider-chat>=0.18.0 +aider-chat From fd3e8f26384455222d511fef5d469c52a6bb125f Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 21 Apr 2025 16:57:21 -0400 Subject: [PATCH 111/146] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 94df364..2e9b068 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,8 +3,8 @@ python-dotenv>=0.19.0 requests>=2.26.0 pyyaml>=5.4.1 pyautogen>=0.2.0 -gitpython>=3.1.40 pre-commit>=3.5.0 urlextract>=1.0.0 beautifulsoup4>=4.9.3 aider-chat +gitpython From ad41579d83e445999097fc9dceacee302f05bef6 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 21 Apr 2025 17:01:45 -0400 Subject: [PATCH 112/146] Update test.yml --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index df24a82..e31cd44 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: '3.x' + python-version: '3.12' - name: Install dependencies run: | From 756b09185b19e511ba5441ddf8bcef65dd23affd Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Tue, 22 Apr 2025 00:43:38 +0000 Subject: [PATCH 113/146] fix: update import path for triggers module in test file --- tests/test_triggers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_triggers.py b/tests/test_triggers.py index 090f053..8bfa734 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -1,4 +1,4 @@ -from triggers import ( +from src.triggers import ( has_blech_bot_tag, has_generate_edit_command_trigger, has_bot_response, From 8bd22b1e5154f6e10f8c5e21d3c424b889f1ecc5 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Tue, 22 Apr 2025 00:50:12 +0000 Subject: [PATCH 114/146] fix: Update import paths and sys.path for test_triggers.py --- tests/test_triggers.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/tests/test_triggers.py b/tests/test_triggers.py index 8bfa734..dc7ef61 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -1,3 +1,10 @@ +import sys +import os + +# Add the src directory to the path so we can import the modules +sys.path.append(os.path.abspath( + os.path.join(os.path.dirname(__file__), '..'))) + from src.triggers import ( has_blech_bot_tag, has_generate_edit_command_trigger, @@ -11,12 +18,6 @@ ) import unittest from unittest.mock import Mock, patch -import sys -import os - -# Add the src directory to the path so we can import the modules -sys.path.append(os.path.abspath( - os.path.join(os.path.dirname(__file__), '../src'))) class TestTriggers(unittest.TestCase): @@ -42,7 +43,7 @@ def test_has_blech_bot_tag(self): self.assertFalse(has_blech_bot_tag(issue_without_tag)) self.assertFalse(has_blech_bot_tag(issue_empty_labels)) - @patch('triggers.get_issue_comments') + @patch('src.triggers.get_issue_comments') def test_has_generate_edit_command_trigger(self, mock_get_comments): # Test with trigger in comments mock_comment_with_trigger = Mock() @@ -63,7 +64,7 @@ def test_has_generate_edit_command_trigger(self, mock_get_comments): self.assertFalse(has_generate_edit_command_trigger(Mock())) - @patch('triggers.get_issue_comments') + @patch('src.triggers.get_issue_comments') def test_has_bot_response(self, mock_get_comments): # Test with bot response mock_bot_comment = Mock() @@ -84,7 +85,7 @@ def test_has_bot_response(self, mock_get_comments): self.assertFalse(has_bot_response(Mock())) - @patch('triggers.get_issue_comments') + @patch('src.triggers.get_issue_comments') def test_has_user_feedback(self, mock_get_comments): # Test with user feedback after bot comment mock_bot_comment = Mock() @@ -110,7 +111,7 @@ def test_has_user_feedback(self, mock_get_comments): self.assertFalse(has_user_feedback(Mock())) - @patch('triggers.get_issue_comments') + @patch('src.triggers.get_issue_comments') def test_has_develop_issue_trigger(self, mock_get_comments): # Test with develop_issue trigger in latest comment mock_comment_with_trigger = Mock() @@ -132,7 +133,7 @@ def test_has_develop_issue_trigger(self, mock_get_comments): self.assertFalse(has_develop_issue_trigger(Mock())) - @patch('triggers.get_issue_comments') + @patch('src.triggers.get_issue_comments') def test_has_pull_request_trigger(self, mock_get_comments): # Test with pull_request trigger in latest comment mock_comment_with_trigger = Mock() @@ -154,7 +155,7 @@ def test_has_pull_request_trigger(self, mock_get_comments): self.assertFalse(has_pull_request_trigger(Mock())) - @patch('triggers.get_issue_comments') + @patch('src.triggers.get_issue_comments') def test_has_pr_creation_comment(self, mock_get_comments): # Test with PR creation comment mock_pr_comment = Mock() @@ -202,9 +203,9 @@ def test_has_error_comment(self, mock_get_comments): self.assertFalse(has_error_comment(Mock())) - @patch('triggers.get_issue_comments') - @patch('triggers.has_linked_pr') - @patch('triggers.get_linked_pr') + @patch('src.triggers.get_issue_comments') + @patch('src.triggers.has_linked_pr') + @patch('src.triggers.get_linked_pr') def test_has_user_comment_on_pr(self, mock_get_linked_pr, mock_has_linked_pr, mock_get_comments): # Test with user comment on issue mock_user_comment = Mock() From a3b685fc4386080bdab53f7f69964572b46a29d9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 00:51:17 +0000 Subject: [PATCH 115/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_triggers.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/test_triggers.py b/tests/test_triggers.py index dc7ef61..6e4470c 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -1,10 +1,5 @@ -import sys -import os - -# Add the src directory to the path so we can import the modules -sys.path.append(os.path.abspath( - os.path.join(os.path.dirname(__file__), '..'))) - +from unittest.mock import Mock, patch +import unittest from src.triggers import ( has_blech_bot_tag, has_generate_edit_command_trigger, @@ -16,8 +11,12 @@ has_error_comment, has_user_comment_on_pr ) -import unittest -from unittest.mock import Mock, patch +import sys +import os + +# Add the src directory to the path so we can import the modules +sys.path.append(os.path.abspath( + os.path.join(os.path.dirname(__file__), '..'))) class TestTriggers(unittest.TestCase): From 8e6a9371ee8763e3503d414507a14eb4f6c46970 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 21 Apr 2025 20:54:37 -0400 Subject: [PATCH 116/146] Update test_triggers.py --- tests/test_triggers.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_triggers.py b/tests/test_triggers.py index 6e4470c..fed35a1 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -1,5 +1,13 @@ from unittest.mock import Mock, patch import unittest + +import sys # noqa +import os # noqa + +# Add the src directory to the path so we can import the modules +sys.path.append(os.path.abspath( + os.path.join(os.path.dirname(__file__), '..'))) # noqa + from src.triggers import ( has_blech_bot_tag, has_generate_edit_command_trigger, @@ -11,12 +19,7 @@ has_error_comment, has_user_comment_on_pr ) -import sys -import os -# Add the src directory to the path so we can import the modules -sys.path.append(os.path.abspath( - os.path.join(os.path.dirname(__file__), '..'))) class TestTriggers(unittest.TestCase): From a1d6c085d39afa85ac4b786851a735185a3e0c15 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 00:54:43 +0000 Subject: [PATCH 117/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_triggers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_triggers.py b/tests/test_triggers.py index fed35a1..583d25e 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -1,12 +1,12 @@ from unittest.mock import Mock, patch import unittest -import sys # noqa -import os # noqa +import sys # noqa +import os # noqa # Add the src directory to the path so we can import the modules sys.path.append(os.path.abspath( - os.path.join(os.path.dirname(__file__), '..'))) # noqa + os.path.join(os.path.dirname(__file__), '..'))) # noqa from src.triggers import ( has_blech_bot_tag, @@ -21,7 +21,6 @@ ) - class TestTriggers(unittest.TestCase): def test_has_blech_bot_tag(self): From 00ac09270cace8be6d431dcf899d1762081ea02e Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Tue, 22 Apr 2025 00:58:35 +0000 Subject: [PATCH 118/146] fix: correct import paths and sys.path in test_triggers.py --- tests/test_triggers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_triggers.py b/tests/test_triggers.py index 583d25e..710d9d1 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -8,6 +8,8 @@ sys.path.append(os.path.abspath( os.path.join(os.path.dirname(__file__), '..'))) # noqa +from src.git_utils import get_issue_comments + from src.triggers import ( has_blech_bot_tag, has_generate_edit_command_trigger, @@ -183,7 +185,7 @@ def test_has_pr_creation_comment(self, mock_get_comments): self.assertFalse(result) self.assertIsNone(comment) - @patch('triggers.get_issue_comments') + @patch('src.triggers.get_issue_comments') def test_has_error_comment(self, mock_get_comments): # Test with error comment mock_error_comment = Mock() From 1028bb0b866bfe985bb91f34ab8e7604f122a046 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Tue, 22 Apr 2025 01:03:21 +0000 Subject: [PATCH 119/146] fix: resolve import errors in test_triggers.py by updating path and imports --- tests/test_triggers.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_triggers.py b/tests/test_triggers.py index 710d9d1..074cd4d 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -1,14 +1,13 @@ from unittest.mock import Mock, patch import unittest - -import sys # noqa -import os # noqa +import sys +import os # Add the src directory to the path so we can import the modules -sys.path.append(os.path.abspath( - os.path.join(os.path.dirname(__file__), '..'))) # noqa +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from src.git_utils import get_issue_comments +from src.git_utils import has_linked_pr, get_linked_pr from src.triggers import ( has_blech_bot_tag, From cf5312eb6783166dcc6c450946a132707fcee506 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 01:03:27 +0000 Subject: [PATCH 120/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_triggers.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/test_triggers.py b/tests/test_triggers.py index 074cd4d..efca4b0 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -1,14 +1,3 @@ -from unittest.mock import Mock, patch -import unittest -import sys -import os - -# Add the src directory to the path so we can import the modules -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from src.git_utils import get_issue_comments -from src.git_utils import has_linked_pr, get_linked_pr - from src.triggers import ( has_blech_bot_tag, has_generate_edit_command_trigger, @@ -20,6 +9,16 @@ has_error_comment, has_user_comment_on_pr ) +from src.git_utils import has_linked_pr, get_linked_pr +from src.git_utils import get_issue_comments +from unittest.mock import Mock, patch +import unittest +import sys +import os + +# Add the src directory to the path so we can import the modules +sys.path.insert(0, os.path.abspath( + os.path.join(os.path.dirname(__file__), '..'))) class TestTriggers(unittest.TestCase): From 915dd4d64eaf380ac47c87afff2d9069b77e3ed8 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Mon, 21 Apr 2025 21:05:23 -0400 Subject: [PATCH 121/146] Update test_triggers.py --- tests/test_triggers.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/test_triggers.py b/tests/test_triggers.py index efca4b0..cab2aba 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -1,3 +1,10 @@ +import sys # noqa +import os # noqa + +# Add the src directory to the path so we can import the modules +sys.path.insert(0, os.path.abspath( + os.path.join(os.path.dirname(__file__), '..'))) # noqa + from src.triggers import ( has_blech_bot_tag, has_generate_edit_command_trigger, @@ -13,12 +20,7 @@ from src.git_utils import get_issue_comments from unittest.mock import Mock, patch import unittest -import sys -import os -# Add the src directory to the path so we can import the modules -sys.path.insert(0, os.path.abspath( - os.path.join(os.path.dirname(__file__), '..'))) class TestTriggers(unittest.TestCase): From d20b66b54e78b0cdcf9d50bd639e0320a09dd0fe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 01:05:29 +0000 Subject: [PATCH 122/146] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_triggers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_triggers.py b/tests/test_triggers.py index cab2aba..b47a269 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -1,9 +1,9 @@ -import sys # noqa -import os # noqa +import sys # noqa +import os # noqa # Add the src directory to the path so we can import the modules sys.path.insert(0, os.path.abspath( - os.path.join(os.path.dirname(__file__), '..'))) # noqa + os.path.join(os.path.dirname(__file__), '..'))) # noqa from src.triggers import ( has_blech_bot_tag, @@ -22,7 +22,6 @@ import unittest - class TestTriggers(unittest.TestCase): def test_has_blech_bot_tag(self): From d1352e1650d60fcd4120be4d523b9a01b231bf36 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 22 Apr 2025 14:24:55 -0400 Subject: [PATCH 123/146] fix(imports): update import paths to include 'src' module - Added `src.` prefix to imports across various modules (agents, git_utils, response_agent, and triggers) to ensure the correct resolution of module paths. - Adjustments facilitate running the application as a package. --- src/__init__.py | 0 src/agents.py | 4 ++-- src/git_utils.py | 2 +- src/response_agent.py | 6 +++--- src/triggers.py | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 src/__init__.py diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/agents.py b/src/agents.py index 396cdf9..a7bcf4c 100644 --- a/src/agents.py +++ b/src/agents.py @@ -5,8 +5,8 @@ import autogen import subprocess from autogen import ConversableAgent, AssistantAgent, UserProxyAgent -import bot_tools -from git_utils import ( +from src import bot_tools +from src.git_utils import ( get_github_client, get_repository, get_issue_comments, diff --git a/src/git_utils.py b/src/git_utils.py index 370e4e0..b4edb82 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -6,7 +6,7 @@ import subprocess import git import traceback -from branch_handler import ( +from src.branch_handler import ( get_issue_related_branches, get_current_branch, checkout_branch, diff --git a/src/response_agent.py b/src/response_agent.py index dda280f..380f829 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -12,7 +12,7 @@ import string import triggers import traceback -from agents import ( +from src.agents import ( create_user_agent, create_agent, generate_prompt, @@ -24,7 +24,7 @@ import bot_tools import os -from git_utils import ( +from src.git_utils import ( get_github_client, get_repository, write_issue_response, @@ -42,7 +42,7 @@ from github.Repository import Repository from github.Issue import Issue from github.PullRequest import PullRequest -from branch_handler import ( +from src.branch_handler import ( checkout_branch, back_to_master_branch, delete_branch diff --git a/src/triggers.py b/src/triggers.py index fde55da..9e06fa5 100644 --- a/src/triggers.py +++ b/src/triggers.py @@ -2,7 +2,7 @@ Functions to check specific conditions """ from github import Issue -from git_utils import get_issue_comments +from src.git_utils import get_issue_comments def has_blech_bot_tag(issue: Issue) -> bool: From 3f798d7e52b285f36c3c87c502aa49217d55e05b Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 22 Apr 2025 14:28:02 -0400 Subject: [PATCH 124/146] refactor(triggers): optimize imports for linked PR functions - Move imports of `has_linked_pr` and `get_linked_pr` to the top of the file for better organization. - Remove redundant local imports within the function `has_user_comment_on_pr`. --- src/triggers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/triggers.py b/src/triggers.py index 9e06fa5..e9b7e39 100644 --- a/src/triggers.py +++ b/src/triggers.py @@ -2,7 +2,7 @@ Functions to check specific conditions """ from github import Issue -from src.git_utils import get_issue_comments +from src.git_utils import get_issue_comments, has_linked_pr, get_linked_pr def has_blech_bot_tag(issue: Issue) -> bool: @@ -145,7 +145,6 @@ def has_user_comment_on_pr(issue: Issue) -> bool: Returns: True if there is a user comment on a PR that needs processing """ - from git_utils import has_linked_pr, get_linked_pr # First check issue comments comments = get_issue_comments(issue) From b97b9389910a862a055c6a4509029e710ee8f250 Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Tue, 22 Apr 2025 18:42:27 +0000 Subject: [PATCH 125/146] feat: Add code coverage reporting with pytest-cov and Codecov integration --- .github/workflows/test.yml | 13 ++++++++++--- README.md | 1 + pytest.ini | 2 ++ requirements.txt | 2 ++ 4 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 pytest.ini diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e31cd44..38e8a30 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,8 +25,15 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install pytest + pip install pytest pytest-cov - - name: Run tests + - name: Run tests with coverage run: | - pytest tests/test_triggers.py -v + pytest tests/test_triggers.py -v --cov=src --cov-report=xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: unittests + name: codecov-umbrella diff --git a/README.md b/README.md index 491382c..a71d212 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/abuzarmahmood/blech_github_bot/main.svg)](https://results.pre-commit.ci/latest/github/abuzarmahmood/blech_github_bot/main) +[![codecov](https://codecov.io/gh/abuzarmahmood/blech_github_bot/branch/main/graph/badge.svg)](https://codecov.io/gh/abuzarmahmood/blech_github_bot) # GitHub Monitor Bot diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..59ace49 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --cov=src --cov-report=term-missing --cov-report=xml diff --git a/requirements.txt b/requirements.txt index 2e9b068..5b182e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,5 @@ urlextract>=1.0.0 beautifulsoup4>=4.9.3 aider-chat gitpython +pytest>=7.0.0 +pytest-cov>=4.0.0 From adc8866e2078610e21a9529b9236445deb986bb9 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 22 Apr 2025 14:48:06 -0400 Subject: [PATCH 126/146] Update test.yml --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 38e8a30..86879b4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,8 +32,9 @@ jobs: pytest tests/test_triggers.py -v --cov=src --cov-report=xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 with: file: ./coverage.xml flags: unittests name: codecov-umbrella + token: ${{ secrets.CODECOV_TOKEN }} From 248e0509048e28af91d67b0fdcc5bee12f5ca746 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Tue, 22 Apr 2025 14:51:04 -0400 Subject: [PATCH 127/146] Update test.yml --- .github/workflows/test.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 86879b4..1fa9f45 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,12 +29,9 @@ jobs: - name: Run tests with coverage run: | - pytest tests/test_triggers.py -v --cov=src --cov-report=xml + pytest tests/test_triggers.py -v --cov --cov-branch --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 with: - file: ./coverage.xml - flags: unittests - name: codecov-umbrella token: ${{ secrets.CODECOV_TOKEN }} From 829c96663798a36788f92b6942eda5a6cdba75ad Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Wed, 30 Apr 2025 14:07:11 +0000 Subject: [PATCH 128/146] fix: Add missing summary_agent configuration to agents.py --- src/agents.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/agents.py b/src/agents.py index a7bcf4c..4f7a124 100644 --- a/src/agents.py +++ b/src/agents.py @@ -45,6 +45,9 @@ Format the command in a way that can be parsed by automated tools. Reply "TERMINATE" in the end when everything is done. """, + "summary_agent": """You are a helpful GitHub bot that reviews issues and generates appropriate responses. + Analyze the issue details carefully and summarize the suggestions and changes made by other agents. + """, "summary_assistant": """You are a helpful GitHub bot that reviews issues and generates appropriate responses. Analyze the issue details carefully and summarize the suggestions and changes made by other agents. """, From 9ada16c3bbdc7028895befdc4ae7cd12fbc009fa Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 30 Apr 2025 10:16:50 -0400 Subject: [PATCH 129/146] refactor(agents): remove duplicate summary agent and update references - Removed duplicate "summary_agent" system message in `agents.py`. - Updated reference to "summary_agent" to "summary_assistant" in `response_agent.py` to ensure consistency and correct usage. --- src/agents.py | 3 --- src/response_agent.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/agents.py b/src/agents.py index 4f7a124..a7bcf4c 100644 --- a/src/agents.py +++ b/src/agents.py @@ -45,9 +45,6 @@ Format the command in a way that can be parsed by automated tools. Reply "TERMINATE" in the end when everything is done. """, - "summary_agent": """You are a helpful GitHub bot that reviews issues and generates appropriate responses. - Analyze the issue details carefully and summarize the suggestions and changes made by other agents. - """, "summary_assistant": """You are a helpful GitHub bot that reviews issues and generates appropriate responses. Analyze the issue details carefully and summarize the suggestions and changes made by other agents. """, diff --git a/src/response_agent.py b/src/response_agent.py index e0b7014..a46c1b6 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -189,7 +189,7 @@ def summarize_text(text: str, max_length: int = 1000) -> str: return text # Use summary agent to create a contextually relevant summary - summary_agent = create_agent("summary_agent", llm_config) + summary_agent = create_agent("summary_assistant", llm_config) # Create a prompt for the summary agent summary_prompt = f""" From 06f3327128dd47409f4db4e56427d04e85ef9245 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 30 Apr 2025 10:21:07 -0400 Subject: [PATCH 130/146] ci(workflow): update test dependencies installation - Add installation of `setuptools` and `wheel` for improved package management. - Ensure dependencies are up-to-date for testing environment setup. --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1fa9f45..483256e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,6 +24,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip + pip install -U setuptools wheel pip install -r requirements.txt pip install pytest pytest-cov From eeb4a2ae5db19dee27fdd023d6906cf6f04f8130 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 30 Apr 2025 10:23:53 -0400 Subject: [PATCH 131/146] ci(test): remove redundant pytest installation in workflow - The pytest and pytest-cov dependencies are removed from the workflow as they are presumably already included in the `requirements.txt` file. --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 483256e..538f32b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,6 @@ jobs: python -m pip install --upgrade pip pip install -U setuptools wheel pip install -r requirements.txt - pip install pytest pytest-cov - name: Run tests with coverage run: | From 2ed1a69bd12ed5d7712c7c9041b7d0a6f58eba3f Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 30 Apr 2025 10:24:23 -0400 Subject: [PATCH 132/146] chore(dependencies): update pytest and pytest-cov requirements - Removed version constraints for pytest and pytest-cov in requirements.txt to allow installation of the latest compatible versions. --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5b182e0..4f1adfc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,5 +8,5 @@ urlextract>=1.0.0 beautifulsoup4>=4.9.3 aider-chat gitpython -pytest>=7.0.0 -pytest-cov>=4.0.0 +pytest +pytest-cov From dda0bea47bc8d2c6431fcf81fa411375a87f3ee2 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 30 Apr 2025 10:29:04 -0400 Subject: [PATCH 133/146] chore(dependencies): add numpy to requirements - Added `numpy==1.18.2` to the `requirements.txt` file to manage numpy as a dependency for the project. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 4f1adfc..1e8a514 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +numpy==1.18.2 PyGithub>=1.55 python-dotenv>=0.19.0 requests>=2.26.0 From 7cedb531c3ea488b8454498856ba6265101f07a6 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 30 Apr 2025 10:38:34 -0400 Subject: [PATCH 134/146] chore(ci): update GitHub Actions versions for checkout and setup-python - Updated the `actions/checkout` action from version 2 to 4. - Updated the `actions/setup-python` action from version 2 to 5. - Ensures compatibility with newer GitHub Actions features and security updates. --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 538f32b..ced2567 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,10 +14,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: '3.12' From b9e95904c7ca34c5ecd2db754ae2bf3373f80ab2 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 30 Apr 2025 10:40:56 -0400 Subject: [PATCH 135/146] chore(dependencies): update numpy version specifier in requirements - Removed specific version pinning for numpy to allow flexibility in version upgrades. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1e8a514..289622e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.18.2 +numpy PyGithub>=1.55 python-dotenv>=0.19.0 requests>=2.26.0 From 3cbf9baa5da93d0d2606c6a55d84d777d6a1afe2 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 30 Apr 2025 10:46:47 -0400 Subject: [PATCH 136/146] feat(setup): simplify installation process with aider-install script - Added `aider_install.sh` for streamlined installation using `aider-install`. - Removed `aider-chat` and `numpy` from `requirements.txt` as they are no longer necessary. --- aider_install.sh | 2 ++ requirements.txt | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 aider_install.sh diff --git a/aider_install.sh b/aider_install.sh new file mode 100644 index 0000000..941fe81 --- /dev/null +++ b/aider_install.sh @@ -0,0 +1,2 @@ +python -m pip install aider-install +aider-install diff --git a/requirements.txt b/requirements.txt index 289622e..6affd55 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -numpy PyGithub>=1.55 python-dotenv>=0.19.0 requests>=2.26.0 @@ -7,7 +6,6 @@ pyautogen>=0.2.0 pre-commit>=3.5.0 urlextract>=1.0.0 beautifulsoup4>=4.9.3 -aider-chat gitpython pytest pytest-cov From 76ffe22828b772d0fdb0242e1045873508498471 Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Wed, 30 Apr 2025 10:47:51 -0400 Subject: [PATCH 137/146] build: add Makefile for project installation and management --- Makefile | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..78fb602 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +.PHONY: all install install-deps install-aider test clean + +all: install + +install: install-deps install-aider + +install-deps: + @echo "Installing Python dependencies..." + python -m pip install -r requirements.txt + +install-aider: + @echo "Installing aider..." + python -m pip install aider-install + aider-install + +test: + @echo "Running tests..." + pytest + +clean: + @echo "Cleaning up..." + find . -type d -name __pycache__ -exec rm -rf {} + + find . -type f -name "*.pyc" -delete + find . -type f -name "*.pyo" -delete + find . -type f -name "*.pyd" -delete + find . -type d -name "*.egg-info" -exec rm -rf {} + + find . -type d -name "*.egg" -exec rm -rf {} + + find . -type d -name ".pytest_cache" -exec rm -rf {} + + find . -type d -name ".coverage" -exec rm -rf {} + + find . -type d -name "htmlcov" -exec rm -rf {} + + find . -type d -name "dist" -exec rm -rf {} + + find . -type d -name "build" -exec rm -rf {} + + +help: + @echo "Available targets:" + @echo " all : Install dependencies and aider (default)" + @echo " install : Install dependencies and aider" + @echo " install-deps : Install Python dependencies from requirements.txt" + @echo " install-aider: Install aider tool" + @echo " test : Run tests with pytest" + @echo " clean : Remove Python cache files and build artifacts" + @echo " help : Show this help message" From 0b8783be4b5bf46f1dfc1f68fd36ffeb178a4245 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 30 Apr 2025 10:48:52 -0400 Subject: [PATCH 138/146] Move commands from aider_intall to Makefile --- aider_install.sh | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 aider_install.sh diff --git a/aider_install.sh b/aider_install.sh deleted file mode 100644 index 941fe81..0000000 --- a/aider_install.sh +++ /dev/null @@ -1,2 +0,0 @@ -python -m pip install aider-install -aider-install From 749480149ad9790b635d65672300a3afde1c7fea Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Wed, 30 Apr 2025 10:49:51 -0400 Subject: [PATCH 139/146] chore: update workflow to use makefile for dependency installation --- .github/workflows/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ced2567..c6aead4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,8 +24,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -U setuptools wheel - pip install -r requirements.txt + make install-deps - name: Run tests with coverage run: | From cf2bf6845840b59022146e854a680b26dae40b72 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 30 Apr 2025 10:50:39 -0400 Subject: [PATCH 140/146] fix(ci): correct make command for installing dependencies - Changed `make install-deps` to `make install` in the GitHub Actions workflow to properly install dependencies before running tests. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c6aead4..c3d5174 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - make install-deps + make install - name: Run tests with coverage run: | From 4af6a9f037b94f063643235a7c9f6a6af67eb275 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 30 Apr 2025 10:53:56 -0400 Subject: [PATCH 141/146] chore(ci): update CI configuration for test trigger - Removed push event trigger for branches and limited it to pull requests only. - Simplified the workflow configuration by removing unnecessary branch filters. --- .github/workflows/test.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c3d5174..8b03e50 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,12 +1,7 @@ name: Run Tests on: - push: - branches: - - main pull_request: - branches: - - main jobs: build: From d778e2f14bcceff4477bd8fc3716b0bdc3a7873c Mon Sep 17 00:00:00 2001 From: "Abuzar Mahmood (aider)" Date: Wed, 30 Apr 2025 11:04:35 -0400 Subject: [PATCH 142/146] docs: Update README with improved installation instructions --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a71d212..4abdb07 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,14 @@ source venv/bin/activate # On Linux/Mac .\venv\Scripts\activate # On Windows ``` -3. Install dependencies: +3. Install dependencies and tools: ```bash -pip install -r requirements.txt +# Install all dependencies and aider +make install + +# Or install components separately +make install-deps # Just Python dependencies +make install-aider # Just aider tool ``` 4. Create a `.env` file with your API tokens: From 5f19187f6dd2563a236d74860ccee5d9e4ffacfb Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 30 Apr 2025 12:20:43 -0400 Subject: [PATCH 143/146] refactor(script): modify paths and improve environment activation - Renamed `run_response_agent.sh` and updated the virtual environment activation logic. - Commented out code handling directory change and echo statement for debugging. - Adjusted paths in `triggers.py` for module import efficiency. - Standardized import ordering in `triggers.py`. - Commented out unnecessary path adjustments in `test_triggers.py`. - Created an `__init__.py` for the tests directory to facilitate module discovery. --- src/run_response_agent.sh => run_response_agent.sh | 13 +++++++------ src/triggers.py | 3 +++ tests/__init__.py | 0 tests/test_triggers.py | 6 +++--- 4 files changed, 13 insertions(+), 9 deletions(-) rename src/run_response_agent.sh => run_response_agent.sh (72%) create mode 100644 tests/__init__.py diff --git a/src/run_response_agent.sh b/run_response_agent.sh similarity index 72% rename from src/run_response_agent.sh rename to run_response_agent.sh index 4918cc6..2854145 100644 --- a/src/run_response_agent.sh +++ b/run_response_agent.sh @@ -13,19 +13,20 @@ while [[ "$#" -gt 0 ]]; do done # Navigate to the script directory -cd "$(dirname "$0")" -echo "Directory: $(pwd)" +# cd "$(dirname "$0")" +# echo "Directory: $(pwd)" # Activate virtual environment if it exists -if [ -f "../venv/bin/activate" ]; then - source ../venv/bin/activate - echo "Virtual environment activated from" $(realpath ../venv/bin/activate) +VENV_ACTIVATE_PATH=$(realpath ./venv/bin/activate) +if [ -f "$VENV_ACTIVATE_PATH" ]; then + source $VENV_ACTIVATE_PATH + echo "Virtual environment activated from" $VENV_ACTIVATE_PATH fi # Run the response_agent.py script in a loop with the specified delay echo "Running response_agent.py with delay of $DELAY seconds" while true; do - python3 response_agent.py + python3 src/response_agent.py echo "Next run in $DELAY seconds" sleep "$DELAY" # Wait for the specified delay before running again done diff --git a/src/triggers.py b/src/triggers.py index e9b7e39..20cb5c1 100644 --- a/src/triggers.py +++ b/src/triggers.py @@ -3,6 +3,9 @@ """ from github import Issue from src.git_utils import get_issue_comments, has_linked_pr, get_linked_pr +import sys +import os +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) def has_blech_bot_tag(issue: Issue) -> bool: diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_triggers.py b/tests/test_triggers.py index b47a269..9a4d306 100644 --- a/tests/test_triggers.py +++ b/tests/test_triggers.py @@ -1,9 +1,9 @@ import sys # noqa import os # noqa -# Add the src directory to the path so we can import the modules -sys.path.insert(0, os.path.abspath( - os.path.join(os.path.dirname(__file__), '..'))) # noqa +# # Add the src directory to the path so we can import the modules +# sys.path.insert(0, os.path.abspath( +# os.path.join(os.path.dirname(__file__), '..'))) # noqa from src.triggers import ( has_blech_bot_tag, From 04856f56a1d8ac3faf2d92b5060e617bd86bf9e3 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Wed, 30 Apr 2025 12:22:59 -0400 Subject: [PATCH 144/146] fix(triggers): reorder imports and remove duplicate entries - Added missing import statement for `os` and `sys`, ensuring compliance with style guidelines. - Removed duplicate appendix of `sys.path` entry to avoid redundancy. --- src/triggers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/triggers.py b/src/triggers.py index 20cb5c1..a0ef1cb 100644 --- a/src/triggers.py +++ b/src/triggers.py @@ -1,11 +1,11 @@ """ Functions to check specific conditions """ +import os # noqa: E501 +import sys # noqa: E501 +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # noqa: E501 from github import Issue from src.git_utils import get_issue_comments, has_linked_pr, get_linked_pr -import sys -import os -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) def has_blech_bot_tag(issue: Issue) -> bool: From 7d4df02f297c5ddb79af1e3b13c04a6cf07a56d6 Mon Sep 17 00:00:00 2001 From: Abuzar Mahmood Date: Thu, 1 May 2025 20:51:44 -0400 Subject: [PATCH 145/146] refactor: Externalize configuration and update repository tracking --- src/agents.py | 3 ++- src/config.py | 33 +++++++++++++++++++++++++++++++++ src/response_agent.py | 26 ++++++++------------------ 3 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 src/config.py diff --git a/src/agents.py b/src/agents.py index a7bcf4c..6bb4871 100644 --- a/src/agents.py +++ b/src/agents.py @@ -132,8 +132,9 @@ def create_user_agent(): return user -def create_agent(agent_name: str, llm_config: dict) -> AssistantAgent: +def create_agent(agent_name: str) -> AssistantAgent: """Create and configure the autogen agents""" + from config import llm_config agent = AssistantAgent( name=agent_name, diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..3aeae0f --- /dev/null +++ b/src/config.py @@ -0,0 +1,33 @@ +""" +Configuration file for the GitHub bot +""" +import os +import random +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +# Define the LLM configuration +llm_config = { + "model": "gpt-4o", + "api_key": os.getenv('OPENAI_API_KEY'), + "temperature": random.uniform(0, 0.2), +} + +# Define repository names as a dictionary +repo_names = { + "repo1": "owner/repo1", + "repo2": "owner/repo2", + # Add more repositories as needed +} + + +def get_tracked_repos(): + """ + Get list of tracked repositories from the repo_names dictionary + + Returns: + List of repository names in format owner/repo + """ + return list(repo_names.values()) diff --git a/src/response_agent.py b/src/response_agent.py index a46c1b6..5d8ba11 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -6,9 +6,9 @@ 2. Skip: Skipped because triggers were not met (e.g., no bot tag, already responded) 3. Error: An error occurred during processing (e.g., exception thrown) """ +from config import llm_config from typing import Optional, Tuple, List, Union -from dotenv import load_dotenv import string import triggers import traceback @@ -52,7 +52,6 @@ import os from pprint import pprint from collections.abc import Callable -import random import traceback import json import re @@ -69,16 +68,6 @@ with open(os.path.join(base_dir, 'config', 'params.json')) as f: params = json.load(f) -api_key = os.getenv('OPENAI_API_KEY') -if not api_key: - raise ValueError("OpenAI API key not found in environment variables") - -llm_config = { - "model": "gpt-4o", - # "model": "o3-mini-2025-01-31", - "api_key": api_key, - "temperature": random.uniform(0, 0.2), -} ############################################################ # Response patterns ############################################################ @@ -302,7 +291,7 @@ def summarize_relevant_comments( } comment_summary_assistant = create_agent( - "comment_summary_assistant", llm_config) + "comment_summary_assistant") summarized_comments = [] for comment in comment_list[:-1]: summary_prompt = generate_prompt( @@ -388,7 +377,7 @@ def generate_feedback_response( "issue": issue, } user = create_user_agent() - feedback_assistant = create_agent("feedback_assistant", llm_config) + feedback_assistant = create_agent("feedback_assistant") comments = get_issue_comments(issue) for comment in reversed(comments): @@ -469,9 +458,9 @@ def generate_new_response( # Create base agents user = create_user_agent() - file_assistant = create_agent("file_assistant", llm_config) - edit_assistant = create_agent("edit_assistant", llm_config) - summary_assistant = create_agent("summary_assistant", llm_config) + file_assistant = create_agent("file_assistant") + edit_assistant = create_agent("edit_assistant") + summary_assistant = create_agent("summary_assistant") # user, file_assistant, edit_assistant = create_agents() # Get prompts and run agents @@ -581,7 +570,7 @@ def generate_edit_command_response( user = create_user_agent() generate_edit_command_assistant = create_agent( - "generate_edit_command_assistant", llm_config) + "generate_edit_command_assistant") if summarized_comments: generate_edit_command_prompt = generate_prompt( "generate_edit_command_assistant", @@ -1247,6 +1236,7 @@ def initialize_bot() -> None: initialize_bot() # Get list of repositories to process + from config import get_tracked_repos tracked_repos = get_tracked_repos() print(f'Found {len(tracked_repos)} tracked repositories') pprint(tracked_repos) From cca62176cf58e3a49e562f1932f22e3722ff026b Mon Sep 17 00:00:00 2001 From: "abuzarmahmood (aider)" Date: Sun, 9 Mar 2025 11:13:21 +0000 Subject: [PATCH 146/146] refactor: Import llm_config from config.py in response_agent.py --- src/response_agent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/response_agent.py b/src/response_agent.py index 5d8ba11..2a4241a 100644 --- a/src/response_agent.py +++ b/src/response_agent.py @@ -39,6 +39,7 @@ get_pr_branch, add_signature_to_comment, ) +from config import llm_config from github.Repository import Repository from github.Issue import Issue from github.PullRequest import PullRequest