How Refactoring Tools for Rename Preview and Dead Code Detection Work in code‑review‑graph

The refactoring tools in code‑review‑graph operate through a graph‑aware module that analyzes static relationships to preview symbol renames and identify unused code without executing the program.

The code_review_graph.refactor module provides the core algorithms, while user‑facing wrappers in code_review_graph/tools/refactor_tools.py expose these capabilities to developers. These refactoring tools for rename preview and dead code detection leverage the GraphStore populated by the parser to perform purely static analysis, avoiding filesystem operations until changes are explicitly applied.

Architecture Overview

Both tools share a common foundation in the graph store abstraction. The GraphStore provides fast edge queries (get_edges_by_target, search_edges_by_target_name) and node lookups, enabling the tools to trace symbol relationships across the entire codebase. All operations remain in‑memory until a refactor is applied, ensuring safe, preview‑first workflows.

The entry points refactor_func(mode="rename") and refactor_func(mode="dead_code") in code_review_graph/tools/refactor_tools.py delegate to rename_preview and find_dead_code respectively, handling input validation and result formatting.

Rename Preview Tool

The rename preview functionality generates a complete list of edits required to safely rename a symbol across the codebase.

Graph‑Based Symbol Resolution

When rename_preview (defined in code_review_graph/refactor.py) receives an old_name and new_name, it first queries the graph store to locate the target symbol:

candidates = store.search_nodes(old_name, limit=10)
node = next((c for c in candidates if c.name == old_name), candidates[0] if candidates else None)

The function then traverses CALLS and IMPORTS_FROM edges to collect every definition site, call site, and import reference for the symbol.

Edit Generation and Storage

For each discovered location, the tool constructs an edit dictionary specifying the file path, line number, old text, new text, and confidence level:

edits = [{
    "file": node.file_path,
    "line": node.line_start,
    "old": old_name,
    "new": new_name,
    "confidence": "high",
}]

The preview is stored in a thread‑safe dictionary _pending_refactors protected by _refactor_lock, keyed by a unique 8‑character refactor_id generated via uuid.uuid4().hex[:8]. This allows subsequent application or inspection of the planned changes without immediately modifying source files.

Dead Code Detection Tool

The find_dead_code function identifies functions and classes that lack any meaningful inbound relationships, indicating they are unreachable or unused.

Filtering Heuristics

Before analyzing relationships, the tool aggressively filters out symbols that are clearly not dead code. In code_review_graph/refactor.py, the implementation skips:

  • Test nodes and files (detected via node.is_test or _is_test_file)
  • Dunder methods (__init__, __str__, etc.)
  • Entry points (functions matching _is_entry_point patterns)
  • Framework‑managed classes (ORM models, Pydantic schemas, CDK constructs, Angular/NestJS components)
  • Symbols referenced only in type annotations
  • Constructors and mock/stub variables

Edge Analysis and Caller Validation

For remaining candidates, the tool inspects incoming edges of kinds CALLS, TESTED_BY, IMPORTS_FROM, REFERENCES, and INHERITS. It also builds auxiliary structures including a type‑referenced name set, class hierarchy map, and import graph to validate bare‑name call edges:

incoming = store.get_edges_by_target(node.qualified_name)

# augment with bare-name edges that pass plausibility check

if not any(e.kind == "CALLS" for e in incoming):
    bare = store.search_edges_by_target_name(node.name, kind="CALLS")
    incoming += [e for e in bare if _is_plausible_caller(e.file_path, node.file_path, node.name)]

A node is marked dead only if it lacks all relationship types and has no plausible callers. The resulting report includes sanitized metadata: name, qualified_name, kind, file_path, and line.

Practical Usage Examples

Generate a rename preview before applying changes:

from code_review_graph.tools.refactor_tools import refactor_func, apply_refactor_func

# Preview renaming a function

preview = refactor_func(
    mode="rename",
    old_name="old_func",
    new_name="new_func",
)
print(preview["summary"])

# Output: "Rename preview: old_func -> new_func, 12 edit(s). Use apply_refactor_tool(... ) to apply."

Detect dead code across the repository:


# List dead symbols

dead = refactor_func(mode="dead_code")
print(f"Found {dead['total']} dead symbols")
for sym in dead["dead_code"]:
    print(f"{sym['kind']}: {sym['qualified_name']} ({sym['file_path']}:{sym['line']})")

Apply a stored refactor with dry‑run verification:


# Dry run to see diff

dry = apply_refactor_func(refactor_id=preview["refactor_id"], dry_run=True)
print("\n".join(dry["diffs"].values()))

# Apply changes

result = apply_refactor_func(refactor_id=preview["refactor_id"], dry_run=False)
print(result["summary"])

Summary

  • Rename preview traces symbol relationships through the graph to generate comprehensive edit lists, storing previews in a thread‑safe dictionary keyed by unique IDs.
  • Dead code detection combines aggressive filtering heuristics with multi‑edge relationship analysis to identify unused functions and classes without false positives from framework code or tests.
  • Both tools reside in code_review_graph/refactor.py and are exposed through code_review_graph/tools/refactor_tools.py, utilizing the GraphStore for purely static analysis.

Frequently Asked Questions

How does the rename preview tool handle symbol conflicts?

The tool validates symbol existence through store.search_nodes and confidence scores. If multiple candidates match the old name, it selects the exact match or the first candidate, allowing developers to verify the preview before application rather than guessing intent.

Can dead code detection distinguish between test utilities and production dead code?

Yes. The implementation explicitly filters nodes where is_test is true or file_path matches test patterns. It also excludes mock objects, stub variables, and symbols within test directories, ensuring production‑only analysis while preserving testing infrastructure.

What edge types indicate a symbol is actually used?

According to the source code in code_review_graph/refactor.py, the presence of CALLS, TESTED_BY, IMPORTS_FROM, REFERENCES, or INHERITS edges prevents a symbol from being marked dead. The tool also validates bare‑name calls through import relationships using _is_plausible_caller.

Is the refactor preview storage persistent across sessions?

No. The _pending_refactors dictionary exists only in memory and includes expiration handling via _cleanup_expired(). Previews must be applied during the same session they are generated, or the refactor_id becomes invalid.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →