Skip to content

PR descriptions#15

Open
Yeshitha-co wants to merge 22 commits into
mainfrom
auto-pr-descriptions
Open

PR descriptions#15
Yeshitha-co wants to merge 22 commits into
mainfrom
auto-pr-descriptions

Conversation

@Yeshitha-co
Copy link
Copy Markdown
Collaborator

@Yeshitha-co Yeshitha-co commented Dec 3, 2025

PR descriptions

Modified

  • .gitignore (+0/-0)
  • src/api/webhooks.py (+100/-130)

    The diff modifies the process_pr_review function to only proceed for "opened", "synchronize", and "reopened" actions. It also removes the rate limit check and begins fetching PR details to generate a description, likely to improve efficiency and focus on relevant PR events.

  • src/github/client.py (+19/-4)

    The update_pr_body function now uses session.patch instead of _api_put to update the PR body, and includes detailed logging of the request and response, including error handling with status code and text. This change likely improves the robustness and debuggability of updating PR descriptions by using the correct HTTP method (PATCH) and providing more informative error messages.

Added

  • .chroma_db/chroma.sqlite3 (0 lines)

    New file with 0 lines

  • PROJECT_STRUCTURE_GUIDE.md (478 lines)

    New file with 478 lines

  • src/utils/pr_description_generator.py (404 lines)

    New file with 404 lines

Removed

No files removed

Summary

  • 6 files changed
  • +1001 additions
  • -134 deletions

Copilot AI review requested due to automatic review settings December 3, 2025 21:01
@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

🤖 Multi-Agent Code Review Summary

Files Reviewed: 1
Total Suggestions: 0
Bugs Found: 10
Security Issues: 0

Issues Found

src/api/webhooks.py

  • 🐛 Bug (low): The function _processed_events uses a mutable default argument (dict). This can lead to unexpected behavior as the same dictionary is shared across multiple calls.- 🐛 Bug (medium): The GitHubClient is instantiated without a context manager or try/finally block, potentially leading to unclosed HTTP sessions if an exception occurs.- 🐛 Bug (medium): The loop for i in range(1, len(changed_ranges)) in parse_diff_for_changed_lines starts from index 1, potentially skipping the first element of changed_ranges. This could lead to incorrect merging of adjacent ranges.- 🐛 Bug (medium): The code calls .get('line_number') and .get('line') on evidence without checking if evidence is None. If finding does not contain the key 'evidence', this will raise an AttributeError.- 🐛 Bug (high): The function snap_to_nearest_diff_line does not check if line_num is None before using it in calculations. If line_num is None, this will raise a TypeError.- 🐛 Bug (medium): The code assumes that comment.get('line') and comment.get('original_line') are integers. If these values are not integers or are missing, it will raise a TypeError.- 🐛 Bug (medium): The loop condition (cutoff - _processed_events[key]).seconds > 3600 only checks if the difference in seconds is greater than 3600, but doesn't account for cases where the difference is exactly 3600 seconds. This could lead to entries being deleted prematurely.- 🐛 Bug (medium): The code calls .get('remaining', 0) on rate_status without checking if rate_status is None. If rate_status is None, this will cause an AttributeError.- 🐛 Bug (medium): The loop iterates from range(start, end + 1), which includes end. However, end is derived from changed_ranges, which represents the last line of a change. Including end + 1 could lead to accessing a line outside the intended range.- 🐛 Bug (medium): The code accesses original_comment = github_client.get_pr_review_comment(repo_full_name, in_reply_to_id) without checking if github_client is None. If github_client is None, this will cause an AttributeError.

Generated by Multi-Agent Code Review System

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR expands the automatic PR description generation feature to trigger not only when a PR is opened, but also when new commits are pushed (synchronize) or when a PR is reopened. This ensures PR descriptions stay up-to-date with code changes throughout the PR lifecycle.

Key Changes

  • Updated the condition in process_pr_review() to generate PR descriptions for "synchronize" and "reopened" actions in addition to "opened"
  • Updated the comment to accurately reflect the expanded trigger conditions

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/api/webhooks.py Outdated
# Generate PR description if PR just opened
if action == "opened":
# Generate PR description on PR open, when commits are pushed, or when PR is reopened
if action in ["opened", "synchronize", "reopened"]:
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

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

The expanded PR description generation now triggers on "synchronize" and "reopened" actions in addition to "opened", but there are no tests covering this webhook behavior. Consider adding tests to verify that PR descriptions are generated for all three action types and that they are skipped for other actions.

Copilot uses AI. Check for mistakes.
@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

🤖 Multi-Agent Code Review Summary

Files Reviewed: 1
Total Suggestions: 0
Bugs Found: 13
Security Issues: 0

Issues Found

src/api/webhooks.py

  • 🐛 Bug (medium): The loop condition (cutoff - _processed_events[key]).seconds > 3600 only checks if the difference in seconds is greater than 3600, but doesn't account for cases where the difference is exactly 3600 seconds. This could lead to entries being deleted prematurely.- 🐛 Bug (medium): The condition if next_start <= end + 3 merges ranges if they are within 3 lines of each other. This might be too aggressive and merge ranges that are logically distinct. Consider reducing the threshold or making it configurable.- 🐛 Bug (medium): The code filters inline comments based on feedback by truncating the inline_comments list. This will not work as intended, as it does not actually filter the comments based on the feedback results. It simply keeps the first len(filtered_comments_data) comments.- 🐛 Bug (low): In the process_pr_review function, the GitHubClient is initialized twice, once with the installation token and once with the personal access token. If the GITHUB_TOKEN environment variable is not set, the second initialization will use the default token, which might not have the necessary permissions to update the PR description. This could lead to the PR description update failing silently.- 🐛 Bug (medium): The code uses ThreadPoolExecutor in the _handle_review_command function to process files in parallel. While this improves performance, it's crucial to ensure that the number of worker threads doesn't exceed the available resources or API rate limits. The current implementation uses a fixed max_workers=5, which might be too high for some environments or LLM API limits, potentially leading to resource exhaustion or rate limiting errors.- 🐛 Bug (low): In several functions (_handle_review_command, _handle_bugs_command, _handle_refactor_command, _handle_security_command), the code iterates through pr.files and accesses the patch attribute. It's assumed that all pr_file objects have a patch attribute. However, the code doesn't explicitly check if the pr_file object has the patch attribute before accessing it. This could lead to an AttributeError if a pr_file object doesn't have the patch attribute.- 🐛 Bug (low): In the _handle_bugs_command function, the code iterates through inline_comments and creates comments_for_feedback. The description field in comments_for_feedback is populated with c.get("body", ""). However, the category and severity fields are hardcoded as "Bug Detection" and "medium", respectively. This means that the feedback system will not have accurate information about the category and severity of each comment, potentially affecting the quality of feedback filtering.- 🐛 Bug (medium): The secret parameter in verify_signature is not validated. An empty or invalid secret could lead to incorrect signature verification.- 🐛 Bug (medium): The GitHubClient's session is not explicitly closed. Consider using a context manager or explicitly closing the session to prevent resource leaks.- 🐛 Bug (medium): The return value of trigger_repo_indexing is not checked for None before accessing its attributes. If trigger_repo_indexing fails and returns None, accessing job_id will raise an AttributeError.- 🐛 Bug (medium): The function parse_diff_for_changed_lines does not handle the case where the input patch contains only newline characters. This could lead to unexpected behavior or errors.- 🐛 Bug (medium): In parse_diff_for_changed_lines, the merging logic merges ranges within 3 lines. However, the end variable is not updated correctly when merging, potentially leading to off-by-one errors in the merged ranges.- 🐛 Bug (medium): The code does not check if finding is None before calling .get() on it. If finding is None, this will raise an AttributeError.

Generated by Multi-Agent Code Review System

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

🤖 Multi-Agent Code Review Summary

Files Reviewed: 2
Total Suggestions: 0
Bugs Found: 26
Security Issues: 0

Issues Found

src/api/webhooks.py

  • 🐛 Bug (medium): In function _handle_review_command, the diff_lines variable is initialized as a set of integers. However, in the loop that populates this set, the range function is used to generate a sequence of numbers from start to end + 1. The start and end variables are integers, but the code does not guarantee that end + 1 will also be an integer. If end is a very large number, adding 1 to it might result in a float due to integer overflow. This could lead to a type mismatch when updating the diff_lines set.- 🐛 Bug (medium): The loop iterates through the keys of _processed_events, but deletes elements during iteration, which can lead to skipping elements or errors. It's safer to iterate over a copy of the keys.- 🐛 Bug (medium): The condition next_start <= end + 3 merges ranges if they are within 3 lines. This might be too aggressive and merge ranges that are not logically connected. Consider reducing the threshold or making it configurable.- 🐛 Bug (medium): The code iterates through filtered_comments to store comments in the feedback system, but uses merged_comments to post the review. This means the stored comments might not match the posted comments if merging occurred. The loop should iterate through merged_comments and extract the relevant metadata from filtered_comments based on the comment body.- 🐛 Bug (medium): The code filters inline comments based on feedback, but then truncates the inline_comments list using slicing inline_comments = inline_comments[:len(filtered_comments_data)]. This assumes that the order of comments in inline_comments and filtered_comments_data is the same, which might not be guaranteed. A safer approach would be to filter the inline_comments list based on the presence of the corresponding description in filtered_comments_data.- 🐛 Bug (high): GitHubClient instance is created but not explicitly closed, potentially leading to resource leaks. Consider using a context manager or try/finally block to ensure proper closure.- 🐛 Bug (medium): The function parse_diff_for_changed_lines splits the input patch string by newline characters. If the input patch is an empty string, the resulting list will contain only an empty string. The subsequent loop will iterate once with line being an empty string, which might lead to unexpected behavior or errors depending on how the empty string is processed within the loop.- 🐛 Bug (medium): The hunk_match.group(1) and hunk_match.group(2) could return None if the regex doesn't match the expected format. This could lead to a TypeError when trying to convert them to integers.- 🐛 Bug (medium): The loop iterates from 1 to len(changed_ranges). If changed_ranges is empty, the loop will not execute, which is correct. However, if changed_ranges has only one element, the loop will not execute either, and the last element will not be appended to the merged list.- 🐛 Bug (medium): The finding.get(field) could return None. This could lead to a TypeError when trying to convert it to an integer.- 🐛 Bug (medium): The line_num could be None. This could lead to a TypeError when trying to subtract it from diff_line.- 🐛 Bug (medium): The rate_status.get('remaining', 0) could return None. This could lead to a TypeError when trying to compare it to 50.- 🐛 Bug (medium): The pr_description_result.get('status') could return None. This could lead to an AttributeError if the return value is None.- 🐛 Bug (medium): The suggestion.get('category', 'Code Review') could return None. This could lead to an AttributeError if the return value is None.- 🐛 Bug (medium): The comment.get("path", "") could return None. This could lead to an AttributeError if the return value is None.- 🐛 Bug (medium): The bugs_result.get('bug_count', 0) could return None. This could lead to a TypeError when trying to use it.

src/github/client.py

  • 🐛 Bug (medium): The code checks for a 403 status code and 'rate limit' in the response text, but it doesn't handle other potential rate limiting scenarios or error codes that might indicate rate limiting. It relies solely on the presence of 'rate limit' in the text, which might not be reliable across all GitHub API responses or future changes.- 🐛 Bug (medium): The code checks if remaining != '?' and int(remaining) < 100 to log a warning about low rate limits. However, if the rate limit is very close to being exhausted (e.g., remaining is 0 or negative due to timing issues), the int(remaining) conversion could raise a ValueError, causing the warning to fail. This could mask the fact that the rate limit is nearly exhausted.- 🐛 Bug (low): The _load_private_key method attempts to load the private key from the environment variable GITHUB_APP_PRIVATE_KEY. It tries to decode it from base64, read it from a file, or use it directly. However, if the key is loaded from a file, the file is opened but not explicitly closed using a try...finally block or a with statement. This could lead to a resource leak if the program encounters an error before the file is automatically closed by the garbage collector.- 🐛 Bug (low): In the from_installation method, the private key is loaded from a file using with open(private_key_raw, "r") as f:. While this ensures the file is closed, the same logic is duplicated in the _load_private_key method. This duplication increases the risk of inconsistencies or errors if the loading logic needs to be updated.- 🐛 Bug (low): The _api_get, _api_post, and _api_put methods retry requests on connection errors and timeouts. However, they also check for rate limit errors (status code 403) and retry in those cases as well. While retrying on rate limits can be beneficial, the current implementation waits a fixed amount of time (at least 60 seconds) before retrying, even if the X-RateLimit-Reset header indicates a shorter wait time. This can lead to unnecessary delays if the rate limit resets sooner than expected.- 🐛 Bug (medium): The installation_id is passed to get_installation_token without validation. If installation_id is not an integer, it will cause a TypeError in get_installation_token when used in the f-string.- 🐛 Bug (medium): The code accesses repo_info.get("default_branch", "main") without checking if repo_info is None. If the API call fails and repo_info is None, this will raise an AttributeError.- 🐛 Bug (medium): The code accesses item["name"] and item["path"] without checking if item is None or if these keys exist. If the API call returns malformed data, this will raise a KeyError.- 🐛 Bug (medium): The line argument in post_review_comment is not validated to be a positive integer. Passing a negative or zero value could lead to unexpected behavior or API errors.- 🐛 Bug (low): The side argument in post_review_comment is not validated. If an invalid value is passed, the API might return an error, or the comment might be placed incorrectly.

Generated by Multi-Agent Code Review System

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

🤖 Multi-Agent Code Review Summary

Files Reviewed: 2
Total Suggestions: 0
Bugs Found: 20
Security Issues: 0

Issues Found

src/api/webhooks.py

  • 🐛 Bug (medium): The loop condition (cutoff - _processed_events[key]).seconds > 3600 only removes entries older than 1 hour, but the cutoff time is re-calculated in each iteration. This can lead to entries older than 1 hour not being removed if they fall within the same second as the current time.- 🐛 Bug (medium): The code calls .get('status') on pr_description_result without checking if pr_description_result is None. If the agent fails, it could return None, leading to an error.- 🐛 Bug (medium): The line diff_lines.update(range(start, end + 1)) includes the end line in the diff_lines set. However, the end value in changed_ranges represents the last changed line, so adding 1 to it might include a line that wasn't actually changed.- 🐛 Bug (medium): The code checks if pr_file.status != "removed" and orchestrator._is_code_file(pr_file.filename) in multiple places. If pr_file.status == "removed", the file should not be processed. However, the code continues to check if it's a code file, which is unnecessary.- 🐛 Bug (medium): The code checks if line_num in diff_lines before snapping to the nearest diff line. However, if line_num is already in diff_lines, it should be returned directly without snapping. The current logic will still try to snap even if the line is already valid.- 🐛 Bug (medium): The code iterates through pr.files and submits each file for processing. However, if an exception occurs during the processing of a file, the continue statement skips to the next file without decrementing files_reviewed or incrementing files_failed. This can lead to an incorrect count of reviewed and failed files.- 🐛 Bug (medium): The code filters comments using inline_comments = inline_comments[:len(filtered_comments_data)]. This truncates the list to the length of the filtered comments, effectively removing any comments that were not included in the filtered data. This is likely incorrect, as it should be filtering the list based on the contents of filtered_comments_data, not just its length.- 🐛 Bug (low): Using a mutable default argument _processed_events can lead to unexpected behavior as it shares state across calls.- 🐛 Bug (medium): The GitHubClient's session is not explicitly closed, which could lead to resource leaks. Consider using a context manager or explicitly closing the session.- 🐛 Bug (medium): The function snap_to_nearest_diff_line does not check if line_num is None before using it in calculations. This could lead to a TypeError.- 🐛 Bug (medium): The condition if line_num in diff_lines: is checked after if not diff_lines or line_num is None:. If diff_lines is empty, the first condition will return None, and the second condition will not be checked. This can be simplified.

src/github/client.py

  • 🐛 Bug (medium): The _token_cache dictionary is defined with a value type of tuple, but the code assigns a tuple[str, float] to it. This could lead to type errors if the code later expects a different type in the tuple.- 🐛 Bug (medium): The get_installation_token function returns a str, but the from_installation method does not have a return type annotation.- 🐛 Bug (low): The _load_private_key method attempts to load the private key from the environment variable GITHUB_APP_PRIVATE_KEY in multiple formats (file path, base64 encoded, direct content). If the key is loaded from a file, the file is opened using with open(key, "r") as f:, which ensures the file is closed. However, if the key is loaded from base64 or direct content, there is no error handling if the decoding or replacement fails. This could lead to unexpected behavior if the environment variable is not set correctly.- 🐛 Bug (low): The get_installation_token function does not handle potential exceptions during the JWT encoding process. If the jwt.encode function fails, the exception will not be caught, potentially crashing the application.- 🐛 Bug (medium): The time.sleep() call within the get_installation_token function can block the event loop if this function is called in an asynchronous context. This can lead to performance issues and prevent other tasks from being executed.- 🐛 Bug (medium): The time.sleep() call within the _api_get function can block the execution of the program, especially if the rate limit is frequently hit. This can lead to performance issues.- 🐛 Bug (medium): The variable private_key_raw is used without a null check before accessing its slice private_key_raw[:50]. If private_key_raw is None or empty, this will raise an exception.- 🐛 Bug (medium): The variable branch is used without a null check before accessing repo_info.get("default_branch", "main"). If repo_info is None, this will raise an exception.- 🐛 Bug (medium): The requests.Session object is not properly closed. This can lead to resource leaks. Use a context manager to ensure it is closed.

Generated by Multi-Agent Code Review System

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

🤖 Multi-Agent Code Review Summary

Files Reviewed: 2
Total Suggestions: 0
Bugs Found: 11
Security Issues: 0

Issues Found

src/api/webhooks.py

  • 🐛 Bug (medium): In function _handle_review_command, the diff_lines variable is initialized as a set of integers. However, in the loop that populates diff_lines, the code uses range(start, end + 1). If start or end are floats, this will cause a TypeError.- 🐛 Bug (medium): The loop condition (cutoff - _processed_events[key]).seconds > 3600 only checks if the difference in seconds is greater than 3600, but doesn't account for cases where the difference might be negative due to clock skew or other issues. This could lead to entries being deleted prematurely or not being deleted at all.- 🐛 Bug (medium): The code calls .get('status') on pr_description_result without checking if pr_description_result is None. If pr_description_result is None, this will cause an AttributeError.- 🐛 Bug (medium): The loop iterates one too many times causing IndexError. The diff_lines set contains the line numbers that are in the diff. The loop condition next_start <= end + 3 merges ranges if the next start is within 3 lines of the current end. However, the end variable is not updated correctly when merging, leading to incorrect range merging.- 🐛 Bug (medium): The code filters inline_comments by slicing it based on the length of filtered_comments_data. This assumes that the order of comments in inline_comments and comments_for_feedback is the same, which might not always be the case. This can lead to incorrect comments being filtered out.

src/github/client.py

  • 🐛 Bug (medium): The _token_cache dictionary is declared as Dict[int, tuple], but the values stored are tuples of (str, float). This is because the token is a string and the expiry is time.time() which returns a float.- 🐛 Bug (medium): time.sleep() is used in a potentially performance-critical section, which can block the execution of the program. Consider using asyncio.sleep() for asynchronous operations.- 🐛 Bug (medium): The variable private_key_raw is used without a null check before accessing its slice private_key_raw[:50]. If private_key_raw is None or empty, this will raise an exception.- 🐛 Bug (medium): The code iterates through extensions without checking if it's None or empty. If extensions is empty, the any function will return True, which might not be the intended behavior.- 🐛 Bug (medium): The code accesses data['content'] without checking if data or data['content'] exists. If data is None or doesn't contain 'content', this will raise an exception.- 🐛 Bug (low): The side parameter in post_review_comment function only accepts 'LEFT' or 'RIGHT'. There is no input validation to ensure the value of side is one of the allowed values. Passing an invalid value could lead to unexpected behavior or errors.

Generated by Multi-Agent Code Review System

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

🤖 Multi-Agent Code Review Summary

Files Reviewed: 2
Total Suggestions: 0
Bugs Found: 14
Security Issues: 1

Issues Found

src/api/webhooks.py

  • 🐛 Bug (medium): The loop iterates through all keys in _processed_events, deleting entries while iterating. This can lead to RuntimeError: dictionary changed size during iteration or skipping elements. It should iterate over a static copy of the keys.- 🐛 Bug (medium): The code calculates (total_score / max_score) * 10 but max_score could be zero if len(vulnerabilities) is zero. This will cause a ZeroDivisionError.- 🐛 Bug (medium): In function process_pr_review, orchestrator.process_task(task) is called, but the return type of process_task is not explicitly defined. Later, the code accesses result["_pending_pr_description"] assuming result is a dictionary. If process_task returns a different type, this will cause a runtime error.- 🐛 Bug (medium): In function handle_agent_command, orchestrator._safe_execute_agent is called with different arguments depending on the command. For example, in _handle_bugs_command, it's called with a tuple (content, diff_context), while in _handle_review_command, it's called with a string diff_context. The _safe_execute_agent function should be able to handle both types of arguments.- 🐛 Bug (medium): In function _handle_review_command, the snap_to_nearest_diff_line function is called with raw_line_num as an argument, which can be None. The snap_to_nearest_diff_line function expects an integer as the first argument.- 🐛 Bug (medium): The code uses ThreadPoolExecutor in _handle_review_command to process files in parallel. While this improves performance, exceptions raised within the process_single_file function can potentially crash the executor if not properly handled.- 🐛 Bug (low): The _processed_events dictionary stores delivery IDs to prevent duplicate processing. However, the cleanup logic only removes entries older than 1 hour. In a high-traffic scenario, this dictionary could grow significantly, consuming memory.- 🐛 Bug (low): The code uses time.sleep in the get_rate_limit_status function of GitHubClient (not shown in the provided code, but implied by the comment). This is a blocking operation that can significantly impact the performance of the webhook handler, especially if the rate limit is frequently exceeded.- 🐛 Bug (low): The code extracts code snippets using _extract_code_snippet. If line_number is near the beginning or end of the file, the start or end indices might be out of bounds, leading to an IndexError.- 🔒 Security (high): Data Exposure

src/github/client.py

  • 🐛 Bug (medium): The _token_cache dictionary is defined as Dict[int, tuple], but the code assigns a tuple of (str, float) to it. The second element of the tuple is a float representing the expiry time, but it is not explicitly typed.- 🐛 Bug (medium): The _api_put function returns Dict[str, Any], but the update_file_in_pr function does not handle the case where the API call fails and raises an exception. If an exception is raised, the function does not return a Dict[str, Any].- 🐛 Bug (medium): The time.sleep() call within the get_installation_token function can block the event loop if this function is called in an asynchronous context. This can lead to performance issues and prevent other tasks from being executed.- 🐛 Bug (medium): The time.sleep() call within the _api_get function can block the execution of the program, especially if the rate limit is frequently hit. This can lead to performance issues.- 🐛 Bug (low): The _load_private_key method attempts to load the private key from the environment variable GITHUB_APP_PRIVATE_KEY. It tries to decode it from base64, read it from a file, or use it directly. However, if the key is loaded from a file, the file is opened using with open(key, "r") as f:, but if the key is loaded from base64 or directly from the environment variable, there is no check to ensure that the key is properly formatted (e.g., starts with "-----BEGIN RSA PRIVATE KEY-----"). This could lead to errors later when the key is used for JWT encoding.

Generated by Multi-Agent Code Review System

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

🤖 Multi-Agent Code Review Summary

Files Reviewed: 4
Total Suggestions: 0
Bugs Found: 20
Security Issues: 0

Issues Found

src/api/webhooks.py

  • 🐛 Bug (low): The _processed_events dictionary, used for tracking processed events to prevent duplicates, is cleaned by iterating through its keys and deleting entries older than 1 hour. However, the cleanup process is performed during the handling of each new event. If the number of keys is large, this could introduce a performance bottleneck, especially under high webhook traffic.- 🐛 Bug (low): The code uses ThreadPoolExecutor with a fixed max_workers=5 in the _handle_review_command function to process files in parallel. While this improves performance, it doesn't account for the available system resources or the complexity of the tasks. Over-subscribing the thread pool can lead to context switching overhead and potentially degrade performance, especially on resource-constrained systems.- 🐛 Bug (low): GitHubClient.from_installation() or GitHubClient() creates a client that may not be properly closed. Consider using a context manager or try/finally block to ensure proper resource cleanup.- 🐛 Bug (low): github_client.get_repo_contents() may leave the resource unclosed if an exception occurs. Consider using a context manager or try/finally block to ensure proper resource cleanup.- 🐛 Bug (medium): The condition next_start <= end + 3 might lead to an off-by-one error when merging adjacent ranges. Consider the case when next_start is exactly end + 1. It might be more appropriate to merge if next_start <= end + 1.- 🐛 Bug (medium): line_num could be None, which would cause an error when calling line_num in diff_line - line_num- 🐛 Bug (medium): rate_status.get('remaining', 0) could return None, which would cause an error when calling remaining < 50- 🐛 Bug (medium): original_comment could be None, which would cause an error when calling original_comment.get("body", "")- 🐛 Bug (medium): The function github_webhook has a mutable default argument _processed_events. This can lead to unexpected behavior if the function is called multiple times without re-initialization.

src/github/client.py

  • 🐛 Bug (medium): The _token_cache dictionary is defined as Dict[int, tuple], but the code assigns a tuple of (token, now + 55 * 60) where token is a string and now + 55 * 60 is a float (representing expiry time). This means the tuple is actually tuple[str, float], which is more specific than just tuple.- 🐛 Bug (medium): In the _parse_repo_url method, the code uses re.search which can return None if no match is found. The code does not check for this None return value before calling .group(1) and .group(2), which will raise an AttributeError.- 🐛 Bug (medium): time.sleep() is used in a potentially performance-critical section, which can block the execution of the program. Consider using asynchronous alternatives if available.- 🐛 Bug (low): The _load_private_key method attempts to load the private key from the environment variable GITHUB_APP_PRIVATE_KEY. It tries to decode it from base64, read it from a file, or use it directly. If the key is loaded from a file, the file is opened using with open(key, "r") as f:, which ensures the file is closed. However, if the key is loaded from the environment variable (either directly or after base64 decoding), there's a potential for the key to contain sensitive information that remains in memory longer than necessary. While not a direct memory leak, it increases the attack surface.- 🐛 Bug (medium): The variable private_key can be None if loading from file or base64 fails, leading to a potential AttributeError when calling jwt.encode in get_jwt_token.- 🐛 Bug (medium): If contents is not a list, it's converted to a single-element list. If contents is an empty dictionary, the loop will still execute once with an empty dictionary, potentially causing issues later when accessing dictionary keys.- 🐛 Bug (medium): If data does not contain the key 'encoding', data.get("content", "") is returned. If data is None, this will raise an AttributeError. A check for data is None should be added.- 🐛 Bug (low): The side parameter in post_review_comment only accepts 'LEFT' or 'RIGHT'. There is no validation to ensure the input is one of these values, potentially leading to unexpected behavior or API errors if an invalid value is passed.

src/utils/pr_description_generator.py

  • 🐛 Bug (medium): The changes field in the FileChange dataclass is calculated as additions + deletions. However, additions and deletions are integers, so changes will also be an integer. There is no guarantee that this integer represents the actual number of changes in the file, as it doesn't account for modifications within existing lines. This could lead to misleading information.- 🐛 Bug (medium): The diff is truncated to 2000 characters, potentially losing important context for the LLM analysis. This could lead to inaccurate or incomplete explanations, especially for large diffs.- 🐛 Bug (medium): The explanation is checked for the substring 'new' using .lower(). This could lead to incorrect behavior if the explanation contains 'NEW' or other variations of 'new' with different casing. Also, the check is only performed in the added section, not in the modified section where it might also be relevant.

Generated by Multi-Agent Code Review System

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

🤖 Multi-Agent Code Review Summary

Files Reviewed: 4
Total Suggestions: 0
Bugs Found: 17
Security Issues: 0

Issues Found

src/api/webhooks.py

  • 🐛 Bug (medium): The cutoff time is calculated only once before the loop, so the loop might iterate longer than intended if the current time changes significantly during the loop. This could lead to unnecessary iterations.- 🐛 Bug (medium): The orchestrator.process_task() method is called without awaiting it, which might lead to blocking the event loop if the method contains synchronous operations.- 🐛 Bug (medium): The diff_lines.update(range(start, end + 1)) includes 'end' in the range, potentially including one line beyond the actual changed range. This could lead to comments being placed on unchanged lines.

src/github/client.py

  • 🐛 Bug (medium): The _token_cache dictionary is defined as Dict[int, tuple], but the code assigns a tuple of (token, now + 55 * 60) where token is a string and now + 55 * 60 is a float (representing expiry time). This means the tuple is actually tuple[str, float], which is more specific than just tuple.- 🐛 Bug (medium): In the _parse_repo_url method, the code uses re.search which can return None if no match is found. The code doesn't handle the case where match is None before calling match.group(1) and match.group(2), which can lead to an AttributeError.- 🐛 Bug (low): The _load_private_key method attempts to load the private key from the environment variable GITHUB_APP_PRIVATE_KEY. It tries to decode the key from base64, read it from a file, or use it directly. If the key is provided directly with escaped newlines (\n), it replaces them with actual newlines. However, if the key is read from a file, it doesn't handle the case where the file might contain escaped newlines. This could lead to incorrect private key loading if the file contains escaped newlines.- 🐛 Bug (low): In the _api_get, _api_post, and _api_put methods, the code checks for rate limiting errors (status code 403 and 'rate limit' in the response text). However, the code only checks for this specific error and doesn't handle other potential 4xx or 5xx errors that might require different handling or logging.- 🐛 Bug (medium): time.sleep() is used in a potentially performance-critical section, which can block the execution of the program. Consider using asynchronous alternatives if available.- 🐛 Bug (medium): The variable private_key_raw is used without a null check before accessing its first 50 characters. If private_key_raw is None or an empty string, this will cause an error.- 🐛 Bug (medium): The variable branch is used without a null check before accessing the default_branch key in the repo_info dictionary. If repo_info does not contain the key default_branch, this will cause an error.- 🐛 Bug (medium): The code iterates through contents without checking if it's empty. If contents is empty, the loop will not execute, but it might be unexpected behavior.- 🐛 Bug (medium): The code accesses data["content"] without checking if data contains the key content. If the key is missing, it will raise a KeyError.- 🐛 Bug (medium): The line argument in post_review_comment is not validated. A negative or zero value could cause unexpected behavior or errors in the GitHub API.- 🐛 Bug (medium): The event argument in create_review is not validated. If the value is not 'APPROVE', 'REQUEST_CHANGES', or 'COMMENT', it could cause unexpected behavior or errors in the GitHub API.

src/utils/pr_description_generator.py

  • 🐛 Bug (medium): The changes field in the FileChange dataclass is calculated as additions + deletions. However, additions and deletions are integers, so changes will also be an integer. There is no guarantee that changes is always used as an integer.- 🐛 Bug (medium): The diff is truncated to 2000 characters. This could lead to incomplete analysis by the LLM, especially for large diffs, and a misleading explanation.- 🐛 Bug (low): The response from the LLM might be None. Calling .strip() on a None value will raise an AttributeError.

Generated by Multi-Agent Code Review System

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

PR descriptions

Modified

  • .gitignore (+0/-0)
  • src/api/webhooks.py (+72/-130)

    The code now only processes PR review webhooks for "opened", "synchronize", and "reopened" actions, ignoring other actions. This change likely aims to reduce unnecessary processing and API calls by focusing only on PR events that require description generation.

  • src/github/client.py (+19/-4)

    The update_pr_body function was changed from using a generic _api_put method to using a session.patch request with explicit error handling and logging. This likely improves reliability and provides better debugging information when updating the PR body fails, and ensures the correct HTTP method is used.

Added

  • .chroma_db/chroma.sqlite3 (0 lines)

    New file with 0 lines

  • PROJECT_STRUCTURE_GUIDE.md (478 lines)

    New file with 478 lines

  • src/utils/pr_description_generator.py (404 lines)

    New file with 404 lines

Removed

No files removed

Summary

  • 6 files changed
  • +973 additions
  • -134 deletions

- Filter files for 'synchronize' action to exclude removed files (only show added/modified/renamed)
- Skip removed files to show only active changes
- Add check to skip generating comment if no changes to describe
- Each push now posts a comment showing only the new/modified files from that push
- 'opened' and 'reopened' actions still show all files (first submission)
The previous change was too aggressive. We're back to analyzing all files
in the PR, but the comment header still shows 'Latest push' context for
synchronize actions. This ensures comments are posted for all PR events.
@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

Latest push

PR descriptions

Modified

  • .gitignore (+0/-0)
  • src/api/webhooks.py (+78/-130)

    The diff changes the PR review processing logic to only proceed for "opened", "synchronize", and "reopened" actions. It also removes the rate limit check and initialization of the orchestrator, instead directly fetching PR details using a GitHub client. This likely simplifies the webhook processing and prioritizes immediate PR analysis over complex orchestration or rate limit concerns.

  • src/github/client.py (+19/-4)

    The code now uses session.patch instead of _api_put to update the PR body, likely to align with GitHub's API requirements for updating specific fields of a pull request. The change also adds more robust error handling and logging, including debugging the token used and the API response.

Added

  • .chroma_db/chroma.sqlite3 (0 lines)

    New file with 0 lines

  • PROJECT_STRUCTURE_GUIDE.md (478 lines)

    New file with 478 lines

  • src/utils/pr_description_generator.py (404 lines)

    New file with 404 lines

Removed

No files removed

Summary

  • 6 files changed
  • +979 additions
  • -134 deletions

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

Latest push

PR descriptions

Modified

  • .gitignore (+0/-0)
  • README.md (+2/-0)

    The diff adds a feature where the bot automatically posts a comment summarizing file changes in a PR. This likely aims to provide a quick overview of the PR's content directly within the PR interface.

  • src/api/webhooks.py (+78/-130)

    The diff modifies the process_pr_review function to only proceed if the PR action is "opened", "synchronize", or "reopened". It also removes the rate limit check and simplifies the initial setup, likely to streamline the review process and rely on other rate limiting mechanisms.

  • src/github/client.py (+19/-4)

    The update_pr_body function was changed from using a generic _api_put method to a more explicit session.patch call, including detailed logging and error handling. This was likely done to improve debugging and resilience when updating the PR body, providing more context in case of failures and ensuring the correct HTTP method is used.

Added

  • .chroma_db/chroma.sqlite3 (0 lines)

    New file with 0 lines

  • PROJECT_STRUCTURE_GUIDE.md (478 lines)

    New file with 478 lines

  • src/utils/pr_description_generator.py (404 lines)

    New file with 404 lines

Removed

No files removed

Summary

  • 7 files changed
  • +981 additions
  • -134 deletions

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

/inspectai_review

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

/inspectai_bugs

Copy link
Copy Markdown

@comse6998-inspectai comse6998-inspectai Bot left a comment

Choose a reason for hiding this comment

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

🐛 InspectAI Bug Detection

Triggered by: @Yeshitha-co
Files Scanned: 3
Issues Found: 7

🟠 High: 1 | 🟡 Medium: 5 | ⚪ Low: 1

I've added 7 inline comments on issues introduced by your changes.

Comment thread src/api/webhooks.py

logger.info(f"Processing PR review for {repo_full_name}#{pr_number} (action: {action})")

# Only process on PR open/push/reopen
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 Missing Input Validation (high): The code directly uses the environment variable GITHUB_TOKEN without any validation. If this variable is not set or is invalid, the GitHubClient initialization will likely fail, leading to a crash or unexpected behavior.
Fix: Add a check to ensure the GITHUB_TOKEN environment variable is set and potentially validate its format before initializing GitHubClient. Provide a default behavior or error message if the variable is missing or invalid.

    logger.info(f"Processing PR review for {repo_full_name}#{pr_number} (action: {action})")
    
    # Only process on PR open/push/reopen
    if action not in ["opened", "synchronize", "reopened"]:
        return {

Comment thread src/api/webhooks.py
"bytez": BYTEZ_MODEL,
"openai": OPENAI_MODEL
}
# Prepare FileChange objects with LLM-powered explanations
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Null/None Handling (medium): The code accesses pr_file.patch without checking if pr_file or pr_file.patch is None. If pr_file.patch is None, it will cause an error when analyze_diff_with_llm is called.
Fix: Add a check to ensure pr_file.patch is not None before calling analyze_diff_with_llm.

        from src.utils.pr_description_generator import PRDescriptionGenerator, FileChange, analyze_diff_with_llm
        
        # Prepare FileChange objects with LLM-powered explanations
        files_changed = []
        for pr_file in pr.files:

Comment thread src/github/client.py
@@ -904,11 +904,26 @@ def update_pr_body(self, repo_url: str, pr_number: int, body: str) -> Dict[str,
owner, repo = self._parse_repo_url(repo_url)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Runtime Issue (medium): The code now uses session.patch to update the PR body, which is correct. However, the original code used _api_put which includes retry logic for rate limits and connection errors. The new code using session.patch does not have this retry logic. This means that if a rate limit or connection error occurs during the session.patch call, the update will fail without any retries, potentially leading to unexpected behavior.
Fix: Reintroduce the retry logic from _api_put into the update_pr_body function, or call _api_put instead of directly using session.patch.

            Updated PR data
        """
        owner, repo = self._parse_repo_url(repo_url)
        
        logger.info(f"Updating PR description for {owner}/{repo}#{pr_number}")

Comment thread src/github/client.py
logger.debug(f"Response headers: {dict(response.headers)}")
response.raise_for_status()
logger.info(f"Successfully updated PR description")
return response.json()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Exception Handling (medium): The code catches all exceptions without specific filtering. This might mask unexpected errors. It's better to catch specific exceptions like requests.exceptions.RequestException.
Fix: Change except Exception as e: to except requests.exceptions.RequestException as e: to catch only request-related exceptions.

            response.raise_for_status()
            logger.info(f"Successfully updated PR description")
            return response.json()
        except Exception as e:
            logger.error(f"Failed to update PR: {e}")

Comment thread src/github/client.py
except Exception as e:
logger.error(f"Failed to update PR: {e}")
if 'response' in locals():
logger.error(f"Response status: {response.status_code}")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Redundant conditional (low): The code checks if response is in locals before accessing its attributes in the exception handler. However, response is assigned within the try block, so it will always be defined if an exception occurs within that block. The check is therefore redundant.
Fix: Remove the if 'response' in locals(): check.

            logger.error(f"Failed to update PR: {e}")
            if 'response' in locals():
                logger.error(f"Response status: {response.status_code}")
                logger.error(f"Response text: {response.text}")
                logger.error(f"Response headers: {dict(response.headers)}")

"""
Analyze a code diff using LLM to generate human-readable explanation.

Args:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Input Validation (medium): The analyze_diff_with_llm function truncates the diff to 2000 characters using diff[:2000]. If the diff is very long, the LLM might not get enough context to provide a meaningful explanation. There is no check to see if the diff was truncated, and no indication to the user that the explanation might be incomplete.
Fix: Add a check to see if the diff was truncated. If it was, add a message to the prompt to inform the LLM that the diff is incomplete. Alternatively, consider using a more sophisticated method for summarizing long diffs.

    Analyze a code diff using LLM to generate human-readable explanation.
    
    Args:
        filename: The name of the changed file
        diff: The git diff content

"""
if not diff or not diff.strip():
return "No diff available"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Exception Handling (medium): The analyze_diff_with_llm function imports get_llm_client inside the try block. If the import fails, the except block will catch the ImportError and return a generic error message. This could mask the underlying import error and make it difficult to debug.
Fix: Move the import statement outside the try block to ensure that import errors are not masked.

    if not diff or not diff.strip():
        return "No diff available"
    
    try:
        # Import here to avoid circular imports

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

/inspectai_refactor

@comse6998-inspectai
Copy link
Copy Markdown

🔍 InspectAI Code Review

Triggered by: @Yeshitha-co
Files Reviewed: 3

No issues found in the changed lines!

The diff looks good. Only changed lines were reviewed.


Use /inspectai_bugs to do a deeper scan of entire files.

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

/inspectai_security

@comse6998-inspectai
Copy link
Copy Markdown

🔒 InspectAI Security Scan

Triggered by: @Yeshitha-co
Files Scanned: 3
Vulnerabilities Found: 0
Risk Score: 🟢 0.0/10

✅ No security vulnerabilities found in changed code!


Use /inspectai_review for code review or /inspectai_bugs for bug detection.

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

/inspectai_tests

@comse6998-inspectai
Copy link
Copy Markdown

🧪 InspectAI Test Generation

Triggered by: @Yeshitha-co
Files Processed: 1
Test Files Generated: 1

⏭️ Skipped 2 large file(s) (>500 lines):

  • src/api/webhooks.py - File too large (2720 lines)
  • src/github/client.py - File too large (942 lines)

Generated Tests

ℹ️ Tests generated for changed code only (not entire files)

📝 test_pr_description_generator.py (for src/utils/pr_description_generator.py)
import pytest
from typing import List
from pr_description_generator import PRDescriptionGenerator, FileChange


@pytest.fixture
def pr_generator():
    """Fixture to create an instance of PRDescriptionGenerator."""
    return PRDescriptionGenerator()


class TestPRDescriptionGenerator:
    """Tests for the PRDescriptionGenerator class."""

    def test_generate_overview_with_key_files(self, pr_generator: PRDescriptionGenerator):
        """Test generating overview with key files."""
        pr_title = "Fix bug in user authentication"
        key_files = ["src/auth.py", "src/user.py"]
        files_changed = [
            FileChange(filename="src/auth.py", status="modified", additions=10, deletions=2, changes=8),
            FileChange(filename="src/user.py", status="modified", additions=5, deletions=1, changes=4),
        ]
        overview = pr_generator._generate_overview(pr_title, key_files, files_changed)
        assert pr_title in overview
        assert "This PR updates `src/auth.py`, `src/user.py`" in overview
        assert "modifying 2 files" in overview

    def test_generate_overview_without_key_files(self, pr_generator: PRDescriptionGenerator):
        """Test generating overview without key files."""
        pr_title = "Update documentation"
        key_files: List[str] = []
        files_changed = [
            FileChange(filename="docs/README.md", status="modified", additions=3, deletions=0, changes=3),
            FileChange(filename="docs/usage.md", status="modified", additions=2, deletions=1, changes=1),
        ]
        overview = pr_generator._generate_overview(pr_title, key_files, files_changed)
        assert pr_title in overview
        assert "This PR makes updates to the codebase" in overview
        assert "modifying 2 files" in overview

    def test_generate_overview_with_added_and_removed_files(self, pr_generator: PRDescriptionGenerator):
        """Test generating overview with added and removed files."""
        pr_title = "Refactor codebase"
        key_files = ["src/main.py"]
        files_changed = [
            FileChange(filename="src/main.py", status="modified", additions=20, deletions=5, changes=15),
            FileChange(filename="src/new_module.py", status="added", additions=15, deletions=0, changes=15),
            FileChange(filename="src/old_module.py", status="removed", additions=0, deletions=10, changes=-10),
        ]
        overview = pr_generator._generate_overview(pr_title, key_files, files_changed)
        assert pr_title in overview
        assert "This PR updates `src/main.py`" in overview
        assert "modifying 1 file, adding 1 new file, removing 1 file" in overview

    def test_generate_overview_with_no_changes(self, pr_generator: PRDescriptionGenerator):
        """Test generating overview with no file changes."""
        pr_title = "Initial commit"
        key_files: List[str] = []
        files_changed: List[FileChange] = []
        overview = pr_generator._generate_overview(pr_title

... truncated (full file is 3914 chars)


Tests generated for changed code only. Copy to your test directory and run pytest.

@Yeshitha-co
Copy link
Copy Markdown
Collaborator Author

/inspectai_docs

@comse6998-inspectai
Copy link
Copy Markdown

📚 InspectAI Documentation Generator

Triggered by: @Yeshitha-co
Files Processed: 3
Files with New Documentation: 3

Updated Files with Docstrings

📝 src/api/webhooks.py
"""GitHub Webhook Handler for automatic PR reviews.

This module handles incoming webhook events from GitHub, specifically:
- Pull Request opened/synchronized events
- Issue comments (for /review command)
- Push events (optional)

Commands:
- /inspectai_review: Reviews ONLY the changed lines in the PR diff
- /inspectai_bugs: Finds bugs in WHOLE files that have changes
- /inspectai_refactor: Code improvement suggestions (style, performance, etc.)
- /inspectai_security: Security vulnerability scan using 4 specialized sub-agents
- /inspectai_tests: Generate unit tests for changed code
- /inspectai_docs: Generate/update documentation for changed code

Setup:
1. Create a GitHub App at https://github.com/settings/apps
2. Set the Webhook URL to: https://your-domain.com/webhook/github
3. Set a Webhook Secret and add it to your .env as GITHUB_WEBHOOK_SECRET
4. Subscribe to events: Pull requests, Issue comments, Push
5. Install the app on your repository

Required Environment Variables:
- GITHUB_WEBHOOK_SECRET: Secret for verifying webhook signatures
- GITHUB_TOKEN: Token for API calls (from GitHub App installation)
"""
import hashlib
import hmac
import json
import os
import re
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple

from fastapi import APIRouter, Request, HTTPException, BackgroundTasks
from pydantic import BaseModel

from ..utils.logger import get_logger
from ..github.client import GitHubClient
from ..memory.pr_memory import get_pr_memory, BugFinding
from ..utils.error_handler import (
    format_error_for_github_comment,
    format_partial_success_for_github_comment,
    GracefulErrorHandler
)
from ..feedback.feedback_system import get_feedback_system
from ..indexer import trigger_repo_indexing, get_context_enricher, get_scheduled_reindexer

logger = get_logger(__name__)

router = APIRouter(prefix="/webhook", tags=["webhooks"])

# Store for tracking processed events (in production, use Redis/DB)
_processed_events: Dict[str, datetime] = {}


class WebhookEvent(BaseModel):
    """Model for tracking webhook events."""
    event_type: str
    action: Optional[str]
    repository: str
    sender: str
    delivery_id: str
    timestamp: datetime


def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
    """Verify GitHub webhook signature.
    
    Args:
        payload: Raw request body.
        signature: X-Hub-Signature-256 header value.
        secret: Webhook secret from GitHub App settings.
        
    Returns:
        True if signature is valid, False otherwise.
    """
    if not signature or not secret:
        return False
    
    expected = "sha256=" + hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(expected, signature)


def is_duplicate_event(delivery_id: str) -> bool:
    """Check if we've already processed this event.
    
    GitHub may retry webhook delivery, so we track processed events.
    
    Args:
        delivery_id: The unique ID of the webhook delivery.
        
    Returns:
        True if the event has already been processed, False otherwise.
    """
    if delivery_id in _processed_events:
        return True
    
    # Clean old entries (older than 1 hour)
    cutoff = datetime.now()
    for key in list(_processed_events.keys()):
        if (cutoff - _processed_events[key]).seconds > 3600:
            del _processed_events[key]
    
    _processed_events[delivery_id] = datetime.now()
    return False


async def _check_contents_permission(github_client: GitHubClient, repo_full_name: str) -> bool:
    """Check if we have permission to read repository contents.
    
    This is needed for codebase indexing. If not granted, we gracefully
    skip indexing and use default PR-only review behavior.
    
    Args:
        github_client: GitHub client instance.
        repo_full_name: Full repository name (owner/repo).
        
    Returns:
        True if we have contents:r

... truncated (full file is 26884 chars)

📝 src/github/client.py
```python
"""GitHub Integration for repository access and PR comments.

This module provides functionality to:
1. Clone/download repositories
2. Read files from repositories
3. Read Pull Request information
4. Post comments on Pull Requests
5. GitHub App authentication with Installation Tokens

Setup for GitHub App (Recommended):
1. Create a GitHub App at https://github.com/settings/apps
2. Set GITHUB_APP_ID environment variable
3. Set GITHUB_APP_PRIVATE_KEY environment variable (or path to .pem file)

Setup for Personal Access Token (Alternative):
1. Create a PAT at https://github.com/settings/tokens
2. Set GITHUB_TOKEN environment variable

Usage:
    from src.github.client import GitHubClient
    
    # Using GitHub App (for any installation)
    client = GitHubClient.from_installation(installation_id=12345)
    
    # Using PAT (personal use)
    client = GitHubClient()
    
    # Clone a repo
    repo_path = client.clone_repo("owner/repo")
    
    # Get PR files
    files = client.get_pr_files("owner/repo", pr_number=123)
    
    # Post review comment
    client.post_review_comment("owner/repo", 123, "Great code!", "file.py", 10)
"""
import os
import re
import shutil
import tempfile
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, List, Optional
from urllib.parse import urlparse

import requests

from ..utils.logger import get_logger

logger = get_logger(__name__)


@dataclass
class PRFile:
    """Represents a file changed in a Pull Request.

    Attributes:
        filename (str): The name of the file.
        status (str): The status of the file change (e.g., 'added', 'modified', 'removed', 'renamed').
        additions (int): The number of lines added in the file.
        deletions (int): The number of lines deleted in the file.
        patch (Optional[str]): The diff patch for the file, if available.
        content (Optional[str]): The full content of the file, if available.
    """
    filename: str
    status: str  # added, modified, removed, renamed
    additions: int
    deletions: int
    patch: Optional[str]  # The diff
    content: Optional[str] = None  # Full file content


@dataclass  
class PullRequest:
    """Represents a Pull Request.

    Attributes:
        number (int): The pull request number.
        title (str): The title of the pull request.
        body (str): The body/description of the pull request.
        state (str): The state of the pull request (e.g., 'open', 'closed').
        head_sha (str): The SHA of the head commit.
        base_sha (str): The SHA of the base commit.
        head_branch (str): The name of the head branch.
        base_branch (str): The name of the base branch.
        files (List[PRFile]): A list of PRFile objects representing the files changed in the pull request.
        author (str): The username of the pull request author.
        url (str): The URL of the pull request.
    """
    number: int
    title: str
    body: str
    state: str  # open, closed
    head_sha: str
    base_sha: str
    head_branch: str
    base_branch: str
    files: List[PRFile]
    author: str
    url: str


def get_jwt_token(app_id: str, private_key: str) -> str:
    """Generate a JWT token for GitHub App authentication.
    
    Args:
        app_id (str): GitHub App ID.
        private_key (str): GitHub App private key (PEM format).
        
    Returns:
        str: JWT token string.

    Raises:
        ImportError: If the `jwt` library is not installed.
    """
    try:
        import jwt
    except ImportError:
        raise ImportError("PyJWT is required for GitHub App auth. Install with: pip install PyJWT")
    
    now = int(time.time())
    payload = {
        "iat": now - 60,  # Issued 60 seconds ago (clock drift)
        "exp": now + (10 * 60),  # Expires in 10 minutes
        "iss": app_id
    }
    
    return jwt.encode(payload, private_key, algorithm="RS256")


def get_installation_token(app_id: str, private_key: str, in

... truncated (full file is 31682 chars)

📝 src/utils/pr_description_generator.py
"""
PR Description Generator - Automatically generates GitHub PR descriptions.

Generates human-readable summaries of PR changes in the style of GitHub Copilot AI,
with LLM-powered analysis to explain the logical changes.

Features:
- What changed (files modified, added, removed)
- Why it changed (LLM analyzes diffs to explain logic changes)
- Key statistics (additions, deletions, files touched)
- Human-readable explanations of each file's changes
- Clear formatting similar to GitHub's PR review style
"""

from typing import List, Dict, Any, Optional
import re
from dataclasses import dataclass
import logging

logger = logging.getLogger(__name__)


@dataclass
class FileChange:
    """Represents a file change in the PR.

    Attributes:
        filename (str): The name of the file.
        status (str): The status of the file change ("added", "modified", "removed").
        additions (int): The number of lines added.
        deletions (int): The number of lines deleted.
        changes (int): The total number of changes (additions + deletions).
        diff (Optional[str]): The actual diff content for LLM analysis. Defaults to None.
        explanation (Optional[str]): LLM-generated explanation of the changes. Defaults to None.
    """
    filename: str
    status: str  # "added", "modified", "removed"
    additions: int
    deletions: int
    changes: int
    diff: Optional[str] = None  # The actual diff content for LLM analysis
    explanation: Optional[str] = None  # LLM-generated explanation


def analyze_diff_with_llm(filename: str, diff: str, llm_client=None) -> str:
    """Analyze a code diff using LLM to generate human-readable explanation.

    Args:
        filename (str): The name of the changed file.
        diff (str): The git diff content.
        llm_client: Optional LLM client (uses Gemini by default).

    Returns:
        str: Human-readable explanation of the changes.

    Raises:
        Exception: If the LLM analysis fails.

    Examples:
        >>> analyze_diff_with_llm("my_file.py", "diff --git a/my_file.py b/my_file.py...")
        'This change introduces a new function to calculate...'
    """
    if not diff or not diff.strip():
        return "No diff available"

    try:
        # Import here to avoid circular imports
        from src.llm.factory import get_llm_client

        if llm_client is None:
            llm_client = get_llm_client()

        # Create prompt for diff analysis
        prompt = f"""Analyze this code diff and provide a brief, human-readable explanation (1-2 sentences max) of what changed and why.

File: {filename}

Diff:


Focus on:
- What functionality changed
- Any significant logic changes
- Why this change was likely made

Keep it concise and technical. Don't mention file stats."""

        # Call LLM using chat method (synchronous)
        messages = [{"role": "user", "content": prompt}]
        response = llm_client.chat(
            messages=messages,
            max_tokens=200,
            temperature=0.3
        )

        explanation = response.strip() if response else "Changes to this file"
        logger.info(f"[PR_DESC] LLM analysis for {filename}: {explanation[:100]}...")
        return explanation

    except Exception as e:
        logger.warning(f"[PR_DESC] LLM analysis failed for {filename}: {e}")
        return f"Modified {filename}"


class PRDescriptionGenerator:
    """Generates GitHub PR descriptions in Copilot AI style.

    The generator analyzes file changes, categorizes them, and uses LLM
    explanations to create a comprehensive PR description.
    """

    def __init__(self):
        """Initialize the PR description generator.

        Defines file categories for better summary generation.
        """
        self.file_categories = {
            "tests": [".test.py", ".spec.py", "test_", "_test.py", "tests/"],
            "docs": [".md", ".rst", ".txt", "docs/", "README", "CHANGELOG"],
            "config": ["config/", ".yml", ".yaml", ".json", ".tom

... truncated (full file is 16971 chars)


Review the generated docstrings and apply them to your codebase.

@hj2713
Copy link
Copy Markdown
Owner

hj2713 commented Dec 7, 2025

/inspectai_review

@comse6998-inspectai
Copy link
Copy Markdown

🔍 InspectAI Code Review

Triggered by: @hj2713
Files Reviewed: 3

No issues found in the changed lines!

The diff looks good. Only changed lines were reviewed.


Use /inspectai_bugs to do a deeper scan of entire files.

@hj2713
Copy link
Copy Markdown
Owner

hj2713 commented Dec 9, 2025

/inspectai_help

@comse6998-inspectai
Copy link
Copy Markdown

🤖 InspectAI Commands

Triggered by: @hj2713

Available Commands

Command Description
/inspectai_review Quick Review - Reviews ONLY the changed lines in your PR. Posts inline comments on issues introduced by your changes. Fast and focused.
/inspectai_bugs Deep Bug Scan - Analyzes entire files (not just diffs) for potential bugs, logic errors, and edge cases. More thorough but slower.
/inspectai_refactor Refactor Suggestions - Suggests code improvements for readability, performance, and maintainability.
/inspectai_security Security Audit - Scans for security vulnerabilities using 4 specialized sub-agents: Injection, Auth, Data Exposure, Dependencies.
/inspectai_tests Test Generation - Generates unit tests for your changed code.
/inspectai_docs Documentation - Generates/updates docstrings for changed Python files using Google-style format.
/inspectai_help Help - Shows this message.

Tips

  • 🚀 Start with /inspectai_review for quick feedback on your changes
  • 🐛 Use /inspectai_bugs when you want a deeper analysis of edge cases
  • 🔐 Run /inspectai_security before merging code that handles user input or authentication
  • Generate tests with /inspectai_tests to improve coverage

Feedback

React with 👍 or 👎 on any InspectAI comment to help improve future reviews!


InspectAI - Your AI Code Review Assistant

@hj2713
Copy link
Copy Markdown
Owner

hj2713 commented Dec 9, 2025

/inspectai_review

@comse6998-inspectai
Copy link
Copy Markdown

🔍 InspectAI Code Review

Triggered by: @hj2713
Files Reviewed: 3

No issues found in the changed lines!

The diff looks good. Only changed lines were reviewed.


Use /inspectai_bugs to do a deeper scan of entire files.

@hj2713
Copy link
Copy Markdown
Owner

hj2713 commented Dec 9, 2025

/inspectai_bugs

Copy link
Copy Markdown

@comse6998-inspectai comse6998-inspectai Bot left a comment

Choose a reason for hiding this comment

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

🐛 InspectAI Bug Detection

Triggered by: @hj2713
Files Scanned: 3
Issues Found: 6

🟠 High: 1 | 🟡 Medium: 4 | ⚪ Low: 1

I've added 6 inline comments on issues introduced by your changes.

Comment thread src/api/webhooks.py

logger.info(f"Processing PR review for {repo_full_name}#{pr_number} (action: {action})")

# Only process on PR open/push/reopen
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 Missing Input Validation (high): The code directly uses the environment variable GITHUB_TOKEN without any validation. If this variable is not set or contains an invalid token, the GitHubClient initialization will likely fail, leading to runtime errors.
Fix: Add a check to ensure GITHUB_TOKEN is set and potentially validate its format before initializing GitHubClient. Provide a fallback mechanism or error message if the token is missing or invalid.

    logger.info(f"Processing PR review for {repo_full_name}#{pr_number} (action: {action})")
    
    # Only process on PR open/push/reopen
    if action not in ["opened", "synchronize", "reopened"]:
        return {

Comment thread src/api/webhooks.py
"bytez": BYTEZ_MODEL,
"openai": OPENAI_MODEL
}
# Prepare FileChange objects with LLM-powered explanations
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Null/None Handling (medium): The code accesses pr_file.patch without checking if pr_file or pr_file.patch is None. If pr_file.patch is None, it could lead to an AttributeError when trying to access its properties or methods later in the code.
Fix: Add a check to ensure pr_file and pr_file.patch are not None before accessing pr_file.patch. Provide a fallback mechanism or skip the analysis if pr_file.patch is None.

        from src.utils.pr_description_generator import PRDescriptionGenerator, FileChange, analyze_diff_with_llm
        
        # Prepare FileChange objects with LLM-powered explanations
        files_changed = []
        for pr_file in pr.files:

Comment thread src/api/webhooks.py
deletions=pr_file.deletions,
changes=pr_file.additions + pr_file.deletions,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Type Mismatch/Implicit Conversion (medium): The code calculates changes by adding pr_file.additions and pr_file.deletions. If these values are strings instead of numbers, the addition will result in string concatenation instead of numerical addition, leading to incorrect changes value.
Fix: Ensure that pr_file.additions and pr_file.deletions are converted to integers before performing the addition. Use int(pr_file.additions) and int(pr_file.deletions) to ensure correct calculation.

                changes=pr_file.additions + pr_file.deletions,
            )
            
            # Get LLM explanation for the diff (if available)
            if pr_file.patch and pr_file.status == "modified":

Comment thread src/github/client.py
except Exception as e:
logger.error(f"Failed to update PR: {e}")
if 'response' in locals():
logger.error(f"Response status: {response.status_code}")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Redundant Conditional (low): The code checks if 'response' in locals(): before accessing response.status_code, response.text, and response.headers. However, if an exception occurs within the try block, response will not be defined in the local scope, and the except block will be executed. The response variable is only assigned within the try block. Therefore, the 'response' in locals() check is redundant because if response is not defined, the code will not reach that point due to the exception.
Fix: Remove the if 'response' in locals(): check.

            logger.error(f"Failed to update PR: {e}")
            if 'response' in locals():
                logger.error(f"Response status: {response.status_code}")
                logger.error(f"Response text: {response.text}")
                logger.error(f"Response headers: {dict(response.headers)}")

"""
Analyze a code diff using LLM to generate human-readable explanation.

Args:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Input Validation (medium): The analyze_diff_with_llm function does not validate the filename argument. A malicious filename could potentially be used in the prompt, leading to prompt injection vulnerabilities.
Fix: Sanitize or validate the filename argument to prevent prompt injection.

    Analyze a code diff using LLM to generate human-readable explanation.
    
    Args:
        filename: The name of the changed file
        diff: The git diff content

response = llm_client.chat(
messages=messages,
max_tokens=200,
temperature=0.3
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 String Handling (medium): The analyze_diff_with_llm function truncates the diff to 2000 characters using diff[:2000]. If the diff contains multi-byte characters, this truncation could split a character, leading to encoding issues or unexpected behavior in the LLM analysis.
Fix: Consider using a more robust method for truncating the diff that handles multi-byte characters correctly.

            messages=messages,
            max_tokens=200,
            temperature=0.3
        )
        

@hj2713
Copy link
Copy Markdown
Owner

hj2713 commented Dec 9, 2025

/inspecai_security

@hj2713
Copy link
Copy Markdown
Owner

hj2713 commented Dec 9, 2025

/inspectai_security

@comse6998-inspectai
Copy link
Copy Markdown

🔒 InspectAI Security Scan

Triggered by: @hj2713
Files Scanned: 3
Vulnerabilities Found: 0
Risk Score: 🟢 0.0/10

✅ No security vulnerabilities found in changed code!


Use /inspectai_review for code review or /inspectai_bugs for bug detection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants