Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 79 additions & 46 deletions code_review_graph/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ def _print_banner() -> None:
version = _get_version()

# ANSI escape codes
c = "\033[36m" if color else "" # cyan — graph art
y = "\033[33m" if color else "" # yellow — center node
b = "\033[1m" if color else "" # bold
d = "\033[2m" if color else "" # dim
g = "\033[32m" if color else "" # green — commands
r = "\033[0m" if color else "" # reset
c = "\033[36m" if color else "" # cyan — graph art
y = "\033[33m" if color else "" # yellow — center node
b = "\033[1m" if color else "" # bold
d = "\033[2m" if color else "" # dim
g = "\033[32m" if color else "" # green — commands
r = "\033[0m" if color else "" # reset

print(f"""
{c} ●──●──●{r}
Expand Down Expand Up @@ -150,74 +150,103 @@ def _handle_init(args: argparse.Namespace) -> None:
print(" 2. Restart your AI coding tool to pick up the new config")


def _cli_post_process(store: object) -> None:
"""Run post-build pipeline and print a summary line for each step."""
from .postprocessing import run_post_processing

pp = run_post_processing(store) # type: ignore[arg-type]
if pp.get("signatures_computed"):
print(f"Signatures: {pp['signatures_computed']} nodes")
if pp.get("fts_indexed"):
print(f"FTS indexed: {pp['fts_indexed']} nodes")
if pp.get("flows_detected") is not None:
print(f"Flows: {pp['flows_detected']}")
if pp.get("communities_detected") is not None:
print(f"Communities: {pp['communities_detected']}")


def main() -> None:
"""Main CLI entry point."""
ap = argparse.ArgumentParser(
prog="code-review-graph",
description="Persistent incremental knowledge graph for code reviews",
)
ap.add_argument(
"-v", "--version", action="store_true", help="Show version and exit"
)
ap.add_argument("-v", "--version", action="store_true", help="Show version and exit")
sub = ap.add_subparsers(dest="command")

# install (primary) + init (alias)
install_cmd = sub.add_parser(
"install", help="Register MCP server with AI coding platforms"
)
install_cmd = sub.add_parser("install", help="Register MCP server with AI coding platforms")
install_cmd.add_argument("--repo", default=None, help="Repository root (auto-detected)")
install_cmd.add_argument(
"--dry-run", action="store_true",
"--dry-run",
action="store_true",
help="Show what would be done without writing files",
)
install_cmd.add_argument(
"--no-skills", action="store_true",
"--no-skills",
action="store_true",
help="Skip generating Claude Code skill files",
)
install_cmd.add_argument(
"--no-hooks", action="store_true",
"--no-hooks",
action="store_true",
help="Skip installing Claude Code hooks",
)
# Legacy flags (kept for backwards compat, now no-ops since all is default)
install_cmd.add_argument("--skills", action="store_true", help=argparse.SUPPRESS)
install_cmd.add_argument("--hooks", action="store_true", help=argparse.SUPPRESS)
install_cmd.add_argument("--all", action="store_true", dest="install_all",
help=argparse.SUPPRESS)
install_cmd.add_argument(
"--all", action="store_true", dest="install_all", help=argparse.SUPPRESS
)
install_cmd.add_argument(
"--platform",
choices=[
"claude", "claude-code", "cursor", "windsurf", "zed",
"continue", "opencode", "antigravity", "all",
"claude",
"claude-code",
"cursor",
"windsurf",
"zed",
"continue",
"opencode",
"antigravity",
"all",
],
default="all",
help="Target platform for MCP config (default: all detected)",
)

init_cmd = sub.add_parser(
"init", help="Alias for install"
)
init_cmd = sub.add_parser("init", help="Alias for install")
init_cmd.add_argument("--repo", default=None, help="Repository root (auto-detected)")
init_cmd.add_argument(
"--dry-run", action="store_true",
"--dry-run",
action="store_true",
help="Show what would be done without writing files",
)
init_cmd.add_argument(
"--no-skills", action="store_true",
"--no-skills",
action="store_true",
help="Skip generating Claude Code skill files",
)
init_cmd.add_argument(
"--no-hooks", action="store_true",
"--no-hooks",
action="store_true",
help="Skip installing Claude Code hooks",
)
init_cmd.add_argument("--skills", action="store_true", help=argparse.SUPPRESS)
init_cmd.add_argument("--hooks", action="store_true", help=argparse.SUPPRESS)
init_cmd.add_argument("--all", action="store_true", dest="install_all",
help=argparse.SUPPRESS)
init_cmd.add_argument("--all", action="store_true", dest="install_all", help=argparse.SUPPRESS)
init_cmd.add_argument(
"--platform",
choices=[
"claude", "claude-code", "cursor", "windsurf", "zed",
"continue", "opencode", "antigravity", "all",
"claude",
"claude-code",
"cursor",
"windsurf",
"zed",
"continue",
"opencode",
"antigravity",
"all",
],
default="all",
help="Target platform for MCP config (default: all detected)",
Expand All @@ -244,15 +273,17 @@ def main() -> None:
vis_cmd = sub.add_parser("visualize", help="Generate interactive HTML graph visualization")
vis_cmd.add_argument("--repo", default=None, help="Repository root (auto-detected)")
vis_cmd.add_argument(
"--serve", action="store_true",
"--serve",
action="store_true",
help="Start a local HTTP server to view the visualization (localhost:8765)",
)

# wiki
wiki_cmd = sub.add_parser("wiki", help="Generate markdown wiki from community structure")
wiki_cmd.add_argument("--repo", default=None, help="Repository root (auto-detected)")
wiki_cmd.add_argument(
"--force", action="store_true",
"--force",
action="store_true",
help="Regenerate all pages even if content unchanged",
)

Expand All @@ -275,9 +306,10 @@ def main() -> None:
# eval
eval_cmd = sub.add_parser("eval", help="Run evaluation benchmarks")
eval_cmd.add_argument(
"--benchmark", default=None,
"--benchmark",
default=None,
help="Comma-separated benchmarks to run (token_efficiency, impact_accuracy, "
"flow_completeness, search_quality, build_performance)",
"flow_completeness, search_quality, build_performance)",
)
eval_cmd.add_argument("--repo", default=None, help="Comma-separated repo config names")
eval_cmd.add_argument("--all", action="store_true", dest="run_all", help="Run all benchmarks")
Expand All @@ -286,12 +318,8 @@ def main() -> None:

# detect-changes
detect_cmd = sub.add_parser("detect-changes", help="Analyze change impact")
detect_cmd.add_argument(
"--base", default="HEAD~1", help="Git diff base (default: HEAD~1)"
)
detect_cmd.add_argument(
"--brief", action="store_true", help="Show brief summary only"
)
detect_cmd.add_argument("--base", default="HEAD~1", help="Git diff base (default: HEAD~1)")
detect_cmd.add_argument("--brief", action="store_true", help="Show brief summary only")
detect_cmd.add_argument("--repo", default=None, help="Repository root (auto-detected)")

# serve
Expand All @@ -310,6 +338,7 @@ def main() -> None:

if args.command == "serve":
from .main import main as serve_main

serve_main(repo_root=args.repo)
return

Expand All @@ -318,9 +347,7 @@ def main() -> None:
from .eval.runner import run_eval

if getattr(args, "report", False):
output_dir = Path(
getattr(args, "output_dir", None) or "evaluate/results"
)
output_dir = Path(getattr(args, "output_dir", None) or "evaluate/results")
report = generate_full_report(output_dir)
report_path = Path("evaluate/reports/summary.md")
report_path.parent.mkdir(parents=True, exist_ok=True)
Expand All @@ -332,9 +359,7 @@ def main() -> None:
print(tables)
else:
repos = (
[r.strip() for r in args.repo.split(",")]
if getattr(args, "repo", None)
else None
[r.strip() for r in args.repo.split(",")] if getattr(args, "repo", None) else None
)
benchmarks = (
[b.strip() for b in args.benchmark.split(",")]
Expand Down Expand Up @@ -427,13 +452,16 @@ def main() -> None:
)
if result["errors"]:
print(f"Errors: {len(result['errors'])}")
_cli_post_process(store)

elif args.command == "update":
result = incremental_update(repo_root, store, base=args.base)
print(
f"Incremental: {result['files_updated']} files updated, "
f"{result['total_nodes']} nodes, {result['total_edges']} edges"
)
if result.get("files_updated", 0) > 0:
_cli_post_process(store)

elif args.command == "status":
stats = store.get_stats()
Expand All @@ -450,6 +478,7 @@ def main() -> None:
if stored_sha:
print(f"Built at commit: {stored_sha[:12]}")
from .incremental import _git_branch_info

current_branch, current_sha = _git_branch_info(repo_root)
if stored_branch and current_branch and stored_branch != current_branch:
print(
Expand All @@ -459,10 +488,13 @@ def main() -> None:
)

elif args.command == "watch":
watch(repo_root, store)
from .postprocessing import run_post_processing

watch(repo_root, store, on_files_updated=run_post_processing)

elif args.command == "visualize":
from .visualization import generate_html

html_path = repo_root / ".code-review-graph" / "graph.html"
generate_html(store, html_path)
print(f"Visualization: {html_path}")
Expand All @@ -488,6 +520,7 @@ def main() -> None:

elif args.command == "wiki":
from .wiki import generate_wiki

wiki_dir = repo_root / ".code-review-graph" / "wiki"
result = generate_wiki(store, wiki_dir, force=args.force)
total = result["pages_generated"] + result["pages_updated"] + result["pages_unchanged"]
Expand Down
Loading