From 85de47e1214a0ecb9fa4e988be3912228db6ae93 Mon Sep 17 00:00:00 2001 From: Safi Date: Sat, 13 Jun 2026 19:34:18 +0100 Subject: [PATCH] fix: community names blank in query/MCP after cluster-only; --graph for graphify-mcp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit export.py: to_json now accepts community_labels and writes community_name onto each node. Previously cluster-only wrote labels only to GRAPH_REPORT.md, graph.html, and .graphify_labels.json — graph.json stored only the numeric cid, so query/MCP showed blank or numeric community values (#1305). __main__.py: pass community_labels=labels to to_json in cluster-only path. explain command now prefers community_name over raw numeric community field. serve.py: query and get_node read paths prefer community_name over community, with fallback so old graphs without the field still work. Adds --graph flag as an alias for the positional argument in graphify-mcp/_main(), fixing "unrecognized arguments: --graph" for users following the documented pattern shared by every other graphify subcommand (#1304). Co-Authored-By: Claude Sonnet 4.6 --- graphify/__main__.py | 4 ++-- graphify/export.py | 8 ++++++-- graphify/serve.py | 18 +++++++++++++----- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/graphify/__main__.py b/graphify/__main__.py index a5b1bd183..50d61fcd5 100644 --- a/graphify/__main__.py +++ b/graphify/__main__.py @@ -2965,7 +2965,7 @@ def main() -> None: f" Source: {d.get('source_file', '')} {d.get('source_location', '')}".rstrip() ) print(f" Type: {d.get('file_type', '')}") - print(f" Community: {d.get('community', '')}") + print(f" Community: {d.get('community_name') or d.get('community', '')}") print(f" Degree: {G.degree(nid)}") from graphify.build import edge_data connections: list[tuple[str, str, dict]] = [] # (direction, neighbor_id, edge_data) @@ -3275,7 +3275,7 @@ def main() -> None: (out / "GRAPH_REPORT.md").write_text(report, encoding="utf-8") from graphify.export import backup_if_protected as _backup _backup(out) - to_json(G, communities, str(out / "graph.json")) + to_json(G, communities, str(out / "graph.json"), community_labels=labels) labels_path.write_text(json.dumps({str(k): v for k, v in labels.items()}, ensure_ascii=False), encoding="utf-8") # Mirror watch.py pattern: gate to_html so core outputs (graph.json + diff --git a/graphify/export.py b/graphify/export.py index d01911485..50faa50d1 100644 --- a/graphify/export.py +++ b/graphify/export.py @@ -481,7 +481,7 @@ def _git_head() -> str | None: return None -def to_json(G: nx.Graph, communities: dict[int, list[str]], output_path: str, *, force: bool = False, built_at_commit: str | None = None) -> bool: +def to_json(G: nx.Graph, communities: dict[int, list[str]], output_path: str, *, force: bool = False, built_at_commit: str | None = None, community_labels: dict[int, str] | None = None) -> bool: # Safety check: refuse to silently shrink an existing graph (#479) existing_path = Path(output_path) if not force and existing_path.exists(): @@ -508,12 +508,16 @@ def to_json(G: nx.Graph, communities: dict[int, list[str]], output_path: str, *, pass # unreadable existing file — proceed with write node_community = _node_community_map(communities) + _labels: dict[int, str] = {int(k): v for k, v in (community_labels or {}).items()} try: data = json_graph.node_link_data(G, edges="links") except TypeError: data = json_graph.node_link_data(G) for node in data["nodes"]: - node["community"] = node_community.get(node["id"]) + cid = node_community.get(node["id"]) + node["community"] = cid + if cid is not None and _labels: + node["community_name"] = _labels.get(cid, f"Community {cid}") node["norm_label"] = _strip_diacritics(node.get("label", "")).lower() for link in data["links"]: if "confidence_score" not in link: diff --git a/graphify/serve.py b/graphify/serve.py index 6e05b6a98..ff8a1d991 100644 --- a/graphify/serve.py +++ b/graphify/serve.py @@ -390,7 +390,7 @@ def _subgraph_to_text(G: nx.Graph, nodes: set[str], edges: list[tuple], token_bu f"NODE {sanitize_label(d.get('label', nid))} " f"[src={sanitize_label(str(d.get('source_file', '')))} " f"loc={sanitize_label(str(d.get('source_location', '')))} " - f"community={sanitize_label(str(d.get('community', '')))}]" + f"community={sanitize_label(str(d.get('community_name') or d.get('community', '')))}]" ) lines.append(line) for u, v in edges: @@ -727,7 +727,7 @@ def _build_server(graph_path: str): f" ID: {sanitize_label(nid)}", f" Source: {sanitize_label(str(d.get('source_file', '')))} {sanitize_label(str(d.get('source_location', '')))}", f" Type: {sanitize_label(str(d.get('file_type', '')))}", - f" Community: {sanitize_label(str(d.get('community', '')))}", + f" Community: {sanitize_label(str(d.get('community_name') or d.get('community', '')))}", f" Degree: {G.degree(nid)}", ]) @@ -1257,9 +1257,16 @@ def _main(argv: list[str] | None = None) -> None: parser.add_argument( "graph_path", nargs="?", - default="graphify-out/graph.json", + default=None, help="Path to graph.json (default: graphify-out/graph.json)", ) + parser.add_argument( + "--graph", + dest="graph_flag", + default=None, + metavar="PATH", + help="Path to graph.json — alias for the positional argument", + ) parser.add_argument( "--transport", choices=["stdio", "http"], @@ -1291,10 +1298,11 @@ def _main(argv: list[str] | None = None) -> None: help="Reap stateful sessions idle this many seconds (default: 3600; 0 disables)", ) args = parser.parse_args(argv) + graph_path = args.graph_flag or args.graph_path or "graphify-out/graph.json" if args.transport == "http": serve_http( - args.graph_path, + graph_path, host=args.host, port=args.port, api_key=args.api_key, @@ -1304,7 +1312,7 @@ def _main(argv: list[str] | None = None) -> None: session_timeout=args.session_timeout, ) else: - serve(args.graph_path) + serve(graph_path) if __name__ == "__main__":