mirror of
https://github.com/aljazceru/Auditor.git
synced 2025-12-17 03:24:18 +01:00
118 lines
5.5 KiB
Python
118 lines
5.5 KiB
Python
"""Analyze the impact radius of code changes using the AST symbol graph."""
|
|
|
|
import platform
|
|
import click
|
|
from pathlib import Path
|
|
|
|
# Detect if running on Windows for character encoding
|
|
IS_WINDOWS = platform.system() == "Windows"
|
|
|
|
|
|
@click.command()
|
|
@click.option("--file", required=True, help="Path to the file containing the code to analyze")
|
|
@click.option("--line", required=True, type=int, help="Line number of the code to analyze")
|
|
@click.option("--db", default=None, help="Path to the SQLite database (default: repo_index.db)")
|
|
@click.option("--json", is_flag=True, help="Output results as JSON")
|
|
@click.option("--max-depth", default=2, type=int, help="Maximum depth for transitive dependencies")
|
|
@click.option("--verbose", is_flag=True, help="Show detailed dependency information")
|
|
@click.option("--trace-to-backend", is_flag=True, help="Trace frontend API calls to backend endpoints (cross-stack analysis)")
|
|
def impact(file, line, db, json, max_depth, verbose, trace_to_backend):
|
|
"""
|
|
Analyze the impact radius of changing code at a specific location.
|
|
|
|
This command traces both upstream dependencies (who calls this code)
|
|
and downstream dependencies (what this code calls) to help understand
|
|
the blast radius of potential changes.
|
|
|
|
Example:
|
|
aud impact --file src/auth.py --line 42
|
|
aud impact --file theauditor/indexer.py --line 100 --verbose
|
|
"""
|
|
from theauditor.impact_analyzer import analyze_impact, format_impact_report
|
|
from theauditor.config_runtime import load_runtime_config
|
|
import json as json_lib
|
|
|
|
# Load configuration for default paths
|
|
config = load_runtime_config(".")
|
|
|
|
# Use default database path if not provided
|
|
if db is None:
|
|
db = config["paths"]["db"]
|
|
|
|
# Verify database exists
|
|
db_path = Path(db)
|
|
if not db_path.exists():
|
|
click.echo(f"Error: Database not found at {db}", err=True)
|
|
click.echo("Run 'aud index' first to build the repository index", err=True)
|
|
raise click.ClickException(f"Database not found: {db}")
|
|
|
|
# Verify file exists (helpful for user)
|
|
file = Path(file)
|
|
if not file.exists():
|
|
click.echo(f"Warning: File {file} not found in filesystem", err=True)
|
|
click.echo("Proceeding with analysis using indexed data...", err=True)
|
|
|
|
# Perform impact analysis
|
|
try:
|
|
result = analyze_impact(
|
|
db_path=str(db_path),
|
|
target_file=str(file),
|
|
target_line=line,
|
|
trace_to_backend=trace_to_backend
|
|
)
|
|
|
|
# Output results
|
|
if json:
|
|
# JSON output for programmatic use
|
|
click.echo(json_lib.dumps(result, indent=2, sort_keys=True))
|
|
else:
|
|
# Human-readable report
|
|
report = format_impact_report(result)
|
|
click.echo(report)
|
|
|
|
# Additional verbose output
|
|
if verbose and not result.get("error"):
|
|
click.echo("\n" + "=" * 60)
|
|
click.echo("DETAILED DEPENDENCY INFORMATION")
|
|
click.echo("=" * 60)
|
|
|
|
# Show transitive upstream
|
|
if result.get("upstream_transitive"):
|
|
click.echo(f"\nTransitive Upstream Dependencies ({len(result['upstream_transitive'])} total):")
|
|
for dep in result["upstream_transitive"][:20]:
|
|
depth_indicator = " " * (3 - dep.get("depth", 1))
|
|
tree_char = "+-" if IS_WINDOWS else "└─"
|
|
click.echo(f"{depth_indicator}{tree_char} {dep['symbol']} in {dep['file']}:{dep['line']}")
|
|
if len(result["upstream_transitive"]) > 20:
|
|
click.echo(f" ... and {len(result['upstream_transitive']) - 20} more")
|
|
|
|
# Show transitive downstream
|
|
if result.get("downstream_transitive"):
|
|
click.echo(f"\nTransitive Downstream Dependencies ({len(result['downstream_transitive'])} total):")
|
|
for dep in result["downstream_transitive"][:20]:
|
|
depth_indicator = " " * (3 - dep.get("depth", 1))
|
|
if dep["file"] != "external":
|
|
tree_char = "+-" if IS_WINDOWS else "└─"
|
|
click.echo(f"{depth_indicator}{tree_char} {dep['symbol']} in {dep['file']}:{dep['line']}")
|
|
else:
|
|
tree_char = "+-" if IS_WINDOWS else "└─"
|
|
click.echo(f"{depth_indicator}{tree_char} {dep['symbol']} (external)")
|
|
if len(result["downstream_transitive"]) > 20:
|
|
click.echo(f" ... and {len(result['downstream_transitive']) - 20} more")
|
|
|
|
# Exit with appropriate code
|
|
if result.get("error"):
|
|
# Error already displayed in the report, just exit with code
|
|
exit(3) # Exit code 3 for analysis errors
|
|
|
|
# Warn if high impact
|
|
summary = result.get("impact_summary", {})
|
|
if summary.get("total_impact", 0) > 20:
|
|
click.echo("\n⚠ WARNING: High impact change detected!", err=True)
|
|
exit(1) # Non-zero exit for CI/CD integration
|
|
|
|
except Exception as e:
|
|
# Only show this for unexpected exceptions, not for already-handled errors
|
|
if "No function or class found at" not in str(e):
|
|
click.echo(f"Error during impact analysis: {e}", err=True)
|
|
raise click.ClickException(str(e)) |