242 lines
8.4 KiB
Python
242 lines
8.4 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Remediation Planner
|
||
|
||
Generates prioritized action plans based on audit findings.
|
||
Uses severity, impact, frequency, and effort to prioritize issues.
|
||
"""
|
||
|
||
from typing import Dict, List
|
||
from datetime import datetime, timedelta
|
||
|
||
|
||
def generate_remediation_plan(findings: Dict[str, List[Dict]], metadata: Dict) -> str:
|
||
"""
|
||
Generate a prioritized remediation plan.
|
||
|
||
Args:
|
||
findings: All findings organized by category
|
||
metadata: Project metadata
|
||
|
||
Returns:
|
||
Markdown-formatted remediation plan
|
||
"""
|
||
plan = []
|
||
|
||
# Header
|
||
plan.append("# Codebase Remediation Plan")
|
||
plan.append(f"\n**Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||
plan.append(f"**Codebase**: `{metadata.get('path', 'Unknown')}`")
|
||
plan.append("\n---\n")
|
||
|
||
# Flatten and prioritize all findings
|
||
all_findings = []
|
||
for category, category_findings in findings.items():
|
||
for finding in category_findings:
|
||
finding['category'] = category
|
||
all_findings.append(finding)
|
||
|
||
# Calculate priority scores
|
||
for finding in all_findings:
|
||
finding['priority_score'] = calculate_priority_score(finding)
|
||
|
||
# Sort by priority score (highest first)
|
||
all_findings.sort(key=lambda x: x['priority_score'], reverse=True)
|
||
|
||
# Group by priority level
|
||
p0_issues = [f for f in all_findings if f['severity'] == 'critical']
|
||
p1_issues = [f for f in all_findings if f['severity'] == 'high']
|
||
p2_issues = [f for f in all_findings if f['severity'] == 'medium']
|
||
p3_issues = [f for f in all_findings if f['severity'] == 'low']
|
||
|
||
# Priority 0: Critical Issues (Fix Immediately)
|
||
if p0_issues:
|
||
plan.append("## Priority 0: Critical Issues (Fix Immediately ⚡)")
|
||
plan.append("\n**Timeline**: Within 24 hours")
|
||
plan.append("**Impact**: Security vulnerabilities, production-breaking bugs, data loss risks\n")
|
||
|
||
for i, finding in enumerate(p0_issues, 1):
|
||
plan.append(f"### {i}. {finding.get('title', 'Untitled')}")
|
||
plan.append(f"**Category**: {finding.get('category', 'Unknown').replace('_', ' ').title()}")
|
||
plan.append(f"**Location**: `{finding.get('file', 'Unknown')}`")
|
||
plan.append(f"**Effort**: {finding.get('effort', 'unknown').upper()}")
|
||
plan.append(f"\n**Issue**: {finding.get('description', 'No description')}")
|
||
plan.append(f"\n**Impact**: {finding.get('impact', 'Unknown impact')}")
|
||
plan.append(f"\n**Action**: {finding.get('remediation', 'No remediation suggested')}\n")
|
||
plan.append("---\n")
|
||
|
||
# Priority 1: High Issues (Fix This Sprint)
|
||
if p1_issues:
|
||
plan.append("## Priority 1: High Issues (Fix This Sprint 📅)")
|
||
plan.append("\n**Timeline**: Within current sprint (2 weeks)")
|
||
plan.append("**Impact**: Significant quality, security, or user experience issues\n")
|
||
|
||
for i, finding in enumerate(p1_issues[:10], 1): # Top 10
|
||
plan.append(f"### {i}. {finding.get('title', 'Untitled')}")
|
||
plan.append(f"**Category**: {finding.get('category', 'Unknown').replace('_', ' ').title()}")
|
||
plan.append(f"**Effort**: {finding.get('effort', 'unknown').upper()}")
|
||
plan.append(f"\n**Action**: {finding.get('remediation', 'No remediation suggested')}\n")
|
||
|
||
if len(p1_issues) > 10:
|
||
plan.append(f"\n*...and {len(p1_issues) - 10} more high-priority issues*\n")
|
||
|
||
plan.append("---\n")
|
||
|
||
# Priority 2: Medium Issues (Fix Next Quarter)
|
||
if p2_issues:
|
||
plan.append("## Priority 2: Medium Issues (Fix Next Quarter 📆)")
|
||
plan.append("\n**Timeline**: Within 3 months")
|
||
plan.append("**Impact**: Code maintainability, developer productivity\n")
|
||
plan.append(f"**Total Issues**: {len(p2_issues)}\n")
|
||
|
||
# Group by subcategory
|
||
subcategories = {}
|
||
for finding in p2_issues:
|
||
subcat = finding.get('subcategory', 'Other')
|
||
if subcat not in subcategories:
|
||
subcategories[subcat] = []
|
||
subcategories[subcat].append(finding)
|
||
|
||
plan.append("**Grouped by Type**:\n")
|
||
for subcat, subcat_findings in subcategories.items():
|
||
plan.append(f"- {subcat.replace('_', ' ').title()}: {len(subcat_findings)} issues")
|
||
|
||
plan.append("\n---\n")
|
||
|
||
# Priority 3: Low Issues (Backlog)
|
||
if p3_issues:
|
||
plan.append("## Priority 3: Low Issues (Backlog 📋)")
|
||
plan.append("\n**Timeline**: When time permits")
|
||
plan.append("**Impact**: Minor improvements, stylistic issues\n")
|
||
plan.append(f"**Total Issues**: {len(p3_issues)}\n")
|
||
plan.append("*Address during dedicated tech debt sprints or slow periods*\n")
|
||
plan.append("---\n")
|
||
|
||
# Implementation Timeline
|
||
plan.append("## Suggested Timeline\n")
|
||
|
||
today = datetime.now()
|
||
|
||
if p0_issues:
|
||
deadline = today + timedelta(days=1)
|
||
plan.append(f"- **{deadline.strftime('%Y-%m-%d')}**: All P0 issues resolved")
|
||
|
||
if p1_issues:
|
||
deadline = today + timedelta(weeks=2)
|
||
plan.append(f"- **{deadline.strftime('%Y-%m-%d')}**: P1 issues addressed (end of sprint)")
|
||
|
||
if p2_issues:
|
||
deadline = today + timedelta(weeks=12)
|
||
plan.append(f"- **{deadline.strftime('%Y-%m-%d')}**: P2 issues resolved (end of quarter)")
|
||
|
||
# Effort Summary
|
||
plan.append("\n## Effort Summary\n")
|
||
|
||
effort_estimates = calculate_effort_summary(all_findings)
|
||
plan.append(f"**Total Estimated Effort**: {effort_estimates['total']} person-days")
|
||
plan.append(f"- Critical/High: {effort_estimates['critical_high']} days")
|
||
plan.append(f"- Medium: {effort_estimates['medium']} days")
|
||
plan.append(f"- Low: {effort_estimates['low']} days")
|
||
|
||
# Team Assignment Suggestions
|
||
plan.append("\n## Team Assignment Suggestions\n")
|
||
plan.append("- **Security Team**: All P0 security issues, P1 vulnerabilities")
|
||
plan.append("- **QA/Testing**: Test coverage improvements, test quality issues")
|
||
plan.append("- **Infrastructure**: CI/CD improvements, build performance")
|
||
plan.append("- **Development Team**: Code quality refactoring, complexity reduction")
|
||
|
||
# Footer
|
||
plan.append("\n---\n")
|
||
plan.append("*Remediation plan generated by Codebase Auditor Skill*")
|
||
plan.append("\n*Priority scoring based on: Impact × 10 + Frequency × 5 - Effort × 2*")
|
||
|
||
return '\n'.join(plan)
|
||
|
||
|
||
def calculate_priority_score(finding: Dict) -> int:
|
||
"""
|
||
Calculate priority score for a finding.
|
||
|
||
Formula: (Impact × 10) + (Frequency × 5) - (Effort × 2)
|
||
|
||
Args:
|
||
finding: Individual finding
|
||
|
||
Returns:
|
||
Priority score (higher = more urgent)
|
||
"""
|
||
# Map severity to impact (1-10)
|
||
severity_impact = {
|
||
'critical': 10,
|
||
'high': 7,
|
||
'medium': 4,
|
||
'low': 2,
|
||
}
|
||
impact = severity_impact.get(finding.get('severity', 'low'), 1)
|
||
|
||
# Estimate frequency (1-10) based on category
|
||
# Security/testing issues affect everything
|
||
category = finding.get('category', '')
|
||
if category in ['security', 'testing']:
|
||
frequency = 10
|
||
elif category in ['quality', 'performance']:
|
||
frequency = 6
|
||
else:
|
||
frequency = 3
|
||
|
||
# Map effort to numeric value (1-10)
|
||
effort_values = {
|
||
'low': 2,
|
||
'medium': 5,
|
||
'high': 8,
|
||
}
|
||
effort = effort_values.get(finding.get('effort', 'medium'), 5)
|
||
|
||
# Calculate score
|
||
score = (impact * 10) + (frequency * 5) - (effort * 2)
|
||
|
||
return max(0, score) # Never negative
|
||
|
||
|
||
def calculate_effort_summary(findings: List[Dict]) -> Dict[str, int]:
|
||
"""
|
||
Calculate total effort estimates.
|
||
|
||
Args:
|
||
findings: All findings
|
||
|
||
Returns:
|
||
Dictionary with effort estimates in person-days
|
||
"""
|
||
# Map effort levels to days
|
||
effort_days = {
|
||
'low': 0.5,
|
||
'medium': 2,
|
||
'high': 5,
|
||
}
|
||
|
||
critical_high_days = sum(
|
||
effort_days.get(f.get('effort', 'medium'), 2)
|
||
for f in findings
|
||
if f.get('severity') in ['critical', 'high']
|
||
)
|
||
|
||
medium_days = sum(
|
||
effort_days.get(f.get('effort', 'medium'), 2)
|
||
for f in findings
|
||
if f.get('severity') == 'medium'
|
||
)
|
||
|
||
low_days = sum(
|
||
effort_days.get(f.get('effort', 'medium'), 2)
|
||
for f in findings
|
||
if f.get('severity') == 'low'
|
||
)
|
||
|
||
return {
|
||
'critical_high': round(critical_high_days, 1),
|
||
'medium': round(medium_days, 1),
|
||
'low': round(low_days, 1),
|
||
'total': round(critical_high_days + medium_days + low_days, 1),
|
||
}
|