diff --git a/graphify/cluster.py b/graphify/cluster.py index 25b9db128..55af24f24 100644 --- a/graphify/cluster.py +++ b/graphify/cluster.py @@ -1,17 +1,44 @@ """Community detection on NetworkX graphs. Uses Leiden (graspologic) if available, falls back to Louvain (networkx). Splits oversized communities. Returns cohesion scores.""" from __future__ import annotations +import contextlib +import io +import os +import sys import networkx as nx +def _suppress_output(): + """Context manager to suppress stdout/stderr during library calls. + + graspologic's leiden() emits ANSI escape sequences (progress bars, + colored warnings) that corrupt PowerShell 5.1's scroll buffer on + Windows (see issue #19). Redirecting stdout/stderr to devnull during + the call prevents this without losing any graphify output. + """ + return contextlib.redirect_stdout(io.StringIO()) + + def _partition(G: nx.Graph) -> dict[str, int]: """Run community detection. Returns {node_id: community_id}. Tries Leiden (graspologic) first — best quality. Falls back to Louvain (built into networkx) if graspologic is not installed. + + Output from graspologic is suppressed to prevent ANSI escape codes + from corrupting terminal scroll buffers on Windows PowerShell 5.1. """ try: from graspologic.partition import leiden - return leiden(G) + # Suppress graspologic output to prevent ANSI escape codes from + # corrupting PowerShell 5.1 scroll buffer (issue #19) + old_stderr = sys.stderr + try: + sys.stderr = io.StringIO() + with _suppress_output(): + result = leiden(G) + finally: + sys.stderr = old_stderr + return result except ImportError: pass diff --git a/graphify/skill-windows.md b/graphify/skill-windows.md index 6675a7697..e8f9c9de1 100644 --- a/graphify/skill-windows.md +++ b/graphify/skill-windows.md @@ -1202,6 +1202,19 @@ graphify claude uninstall # remove the section --- +## Troubleshooting + +### PowerShell 5.1: Vertical scrolling stops working + +If vertical scrolling breaks in PowerShell after running graphify, this is caused by ANSI escape sequences from the `graspologic` library. Graphify v0.3.10+ suppresses this output, but if you still see the issue: + +1. **Upgrade graphify**: `pip install --upgrade graphifyy` +2. **Use Windows Terminal** instead of the legacy PowerShell console — Windows Terminal handles ANSI codes correctly +3. **Reset your terminal**: close and reopen PowerShell +4. **Skip graspologic**: uninstall it (`pip uninstall graspologic`) and graphify will fall back to NetworkX's built-in Louvain algorithm, which produces no ANSI output + +--- + ## Honesty Rules - Never invent an edge. If unsure, use AMBIGUOUS. diff --git a/tests/test_cluster.py b/tests/test_cluster.py index de534016f..b5c16fad6 100644 --- a/tests/test_cluster.py +++ b/tests/test_cluster.py @@ -1,4 +1,5 @@ import json +import sys import networkx as nx from pathlib import Path from graphify.build import build_from_json @@ -50,3 +51,26 @@ def test_score_all_keys_match_communities(): communities = cluster(G) scores = score_all(G, communities) assert set(scores.keys()) == set(communities.keys()) + + +def test_cluster_does_not_write_to_stdout(capsys): + """Clustering should not emit ANSI escape codes or other output. + + graspologic's leiden() can emit ANSI escape sequences that break + PowerShell 5.1's scroll buffer on Windows (issue #19). The output + suppression in _partition() should prevent any output from leaking. + """ + G = make_graph() + cluster(G) + captured = capsys.readouterr() + assert captured.out == "", f"cluster() wrote to stdout: {captured.out!r}" + + +def test_cluster_does_not_write_to_stderr(capsys): + """Same as above but for stderr — ANSI codes can go to either stream.""" + G = make_graph() + cluster(G) + captured = capsys.readouterr() + # Allow logging output (starts with [graphify]) but no raw ANSI codes + for line in captured.err.splitlines(): + assert "\x1b" not in line, f"cluster() wrote ANSI to stderr: {line!r}"