-
Notifications
You must be signed in to change notification settings - Fork 3
Implement multi-factor risk assessment for refactoring operations #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
23151c4
df7ab68
a523277
8fd2a2b
a107630
18f8459
4aae87a
833060c
ea7580e
e49339b
4c9de4b
3f2103e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -145,6 +145,43 @@ def _print_refactor_messages(summary: dict, preview: bool) -> None: | |
| console.print("\n[green]✅ Refactoring completed! Don't forget to test your code.[/green]") | ||
|
|
||
|
|
||
| def _print_operation_with_risk(operation) -> None: | ||
| """Print refactoring operation with color-coded risk visualization.""" | ||
| from rich.panel import Panel | ||
|
|
||
| from refactron.core.risk_assessment import RiskAssessor | ||
|
|
||
| # Determine risk level and color using centralized logic | ||
| assessor = RiskAssessor() | ||
| risk_score = operation.risk_score | ||
| risk_color = assessor.get_risk_display_color(risk_score) | ||
| risk_label = assessor.get_risk_display_label(risk_score) | ||
|
|
||
| # Build content | ||
| content = f"[bold]{operation.operation_type}[/bold]\n" | ||
| content += f"Location: {operation.file_path}:{operation.line_number}\n" | ||
| content += f"Risk: [{risk_color}]{risk_score:.2f} - {risk_label}[/{risk_color}]\n" | ||
| content += f"\n{operation.description}" | ||
|
|
||
| # Add risk factors if available | ||
| if "risk_factors" in operation.metadata: | ||
| risk_factors = operation.metadata["risk_factors"] | ||
| content += "\n\n[bold]Risk Breakdown:[/bold]\n" | ||
| content += f" • Impact Scope: {risk_factors.get('impact_scope', 0):.2f}\n" | ||
| content += f" • Change Type: {risk_factors.get('change_type_risk', 0):.2f}\n" | ||
| content += f" • Test Coverage: {risk_factors.get('test_coverage_risk', 0):.2f}\n" | ||
| content += f" • Dependencies: {risk_factors.get('dependency_risk', 0):.2f}\n" | ||
| content += f" • Complexity: {risk_factors.get('complexity_risk', 0):.2f}" | ||
|
|
||
| if risk_factors.get("test_file_exists"): | ||
| content += "\n\n✓ Test file exists" | ||
| else: | ||
| content += "\n\n⚠ No test file found - higher risk" | ||
|
|
||
| panel = Panel(content, border_style=risk_color, expand=False) | ||
| console.print(panel) | ||
|
|
||
|
Comment on lines
+148
to
+183
|
||
|
|
||
| @click.group() | ||
| @click.version_option(version="1.0.0") | ||
| def main() -> None: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,7 +39,7 @@ def operations_by_type(self, operation_type: str) -> List[RefactoringOperation]: | |
| return [op for op in self.operations if op.operation_type == operation_type] | ||
|
|
||
| def show_diff(self) -> str: | ||
| """Show a diff of all operations.""" | ||
| """Show a diff of all operations with detailed risk assessment.""" | ||
| lines = [] | ||
| lines.append("=" * 80) | ||
| lines.append("REFACTORING PREVIEW") | ||
|
|
@@ -54,7 +54,39 @@ def show_diff(self) -> str: | |
| lines.append("-" * 80) | ||
| lines.append(f"Operation {i}: {op.operation_type}") | ||
| lines.append(f"Location: {op.file_path}:{op.line_number}") | ||
| lines.append(f"Risk Score: {op.risk_score:.2f}") | ||
|
|
||
| # Show risk with visual indicator | ||
| risk_icon = self._get_risk_icon(op.risk_score) | ||
| lines.append(f"Risk Score: {op.risk_score:.2f} {risk_icon}") | ||
|
|
||
| # Show detailed risk factors if available | ||
| if "risk_factors" in op.metadata: | ||
| lines.append("") | ||
| lines.append(" Risk Breakdown:") | ||
| risk_factors = op.metadata["risk_factors"] | ||
| lines.append(f" • Impact Scope: {risk_factors.get('impact_scope', 0):.2f}") | ||
| lines.append( | ||
| f" • Change Type Risk: {risk_factors.get('change_type_risk', 0):.2f}" | ||
| ) | ||
| lines.append( | ||
| f" • Test Coverage Risk: {risk_factors.get('test_coverage_risk', 0):.2f}" | ||
| ) | ||
| lines.append(f" • Dependency Risk: {risk_factors.get('dependency_risk', 0):.2f}") | ||
| lines.append(f" • Complexity Risk: {risk_factors.get('complexity_risk', 0):.2f}") | ||
|
|
||
| # Show affected components | ||
| if risk_factors.get("affected_functions"): | ||
| lines.append( | ||
| f" • Affected Functions: {', '.join(risk_factors['affected_functions'])}" | ||
| ) | ||
|
|
||
| # Show test coverage status | ||
| if risk_factors.get("test_file_exists"): | ||
| lines.append(" • Test Coverage: ✓ Test file exists") | ||
| else: | ||
| lines.append(" • Test Coverage: ⚠ No test file found") | ||
|
|
||
| lines.append("") | ||
| lines.append(f"Description: {op.description}") | ||
|
|
||
| if op.reasoning: | ||
|
|
@@ -74,6 +106,13 @@ def show_diff(self) -> str: | |
| lines.append("=" * 80) | ||
| return "\n".join(lines) | ||
|
|
||
| def _get_risk_icon(self, risk_score: float) -> str: | ||
| """Get visual indicator for risk level.""" | ||
| from refactron.core.risk_assessment import RiskAssessor | ||
|
|
||
| assessor = RiskAssessor() | ||
| return assessor.get_risk_display_label(risk_score) | ||
|
Comment on lines
+109
to
+114
|
||
|
|
||
| def apply(self) -> bool: | ||
| """Apply the refactoring operations (placeholder).""" | ||
| # This would actually apply the changes to files | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A new RiskAssessor instance is created in _print_operation_with_risk for every operation printed. If this function is called multiple times (e.g., in a loop for many operations), it creates unnecessary objects. Consider accepting a RiskAssessor instance as a parameter or creating it once in the calling code and reusing it.