Merge pull request 'hotfix/workflow-duplication' (#21) from hotfix/workflow-duplication into main
All checks were successful
Enterprise AI Code Review / ai-review (pull_request) Successful in 32s

Reviewed-on: #21
This commit was merged in pull request #21.
This commit is contained in:
2025-12-29 10:34:48 +00:00
9 changed files with 719 additions and 44 deletions

View File

@@ -1,17 +1,32 @@
name: AI Chat (Bartender)
# WORKFLOW ROUTING:
# This workflow handles FREE-FORM questions/chat (no specific command)
# Other workflows: ai-issue-triage.yml (@codebot triage), ai-comment-reply.yml (specific commands)
# This is the FALLBACK for any @codebot mention that isn't a known command
on:
issue_comment:
types: [created]
# CUSTOMIZE YOUR BOT NAME:
# Change '@ai-bot' below to match your config.yml mention_prefix
# Change '@codebot' in all conditions below to match your config.yml mention_prefix
# Examples: '@bartender', '@uni', '@joey', '@codebot'
jobs:
ai-chat:
# Only run if comment mentions the bot
if: contains(github.event.comment.body, '@codebot') # <-- Change this to your bot name
# Only run if comment mentions the bot but NOT a specific command
# This prevents duplicate runs with ai-comment-reply.yml and ai-issue-triage.yml
if: |
contains(github.event.comment.body, '@codebot') &&
!contains(github.event.comment.body, '@codebot triage') &&
!contains(github.event.comment.body, '@codebot help') &&
!contains(github.event.comment.body, '@codebot explain') &&
!contains(github.event.comment.body, '@codebot suggest') &&
!contains(github.event.comment.body, '@codebot security') &&
!contains(github.event.comment.body, '@codebot summarize') &&
!contains(github.event.comment.body, '@codebot review-again') &&
!contains(github.event.comment.body, '@codebot setup-labels')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

View File

@@ -1,17 +1,30 @@
name: AI Comment Reply
# WORKFLOW ROUTING:
# This workflow handles SPECIFIC commands: help, explain, suggest, security, summarize, review-again, setup-labels
# Other workflows: ai-issue-triage.yml (@codebot triage), ai-chat.yml (free-form questions)
on:
issue_comment:
types: [created]
# CUSTOMIZE YOUR BOT NAME:
# Change '@ai-bot' below to match your config.yml mention_prefix
# Change '@codebot' in the 'if' condition below to match your config.yml mention_prefix
# Examples: '@bartender', '@uni', '@joey', '@codebot'
jobs:
ai-reply:
runs-on: ubuntu-latest
if: contains(github.event.comment.body, '@codebot') # <-- Change this to your bot name
# Only run for specific commands (not free-form chat or triage)
# This prevents duplicate runs with ai-chat.yml and ai-issue-triage.yml
if: |
(contains(github.event.comment.body, '@codebot help') ||
contains(github.event.comment.body, '@codebot explain') ||
contains(github.event.comment.body, '@codebot suggest') ||
contains(github.event.comment.body, '@codebot security') ||
contains(github.event.comment.body, '@codebot summarize') ||
contains(github.event.comment.body, '@codebot review-again') ||
contains(github.event.comment.body, '@codebot setup-labels'))
steps:
- uses: actions/checkout@v4

View File

@@ -1,5 +1,9 @@
name: AI Issue Triage
# WORKFLOW ROUTING:
# This workflow handles ONLY the 'triage' command
# Other workflows: ai-comment-reply.yml (specific commands), ai-chat.yml (free-form questions)
on:
issue_comment:
types: [created]

View File

@@ -186,14 +186,21 @@ Optional:
## Workflow Architecture
Workflows are located in `.gitea/workflows/`:
Workflows are located in `.gitea/workflows/` and are **mutually exclusive** to prevent duplicate runs:
- **ai-review.yml** / **enterprise-ai-review.yml** - Triggered on PR open/sync
- **ai-issue-triage.yml** - Triggered on `@codebot triage` mention in issue comments
- **ai-comment-reply.yml** - Triggered on issue comments with @bot mentions
- **ai-chat.yml** - Triggered on issue comments for chat (non-command mentions)
- **enterprise-ai-review.yml** - Triggered on PR open/sync
- **ai-issue-triage.yml** - Triggered ONLY on `@codebot triage` in comments
- **ai-comment-reply.yml** - Triggered on specific commands: `help`, `explain`, `suggest`, `security`, `summarize`, `review-again`, `setup-labels`
- **ai-chat.yml** - Triggered on `@codebot` mentions that are NOT specific commands (free-form questions)
- **ai-codebase-review.yml** - Scheduled weekly analysis
**Workflow Routing Logic:**
1. If comment contains `@codebot triage` → ai-issue-triage.yml only
2. If comment contains specific command (e.g., `@codebot help`) → ai-comment-reply.yml only
3. If comment contains `@codebot <question>` (no command) → ai-chat.yml only
This prevents the issue where all three workflows would trigger on every `@codebot` mention, causing massive duplication.
**Note**: Issue triage is now **opt-in** via `@codebot triage` command, not automatic on issue creation.
Key workflow pattern:
@@ -208,6 +215,7 @@ Key workflow pattern:
Prompts are stored in `tools/ai-review/prompts/` as Markdown files:
- `base.md` - Base instructions for all reviews
- `pr_summary.md` - PR summary generation template
- `issue_triage.md` - Issue classification template
- `issue_response.md` - Issue response template
@@ -407,6 +415,59 @@ pytest tests/test_ai_review.py::TestSecurityScanner -v
## Common Development Tasks
### PR Summary Generation
The PR summary feature automatically generates comprehensive summaries for pull requests.
**Key Features:**
- Auto-generates summary for PRs with empty descriptions
- Can be manually triggered with `@codebot summarize` in PR comments
- Analyzes diff to extract key changes, files affected, and impact
- Categorizes change type (Feature/Bugfix/Refactor/Documentation/Testing)
- Posts as comment or updates PR description (configurable)
**Implementation Details:**
1. **Auto-Summary on PR Open** - `PRAgent.execute()`:
- Checks if PR body is empty and `auto_summary.enabled` is true
- Calls `_generate_pr_summary()` automatically
- Continues with normal PR review after posting summary
2. **Manual Trigger** - `@codebot summarize` in PR comments:
- `PRAgent.can_handle()` detects `summarize` command in PR comments
- Routes to `_handle_summarize_command()`
- Generates and posts summary on demand
3. **Summary Generation** - `_generate_pr_summary()`:
- Fetches PR diff using `_get_diff()`
- Loads `prompts/pr_summary.md` template
- Calls LLM with diff to analyze changes
- Returns structured JSON with summary data
- Formats using `_format_pr_summary()`
- Posts as comment or updates description based on config
4. **Configuration** - `config.yml`:
```yaml
agents:
pr:
auto_summary:
enabled: true # Auto-generate for empty PRs
post_as_comment: true # true = comment, false = update description
```
**Summary Structure:**
- Brief 2-3 sentence overview
- Change type categorization (Feature/Bugfix/Refactor/etc)
- Key changes (Added/Modified/Removed)
- Files affected with descriptions
- Impact assessment (scope: small/medium/large)
**Common Use Cases:**
- Developers who forget to write PR descriptions
- Quick understanding of complex changes
- Standardized documentation format
- Pre-review context for reviewers
### Review-Again Command Implementation
The `@codebot review-again` command allows manual re-triggering of PR reviews without new commits.
@@ -463,6 +524,7 @@ Example commands:
- `@codebot triage` - Full issue triage with labeling
- `@codebot explain` - Explain the issue
- `@codebot suggest` - Suggest solutions
- `@codebot summarize` - Generate PR summary or issue summary (works on both)
- `@codebot setup-labels` - Automatic label setup (built-in, not in config)
- `@codebot review-again` - Re-run PR review without new commits (PR comments only)

View File

@@ -9,9 +9,10 @@ Enterprise-grade AI code review system for **Gitea** with automated PR review, i
| Feature | Description |
|---------|-------------|
| **PR Review** | Inline comments, security scanning, severity-based CI failure |
| **PR Summaries** | Auto-generate comprehensive PR summaries with change analysis and impact assessment |
| **Issue Triage** | On-demand classification, labeling, priority assignment via `@codebot triage` |
| **Chat** | Interactive AI chat with codebase search and web search tools |
| **@codebot Commands** | `@codebot summarize`, `explain`, `suggest`, `triage` in issue comments |
| **@codebot Commands** | `@codebot summarize`, `explain`, `suggest`, `triage`, `review-again` in comments |
| **Codebase Analysis** | Health scores, tech debt tracking, weekly reports |
| **Security Scanner** | 17 OWASP-aligned rules for vulnerability detection |
| **Enterprise Ready** | Audit logging, metrics, Prometheus export |
@@ -189,8 +190,51 @@ In any PR comment:
| Command | Description |
|---------|-------------|
| `@codebot summarize` | Generate a comprehensive PR summary with changes, files affected, and impact |
| `@codebot review-again` | Re-run AI code review on current PR state without new commits |
#### PR Summary (`@codebot summarize`)
**Features:**
- 📋 Generates structured summary of PR changes
- ✨ Categorizes change type (Feature/Bugfix/Refactor/Documentation/Testing)
- 📝 Lists what was added, modified, and removed
- 📁 Shows all affected files with descriptions
- 🎯 Assesses impact scope (small/medium/large)
- 🤖 Automatically generates on PRs with empty descriptions
**When to use:**
- When a PR lacks a description
- To quickly understand what changed
- For standardized PR documentation
- Before reviewing complex PRs
**Example output:**
```markdown
## 📋 Pull Request Summary
This PR implements automatic PR summary generation...
**Type:** ✨ Feature
## Changes
✅ Added:
- PR summary generation in PRAgent
- Auto-summary for empty PR descriptions
📝 Modified:
- Updated config.yml with new settings
## Files Affected
- `tools/ai-review/prompts/pr_summary.md` - New prompt template
- 📝 `tools/ai-review/agents/pr_agent.py` - Added summary methods
## Impact
🟡 **Scope:** Medium
Adds new feature without affecting existing functionality
```
#### Review Again (`@codebot review-again`)
**Features:**
- ✅ Shows diff from previous review (resolved/new/changed issues)
- 🏷️ Updates labels based on new severity

View File

@@ -588,5 +588,242 @@ class TestLabelSetup:
assert "Kind/Bug" in config["aliases"]
class TestPRSummaryGeneration:
"""Test PR summary generation functionality."""
def test_pr_summary_prompt_exists(self):
"""Verify pr_summary.md prompt file exists."""
prompt_path = os.path.join(
os.path.dirname(__file__),
"..",
"tools",
"ai-review",
"prompts",
"pr_summary.md",
)
assert os.path.exists(prompt_path), "pr_summary.md prompt file not found"
def test_pr_summary_prompt_formatting(self):
"""Test that pr_summary.md can be loaded without errors."""
prompt_path = os.path.join(
os.path.dirname(__file__),
"..",
"tools",
"ai-review",
"prompts",
"pr_summary.md",
)
with open(prompt_path) as f:
prompt = f.read()
# Check for key elements
assert "summary" in prompt.lower()
assert "change_type" in prompt.lower()
assert "files_affected" in prompt.lower()
assert "impact" in prompt.lower()
assert "JSON" in prompt
# Verify JSON examples use double curly braces (escaped)
# Should not raise KeyError when formatted with empty string
try:
formatted = prompt.format()
except KeyError as e:
pytest.fail(f"Prompt has unescaped placeholders: {e}")
def test_pr_agent_has_summary_marker(self):
"""Verify PRAgent has PR_SUMMARY_MARKER constant."""
from agents.pr_agent import PRAgent
assert hasattr(PRAgent, "PR_SUMMARY_MARKER")
assert PRAgent.PR_SUMMARY_MARKER == "<!-- AI_PR_SUMMARY -->"
def test_pr_agent_can_handle_summarize_command(self):
"""Test that PRAgent can handle @codebot summarize in PR comments."""
from agents.pr_agent import PRAgent
config = {
"agents": {
"pr": {
"enabled": True,
}
},
"interaction": {
"mention_prefix": "@codebot",
},
}
agent = PRAgent(config=config)
# Test summarize command in PR comment
event_data = {
"action": "created",
"issue": {
"number": 123,
"pull_request": {}, # Indicates this is a PR
},
"comment": {
"body": "@codebot summarize this PR please",
},
}
assert agent.can_handle("issue_comment", event_data) is True
def test_pr_agent_can_handle_summarize_case_insensitive(self):
"""Test that summarize command is case-insensitive."""
from agents.pr_agent import PRAgent
config = {
"agents": {
"pr": {
"enabled": True,
}
},
"interaction": {
"mention_prefix": "@codebot",
},
}
agent = PRAgent(config=config)
# Test various casings
for body in [
"@codebot SUMMARIZE",
"@codebot Summarize",
"@codebot SuMmArIzE",
]:
event_data = {
"action": "created",
"issue": {
"number": 123,
"pull_request": {},
},
"comment": {"body": body},
}
assert agent.can_handle("issue_comment", event_data) is True
def test_pr_agent_ignores_summarize_on_non_pr(self):
"""Test that summarize command is ignored on regular issues."""
from agents.pr_agent import PRAgent
config = {
"agents": {
"pr": {
"enabled": True,
}
},
"interaction": {
"mention_prefix": "@codebot",
},
}
agent = PRAgent(config=config)
# Regular issue (no pull_request field)
event_data = {
"action": "created",
"issue": {
"number": 123,
# No pull_request field
},
"comment": {
"body": "@codebot summarize",
},
}
assert agent.can_handle("issue_comment", event_data) is False
def test_format_pr_summary_structure(self):
"""Test _format_pr_summary generates correct markdown structure."""
from agents.pr_agent import PRAgent
agent = PRAgent(config={})
summary_data = {
"summary": "This PR adds a new feature",
"change_type": "Feature",
"key_changes": {
"added": ["New authentication module", "User login endpoint"],
"modified": ["Updated config file"],
"removed": ["Deprecated legacy auth"],
},
"files_affected": [
{
"path": "src/auth.py",
"description": "New authentication module",
"change_type": "added",
},
{
"path": "config.yml",
"description": "Added auth settings",
"change_type": "modified",
},
],
"impact": {
"scope": "medium",
"description": "Adds authentication without breaking existing features",
},
}
result = agent._format_pr_summary(summary_data)
# Verify structure
assert "## 📋 Pull Request Summary" in result
assert "This PR adds a new feature" in result
assert "**Type:** ✨ Feature" in result
assert "## Changes" in result
assert "**✅ Added:**" in result
assert "**📝 Modified:**" in result
assert "**❌ Removed:**" in result
assert "## Files Affected" in result
assert " `src/auth.py`" in result
assert "📝 `config.yml`" in result
assert "## Impact" in result
assert "🟡 **Scope:** Medium" in result
def test_format_pr_summary_change_types(self):
"""Test that all change types have correct emojis."""
from agents.pr_agent import PRAgent
agent = PRAgent(config={})
change_types = {
"Feature": "",
"Bugfix": "🐛",
"Refactor": "♻️",
"Documentation": "📚",
"Testing": "🧪",
"Mixed": "🔀",
}
for change_type, expected_emoji in change_types.items():
summary_data = {
"summary": "Test",
"change_type": change_type,
"key_changes": {},
"files_affected": [],
"impact": {},
}
result = agent._format_pr_summary(summary_data)
assert f"**Type:** {expected_emoji} {change_type}" in result
def test_config_has_auto_summary_settings(self):
"""Verify config.yml has auto_summary configuration."""
config_path = os.path.join(
os.path.dirname(__file__), "..", "tools", "ai-review", "config.yml"
)
import yaml
with open(config_path) as f:
config = yaml.safe_load(f)
assert "agents" in config
assert "pr" in config["agents"]
assert "auto_summary" in config["agents"]["pr"]
assert "enabled" in config["agents"]["pr"]["auto_summary"]
assert "post_as_comment" in config["agents"]["pr"]["auto_summary"]
if __name__ == "__main__":
pytest.main([__file__, "-v"])

View File

@@ -39,6 +39,7 @@ class PRAgent(BaseAgent):
# Marker specific to PR reviews
PR_AI_MARKER = "<!-- AI_PR_REVIEW -->"
PR_SUMMARY_MARKER = "<!-- AI_PR_SUMMARY -->"
def _get_label_config(self, category: str, key: str) -> dict:
"""Get full label configuration from config.
@@ -83,7 +84,7 @@ class PRAgent(BaseAgent):
allowed_events = agent_config.get("events", ["opened", "synchronize"])
return action in allowed_events
# Handle issue comments on PRs (for review-again command)
# Handle issue comments on PRs (for review-again and summarize commands)
if event_type == "issue_comment":
action = event_data.get("action", "")
if action == "created":
@@ -91,20 +92,28 @@ class PRAgent(BaseAgent):
mention_prefix = self.config.get("interaction", {}).get(
"mention_prefix", "@codebot"
)
# Only handle if this is a PR and contains review-again command
# Only handle if this is a PR
issue = event_data.get("issue", {})
is_pr = issue.get("pull_request") is not None
has_review_again = (
f"{mention_prefix} review-again" in comment_body.lower()
)
return is_pr and has_review_again
has_summarize = f"{mention_prefix} summarize" in comment_body.lower()
return is_pr and (has_review_again or has_summarize)
return False
def execute(self, context: AgentContext) -> AgentResult:
"""Execute the PR review agent."""
# Check if this is a review-again command
# Check if this is a comment-based command
if context.event_type == "issue_comment":
comment_body = context.event_data.get("comment", {}).get("body", "")
mention_prefix = self.config.get("interaction", {}).get(
"mention_prefix", "@codebot"
)
if f"{mention_prefix} summarize" in comment_body.lower():
return self._handle_summarize_command(context)
elif f"{mention_prefix} review-again" in comment_body.lower():
return self._handle_review_again(context)
pr = context.event_data.get("pull_request", {})
@@ -114,6 +123,24 @@ class PRAgent(BaseAgent):
actions_taken = []
# Check if PR has empty description and auto-summary is enabled
pr_body = pr.get("body", "").strip()
agent_config = self.config.get("agents", {}).get("pr", {})
auto_summary_enabled = agent_config.get("auto_summary", {}).get("enabled", True)
if (
not pr_body
and auto_summary_enabled
and context.event_data.get("action") == "opened"
):
# Generate and post summary for empty PR descriptions
summary_result = self._generate_pr_summary(
context.owner, context.repo, pr_number
)
if summary_result:
actions_taken.append("Generated PR summary for empty description")
# Don't return here - continue with regular review
# Step 1: Get PR diff
diff = self._get_diff(context.owner, context.repo, pr_number)
if not diff.strip():
@@ -791,3 +818,206 @@ class PRAgent(BaseAgent):
self.logger.warning(f"Failed to add labels: {e}")
return []
def _generate_pr_summary(self, owner: str, repo: str, pr_number: int) -> bool:
"""Generate and post a summary for a PR.
Args:
owner: Repository owner
repo: Repository name
pr_number: PR number
Returns:
True if summary was generated successfully, False otherwise
"""
try:
# Get PR diff
diff = self._get_diff(owner, repo, pr_number)
if not diff.strip():
self.logger.info(f"No diff to summarize for PR #{pr_number}")
return False
# Load summary prompt
prompt_template = self.load_prompt("pr_summary")
prompt = f"{prompt_template}\n{diff}"
# Call LLM to generate summary
result = self.call_llm_json(prompt)
# Format the summary comment
summary_comment = self._format_pr_summary(result)
# Post as first comment (or update PR description based on config)
agent_config = self.config.get("agents", {}).get("pr", {})
auto_summary_config = agent_config.get("auto_summary", {})
post_as_comment = auto_summary_config.get("post_as_comment", True)
if post_as_comment:
# Post as comment
self.gitea.create_issue_comment(owner, repo, pr_number, summary_comment)
self.logger.info(f"Posted PR summary as comment for PR #{pr_number}")
else:
# Update PR description (requires different API call)
# Note: Gitea API may not support updating PR description
# In that case, fall back to posting as comment
try:
self.gitea.update_pull_request(
owner, repo, pr_number, body=summary_comment
)
self.logger.info(
f"Updated PR description with summary for PR #{pr_number}"
)
except Exception as e:
self.logger.warning(
f"Could not update PR description, posting as comment: {e}"
)
self.gitea.create_issue_comment(
owner, repo, pr_number, summary_comment
)
return True
except Exception as e:
self.logger.error(f"Failed to generate PR summary: {e}")
return False
def _format_pr_summary(self, summary_data: dict) -> str:
"""Format the PR summary data into a readable comment.
Args:
summary_data: JSON data from LLM containing summary information
Returns:
Formatted markdown comment
"""
lines = [
self.AI_DISCLAIMER,
"",
"## 📋 Pull Request Summary",
"",
summary_data.get("summary", "Summary unavailable"),
"",
]
# Change type
change_type = summary_data.get("change_type", "Unknown")
change_type_emoji = {
"Feature": "",
"Bugfix": "🐛",
"Refactor": "♻️",
"Documentation": "📚",
"Testing": "🧪",
"Mixed": "🔀",
}
emoji = change_type_emoji.get(change_type, "🔀")
lines.append(f"**Type:** {emoji} {change_type}")
lines.append("")
# Key changes
key_changes = summary_data.get("key_changes", {})
if key_changes:
lines.append("## Changes")
lines.append("")
added = key_changes.get("added", [])
if added:
lines.append("**✅ Added:**")
for item in added:
lines.append(f"- {item}")
lines.append("")
modified = key_changes.get("modified", [])
if modified:
lines.append("**📝 Modified:**")
for item in modified:
lines.append(f"- {item}")
lines.append("")
removed = key_changes.get("removed", [])
if removed:
lines.append("**❌ Removed:**")
for item in removed:
lines.append(f"- {item}")
lines.append("")
# Files affected
files = summary_data.get("files_affected", [])
if files:
lines.append("## Files Affected")
lines.append("")
for file_info in files[:10]: # Limit to first 10 files
path = file_info.get("path", "unknown")
desc = file_info.get("description", "")
change_type = file_info.get("change_type", "modified")
type_icon = {"added": "", "modified": "📝", "deleted": ""}
icon = type_icon.get(change_type, "📝")
lines.append(f"- {icon} `{path}` - {desc}")
if len(files) > 10:
lines.append(f"- ... and {len(files) - 10} more files")
lines.append("")
# Impact assessment
impact = summary_data.get("impact", {})
if impact:
scope = impact.get("scope", "unknown")
description = impact.get("description", "")
scope_emoji = {"small": "🟢", "medium": "🟡", "large": "🔴"}
emoji = scope_emoji.get(scope, "")
lines.append("## Impact")
lines.append(f"{emoji} **Scope:** {scope.capitalize()}")
lines.append(f"{description}")
return "\n".join(lines)
def _handle_summarize_command(self, context: AgentContext) -> AgentResult:
"""Handle @codebot summarize command from PR comments.
Args:
context: Agent context with event data
Returns:
AgentResult with success status and actions taken
"""
issue = context.event_data.get("issue", {})
pr_number = issue.get("number")
comment_author = (
context.event_data.get("comment", {}).get("user", {}).get("login", "user")
)
self.logger.info(f"Generating PR summary for PR #{pr_number} at user request")
# Generate and post summary
success = self._generate_pr_summary(context.owner, context.repo, pr_number)
if success:
return AgentResult(
success=True,
message=f"Generated PR summary for PR #{pr_number}",
actions_taken=["Posted PR summary comment"],
)
else:
# Post error message
error_msg = (
f"@{comment_author}\n\n"
f"{self.AI_DISCLAIMER}\n\n"
"**⚠️ Summary Generation Failed**\n\n"
"I was unable to generate a summary for this PR. "
"This could be because:\n"
"- The PR has no changes\n"
"- There was an error accessing the diff\n"
"- The LLM service is unavailable"
)
self.gitea.create_issue_comment(
context.owner, context.repo, pr_number, error_msg
)
return AgentResult(
success=False,
message=f"Failed to generate PR summary for PR #{pr_number}",
error="Summary generation failed",
)

View File

@@ -32,6 +32,9 @@ agents:
events:
- opened
- synchronize
auto_summary:
enabled: true # Auto-generate summary for PRs with empty descriptions
post_as_comment: true # true = post as comment, false = update PR description
codebase:
enabled: true
schedule: "0 0 * * 0" # Weekly on Sunday
@@ -63,7 +66,7 @@ interaction:
- explain
- suggest
- security
- summarize
- summarize # Generate PR summary (works on both issues and PRs)
- triage
- review-again

View File

@@ -0,0 +1,67 @@
You are an experienced senior software engineer analyzing a pull request diff to generate a comprehensive, informative summary.
Your goal is to create a **clear, structured summary** that helps reviewers quickly understand:
- What changes were made
- Why these changes matter
- Which files and components are affected
- The type of change (feature/bugfix/refactor/documentation)
---
## Requirements
Analyze the PR diff and generate a summary that includes:
1. **Brief Overview**: 2-3 sentence summary of the changes
2. **Key Changes**: Bullet points of the most important modifications
- What was added
- What was modified
- What was removed (if applicable)
3. **Files Affected**: List of changed files with brief descriptions
4. **Change Type**: Classify as Feature, Bugfix, Refactor, Documentation, Testing, or Mixed
5. **Impact Assessment**: Brief note on the scope and potential impact
---
## Output Format
Return a JSON object with this structure:
```json
{{{{
"summary": "Brief 2-3 sentence overview of what this PR accomplishes",
"change_type": "Feature" | "Bugfix" | "Refactor" | "Documentation" | "Testing" | "Mixed",
"key_changes": {{{{
"added": ["List of new features/files/functionality added"],
"modified": ["List of existing components that were changed"],
"removed": ["List of removed features/files (if any)"]
}}}},
"files_affected": [
{{{{
"path": "path/to/file.py",
"description": "Brief description of changes in this file",
"change_type": "added" | "modified" | "deleted"
}}}}
],
"impact": {{{{
"scope": "small" | "medium" | "large",
"description": "Brief assessment of the impact and scope of changes"
}}}}
}}}}
```
---
## Rules
1. **Be concise**: Keep descriptions clear and to the point
2. **Focus on intent**: Explain *what* and *why*, not just *how*
3. **Identify patterns**: Group related changes together
4. **Highlight significance**: Emphasize important architectural or behavioral changes
5. **Be objective**: Base analysis purely on the code changes
6. **Output only JSON**: No additional text before or after the JSON object
---
## Diff to Analyze