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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 25 additions & 10 deletions src/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,31 @@
"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.
""",
"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.
Focus on addressing specific concerns raised by the user.
Maintain a professional and helpful tone.
DO NOT MAKE ANY CHANGES TO THE FILES OR CREATE NEW FILES. Only provide information or suggestions.
If no changes are needed, respond accordingly.
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.
Provide code blocks where you can.
Include any relevant code snippets or technical details from the original response that should be preserved.
"feedback_assistant": """You are a helpful GitHub bot that processes user feedback and error messages.
You can handle two types of input:
1. User feedback on previous bot responses
2. Error messages from GitHub Actions workflow runs

For user feedback:
- Analyze feedback carefully and suggest improvements
- Focus on addressing specific concerns
- Maintain a professional tone
- Suggest concrete code changes where possible
- Include relevant code snippets

For workflow errors:
- Analyze the error messages carefully
- Identify the root cause of the failure
- Suggest specific fixes for the errors
- Provide code examples where helpful
- Include any relevant documentation links

General guidelines:
- DO NOT MAKE ANY CHANGES TO FILES. Only provide suggestions.
- Be specific about files and lines that need changes
- NEVER ask for user input
- Use code blocks for all code examples
- Keep responses clear and actionable
""",
"generate_edit_command_assistant": """You are a helpful GitHub bot that synthesizes all discussion in an issue thread to generate a command for a bot to make edits.
Analyze the issue details and comments carefully to generate a detailed and well-organized command.
Expand Down
258 changes: 258 additions & 0 deletions src/git_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,36 @@
import os
import subprocess
import git
import requests
import re
from github.PullRequest import PullRequest

def get_workflow_logs(repo_full_name, run_id, token):
"""Fetch logs for a specific GitHub Actions workflow run"""
url = f"https://api.github.com/repos/{repo_full_name}/actions/runs/{run_id}/logs"
headers = {"Authorization": f"token {token}"}
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.content
else:
raise Exception(f"Failed to fetch logs: {response.status_code}")

def create_branch_for_fixes(repo, base_branch, new_branch_name):
"""Create a new branch based on the existing branch"""
repo.git.checkout(base_branch)
repo.git.checkout('-b', new_branch_name)


def clean_response(response: str) -> str:
"""Remove any existing signatures or TERMINATE flags from response text"""
# 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)

return response.strip()
from branch_handler import (
get_issue_related_branches,
get_current_branch,
Expand Down Expand Up @@ -345,6 +375,120 @@ def push_changes_with_authentication(
return success_bool, error_msg


def get_workflow_run_logs(repo: Repository, run_id: int) -> List[str]:
"""
Fetch and parse logs from a GitHub Actions workflow run

Args:
repo: The GitHub repository
run_id: ID of the workflow run

Returns:
List of extracted log lines

Raises:
RuntimeError: If logs cannot be fetched
ValueError: If GitHub token is missing
PermissionError: If user lacks required permissions
"""
token = os.getenv('GITHUB_TOKEN')
if not token:
raise ValueError("GitHub token not found in environment variables")

try:
# Get workflow run logs
logs_url = f"https://api.github.com/repos/{repo.full_name}/actions/runs/{run_id}/logs"
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {token}",
"X-GitHub-Api-Version": "2022-11-28"
}
response = requests.get(logs_url, headers=headers)

if response.status_code == 403:
raise PermissionError(
"Insufficient permissions to access workflow logs. "
"Admin rights to the repository are required."
)
elif response.status_code == 404:
raise RuntimeError(
f"Workflow run with ID {run_id} not found in repository {repo.full_name}"
)

response.raise_for_status()

# Check if response is HTML (error page) instead of logs
content_type = response.headers.get('content-type', '')
if 'text/html' in content_type:
raise RuntimeError(
"Received HTML response instead of logs. "
"This likely indicates an authentication or permission issue."
)

return response.text.splitlines()

except requests.exceptions.RequestException as e:
raise RuntimeError(f"Failed to fetch workflow logs: {str(e)}")


def extract_errors_from_logs(log_lines: List[str]) -> List[str]:
"""
Extract error messages from workflow log lines

Args:
log_lines: List of log lines to parse

Returns:
List of extracted error messages
"""
error_lines = []
capture = False
for line in log_lines:
# Start capturing on error indicators
if any(indicator in line for indicator in [
"Traceback (most recent call last):",
"error:",
"Error:",
"ERROR:",
"FAILED"
]):
capture = True
error_lines.append(line)
continue

# Keep capturing until we hit a likely end
if capture:
error_lines.append(line)
if line.strip() == "" or "Process completed" in line:
capture = False

return error_lines


def get_auto_fix_attempt_count(pr: PullRequest) -> int:
"""
Get the number of auto-fix attempts from PR comments

Args:
pr: The GitHub pull request

Returns:
Number of auto-fix attempts made so far
"""
comments = list(pr.get_issue_comments())
attempt_count = 0

for comment in comments:
if "generated by blech_bot" in comment.body and "Attempt " in comment.body:
# Extract attempt number using regex
match = re.search(r"Attempt (\d+)/", comment.body)
if match:
current_attempt = int(match.group(1))
attempt_count = max(attempt_count, current_attempt)

return attempt_count


def has_linked_pr(issue: Issue) -> bool:
"""
Check if an issue has a linked pull request
Expand All @@ -368,6 +512,120 @@ def has_linked_pr(issue: Issue) -> bool:
return False


def get_workflow_run_logs(repo: Repository, run_id: int) -> List[str]:
"""
Fetch and parse logs from a GitHub Actions workflow run

Args:
repo: The GitHub repository
run_id: ID of the workflow run

Returns:
List of extracted log lines

Raises:
RuntimeError: If logs cannot be fetched
ValueError: If GitHub token is missing
PermissionError: If user lacks required permissions
"""
token = os.getenv('GITHUB_TOKEN')
if not token:
raise ValueError("GitHub token not found in environment variables")

try:
# Get workflow run logs
logs_url = f"https://api.github.com/repos/{repo.full_name}/actions/runs/{run_id}/logs"
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {token}",
"X-GitHub-Api-Version": "2022-11-28"
}
response = requests.get(logs_url, headers=headers)

if response.status_code == 403:
raise PermissionError(
"Insufficient permissions to access workflow logs. "
"Admin rights to the repository are required."
)
elif response.status_code == 404:
raise RuntimeError(
f"Workflow run with ID {run_id} not found in repository {repo.full_name}"
)

response.raise_for_status()

# Check if response is HTML (error page) instead of logs
content_type = response.headers.get('content-type', '')
if 'text/html' in content_type:
raise RuntimeError(
"Received HTML response instead of logs. "
"This likely indicates an authentication or permission issue."
)

return response.text.splitlines()

except requests.exceptions.RequestException as e:
raise RuntimeError(f"Failed to fetch workflow logs: {str(e)}")


def extract_errors_from_logs(log_lines: List[str]) -> List[str]:
"""
Extract error messages from workflow log lines

Args:
log_lines: List of log lines to parse

Returns:
List of extracted error messages
"""
error_lines = []
capture = False
for line in log_lines:
# Start capturing on error indicators
if any(indicator in line for indicator in [
"Traceback (most recent call last):",
"error:",
"Error:",
"ERROR:",
"FAILED"
]):
capture = True
error_lines.append(line)
continue

# Keep capturing until we hit a likely end
if capture:
error_lines.append(line)
if line.strip() == "" or "Process completed" in line:
capture = False

return error_lines


def get_auto_fix_attempt_count(pr: PullRequest) -> int:
"""
Get the number of auto-fix attempts from PR comments

Args:
pr: The GitHub pull request

Returns:
Number of auto-fix attempts made so far
"""
comments = list(pr.get_issue_comments())
attempt_count = 0

for comment in comments:
if "generated by blech_bot" in comment.body and "Attempt " in comment.body:
# Extract attempt number using regex
match = re.search(r"Attempt (\d+)/", comment.body)
if match:
current_attempt = int(match.group(1))
attempt_count = max(attempt_count, current_attempt)

return attempt_count


if __name__ == '__main__':
client = get_github_client()
repo = get_repository(client, 'katzlabbrandeis/blech_clust')
Expand Down
Loading