From 2aaa216825e93fa6bec616f743e5967182c744d5 Mon Sep 17 00:00:00 2001 From: Christopher Beaulieu Date: Sun, 17 May 2026 06:32:53 -0400 Subject: [PATCH] fix(analyze): exclude npm dep-block keys from god-node selection (#905) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(analyze): exclude npm dep-block keys from god-node selection Extends _JSON_NOISE_LABELS in graphify/analyze.py with the six npm package.json dependency-block keys (dependencies, devDependencies, peerDependencies, optionalDependencies, bundledDependencies, bundleDependencies — lowercased to match the existing .strip().lower() comparison in _is_json_key_node). On JS/TS corpora with non-trivial dependency counts, the dep-block key node accumulates contains+imports edges to every package entry and was surfacing as the top god-node in the report. The fix is a one-line extension of the existing frozenset; no new helpers or code paths. Includes a parametrized regression test in tests/test_analyze.py covering all five npm keys: dependencies, devDependencies, peerDependencies, optionalDependencies, bundledDependencies. Validated live against rsl-siege-manager @ 6085fd66 — zero npm dep-block keys in the top-10 god-nodes after the fix. Pre-existing failures (Windows symlink + SQL tree-sitter): 17, unchanged. Closes #2 Co-Authored-By: Claude Sonnet 4.6 * style: drop fork-local issue refs from analyze comments --------- Co-authored-by: Claude Sonnet 4.6 --- graphify/analyze.py | 2 ++ tests/test_analyze.py | 80 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/graphify/analyze.py b/graphify/analyze.py index f385d5e..b72f401 100644 --- a/graphify/analyze.py +++ b/graphify/analyze.py @@ -68,6 +68,8 @@ def _is_file_node(G: nx.Graph, node_id: str) -> bool: _JSON_NOISE_LABELS: frozenset[str] = frozenset({ "start", "end", "name", "id", "type", "properties", "value", "key", "data", "items", "title", "description", "version", + "dependencies", "devdependencies", "peerdependencies", + "optionaldependencies", "bundleddependencies", "bundledependencies", }) diff --git a/tests/test_analyze.py b/tests/test_analyze.py index 1e48c95..d24b617 100644 --- a/tests/test_analyze.py +++ b/tests/test_analyze.py @@ -1,6 +1,7 @@ """Tests for analyze.py.""" import json import networkx as nx +import pytest from pathlib import Path from graphify.build import build_from_json from graphify.cluster import cluster @@ -457,6 +458,85 @@ def test_is_json_key_node_non_json_file(): assert _is_json_key_node(G, "n1") is False +# --- npm dep-block key god-node filtering tests --- + +@pytest.mark.parametrize("dep_key", [ + "dependencies", + "devDependencies", + "peerDependencies", + "optionalDependencies", + "bundledDependencies", +]) +def test_god_nodes_excludes_npm_dep_block_keys(dep_key: str) -> None: + """npm package.json dep-block keys must be filtered from god_nodes output. + + Constructs a small graph with one node labelled with an npm dep-block key + (sourced from a .json file) and one real-domain node that has high degree. + Asserts that god_nodes() excludes the dep-block node even when it has the + highest degree, while the real-domain node is included. + + Args: + dep_key: The npm dependency-block key label to test (parametrized). + """ + G = nx.Graph() + # Real-domain node with a realistic source file. + G.add_node( + "real_node", + label="AuthService", + source_file="src/auth.py", + file_type="code", + source_location="L1", + ) + # npm dep-block key node — sourced from a JSON file so _is_json_key_node fires. + G.add_node( + "dep_node", + label=dep_key, + source_file="frontend/package.json", + file_type="code", + source_location="L1", + ) + # Wire up enough edges so dep_node has high degree — it would be a god-node + # without the filter. + for i in range(20): + peer = f"pkg_{i}" + G.add_node( + peer, + label=f"package-{i}", + source_file="frontend/package.json", + file_type="code", + source_location=f"L{i + 2}", + ) + G.add_edge( + "dep_node", + peer, + relation="contains", + confidence="EXTRACTED", + source_file="frontend/package.json", + weight=1.0, + ) + # Give real_node a couple of edges too. + G.add_edge( + "real_node", + "dep_node", + relation="imports", + confidence="EXTRACTED", + source_file="src/auth.py", + weight=1.0, + ) + + result = god_nodes(G, top_n=10) + result_ids = [r["id"] for r in result] + + assert "dep_node" not in result_ids, ( + f"god_nodes() should filter npm dep-block key '{dep_key}' " + f"but it appeared in the result: {result}" + ) + assert "real_node" in result_ids, ( + f"god_nodes() should include real-domain node 'AuthService' " + f"but it was absent: {result}" + ) + + def test_is_json_key_node_real_label(): G = nx.Graph() G.add_node("j2", label="UserProfile", source_file="schema.json")