mirror of
https://github.com/q3alique/codeflow
synced 2026-06-21 14:06:02 +00:00
3fe6011162
- cli.py: --llm -> --review, param and output key updated - orchestrator.py: generate_llm -> generate_review, output .llm.md -> .review.md, import llm_exporter -> review_exporter - llm_exporter.py renamed to review_exporter.py; internal header and section titles updated to remove tool-name references - .gitignore: report.llm.md / *.llm.md -> report.review.md / *.review.md - README.md, demo/README.md: all --llm flags and report.llm.md references updated to --review and report.review.md
86 lines
3.3 KiB
Python
86 lines
3.3 KiB
Python
from __future__ import annotations
|
|
import click
|
|
import traceback
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
@click.group()
|
|
def cli():
|
|
"""codeflow — static analysis and visualization for any code repository."""
|
|
pass
|
|
|
|
|
|
@cli.command("analyze")
|
|
@click.argument("repo_path", type=click.Path(
|
|
exists=True, file_okay=False, dir_okay=True, readable=True, path_type=str,
|
|
))
|
|
@click.option("--output", "-o", required=True, type=click.Path(path_type=str),
|
|
help="Path for the output HTML report (e.g., report.html)")
|
|
@click.option("--cluster-threshold", default=5, show_default=True,
|
|
type=click.IntRange(0, 100),
|
|
help="Number of sibling nodes above which clustering is triggered. 0 disables clustering.")
|
|
@click.option("--max-file-size", default=5, show_default=True, type=float,
|
|
help="Skip files larger than this many megabytes.")
|
|
@click.option("--burp", is_flag=True, default=False,
|
|
help="Generate a Burp Suite JSON export file alongside the report.")
|
|
@click.option("--review", is_flag=True, default=False,
|
|
help="Generate a Markdown taint review document alongside the report (report.review.md).")
|
|
@click.option("--verbose", "-v", is_flag=True, default=False,
|
|
help="Print verbose progress to stderr.")
|
|
def analyze(repo_path, output, cluster_threshold, max_file_size, burp, review, verbose):
|
|
"""Analyze a repository and produce an HTML report."""
|
|
output_path = Path(output)
|
|
parent = output_path.parent
|
|
if not parent.exists():
|
|
click.echo(f"Error: output directory does not exist: {parent}", err=True)
|
|
sys.exit(1)
|
|
if not output.lower().endswith(".html"):
|
|
click.echo("Error: output must be a .html file", err=True)
|
|
sys.exit(1)
|
|
|
|
click.echo(f"[codeflow] Analyzing {repo_path} ...", err=True)
|
|
|
|
try:
|
|
from codeflow.orchestrator import run_analysis
|
|
result = run_analysis(
|
|
repo_path=Path(repo_path).resolve(),
|
|
output_path=output_path.resolve(),
|
|
cluster_threshold=cluster_threshold,
|
|
max_file_size_mb=max_file_size,
|
|
generate_burp=burp,
|
|
generate_review=review,
|
|
verbose=verbose,
|
|
)
|
|
except Exception as e:
|
|
click.echo(f"[codeflow] FATAL ERROR: {type(e).__name__}: {e}", err=True)
|
|
if verbose:
|
|
traceback.print_exc()
|
|
sys.exit(2)
|
|
|
|
html_path = result.get("html_path", "")
|
|
burp_path = result.get("burp_path")
|
|
file_count = result.get("file_count", 0)
|
|
node_count = result.get("node_count", 0)
|
|
flow_count = result.get("flow_count", 0)
|
|
elapsed = result.get("elapsed_seconds", 0.0)
|
|
errors = result.get("errors", [])
|
|
|
|
review_path = result.get("review_path")
|
|
|
|
click.echo("[codeflow] Done.")
|
|
click.echo(f" Report: {html_path}")
|
|
if burp_path:
|
|
click.echo(f" Burp export: {burp_path}")
|
|
if review_path:
|
|
click.echo(f" Review doc: {review_path}")
|
|
click.echo(f" Files: {file_count}")
|
|
click.echo(f" Nodes: {node_count}")
|
|
click.echo(f" Flows: {flow_count}")
|
|
click.echo(f" Time: {elapsed:.1f}s")
|
|
|
|
if errors:
|
|
click.echo(f"[codeflow] WARNING: {len(errors)} file(s) had parse errors:", err=True)
|
|
for err in errors:
|
|
click.echo(f" - {err}", err=True)
|