Add CLI commands and output renderer
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-02-03 06:56:39 +00:00
parent d8717d4951
commit 97f5938006

View 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