Initial upload: Local AI Commit Reviewer CLI with CI/CD workflow
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-02-05 06:34:35 +00:00
parent 5c6cb3bfd6
commit b0dc33ed55

View File

@@ -0,0 +1,141 @@
from abc import ABC, abstractmethod
from rich.console import Console
from rich.panel import Panel
from rich.style import Style
from rich.table import Table
from rich.text import Text
from ..core import Issue, IssueCategory, IssueSeverity, ReviewResult
class BaseFormatter(ABC):
@abstractmethod
def format(self, result: ReviewResult) -> str:
pass
class TerminalFormatter(BaseFormatter):
def __init__(self, theme: str = "auto", show_line_numbers: bool = True):
self.console = Console()
self.show_line_numbers = show_line_numbers
self.use_colors = theme != "dark" if theme == "auto" else theme == "dark"
def _get_severity_style(self, severity: IssueSeverity) -> Style:
styles = {
IssueSeverity.CRITICAL: Style(color="red", bold=True),
IssueSeverity.WARNING: Style(color="yellow"),
IssueSeverity.INFO: Style(color="blue"),
}
return styles.get(severity, Style())
def _get_category_icon(self, category: IssueCategory) -> str:
icons = {
IssueCategory.BUG: "[BUG]",
IssueCategory.SECURITY: "[SECURITY]",
IssueCategory.STYLE: "[STYLE]",
IssueCategory.PERFORMANCE: "[PERF]",
IssueCategory.DOCUMENTATION: "[DOC]",
}
return icons.get(category, "")
def _format_issue(self, issue: Issue) -> Text:
text = Text()
text.append(f"{issue.file}:{issue.line} ", style="dim")
text.append(f"[{issue.severity.value.upper()}] ", self._get_severity_style(issue.severity))
text.append(f"{self._get_category_icon(issue.category)} ")
text.append(issue.message)
if issue.suggestion:
text.append("\n Suggestion: ", style="dim")
text.append(issue.suggestion)
return text
def format(self, result: ReviewResult) -> str:
output = []
if result.error:
output.append(Panel(
f"[red]Error: {result.error}[/red]",
title="Review Failed",
expand=False
))
return "\n".join(str(p) for p in output)
summary = result.summary
summary_panel = Panel(
f"[bold]Files Reviewed:[/bold] {summary.files_reviewed}\n"
f"[bold]Lines Changed:[/bold] {summary.lines_changed}\n\n"
f"[red]Critical:[/red] {summary.critical_count} "
f"[yellow]Warnings:[/yellow] {summary.warning_count} "
f"[blue]Info:[/blue] {summary.info_count}\n\n"
f"[bold]Assessment:[/bold] {summary.overall_assessment}",
title="Review Summary",
expand=False
)
output.append(summary_panel)
if result.issues:
issues_table = Table(title="Issues Found", show_header=True)
issues_table.add_column("File", style="dim")
issues_table.add_column("Line", justify="right", style="dim")
issues_table.add_column("Severity", width=10)
issues_table.add_column("Category", width=12)
issues_table.add_column("Message")
for issue in result.issues:
issues_table.add_row(
issue.file,
str(issue.line),
f"[{issue.severity.value.upper()}]",
f"[{issue.category.value.upper()}]",
issue.message,
style=self._get_severity_style(issue.severity)
)
output.append(issues_table)
suggestions_panel = Panel(
"\n".join(
f"[bold]{issue.file}:{issue.line}[/bold]\n"
f" {issue.message}\n"
+ (f" [green]→ {issue.suggestion}[/green]\n" if issue.suggestion else "")
for issue in result.issues if issue.suggestion
),
title="Suggestions",
expand=False
)
output.append(suggestions_panel)
model_info = Panel(
f"[bold]Model:[/bold] {result.model_used}\n"
f"[bold]Tokens Used:[/bold] {result.tokens_used}\n"
f"[bold]Mode:[/bold] {result.review_mode}",
title="Review Info",
expand=False
)
output.append(model_info)
return "\n".join(str(o) for o in output)
class JSONFormatter(BaseFormatter):
def format(self, result: ReviewResult) -> str:
return result.to_json()
class MarkdownFormatter(BaseFormatter):
def format(self, result: ReviewResult) -> str:
return result.to_markdown()
def get_formatter(format_type: str = "terminal", **kwargs) -> BaseFormatter:
formatters = {
"terminal": TerminalFormatter,
"json": JSONFormatter,
"markdown": MarkdownFormatter,
}
formatter_class = formatters.get(format_type, TerminalFormatter)
return formatter_class(**kwargs)