Summary
After a full /graphify <path> build, subsequent graphify update <path> calls
produce inconsistent output: graph.json is correctly preserved (the "Refusing
to overwrite" guard fires), but GRAPH_REPORT.md and graph.html are
regenerated from the smaller AST-only working set anyway. Result: the derived
artifacts describe fewer nodes/communities than graph.json actually contains.
This is a silent desync — the warning is logged but the artifacts are written
without further indication.
Reproduction
On a corpus with both code and docs (so the semantic cache is smaller than
the merged graph):
-
/graphify <path> to build a full graph (AST + semantic). Observe e.g.
graph.json = 1955 nodes / 4171 edges / 139 communities, with
GRAPH_REPORT.md reporting the same.
-
Make any small change (e.g. edit a .md file at the root).
-
Run graphify update <path> directly, OR install the post-commit hook
(graphify hook install) and commit.
-
Observe output:
[graphify hook] 1 file(s) changed - rebuilding graph...
AST extraction: 100/183 files (54%)
AST extraction: 183/183 files (100%)
[graphify] WARNING: new graph has 1772 nodes but existing graph.json
has 1955. Refusing to overwrite — you may be missing chunk files
from a previous session. Pass force=True to override.
[graphify watch] Rebuilt: 1772 nodes, 3484 edges, 118 communities
[graphify watch] graph.json, graph.html and GRAPH_REPORT.md
updated in graphify-out
-
Inspect afterwards:
graph.json → still 1955 nodes (correctly preserved)
GRAPH_REPORT.md → reports 1772 nodes · 3484 edges · 43 communities
(regenerated from the smaller view)
graph.html → same — visualizes the smaller graph
Expected behavior
When graphify update decides to refuse overwriting graph.json (because the
new view has fewer nodes), it should apply the same protection to derived
artifacts. Two reasonable fixes:
- (a) Skip regeneration of
GRAPH_REPORT.md and graph.html entirely
when refusing the merge.
- (b) Regenerate
GRAPH_REPORT.md and graph.html from the preserved
graph.json (re-cluster + render), so the artifacts stay consistent with
the data file.
Either resolution avoids the silent desync.
Actual behavior
graph.json is preserved. GRAPH_REPORT.md and graph.html are clobbered
with the smaller view's data. The desync is not flagged in the success
message, which says "graph.json, graph.html and GRAPH_REPORT.md updated"
even though graph.json was not in fact updated.
Why this matters
Downstream consumers (Claude Code agents reading GRAPH_REPORT.md for god
nodes / community structure, viewers opening graph.html) trust the report
to describe the graph. Silent disagreement between the report and the
underlying data leads to incomplete answers and potentially incorrect
navigation.
The post-commit hook (graphify hook install) makes this fire on every
commit, so the desync is perpetual once a corpus has more semantic nodes
than the AST cache covers (which is the normal case for any project with
documentation).
Workaround (for users)
Until fixed, regenerate report/HTML directly from graph.json after any
graphify update:
import json
from pathlib import Path
from networkx.readwrite import json_graph
from graphify.cluster import cluster, score_all
from graphify.analyze import god_nodes, surprising_connections, suggest_questions
from graphify.report import generate
from graphify.export import to_html
data = json.loads(Path('graphify-out/graph.json').read_text())
G = json_graph.node_link_graph(data, edges='links')
communities = cluster(G)
cohesion = score_all(G, communities)
gods = god_nodes(G)
surprises = surprising_connections(G, communities)
labels = {cid: f'Community {cid}' for cid in communities} # or curated labels
questions = suggest_questions(G, communities, labels)
report = generate(
G, communities, cohesion, labels, gods, surprises,
detection={'total_files': 0, 'total_words': 0, 'needs_graph': True,
'warning': None, 'files': {'code': [], 'document': [],
'paper': [], 'image': []}},
tokens={'input': 0, 'output': 0},
project_path='.',
suggested_questions=questions,
)
Path('graphify-out/GRAPH_REPORT.md').write_text(report)
to_html(G, communities, 'graphify-out/graph.html', community_labels=labels)
Environment
- graphifyy 0.5.0 (pip)
- Python 3.13
- macOS Darwin 25.4.0
- Corpus: 251 files (183 code, 63 docs, 5 images), ~268k words
Notes
- Initial full
/graphify . run produced the 1955-node graph cleanly.
- Bug surfaces on the first incremental update afterward.
graph.json mtime stays at the original-build time, while
GRAPH_REPORT.md and graph.html mtimes update — easy way to confirm
the desync at the filesystem level.
Happy to test a patch if helpful.
Summary
After a full
/graphify <path>build, subsequentgraphify update <path>callsproduce inconsistent output:
graph.jsonis correctly preserved (the "Refusingto overwrite" guard fires), but
GRAPH_REPORT.mdandgraph.htmlareregenerated from the smaller AST-only working set anyway. Result: the derived
artifacts describe fewer nodes/communities than
graph.jsonactually contains.This is a silent desync — the warning is logged but the artifacts are written
without further indication.
Reproduction
On a corpus with both code and docs (so the semantic cache is smaller than
the merged graph):
/graphify <path>to build a full graph (AST + semantic). Observe e.g.graph.json= 1955 nodes / 4171 edges / 139 communities, withGRAPH_REPORT.mdreporting the same.Make any small change (e.g. edit a
.mdfile at the root).Run
graphify update <path>directly, OR install the post-commit hook(
graphify hook install) and commit.Observe output:
Inspect afterwards:
graph.json→ still 1955 nodes (correctly preserved)GRAPH_REPORT.md→ reports1772 nodes · 3484 edges · 43 communities(regenerated from the smaller view)
graph.html→ same — visualizes the smaller graphExpected behavior
When
graphify updatedecides to refuse overwritinggraph.json(because thenew view has fewer nodes), it should apply the same protection to derived
artifacts. Two reasonable fixes:
GRAPH_REPORT.mdandgraph.htmlentirelywhen refusing the merge.
GRAPH_REPORT.mdandgraph.htmlfrom the preservedgraph.json(re-cluster + render), so the artifacts stay consistent withthe data file.
Either resolution avoids the silent desync.
Actual behavior
graph.jsonis preserved.GRAPH_REPORT.mdandgraph.htmlare clobberedwith the smaller view's data. The desync is not flagged in the success
message, which says "graph.json, graph.html and GRAPH_REPORT.md updated"
even though
graph.jsonwas not in fact updated.Why this matters
Downstream consumers (Claude Code agents reading
GRAPH_REPORT.mdfor godnodes / community structure, viewers opening
graph.html) trust the reportto describe the graph. Silent disagreement between the report and the
underlying data leads to incomplete answers and potentially incorrect
navigation.
The post-commit hook (
graphify hook install) makes this fire on everycommit, so the desync is perpetual once a corpus has more semantic nodes
than the AST cache covers (which is the normal case for any project with
documentation).
Workaround (for users)
Until fixed, regenerate report/HTML directly from
graph.jsonafter anygraphify update:Environment
Notes
/graphify .run produced the 1955-node graph cleanly.graph.jsonmtime stays at the original-build time, whileGRAPH_REPORT.mdandgraph.htmlmtimes update — easy way to confirmthe desync at the filesystem level.
Happy to test a patch if helpful.