From d14e8a72d1a8042e19993d65423f7b69285a7056 Mon Sep 17 00:00:00 2001 From: Safi Date: Sat, 16 May 2026 00:07:18 +0100 Subject: [PATCH] Suppress cross-language INFERRED calls/uses edges in surprising connections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Python+TypeScript monorepos, the call and import resolvers match by label across language boundaries (AuthError -> Member), producing false positives that dominate 'Surprising Connections' due to cross-dir (+2) and cross-community (+1) bonuses. Expand the existing calls guard to also cover uses edges, and zero the cross-dir and cross-community bonuses for these pairs — not just conf_bonus. Leaves semantically_similar_to, EXTRACTED, and AMBIGUOUS edges unaffected. 5 new tests: calls suppressed, uses suppressed, semantically_similar_to preserved, same-language INFERRED preserved, cross-language EXTRACTED preserved. --- graphify/analyze.py | 21 +++++++--- tests/test_analyze.py | 92 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/graphify/analyze.py b/graphify/analyze.py index 43948bfd..6845567d 100644 --- a/graphify/analyze.py +++ b/graphify/analyze.py @@ -175,9 +175,20 @@ def _surprise_score( relation = data.get("relation", "") conf_bonus = {"AMBIGUOUS": 3, "INFERRED": 2, "EXTRACTED": 1}.get(conf, 1) - # Cross-language INFERRED calls are likely resolver pollution, not real surprises - if conf == "INFERRED" and relation == "calls" and _cross_language(u_source, v_source): - conf_bonus = 0 # downgrade: don't promote likely false positives + # Cross-language INFERRED calls/uses edges are resolver pollution in monorepos: + # the call and import resolvers match by label across language boundaries, so + # a Python `AuthError` resolves to a TypeScript `Member` purely by name. + # Zero all structural bonuses for these — they would otherwise score 4-5 from + # cross-dir + cross-community and dominate "Surprising Connections". + # Excludes `semantically_similar_to` (LLM-emitted, explicitly cross-language + # insight) and all AMBIGUOUS/EXTRACTED edges (not from the resolver path). + _suppress_structural = ( + conf == "INFERRED" + and relation in ("calls", "uses") + and _cross_language(u_source, v_source) + ) + if _suppress_structural: + conf_bonus = 0 score += conf_bonus if conf in ("AMBIGUOUS", "INFERRED"): @@ -191,14 +202,14 @@ def _surprise_score( reasons.append(f"crosses file types ({cat_u} ↔ {cat_v})") # 3. Cross-repo bonus - different top-level directory - if _top_level_dir(u_source) != _top_level_dir(v_source): + if _top_level_dir(u_source) != _top_level_dir(v_source) and not _suppress_structural: score += 2 reasons.append("connects across different repos/directories") # 4. Cross-community bonus - Leiden says these are structurally distant cid_u = node_community.get(u) cid_v = node_community.get(v) - if cid_u is not None and cid_v is not None and cid_u != cid_v: + if cid_u is not None and cid_v is not None and cid_u != cid_v and not _suppress_structural: score += 1 reasons.append("bridges separate communities") diff --git a/tests/test_analyze.py b/tests/test_analyze.py index 1017da8b..540c7404 100644 --- a/tests/test_analyze.py +++ b/tests/test_analyze.py @@ -123,6 +123,98 @@ def test_surprising_connections_cross_type_scores_higher(): assert any("code" in r and "paper" in r for r in reasons_cross) +def _make_cross_lang_graph(): + """Helper: Python node in backend/, TypeScript node in frontend/, different communities.""" + G = nx.Graph() + G.add_node("py_auth", label="AuthError", source_file="backend/auth.py", file_type="code") + G.add_node("ts_member", label="Member", source_file="frontend/types.ts", file_type="code") + G.add_node("py_a", label="ServiceA", source_file="backend/service.py", file_type="code") + G.add_node("py_b", label="ServiceB", source_file="backend/utils.py", file_type="code") + return G + + +def test_cross_language_inferred_calls_suppressed(): + """Cross-language INFERRED calls edge should score lower than same-language EXTRACTED.""" + G = _make_cross_lang_graph() + G.add_edge("py_auth", "ts_member", relation="calls", confidence="INFERRED", + weight=0.8, source_file="backend/auth.py") + G.add_edge("py_a", "py_b", relation="calls", confidence="EXTRACTED", + weight=1.0, source_file="backend/service.py") + nc = {"py_auth": 0, "ts_member": 1, "py_a": 0, "py_b": 0} + score_cross, _ = _surprise_score(G, "py_auth", "ts_member", + G.edges["py_auth", "ts_member"], nc, + "backend/auth.py", "frontend/types.ts") + score_same, _ = _surprise_score(G, "py_a", "py_b", + G.edges["py_a", "py_b"], nc, + "backend/service.py", "backend/utils.py") + assert score_cross <= score_same + + +def test_cross_language_inferred_uses_suppressed(): + """Cross-language INFERRED uses edge (the exact rsl-siege-manager false positive) should be suppressed.""" + G = _make_cross_lang_graph() + G.add_edge("py_auth", "ts_member", relation="uses", confidence="INFERRED", + weight=0.8, source_file="backend/auth.py") + G.add_edge("py_a", "py_b", relation="calls", confidence="EXTRACTED", + weight=1.0, source_file="backend/service.py") + nc = {"py_auth": 0, "ts_member": 1, "py_a": 0, "py_b": 0} + score_cross, _ = _surprise_score(G, "py_auth", "ts_member", + G.edges["py_auth", "ts_member"], nc, + "backend/auth.py", "frontend/types.ts") + score_same, _ = _surprise_score(G, "py_a", "py_b", + G.edges["py_a", "py_b"], nc, + "backend/service.py", "backend/utils.py") + assert score_cross <= score_same + + +def test_cross_language_semantically_similar_not_suppressed(): + """`semantically_similar_to` across languages is a genuine insight — must not be suppressed.""" + G = _make_cross_lang_graph() + G.add_edge("py_auth", "ts_member", relation="semantically_similar_to", + confidence="INFERRED", weight=0.85, source_file="backend/auth.py") + G.add_edge("py_a", "py_b", relation="calls", confidence="EXTRACTED", + weight=1.0, source_file="backend/service.py") + nc = {"py_auth": 0, "ts_member": 1, "py_a": 0, "py_b": 0} + score_sem, _ = _surprise_score(G, "py_auth", "ts_member", + G.edges["py_auth", "ts_member"], nc, + "backend/auth.py", "frontend/types.ts") + score_same, _ = _surprise_score(G, "py_a", "py_b", + G.edges["py_a", "py_b"], nc, + "backend/service.py", "backend/utils.py") + assert score_sem > score_same + + +def test_same_language_inferred_calls_not_suppressed(): + """INFERRED calls within the same language family must not be affected.""" + G = nx.Graph() + G.add_node("py_a", label="ModuleA", source_file="src/a.py", file_type="code") + G.add_node("py_b", label="ModuleB", source_file="src/b.py", file_type="code") + G.add_node("py_c", label="ModuleC", source_file="src/c.py", file_type="code") + G.add_node("py_d", label="ModuleD", source_file="src/d.py", file_type="code") + G.add_edge("py_a", "py_b", relation="calls", confidence="INFERRED", + weight=0.8, source_file="src/a.py") + G.add_edge("py_c", "py_d", relation="calls", confidence="EXTRACTED", + weight=1.0, source_file="src/c.py") + nc = {"py_a": 0, "py_b": 1, "py_c": 0, "py_d": 1} + score_inf, _ = _surprise_score(G, "py_a", "py_b", G.edges["py_a", "py_b"], nc, + "src/a.py", "src/b.py") + score_ext, _ = _surprise_score(G, "py_c", "py_d", G.edges["py_c", "py_d"], nc, + "src/c.py", "src/d.py") + assert score_inf > score_ext + + +def test_cross_language_extracted_calls_not_suppressed(): + """EXTRACTED cross-language edges are real structural facts — must not be penalised.""" + G = _make_cross_lang_graph() + G.add_edge("py_auth", "ts_member", relation="calls", confidence="EXTRACTED", + weight=1.0, source_file="backend/auth.py") + nc = {"py_auth": 0, "ts_member": 1} + score, _ = _surprise_score(G, "py_auth", "ts_member", + G.edges["py_auth", "ts_member"], nc, + "backend/auth.py", "frontend/types.ts") + assert score >= 1 + + def test_surprising_connections_have_why_field(): G = make_graph() communities = cluster(G)