From 2ab2302112ff1ef81785e6accbb078a60292863c Mon Sep 17 00:00:00 2001 From: s22hyun Date: Fri, 12 Jun 2026 15:33:33 +0900 Subject: [PATCH] fix(affected): handle "edges"-keyed graph.json in load_graph (KeyError: 'links') --- graphify/affected.py | 6 ++++++ tests/test_affected_cli.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/graphify/affected.py b/graphify/affected.py index 80bdc204..d73c2c0c 100644 --- a/graphify/affected.py +++ b/graphify/affected.py @@ -165,6 +165,12 @@ def load_graph(path: Path) -> nx.Graph: # Force directed so stored caller→callee direction survives the round-trip; # mirrors serve.py and __main__.py (#1174). raw = {**raw, "directed": True} + # Normalize the edge key: graphify's `extract` output uses "edges" while + # networkx's node_link_data default is "links". Without this, an edges-keyed + # graph.json raises an uncaught KeyError: 'links' here — every other loader + # (__main__.py) already normalizes this (#738; same class as #1198). + if "links" not in raw and "edges" in raw: + raw = dict(raw, links=raw["edges"]) try: return json_graph.node_link_graph(raw, edges="links") except TypeError: diff --git a/tests/test_affected_cli.py b/tests/test_affected_cli.py index 40e0d68e..7a332850 100644 --- a/tests/test_affected_cli.py +++ b/tests/test_affected_cli.py @@ -94,6 +94,36 @@ def test_affected_cli_forces_directed_on_undirected_graph(monkeypatch, tmp_path, assert "No affected nodes found." not in out +def test_affected_cli_loads_edges_keyed_graph(monkeypatch, tmp_path, capsys): + """graphify's `extract` writes graph.json with an "edges" key (not networkx's + default "links"). affected.load_graph must handle it; before the edges/links + normalization it raised an uncaught KeyError: 'links' (same class as #1198).""" + graph = nx.DiGraph() + graph.add_node("target", label="Foo", source_file="pkg/foo.py", source_location="L1") + graph.add_node("caller", label="X()", source_file="app.py", source_location="L4") + graph.add_edge("caller", "target", relation="calls", context="call", confidence="EXTRACTED") + + # Emulate graphify extract output: top-level "edges" key instead of "links". + data = json_graph.node_link_data(graph, edges="links") + data["edges"] = data.pop("links") + graph_path = tmp_path / "graph.json" + graph_path.write_text(json.dumps(data), encoding="utf-8") + + monkeypatch.setattr(mainmod, "_check_skill_version", lambda _: None) + monkeypatch.setattr( + mainmod.sys, + "argv", + ["graphify", "affected", "Foo", "--graph", str(graph_path)], + ) + + mainmod.main() + + out = capsys.readouterr().out + assert "Affected nodes for Foo" in out + assert "X()" in out + assert "calls" in out + + def test_resolve_seed_bare_name_matches_callable_label(): from graphify.affected import resolve_seed