From 893acb19df532376b4eda06eb73885ce7f3b4e18 Mon Sep 17 00:00:00 2001 From: Safi Date: Sat, 2 May 2026 18:09:32 +0100 Subject: [PATCH] Fix #664: filter thin communities from GRAPH_REPORT.md by default Co-Authored-By: Claude Sonnet 4.6 --- graphify/__main__.py | 5 ++++- graphify/report.py | 15 +++++++++------ tests/test_report.py | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/graphify/__main__.py b/graphify/__main__.py index 6dae11e8..638df197 100644 --- a/graphify/__main__.py +++ b/graphify/__main__.py @@ -1463,6 +1463,8 @@ def main() -> None: elif cmd == "cluster-only": watch_path = Path(sys.argv[2]) if len(sys.argv) > 2 else Path(".") no_viz = "--no-viz" in sys.argv + _min_cs_arg = next((a for a in sys.argv if a.startswith("--min-community-size=")), None) + min_community_size = int(_min_cs_arg.split("=")[1]) if _min_cs_arg else 3 graph_json = watch_path / "graphify-out" / "graph.json" if not graph_json.exists(): print(f"error: no graph found at {graph_json} — run /graphify first", file=sys.stderr) @@ -1488,7 +1490,8 @@ def main() -> None: tokens = {"input": 0, "output": 0} report = generate(G, communities, cohesion, labels, gods, surprises, {"warning": "cluster-only mode — file stats not available"}, - tokens, str(watch_path), suggested_questions=questions) + tokens, str(watch_path), suggested_questions=questions, + min_community_size=min_community_size) out = watch_path / "graphify-out" (out / "GRAPH_REPORT.md").write_text(report, encoding="utf-8") to_json(G, communities, str(out / "graph.json")) diff --git a/graphify/report.py b/graphify/report.py index 103765dc..7183b18f 100644 --- a/graphify/report.py +++ b/graphify/report.py @@ -23,6 +23,7 @@ def generate( token_cost: dict, root: str, suggested_questions: list[dict] | None = None, + min_community_size: int = 3, ) -> str: today = date.today().isoformat() @@ -108,7 +109,11 @@ def generate( conf_tag = f"{conf} {cscore:.2f}" if cscore is not None else conf lines.append(f"- **{h.get('label', h.get('id', ''))}** — {node_labels} [{conf_tag}]") - lines += ["", "## Communities"] + thin_count = sum( + 1 for nodes in communities.values() + if 0 < sum(1 for n in nodes if not _ifn(G, n)) < min_community_size + ) + lines += ["", f"## Communities ({len(communities)} total, {thin_count} thin omitted)"] for cid, nodes in communities.items(): label = community_labels.get(cid, f"Community {cid}") score = cohesion_scores.get(cid, 0.0) @@ -116,6 +121,8 @@ def generate( real_nodes = [n for n in nodes if not _ifn(G, n)] if not real_nodes: continue + if len(real_nodes) < min_community_size: + continue display = [G.nodes[n].get("label", n) for n in real_nodes[:8]] suffix = f" (+{len(real_nodes)-8} more)" if len(real_nodes) > 8 else "" lines += [ @@ -157,11 +164,7 @@ def generate( lines.append(f"- **{len(isolated)} isolated node(s):** {', '.join(f'`{l}`' for l in isolated_labels)}{suffix}") lines.append(" These have ≤1 connection - possible missing edges or undocumented components.") if thin_communities: - for cid, nodes in thin_communities.items(): - label = community_labels.get(cid, f"Community {cid}") - node_labels = [G.nodes[n].get("label", n) for n in nodes] - lines.append(f"- **Thin community `{label}`** ({len(nodes)} nodes): {', '.join(f'`{l}`' for l in node_labels)}") - lines.append(" Too small to be a meaningful cluster - may be noise or needs more connections extracted.") + lines.append(f"- **{len(thin_communities)} thin communities (<{min_community_size} nodes) omitted from report** — run `graphify query` to explore isolated nodes.") if amb_pct > 20: lines.append(f"- **High ambiguity: {amb_pct}% of edges are AMBIGUOUS.** Review the Ambiguous Edges section above.") diff --git a/tests/test_report.py b/tests/test_report.py index 7afbea87..a5b3916a 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -57,7 +57,7 @@ def test_report_shows_token_cost(): def test_report_shows_raw_cohesion_scores(): G, communities, cohesion, labels, gods, surprises, detection, tokens = make_inputs() - report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, "./project") + report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, "./project", min_community_size=1) assert "Cohesion:" in report assert "✓" not in report assert "⚠" not in report