How the Refactor Tool Detects Dead Code Across Communities: A Complete Technical Guide

The refactor tool detects dead code by analyzing the graph store representation of a repository, applying multi-phase filtering to exclude intentionally reachable symbols, and then verifying the absence of any CALLS, TESTED_BY, IMPORTS_FROM, REFERENCES, or INHERITS edges.

The code-review-graph repository provides a sophisticated dead-code detection system that goes beyond simple reachability analysis. This article breaks down exactly how the tool identifies unused symbols and generates community-aware refactoring suggestions, with direct references to the source implementation in code_review_graph/refactor.py.

How Dead Code Detection Works in Three Phases

The core detection logic resides in the find_dead_code function. The algorithm proceeds through three distinct phases before determining whether a symbol is truly dead.

Phase 1: Candidate Gathering

The tool begins by collecting all potential targets from the graph store. Any node of kind Function or Class becomes a candidate for dead-code analysis.


# From code_review_graph/refactor.py, lines 75-78

def find_dead_code(store, kind=None):
    if kind:
        candidates = store.get_nodes_by_kind(kind)
    else:
        candidates = store.get_nodes_by_kind("Function") + \
                     store.get_nodes_by_kind("Class")

This query returns every symbol defined in the codebase, regardless of whether it's actually used.

Phase 2: Filtering Out Known-Alive Symbols

The tool applies ten distinct filters to remove symbols that are intentionally reachable, even without explicit caller edges. These safety filters prevent false positives on framework code, test infrastructure, and language constructs.

Filter Purpose Implementation
Test files Exclude *_test.py, test_*.py, conftest.py _is_test_file (lines 56-84)
Dunder methods Preserve __init__, __repr__, __str__, etc. Pattern matching (lines 90-104)
Constructors Detect new ClassName() invocations Constructor edge analysis (lines 115-124)
Mock/stub variables Skip variables matching _MOCK_NAME_RE Regex pattern _MOCK_NAME_RE (lines 129-138)
Framework base classes Exclude subclasses of Django, Flask, FastAPI bases _FRAMEWORK_BASE_CLASSES (lines 144-155)
CDK/IaC classes Preserve classes with CDK suffixes _CDK_CLASS_SUFFIXES (lines 166-176)
Type annotations Keep types referenced in signatures _collect_type_referenced_names (lines 184-199)
Entry points Detect main(), cli(), @click.command(), etc. _is_entry_point (lines 206-226)
Framework decorators Preserve @property, @abstractmethod, @staticmethod _has_framework_decorator (lines 232-254)
Abstract overrides & dataclasses Keep protocol implementations Special-case handling (lines 258-306)

Each filter is implemented as an early-exit check. If any filter matches, the symbol is immediately marked as alive and removed from further consideration.

Phase 3: Final Deadness Verification

After filtering, remaining candidates undergo strict reachability verification. The tool checks for five edge types that indicate usage:

  • CALLS: Another function invokes this symbol
  • TESTED_BY: A test case exercises this symbol
  • IMPORTS_FROM: Another module imports from this symbol
  • REFERENCES: Static references exist without direct calls
  • INHERITS: Class inheritance relationships

# Simplified logic from lines 374-418, 420-463, 465-511

def _is_actually_dead(node, store):
    # Check all usage edge types

    if store.get_edges_from(node.qn, "CALLS"): return False
    if store.get_edges_from(node.qn, "TESTED_BY"): return False
    if store.get_edges_from(node.qn, "IMPORTS_FROM"): return False
    if store.get_edges_from(node.qn, "REFERENCES"): return False
    if store.get_edges_from(node.qn, "INHERITS"): return False
    
    # For classes: check if any member has callers

    if node.kind == "Class":
        for member in node.members:
            if store.get_edges_from(member.qn, "CALLS"):
                return False
    
    # For methods: polymorphic dispatch check

    if node.kind == "Function" and node.parent_class:
        base_callers = _find_base_class_callers(node, store)
        if base_callers:
            return False
    
    return True

The polymorphic dispatch check is particularly important: if a method overrides a base class implementation, the tool verifies whether the base method has callers, since those calls may dynamically dispatch to the override.

Community-Aware Refactoring: Moving Beyond Dead Code Detection

The dead-code detection described above is community-agnostic—it operates purely on graph connectivity. The community-aware features emerge in suggest_refactorings, which extends the analysis to detect misplaced code.

How Cross-Community Detection Works

The suggest_refactorings function (lines 631-692, 702-726, 732-754) implements a two-pass approach:

  1. Collect dead symbols using find_dead_code described above
  2. Analyze live symbols for community misalignment

# From code_review_graph/refactor.py, lines 631-692

def suggest_refactorings(store):
    suggestions = []
    
    # Pass 1: Dead code → remove suggestions

    dead = find_dead_code(store)
    for symbol in dead:
        suggestions.append({
            "type": "remove",
            "symbol": symbol["qualified_name"],
            "description": f"Remove unused {symbol['kind']} {symbol['qualified_name']}"
        })
    
    # Pass 2: Community misalignment → move suggestions

    communities = store.get_communities_list()          # lines 702-726

    node_to_community = _build_community_map(store)     # lines 732-754

    
    for func in store.get_nodes_by_kind("Function"):
        f_community = node_to_community.get(func.qn)
        caller_communities = _get_caller_communities(func, store)
        
        # All callers belong to ONE different community

        if len(caller_communities) == 1 and \
           f_community not in caller_communities:
            target_community = list(caller_communities)[0]
            suggestions.append({
                "type": "move",
                "symbol": func.qn,
                "from_community": f_community,
                "to_community": target_community,
                "description": f"Move {func.qn} from {f_community} to {target_community}"
            })
    
    return suggestions

A move suggestion is generated when:

  • The function has at least one caller (it's not dead)
  • All callers reside in a single community
  • That community differs from the function's current community

This pattern indicates a cohesion violation: the implementation lives in one logical grouping but all its consumers live elsewhere.

Practical Usage Examples

Finding Dead Code in a Python Project

from code_review_graph.graph import GraphStore
from code_review_graph.refactor import find_dead_code

store = GraphStore()
store.load_from_path("./my-project")

dead = find_dead_code(store)
for symbol in dead:
    print(f"{symbol['kind']} {symbol['qualified_name']} is dead")
    # Example output:

    # Function my_project.utils.legacy_parser is dead

    # Class my_project.models.DeprecatedSchema is dead

Generating Community-Aware Refactoring Suggestions

from code_review_graph.refactor import suggest_refactorings

suggestions = suggest_refactorings(store)
for s in suggestions:
    print(f"{s['type'].capitalize()}: {s['description']}")
    # Example output:

    # Remove: Remove unused Function my_project.old_api.v1_handler

    # Move: Move my_project.auth.helpers.token_decoder from auth to api_gateway

The move suggestion in this example indicates that token_decoder is defined in the auth community but only called from api_gateway—a clear signal for reorganization.

Preview and Apply Refactorings Safely

from code_review_graph.refactor import rename_preview, apply_refactor
import pathlib

# Preview any refactoring type (not just renames)

preview = rename_preview(store, "oldFunc", "newFunc")
if not preview:
    print("No changes needed or symbol not found")
    exit()

# Always dry-run first

dry_result = apply_refactor(
    preview["refactor_id"], 
    pathlib.Path("."), 
    dry_run=True
)

for filepath, diff in dry_result["diffs"].items():
    print(f"\n=== {filepath} ===")
    print(diff)

# Apply only after review

real_result = apply_refactor(preview["refactor_id"], pathlib.Path("."))
print(f"Applied {real_result['edits_applied']} edits across {real_result['files_modified']} files")

Key Implementation Files

File Role in Dead Code Detection
code_review_graph/refactor.py Core implementation: find_dead_code, suggest_refactorings, preview/apply workflow
code_review_graph/flows.py Entry-point detection helpers: _has_framework_decorator, _matches_entry_name
code_review_graph/communities.py Community definitions and member lookups for move suggestions
tests/test_refactor.py Unit tests for dead-code detection and preview-apply cycle
tests/test_python_reachability.py Python-specific dead-code test cases

Summary

  • Dead code detection operates in three phases: candidate gathering, alive-symbol filtering via ten safety checks, and final edge-based verification
  • Community-aware analysis extends dead-code detection to identify symbols that exist in the wrong logical grouping, generating move suggestions when all callers belong to a different community
  • Framework-aware filters prevent false positives on test code, decorators, entry points, and language constructs like __init__ and abstract methods
  • Polymorphic dispatch handling ensures method overrides are not incorrectly flagged when base-class callers exist

Frequently Asked Questions

How does the tool avoid false positives on dead code detection?

The tool implements ten distinct safety filters in code_review_graph/refactor.py lines 56-306. These filters recognize test files, dunder methods, constructors, framework decorators, entry points, and type annotations as intentionally reachable patterns. Only symbols passing all filters undergo the final edge-based deadness check.

What edge types indicate a symbol is alive?

The verification phase checks for five edge types: CALLS (direct invocation), TESTED_BY (test coverage), IMPORTS_FROM (module imports), REFERENCES (static references), and INHERITS (class inheritance). For classes, member function callers are also verified. For methods, base-class callers are checked to handle polymorphic dispatch.

When does the tool suggest moving code versus removing it?

Remove suggestions apply to symbols with zero incoming edges after all filters. Move suggestions apply to symbols with callers that all reside in a single different community—defined by the communities table in the graph store. This distinction helps teams reorganize monorepos while preserving functionality.

Can the dead code detection handle dynamic Python features?

Yes, though with limitations. The polymorphic dispatch check (lines 465-511) handles method overrides by tracing to base-class implementations. However, highly dynamic patterns like getattr dispatch, eval usage, or runtime module injection may escape detection. The tool prioritizes soundness over completeness—it may miss some dead code but rarely flags live code as dead.

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 →