How Code-Graph-RAG Handles Duplicate Code Definitions and Method Overloading

Code-Graph-RAG treats duplicate code definitions—including method overloads—as distinct entities tagged with line-number markers, enabling precise detection of clones while preserving semantic relationships for accurate call resolution.

This article explains the architectural approach used by vitali87/code-graph-rag to identify, group, and resolve duplicate code definitions. The system handles everything from exact copy-paste clones to language-level overloading through a unified duplicate detection pipeline.

Duplicate Detection Architecture

The duplicate detection system operates through two complementary strategies implemented in [codebase_rag/duplicates.py](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/duplicates.py).

Exact-Copy Detection

Code-Graph-RAG first identifies structurally identical definitions by computing a whole-AST fingerprint—a cryptographic hash of the complete abstract syntax tree representation. Identical fingerprints guarantee identical code structure regardless of source location.

The collect_duplicates() function delegates to _exact_groups() for this phase:


# codebase_rag/duplicates.py (lines 61-78)

def _exact_groups(members: list[Member]) -> list[DuplicateGroup]:
    fingerprint_map: dict[str, list[Member]] = defaultdict(list)
    for m in members:
        fp = m.structural_fingerprint()
        fingerprint_map[fp].append(m)
    # Groups with >1 member are exact duplicates

    return [
        DuplicateGroup(kind="exact", similarity=1.0, members=ms)
        for fp, ms in fingerprint_map.items()
        if len(ms) > 1
    ]

This approach is computationally efficient—O(n) for n definitions—and catches copy-paste duplication without false negatives.

Similarity-Based Detection

For definitions that differ slightly (refactored parameters, renamed variables, modified bodies), Code-Graph-RAG employs branch-fingerprint similarity using Jaccard overlap:


# codebase_rag/duplicates.py (lines 41-50)

def _similar_groups(
    members: list[Member],
    threshold: float = 0.6
) -> list[DuplicateGroup]:
    # Build prefix index for efficient candidate generation

    index = PrefixIndex(m.branch_fingerprint for m in members)
    candidates = _candidate_pairs(index, threshold)
    # Find maximal cliques of mutually similar members

    cliques = _maximal_cliques(candidates, members)
    return [
        DuplicateGroup(kind="similar", similarity=score, members=clique)
        for clique, score in cliques
    ]

The AllPairs/PPJoin indexing strategy reduces the search space from O(n²) to near-linear for typical codebases.

Nested Member Pruning

To prevent false positives where an inner function appears "similar" to its containing outer function, the system drops contained members:


# codebase_rag/duplicates.py

def _drop_contained_members(members: list[Member]) -> list[Member]:
    return [
        m for m in members
        if not any(_member_nested_in(other, m) for other in members)
    ]

This ensures that legitimate nesting relationships don't trigger spurious duplicate reports.

The DUP_QN_MARKER System for Overloads

The core mechanism distinguishing duplicate definitions is the DUP_QN_MARKER suffix appended to qualified names.

Marker Definition

Located in [codebase_rag/constants/core.py](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/constants/core.py):


# codebase_rag/constants/core.py

DUP_QN_MARKER = "@"           # <qualified_name>@<start_line>

DUP_QN_COLUMN_MARKER = "_"    # Optional: @<line>_<column>

When a qualified name appears multiple times, each instance receives a unique marker encoding its source position.

Parser Integration

Java method resolution demonstrates the marker application in [codebase_rag/parsers/java/method_resolver.py](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/parsers/java/method_resolver.py):


# codebase_rag/parsers/java/method_resolver.py

def _resolve_method_declaration(node: Tree, ctx: ParseContext) -> Method:
    natural_qn = f"{ctx.current_type}.{node.name}"
    start_line = node.start_point[0] + 1  # 1-indexed

    
    # Append duplicate marker for overloads

    if ctx.is_overloaded(natural_qn):
        variant = f"{natural_qn}{cs.DUP_QN_MARKER}{start_line}"
    else:
        variant = natural_qn
    
    return Method(qualified_name=variant, ...)

This guarantees that Vehicle.start() on line 45 and Vehicle.start() on line 78 become Vehicle.start@45 and Vehicle.start@78 respectively.

Normalization for Semantic Comparison

Downstream components strip markers before hierarchical or signature-based comparisons:


# codebase_rag/duplicates.py

import re
from codebase_rag.constants import core as cs

_DUP_QN_MARKER_RE = re.compile(
    re.escape(cs.DUP_QN_MARKER) + r"\d+(?:" 
    + re.escape(cs.DUP_QN_COLUMN_MARKER) + r"\d+)?$"
)

def _qn_normalized(qn: str) -> str:
    """Remove overload/duplicate markers for semantic comparison."""
    return _DUP_QN_MARKER_RE.sub("", qn)

This dual representation enables both precision and flexibility: the marked qn preserves identity for graph storage, while the normalized form enables type hierarchy and signature matching.

Overload Resolution in Practice

The Java parser implements complete overload resolution through ranked candidate selection.

Candidate Collection and Ranking


# codebase_rag/parsers/java/method_resolver.py

def resolve_overload(
    base_qn: str,           # e.g., "Utils.toString"

    arg_types: tuple[str | None, ...],
    candidates: list[Method]
) -> Method | None:
    # Filter by normalized name

    overloads = [m for m in candidates 
                 if _qn_normalized(m.qn) == base_qn]
    
    # Score each candidate

    ranked = [
        (_overload_rank(m, arg_types), m) 
        for m in overloads
    ]
    ranked = [(score, m) for score, m in ranked if score is not None]
    
    return min(ranked, key=lambda x: x[0])[1] if ranked else None

def _overload_rank(method: Method, arg_types: tuple) -> int | None:
    params = method.parameter_types
    if len(params) != len(arg_types):
        return None  # Arity mismatch

    
    # Exact type matches score 0, inheritance distance scores higher

    score = sum(
        _inheritance_distance(arg, param) 
        for arg, param in zip(arg_types, params)
    )
    return score

The ranking algorithm prefers exact signature matches (score 0), then closest inheritance distance for polymorphic arguments.

Python @overload Handling

For Python's @overload decorator pattern, the parser treats each stub as a distinct definition:


# Example: Python overload stubs become separate graph nodes

from typing import overload

@overload
def process(data: bytes) -> str: ...

@overload
def process(data: str) -> bytes: ...

def process(data):  # Implementation (line 11)

    # runtime logic

    pass

Graph representation:

  • mymodule.process@3 → first overload (bytes → str)
  • mymodule.process@6 → second overload (str → bytes)
  • mymodule.process@11@11 → implementation (note: implementation may also receive marker if name collision detected)

The duplicate detection engine groups these via _qn_normalized(), while call resolution uses argument type inference to select the appropriate overload stub.

Duplicate Group Output Format

The final DuplicateGroup data structure carries complete provenance:

@dataclass
class DuplicateGroup:
    kind: Literal["exact", "similar"]
    similarity: float  # 1.0 for exact, 0.0-1.0 for similar

    members: list[Member]
    # Metadata for diagnostics

    skipped_symbols: list[str] = field(default_factory=list)
    truncated: bool = False

This enables downstream tools to:

  • Flag exact clones for refactoring prioritization
  • Review similar code for abstraction opportunities
  • Preserve overload relationships in API documentation

Summary

Frequently Asked Questions

How does Code-Graph-RAG distinguish between code clones and legitimate overloads?

Both clones and overloads receive DUP_QN_MARKER suffixes to create unique graph nodes. The distinction emerges in downstream processing: clones typically share fingerprints (exact) or high Jaccard similarity, while overloads have different signatures and participate in method resolution. The _qn_normalized() function treats them identically for type hierarchy queries.

What happens when more than two definitions share the same qualified name?

The marker system scales arbitrarily. Each additional definition receives its own <qn>@<line> entry. The duplicate detection engine forms maximal cliques of similar definitions, so a method with five overloads creates a single group containing all five variants if they're structurally similar, or multiple groups if some variants diverge significantly.

Does the marker system affect cross-language analysis?

The DUP_QN_MARKER constant is language-agnostic. Java, Python, and TypeScript parsers all apply the same convention, enabling unified duplicate detection across polyglot codebases. Language-specific overload rules (Java's subtyping vs. Python's nominal typing) are handled in their respective method resolvers before the generic duplicate grouping logic executes.

Can duplicate detection be tuned for specific similarity thresholds?

Yes. The _similar_groups() function accepts a threshold parameter defaulting to 0.6. Lower values detect more aggressive refactoring candidates; higher values restrict reports to near-identical code. This threshold propagates from CLI configuration through [duplicates.py](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/duplicates.py) without requiring code changes.

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 →