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