Skip to content

Commit 64e4c64

Browse files
safishamsiclaude
andcommitted
fix: deterministic GRAPH_REPORT on large graphs, stable edge node IDs, correct common-root inference
- analyze.py: add seed=42 to betweenness_centrality() — eliminates non-deterministic GRAPH_REPORT.md diffs on graphs >1000 nodes (#499) - extract.py: fix common-root inference to stop at first diverging segment not sum of all matches (#502) - extract.py: resolve root to absolute path; post-process file node IDs to project-relative after extraction so graph.json edge endpoints are stable across machines (#502) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7891fa8 commit 64e4c64

3 files changed

Lines changed: 32 additions & 7 deletions

File tree

graphify/analyze.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ def suggest_questions(
363363
# 2. Bridge nodes (high betweenness) → cross-cutting concern questions
364364
if G.number_of_edges() > 0:
365365
k = min(100, G.number_of_nodes()) if G.number_of_nodes() > 1000 else None
366-
betweenness = nx.betweenness_centrality(G, k=k)
366+
betweenness = nx.betweenness_centrality(G, k=k, seed=42)
367367
# Top bridge nodes that are NOT file-level hubs
368368
bridges = sorted(
369369
[(n, s) for n, s in betweenness.items()

graphify/extract.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3085,20 +3085,24 @@ def extract(paths: list[Path], cache_root: Path | None = None) -> dict:
30853085
_check_tree_sitter_version()
30863086
per_file: list[dict] = []
30873087

3088-
# Infer a common root for cache keys
3088+
# Infer a common root for cache keys (use first diverging segment, not sum of all matches)
30893089
try:
30903090
if not paths:
30913091
root = Path(".")
30923092
elif len(paths) == 1:
30933093
root = paths[0].parent
30943094
else:
3095-
common_len = sum(
3096-
1 for i in range(min(len(p.parts) for p in paths))
3097-
if len({p.parts[i] for p in paths}) == 1
3098-
)
3095+
min_parts = min(len(p.parts) for p in paths)
3096+
common_len = 0
3097+
for i in range(min_parts):
3098+
if len({p.parts[i] for p in paths}) == 1:
3099+
common_len += 1
3100+
else:
3101+
break
30993102
root = Path(*paths[0].parts[:common_len]) if common_len else Path(".")
31003103
except Exception:
31013104
root = Path(".")
3105+
root = root.resolve()
31023106

31033107
_DISPATCH: dict[str, Any] = {
31043108
".py": extract_python,
@@ -3168,6 +3172,27 @@ def extract(paths: list[Path], cache_root: Path | None = None) -> dict:
31683172
all_nodes.extend(result.get("nodes", []))
31693173
all_edges.extend(result.get("edges", []))
31703174

3175+
# Remap file node IDs from absolute-path-derived to project-relative so
3176+
# graph.json edge endpoints are stable across machines (#502)
3177+
id_remap: dict[str, str] = {}
3178+
for path in paths:
3179+
old_id = _make_id(str(path))
3180+
try:
3181+
new_id = _make_id(str(path.relative_to(root)))
3182+
except ValueError:
3183+
continue
3184+
if old_id != new_id:
3185+
id_remap[old_id] = new_id
3186+
if id_remap:
3187+
for n in all_nodes:
3188+
if n.get("id") in id_remap:
3189+
n["id"] = id_remap[n["id"]]
3190+
for e in all_edges:
3191+
if e.get("source") in id_remap:
3192+
e["source"] = id_remap[e["source"]]
3193+
if e.get("target") in id_remap:
3194+
e["target"] = id_remap[e["target"]]
3195+
31713196
# Add cross-file class-level edges (Python only - uses Python parser internally)
31723197
py_paths = [p for p in paths if p.suffix == ".py"]
31733198
if py_paths:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "graphifyy"
7-
version = "0.4.26"
7+
version = "0.4.27"
88
description = "AI coding assistant skill (Claude Code, Codex, OpenCode, Cursor, Gemini CLI, Aider, OpenClaw, Factory Droid, Trae, Hermes, Kiro, Google Antigravity) - turn any folder of code, docs, papers, images, or videos into a queryable knowledge graph"
99
readme = "README.md"
1010
license = { file = "LICENSE" }

0 commit comments

Comments
 (0)