Files
Aperant/apps/backend/runners/github/validator_example.py
T
Andy 348de6dfe7 Feat/Auto Fix Github issues and do extensive AI PR reviews (#250)
* feat(github): add GitHub automation system for issues and PRs

Implements comprehensive GitHub automation with three major components:

1. Issue Auto-Fix: Automatically creates specs from labeled issues
   - AutoFixButton component with progress tracking
   - useAutoFix hook for config and queue management
   - Backend handlers for spec creation from issues

2. GitHub PRs Tool: AI-powered PR review sidebar
   - New sidebar tab (Cmd+Shift+P) alongside GitHub Issues
   - PRList/PRDetail components for viewing PRs
   - Review system with findings by severity
   - Post review comments to GitHub

3. Issue Triage: Duplicate/spam/feature-creep detection
   - Triage handlers with label application
   - Configurable detection thresholds

Also adds:
- Debug logging (DEBUG=true) for all GitHub handlers
- Backend runners/github module with orchestrator
- AI prompts for PR review, triage, duplicate/spam detection
- dev:debug npm script for development with logging

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(github-runner): resolve import errors for direct script execution

Changes runner.py and orchestrator.py to handle both:
- Package import: `from runners.github import ...`
- Direct script: `python runners/github/runner.py`

Uses try/except pattern for relative vs direct imports.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(github): correct argparse argument order for runner.py

Move --project global argument before subcommand so argparse can
correctly parse it. Fixes "unrecognized arguments: --project" error.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* logs when debug mode is on

* refactor(github): extract service layer and fix linting errors

Major refactoring to improve maintainability and code quality:

Backend (Python):
- Extracted orchestrator.py (2,600 → 835 lines, 68% reduction) into 7 service modules:
  - prompt_manager.py: Prompt template management
  - response_parsers.py: AI response parsing
  - pr_review_engine.py: PR review orchestration
  - triage_engine.py: Issue triage logic
  - autofix_processor.py: Auto-fix workflow
  - batch_processor.py: Batch issue handling
- Fixed 18 ruff linting errors (F401, C405, C414, E741):
  - Removed unused imports (BatchValidationResult, AuditAction, locked_json_write)
  - Optimized collection literals (set([n]) → {n})
  - Removed unnecessary list() calls
  - Renamed ambiguous variable 'l' to 'label' throughout

Frontend (TypeScript):
- Refactored IPC handlers (19% overall reduction) with shared utilities:
  - autofix-handlers.ts: 1,042 → 818 lines
  - pr-handlers.ts: 648 → 543 lines
  - triage-handlers.ts: 437 lines (no duplication)
- Created utils layer: logger, ipc-communicator, project-middleware, subprocess-runner
- Split github-store.ts into focused stores: issues, pr-review, investigation, sync-status
- Split ReviewFindings.tsx into focused components

All imports verified, type checks passing, linting clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 16:43:20 +01:00

215 lines
7.7 KiB
Python

"""
Example: Using the Output Validator in PR Review Workflow
=========================================================
This example demonstrates how to integrate the FindingValidator
into a PR review system to improve finding quality.
"""
from pathlib import Path
from models import PRReviewFinding, ReviewCategory, ReviewSeverity
from output_validator import FindingValidator
def example_pr_review_with_validation():
"""Example PR review workflow with validation."""
# Simulate changed files from a PR
changed_files = {
"src/auth.py": """import hashlib
def authenticate(username, password):
# Security issue: MD5 is broken
hashed = hashlib.md5(password.encode()).hexdigest()
return check_password(username, hashed)
def check_password(username, password_hash):
# Security issue: SQL injection
query = f"SELECT * FROM users WHERE name='{username}' AND pass='{password_hash}'"
return execute_query(query)
""",
"src/utils.py": """def process_items(items):
result = []
for item in items:
result.append(item * 2)
return result
""",
}
# Simulate AI-generated findings (including some false positives)
raw_findings = [
# Valid critical security finding
PRReviewFinding(
id="SEC001",
severity=ReviewSeverity.CRITICAL,
category=ReviewCategory.SECURITY,
title="SQL Injection Vulnerability in Authentication",
description="The check_password function constructs SQL queries using f-strings with unsanitized user input. This allows attackers to inject malicious SQL code through the username parameter, potentially compromising the entire database.",
file="src/auth.py",
line=10,
suggested_fix="Use parameterized queries: cursor.execute('SELECT * FROM users WHERE name=? AND pass=?', (username, password_hash))",
fixable=True,
),
# Valid high severity security finding
PRReviewFinding(
id="SEC002",
severity=ReviewSeverity.HIGH,
category=ReviewCategory.SECURITY,
title="Weak Cryptographic Hash Function",
description="MD5 is cryptographically broken and unsuitable for password hashing. It's vulnerable to collision attacks and rainbow tables.",
file="src/auth.py",
line=5,
suggested_fix="Use bcrypt: import bcrypt; hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())",
fixable=True,
),
# False positive: Vague low severity
PRReviewFinding(
id="QUAL001",
severity=ReviewSeverity.LOW,
category=ReviewCategory.QUALITY,
title="Code Could Be Better",
description="This code could be improved by considering better practices.",
file="src/utils.py",
line=1,
suggested_fix="Improve it", # Too vague
),
# False positive: Non-existent file
PRReviewFinding(
id="TEST001",
severity=ReviewSeverity.MEDIUM,
category=ReviewCategory.TEST,
title="Missing Test Coverage",
description="This file needs comprehensive test coverage for all functions.",
file="tests/test_nonexistent.py", # Doesn't exist
line=1,
),
# Valid but needs line correction
PRReviewFinding(
id="PERF001",
severity=ReviewSeverity.MEDIUM,
category=ReviewCategory.PERFORMANCE,
title="List Comprehension Opportunity",
description="The process_items function uses a loop with append which is less efficient than a list comprehension for this simple transformation.",
file="src/utils.py",
line=5, # Wrong line, should be around 2-3
suggested_fix="Use list comprehension: return [item * 2 for item in items]",
fixable=True,
),
# False positive: Style without good suggestion
PRReviewFinding(
id="STYLE001",
severity=ReviewSeverity.LOW,
category=ReviewCategory.STYLE,
title="Formatting Style Issue",
description="The code formatting doesn't follow best practices.",
file="src/utils.py",
line=1,
suggested_fix="", # No suggestion
),
]
print(f"🔍 Raw findings from AI: {len(raw_findings)}")
print()
# Initialize validator
project_root = Path("/path/to/project")
validator = FindingValidator(project_root, changed_files)
# Validate findings
validated_findings = validator.validate_findings(raw_findings)
print(f"✅ Validated findings: {len(validated_findings)}")
print()
# Display validated findings
for finding in validated_findings:
confidence = getattr(finding, "confidence", 0.0)
print(f"[{finding.severity.value.upper()}] {finding.title}")
print(f" File: {finding.file}:{finding.line}")
print(f" Confidence: {confidence:.2f}")
print(f" Fixable: {finding.fixable}")
print()
# Get validation statistics
stats = validator.get_validation_stats(raw_findings, validated_findings)
print("📊 Validation Statistics:")
print(f" Total findings: {stats['total_findings']}")
print(f" Kept: {stats['kept_findings']}")
print(f" Filtered: {stats['filtered_findings']}")
print(f" Filter rate: {stats['filter_rate']:.1%}")
print(f" Average actionability: {stats['average_actionability']:.2f}")
print(f" Fixable count: {stats['fixable_count']}")
print()
print("🎯 Severity Distribution:")
for severity, count in stats["severity_distribution"].items():
if count > 0:
print(f" {severity}: {count}")
print()
print("📂 Category Distribution:")
for category, count in stats["category_distribution"].items():
if count > 0:
print(f" {category}: {count}")
print()
# Return results for further processing (e.g., posting to GitHub)
return {
"validated_findings": validated_findings,
"stats": stats,
"ready_for_posting": len(validated_findings) > 0,
}
def example_integration_with_github_api():
"""Example of using validated findings with GitHub API."""
# Run validation
result = example_pr_review_with_validation()
if not result["ready_for_posting"]:
print("⚠️ No high-quality findings to post to GitHub")
return
# Simulate posting to GitHub (you would use actual GitHub API here)
print("📤 Posting to GitHub PR...")
for finding in result["validated_findings"]:
# Format as GitHub review comment
comment = {
"path": finding.file,
"line": finding.line,
"body": f"**{finding.title}**\n\n{finding.description}",
}
if finding.suggested_fix:
comment["body"] += (
f"\n\n**Suggested fix:**\n```\n{finding.suggested_fix}\n```"
)
print(f" ✓ Posted comment on {finding.file}:{finding.line}")
print(f"✅ Posted {len(result['validated_findings'])} high-quality findings to PR")
if __name__ == "__main__":
print("=" * 70)
print("Output Validator Example")
print("=" * 70)
print()
# Run the example
example_integration_with_github_api()
print()
print("=" * 70)
print("Key Takeaways:")
print("=" * 70)
print("✓ Critical security issues preserved (SQL injection, weak crypto)")
print("✓ Valid performance suggestions kept")
print("✓ Vague/generic findings filtered out")
print("✓ Non-existent files filtered out")
print("✓ Line numbers auto-corrected when possible")
print("✓ Only actionable findings posted to PR")
print()