fix: community names blank in query/MCP after cluster-only; --graph for graphify-mcp

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 <noreply@anthropic.com>
This commit is contained in:
Safi
2026-06-13 19:34:18 +01:00
co-authored by Claude Sonnet 4.6
parent b61c985c12
commit 85de47e121
3 changed files with 21 additions and 9 deletions
+2 -2
View File
@@ -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 +
+6 -2
View File
@@ -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:
+13 -5
View File
@@ -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__":