Add CLI commands and output renderer
This commit is contained in:
123
vibeguard/cli/commands/analyze.py
Normal file
123
vibeguard/cli/commands/analyze.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""Analyze command for VibeGuard CLI."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import click
|
||||
from rich.console import Console
|
||||
|
||||
from vibeguard.analyzers.factory import AnalyzerFactory
|
||||
from vibeguard.patterns.manager import PatternManager
|
||||
from vibeguard.reports.generator import ReportGenerator
|
||||
from vibeguard.utils.config import Config
|
||||
from vibeguard.utils.file_finder import FileFinder
|
||||
|
||||
|
||||
@click.command(name="analyze")
|
||||
@click.argument("path", type=click.Path(exists=True))
|
||||
@click.option("--json", "output_json", is_flag=True, help="Output as JSON")
|
||||
@click.option("--html", "output_html", type=click.Path(), help="Output HTML report to file")
|
||||
@click.option("--sarif", "output_sarif", type=click.Path(), help="Output SARIF report to file")
|
||||
@click.option(
|
||||
"--severity",
|
||||
type=click.Choice(["critical", "error", "warning", "info"]),
|
||||
help="Minimum severity level to report",
|
||||
)
|
||||
@click.option("--exit-zero", is_flag=True, help="Exit with 0 even if issues found")
|
||||
@click.option("--workers", type=int, default=0, help="Number of parallel workers (0=auto)")
|
||||
@click.pass_obj
|
||||
def analyze(
|
||||
ctx: dict[str, Any],
|
||||
path: str,
|
||||
output_json: bool,
|
||||
output_html: str | None,
|
||||
output_sarif: str | None,
|
||||
severity: str | None,
|
||||
exit_zero: bool,
|
||||
workers: int,
|
||||
) -> None:
|
||||
"""Analyze a repository or file for AI anti-patterns."""
|
||||
config: Config = ctx["config"]
|
||||
console: Console = ctx["console"]
|
||||
|
||||
if severity:
|
||||
config.analyze.severity_threshold = severity
|
||||
if workers > 0:
|
||||
config.analyze.workers = workers
|
||||
|
||||
pattern_manager = PatternManager()
|
||||
pattern_manager.load_all_patterns()
|
||||
|
||||
file_finder = FileFinder(config.ignore.paths)
|
||||
files = file_finder.find_files(path)
|
||||
|
||||
if not files:
|
||||
console.print("[warning]No files found to analyze[/warning]")
|
||||
return
|
||||
|
||||
console.print(f"[info]Found {len(files)} files to analyze[/info]\n")
|
||||
|
||||
issues: list[dict[str, Any]] = []
|
||||
|
||||
for file_path in files:
|
||||
file_issues = analyze_file(file_path, pattern_manager, config)
|
||||
issues.extend(file_issues)
|
||||
|
||||
console.print(f"\n[summary]Analysis Complete[/summary]")
|
||||
console.print(f"Files scanned: {len(files)}")
|
||||
console.print(f"Issues found: {len(issues)}\n")
|
||||
|
||||
severity_counts = {"critical": 0, "error": 0, "warning": 0, "info": 0}
|
||||
for issue in issues:
|
||||
severity = issue.get("severity", "info")
|
||||
severity_counts[severity] = severity_counts.get(severity, 0) + 1
|
||||
|
||||
console.print("[critical]Critical:[/critical]", severity_counts.get("critical", 0))
|
||||
console.print("[error]Errors:[/error]", severity_counts.get("error", 0))
|
||||
console.print("[warning]Warnings:[/warning]", severity_counts.get("warning", 0))
|
||||
console.print("[info]Info:[/info]", severity_counts.get("info", 0))
|
||||
console.print()
|
||||
|
||||
if output_json:
|
||||
import json
|
||||
|
||||
console.print_json(data={"issues": issues, "summary": severity_counts})
|
||||
elif output_html:
|
||||
generator = ReportGenerator()
|
||||
generator.generate_html(issues, output_html, severity_counts)
|
||||
console.print(f"[success]HTML report saved to: {output_html}[/success]")
|
||||
elif output_sarif:
|
||||
generator = ReportGenerator()
|
||||
generator.generate_sarif(issues, output_sarif)
|
||||
console.print(f"[success]SARIF report saved to: {output_sarif}[/success]")
|
||||
|
||||
if issues and not exit_zero:
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def analyze_file(
|
||||
path: Path, pattern_manager: PatternManager, config: Config
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Analyze a single file for anti-patterns."""
|
||||
from vibeguard.analyzers.base import BaseAnalyzer
|
||||
|
||||
issues: list[dict[str, Any]] = []
|
||||
|
||||
analyzer = AnalyzerFactory.get_analyzer(path)
|
||||
if not analyzer:
|
||||
return issues
|
||||
|
||||
try:
|
||||
tree = analyzer.parse_file(path)
|
||||
if not tree:
|
||||
return issues
|
||||
|
||||
file_issues = analyzer.analyze(tree, path)
|
||||
issues.extend(file_issues)
|
||||
|
||||
except Exception as e:
|
||||
console = Console()
|
||||
console.print(f"[warning]Error analyzing {path}: {e}[/warning]")
|
||||
|
||||
return issues
|
||||
Reference in New Issue
Block a user