Skip to content

detect_clones embeds symbol name+docstring, never the actual function body — finds name-alike functions, misses real code duplication #33

Description

@murari316

Summary

detect_clones (crates/infigraph-mcp/src/tools/analysis/clones.rs, tool_detect_clones) is described as "vector similarity" clone detection, but the embedding it computes similarity over is never the function's actual implementation — only its name and docstring:

let text = if !s.docstring.is_empty() {
    format!("{} {}: {}", s.kind, s.name, s.docstring)
} else {
    format!("{} {}", s.kind, s.name)
};
let emb = ... embedder.embed(&text) ...

This means two functions with similar-sounding names and docstrings but completely different bodies will score as "clones," while two functions that are byte-for-byte (or near-identical) copy-paste implementations under differently-named symbols with no/different docstrings will score as unrelated and never surface — which is exactly the case the tool exists to catch (its own doc comment says "identify copy-paste code and refactoring opportunities").

Root cause

embed() is a generic text: &str -> Vec<f32> embedding call (EmbedProvider::embed, crates/infigraph-core/src/embed/mod.rs) — it will happily embed anything it's given. detect_clones simply never gives it the source code. There is no structural or body-text signal anywhere in the current pipeline for this feature.

Why this is cheap to fix, not just theoretically nice

The actual source body for every symbol is already trivially available without any new AST work: every Symbol has start_line/end_line and a resolvable file path, and get_code_snippet (tool_get_code_snippet in crates/infigraph-mcp/src/tools/graph.rs) already does exactly this lookup via infigraph_core::search::read_lines_from_file(&file_path, detail.start_line, detail.end_line). detect_clones can reuse that same helper to pull each symbol's real body before embedding it, instead of (or in addition to) the name/docstring text it uses today.

Proposed fix

  1. In tool_detect_clones, read each candidate symbol's actual source body via read_lines_from_file (same call get_code_snippet already makes), and embed that — either instead of, or concatenated with, the current kind+name+docstring text.
  2. Consider exposing both signals as separate, combinable scores (e.g. name_similarity vs body_similarity) rather than collapsing them into one number — a near-identical body with a differently-named symbol (the real "copy-paste and renamed" case) is arguably the single most valuable clone class to catch, and it's specifically the one the current implementation is blind to.
  3. Normalize whitespace/formatting differences before embedding the body (or rely on the embedder's own robustness to this) so trivial reformatting doesn't tank similarity scores for otherwise-identical logic.
  4. Add a regression test: two functions with different names/no docstrings but identical (or near-identical, e.g. renamed local variables) bodies must be detected as a clone pair above the default threshold; two functions with similar names/docstrings but unrelated bodies must NOT be detected as a clone pair.

Scope note

This does not require touching tool_refactor (crates/infigraph-core/src/refactor), which is a separate consumer of the embeddings pipeline — but worth checking during implementation whether it has the same name/docstring-only blind spot, since it's built on the same embed_path/embeddings.bin cache.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions