Skip to content
Merged
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
4 changes: 4 additions & 0 deletions code_review_graph/docs/LLM-OPTIMIZED-REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ MCP prompts (5): review_changes, architecture_map, debug_issue, onboard_develope
Skills: build-graph, debug-issue, explore-codebase, refactor-safely, review-changes, review-delta, review-pr
CLI: code-review-graph [install|init|build|update|status|watch|visualize|serve|mcp|wiki|detect-changes|postprocess|embed|register|unregister|repos|eval|daemon]
Token efficiency: Prefer detail_level="minimal" where available. Always call get_minimal_context_tool first. Some review/context tools return compact estimated context_savings metadata.

Result bounds: every tool that returns a list is bounded. Defaults are small; pass the tool's cap parameter to widen up to its hard ceiling, or a smaller value to narrow. Truncation is never silent — the response reports the untruncated count (`total`, or a `*_total` field per list), sets `truncated: true`, and the summary line says how many of how many are shown.
Cap parameters by tool: max_results (query_graph, get_review_context, detect_changes, list_communities, get_architecture_overview, refactor, cross_repo_search), max_flows (get_affected_flows, detect_changes), max_members (list_communities, get_community, get_architecture_overview), max_steps + max_source_lines (get_flow), max_per_category (get_knowledge_gaps), top_n (get_hub_nodes, get_bridge_nodes, get_surprising_connections), limit (list_flows, semantic_search_nodes, find_large_functions), max_files (get_review_context), max_chars (get_wiki_page), max_diff_files (apply_refactor).
Bounds reject values below 1 and reject booleans. get_affected_flows keeps max_flows=0 as "no caller limit", still subject to its ceiling (25 flows in standard mode, 500 in minimal, plus a shared 400-step budget) — see #849.
</section>

<section name="legal">
Expand Down
105 changes: 94 additions & 11 deletions code_review_graph/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ def get_review_context_tool(
repo_root: Optional[str] = None,
base: str = "HEAD~1",
detail_level: str = "standard",
max_results: int = 100,
max_files: int = 25,
) -> dict:
"""Generate a focused, token-efficient review context for code changes.

Expand All @@ -309,12 +311,17 @@ def get_review_context_tool(
base: Git ref for change detection. Default: HEAD~1.
detail_level: "standard" for full output, "minimal" for
token-efficient summary. Default: standard.
max_results: Maximum graph nodes per list and edges to return.
Default: 50. Each list reports its untruncated ``*_total``.
max_files: Maximum files listed and given source snippets.
Default: 25. Snippets share an 800-line budget.
"""
root = _resolve_repo_root(repo_root)
return with_provenance(get_review_context(
changed_files=changed_files, max_depth=max_depth,
include_source=include_source, max_lines_per_file=max_lines_per_file,
repo_root=root, base=base, detail_level=detail_level,
max_results=max_results, max_files=max_files,
), root)


Expand Down Expand Up @@ -504,6 +511,8 @@ def get_flow_tool(
flow_name: Optional[str] = None,
include_source: bool = False,
repo_root: Optional[str] = None,
max_steps: int = 50,
max_source_lines: int = 400,
) -> dict:
"""Get detailed information about a single execution flow.

Expand All @@ -517,11 +526,16 @@ def get_flow_tool(
flow_name: Name to search for (partial match). Ignored if flow_id given.
include_source: Include source code snippets for each step. Default: False.
repo_root: Repository root path. Auto-detected if omitted.
max_steps: Maximum steps to return; flow.total_steps reports the
full count. Default: 50.
max_source_lines: Total source lines across all steps when
include_source is set. Default: 400.
"""
root = _resolve_repo_root(repo_root)
return with_provenance(get_flow(
flow_id=flow_id, flow_name=flow_name,
include_source=include_source, repo_root=root,
max_steps=max_steps, max_source_lines=max_source_lines,
), root)


Expand All @@ -546,7 +560,9 @@ def get_affected_flows_tool(
detail_level: "standard" for full step details, "minimal" for per-flow
metadata only. Default: standard.
max_flows: Maximum flows to return; total reports the full count.
Default: 50. Pass 0 to disable the limit.
Default: 50. Pass 0 for no caller limit. Standard mode
additionally caps visible flows at 25 and minimal mode at 500,
because a standard flow costs ~980 tokens against ~18 minimal.
"""
root = _resolve_repo_root(repo_root)
return with_provenance(get_affected_flows_func(
Expand All @@ -561,6 +577,8 @@ def list_communities_tool(
min_size: int = 0,
detail_level: str = "standard",
repo_root: Optional[str] = None,
max_results: int = 50,
max_members: int = 10,
) -> dict:
"""List detected code communities in the codebase.

Expand All @@ -575,11 +593,17 @@ def list_communities_tool(
"minimal" returns only name, size, and cohesion
per community.
repo_root: Repository root path. Auto-detected if omitted.
max_results: Maximum communities to return; total reports the full
count. Default: 50.
max_members: Maximum member names listed per community in standard
mode. Each community's size still reports its true member
count. Default: 10.
"""
root = _resolve_repo_root(repo_root)
return with_provenance(list_communities_func(
repo_root=root, sort_by=sort_by, min_size=min_size,
detail_level=detail_level,
detail_level=detail_level, max_results=max_results,
max_members=max_members,
), root)


Expand All @@ -589,6 +613,7 @@ def get_community_tool(
community_id: Optional[int] = None,
include_members: bool = False,
repo_root: Optional[str] = None,
max_members: int = 25,
) -> dict:
"""Get detailed information about a single code community.

Expand All @@ -603,18 +628,24 @@ def get_community_tool(
community_id: Database ID of the community.
include_members: Include full member node details. Default: False.
repo_root: Repository root path. Auto-detected if omitted.
max_members: Maximum member entries to include; the community's
size still reports its true member count and
members_truncated marks the cut. Default: 25.
"""
root = _resolve_repo_root(repo_root)
return with_provenance(get_community_func(
community_name=community_name, community_id=community_id,
include_members=include_members, repo_root=root,
max_members=max_members,
), root)


@mcp.tool()
def get_architecture_overview_tool(
repo_root: Optional[str] = None,
detail_level: str = "minimal",
max_results: int = 100,
max_members: int = 10,
) -> dict:
"""Generate an architecture overview based on community structure.

Expand All @@ -628,11 +659,17 @@ def get_architecture_overview_tool(
and aggregates cross-community edges to one row per
community pair (typical reduction: 600KB -> <5KB);
"standard" returns full per-edge detail.
max_results: Maximum cross-community rows and warnings to return;
cross_community_edges_total reports the full count. Default: 100.
max_members: Maximum member names per community in standard mode.
Default: 10.
"""
root = _resolve_repo_root(repo_root)
return with_provenance(get_architecture_overview_func(
repo_root=root,
detail_level=detail_level,
max_results=max_results,
max_members=max_members,
), root)


Expand All @@ -644,6 +681,8 @@ async def detect_changes_tool(
max_depth: int = 2,
repo_root: Optional[str] = None,
detail_level: str = "standard",
max_results: int = 25,
max_flows: int = 20,
) -> dict:
"""Detect changes and produce risk-scored, priority-ordered review guidance.

Expand All @@ -663,6 +702,12 @@ async def detect_changes_tool(
repo_root: Repository root path. Auto-detected if omitted.
detail_level: "standard" for full output, "minimal" for
token-efficient summary. Default: standard.
max_results: Maximum changed functions, test gaps, and changed files
to return; the matching *_total fields report the full counts.
Default: 25.
max_flows: Maximum affected flows to embed. Embedded flows carry
per-flow metadata only — use get_affected_flows_tool for step
detail. Default: 20.
"""
root = _resolve_repo_root(repo_root)

Expand All @@ -671,6 +716,7 @@ def _run() -> dict:
base=base, changed_files=changed_files,
include_source=include_source, max_depth=max_depth,
repo_root=root, detail_level=detail_level,
max_results=max_results, max_flows=max_flows,
), root)

coro = asyncio.to_thread(_run)
Expand Down Expand Up @@ -701,6 +747,8 @@ def refactor_tool(
kind: Optional[str] = None,
file_pattern: Optional[str] = None,
repo_root: Optional[str] = None,
max_results: int = 50,
detail_level: str = "standard",
) -> dict:
"""Graph-powered refactoring operations.

Expand All @@ -722,11 +770,18 @@ def refactor_tool(
kind: (dead_code) Optional filter: Function or Class.
file_pattern: (dead_code) Filter by file path substring.
repo_root: Repository root path. Auto-detected if omitted.
max_results: Maximum edits/symbols/suggestions in the response;
total reports the full count. The stored rename preview keeps
every edit, so apply_refactor_tool still applies them all.
Default: 50.
detail_level: "standard" for full records, "minimal" for
identifying fields only. Default: standard.
"""
root = _resolve_repo_root(repo_root)
return with_provenance(refactor_func(
mode=mode, old_name=old_name, new_name=new_name,
kind=kind, file_pattern=file_pattern, repo_root=root,
max_results=max_results, detail_level=detail_level,
), root)


Expand All @@ -735,6 +790,7 @@ def apply_refactor_tool(
refactor_id: str,
repo_root: Optional[str] = None,
dry_run: bool = False,
max_diff_files: int = 25,
) -> dict:
"""Apply a previously previewed refactoring to source files.

Expand All @@ -753,11 +809,13 @@ def apply_refactor_tool(
the same preview can be applied in a follow-up call without
dry_run. Use this for a human-in-the-loop review before
committing changes to disk. See: #176
max_diff_files: Maximum per-file diffs to include in a dry run.
would_modify still lists every file. Default: 25.
"""
root = _resolve_repo_root(repo_root)
return with_provenance(apply_refactor_func(
refactor_id=refactor_id, repo_root=root,
dry_run=dry_run,
dry_run=dry_run, max_diff_files=max_diff_files,
), root)


Expand Down Expand Up @@ -794,6 +852,7 @@ def _run() -> dict:
def get_wiki_page_tool(
community_name: str,
repo_root: Optional[str] = None,
max_chars: int = 20000,
) -> dict:
"""Retrieve a specific wiki page by community name.

Expand All @@ -803,37 +862,44 @@ def get_wiki_page_tool(
Args:
community_name: Community name to look up.
repo_root: Repository root path. Auto-detected if omitted.
max_chars: Maximum characters of page content to return;
total_chars reports the real length. Default: 20000.
"""
root = _resolve_repo_root(repo_root)
return with_provenance(get_wiki_page_func(
community_name=community_name, repo_root=root,
max_chars=max_chars,
), root)


@mcp.tool()
def get_hub_nodes_tool(
top_n: int = 10,
repo_root: Optional[str] = None,
detail_level: str = "standard",
) -> dict:
"""Find the most connected nodes in the codebase (architectural hotspots).

Hub nodes have the highest total degree (in + out edges). Changes to
them have disproportionate blast radius. Excludes File nodes.

Args:
top_n: Number of top hubs to return. Default: 10.
top_n: Number of top hubs to return (capped at 100). Default: 10.
repo_root: Repository root path. Auto-detected if omitted.
detail_level: "standard" for full node data, "minimal" for name,
kind, and total_degree only. Default: standard.
"""
root = _resolve_repo_root(repo_root)
return with_provenance(get_hub_nodes_func(
repo_root=root, top_n=top_n,
repo_root=root, top_n=top_n, detail_level=detail_level,
), root)


@mcp.tool()
def get_bridge_nodes_tool(
top_n: int = 10,
repo_root: Optional[str] = None,
detail_level: str = "standard",
) -> dict:
"""Find architectural chokepoints via betweenness centrality.

Expand All @@ -842,18 +908,22 @@ def get_bridge_nodes_tool(
Uses sampling approximation for graphs > 5000 nodes.

Args:
top_n: Number of top bridges to return. Default: 10.
top_n: Number of top bridges to return (capped at 100). Default: 10.
repo_root: Repository root path. Auto-detected if omitted.
detail_level: "standard" for full node data, "minimal" for name,
kind, and betweenness only. Default: standard.
"""
root = _resolve_repo_root(repo_root)
return with_provenance(get_bridge_nodes_func(
repo_root=root, top_n=top_n,
repo_root=root, top_n=top_n, detail_level=detail_level,
), root)


@mcp.tool()
def get_knowledge_gaps_tool(
repo_root: Optional[str] = None,
max_per_category: int = 15,
detail_level: str = "standard",
) -> dict:
"""Identify structural weaknesses in the codebase graph.

Expand All @@ -863,17 +933,23 @@ def get_knowledge_gaps_tool(

Args:
repo_root: Repository root path. Auto-detected if omitted.
max_per_category: Maximum entries per gap category; summary and
total_gaps still report the untruncated counts. Default: 15.
detail_level: "standard" for full gap records, "minimal" to drop
file paths. Default: standard.
"""
root = _resolve_repo_root(repo_root)
return with_provenance(get_knowledge_gaps_func(
repo_root=root,
repo_root=root, max_per_category=max_per_category,
detail_level=detail_level,
), root)


@mcp.tool()
def get_surprising_connections_tool(
top_n: int = 15,
repo_root: Optional[str] = None,
detail_level: str = "standard",
) -> dict:
"""Find unexpected architectural coupling via composite surprise scoring.

Expand All @@ -882,12 +958,14 @@ def get_surprising_connections_tool(
unusual edge kinds (+0.15).

Args:
top_n: Number of top surprises to return. Default: 15.
top_n: Number of top surprises to return (capped at 100). Default: 15.
repo_root: Repository root path. Auto-detected if omitted.
detail_level: "standard" for full edge records, "minimal" for
source, target, kind, and score only. Default: standard.
"""
root = _resolve_repo_root(repo_root)
return with_provenance(get_surprising_connections_func(
repo_root=root, top_n=top_n,
repo_root=root, top_n=top_n, detail_level=detail_level,
), root)


Expand Down Expand Up @@ -956,6 +1034,7 @@ def cross_repo_search_tool(
query: str,
kind: Optional[str] = None,
limit: int = 20,
max_results: int = 50,
) -> dict:
"""Search for code entities across all registered repositories.

Expand All @@ -968,8 +1047,12 @@ def cross_repo_search_tool(
query: Search string to match against node names.
kind: Optional filter: File, Class, Function, Type, or Test.
limit: Maximum results per repo. Default: 20.
max_results: Maximum merged results across all repos; total reports
the untruncated merged count. Default: 50.
"""
return cross_repo_search_func(query=query, kind=kind, limit=limit)
return cross_repo_search_func(
query=query, kind=kind, limit=limit, max_results=max_results,
)


@mcp.prompt()
Expand Down
Loading