Repository navigation
Incremental rebuild drops cross-file references edges between Markdown docs; GRAPHIFY_FORCE does not help in the hook (follow-up to #1018) #2211
Description
Summary
An incremental rebuild re-extracts a changed Markdown file but does not re-establish that file's cross-file references edges to unchanged neighbour documents. The links are still literally present in the text — only the graph loses them. graphify update . --force restores all of them, so the extraction itself is fine; the incremental merge is what drops them.
Because the post-commit hook rebuilds incrementally after every commit that touches a graph-tracked file — including .md files — the doc half of the graph erodes a little with every documentation commit, silently.
Measurement (graphifyy 0.9.26)
A commit editing four Markdown files (CLAUDE.md, docs/FEATURE-INVENTORY.md, docs/features/overlay.md, docs/features/configuration.md) in a 44-file repo:
before: 408 nodes / 693 edges
after: 409 nodes / 676 edges <- 18 edges lost, 1 node gained
All 18 lost edges are references edges whose source is one of the four edited files, and whose target is an unchanged document:
CLAUDE.md --references--> diagnostics.md, input.md, output.md,
pythonsidecar.md, recognition.md, setup.md (6)
FEATURE-INVENTORY.md --references--> diagnostics.md, input.md, output.md,
recognition.md, setup.md, history.md (6)
overlay.md --references--> diagnostics.md, output.md, pythonsidecar.md,
recognition.md, setup.md (5)
configuration.md --references--> output.md (1)
No code node and no edge outside those four source files was affected.
The links were never removed from the text. Counted with grep in the post-rebuild working tree:
CLAUDE.md -> docs/features/setup.md 10 occurrences
CLAUDE.md -> docs/features/diagnostics.md 2 occurrences
FEATURE-INVENTORY.md -> features/overlay.md 28 occurrences
FEATURE-INVENTORY.md -> features/recognition.md 16 occurrences
(etc. — every dropped target still linked)
Running graphify update . --force immediately afterwards, with no further file changes:
409 nodes / 695 edges -> all 18 edges restored (plus 2 new ones for content added in the same commit)
No cost.json was written, so this is the plain AST path — the edges are structurally extractable, they are simply not re-linked on the incremental path.
GRAPHIFY_FORCE=1 does not mitigate this in the hook
Setting GRAPHIFY_FORCE=1 as a persistent environment variable had no effect (verified: variable present in the committing shell, edges still dropped 695 -> 687 on the next doc commit). The installed post-commit script always passes an explicit changed-file list:
sh
CHANGED=$(git diff --name-only HEAD~1 HEAD 2>/dev/null || git diff --name-only HEAD 2>/dev/null)
export GRAPHIFY_CHANGED="$CHANGED"
python
_force = os.environ.get('GRAPHIFY_FORCE', '').lower() in ('1', 'true', 'yes')
_rebuild_code(_root, changed_paths=changed, force=_force)
Since changed_paths is always supplied, force only lifts the overwrite/fail-closed guard — it does not widen the re-extraction scope the way the CLI's graphify update . --force (no changed_paths) does. So there is currently no way to get the hook to produce a correct doc graph.
Relation to #1018
#1018 (closed) asked for the hook to "skip when no files matching CODE_EXTENSIONS changed (docs-only commits → manual refresh, as documented)". In 0.9.26 that is only partially implemented: the hook does skip when nothing graph-tracked changed — a docs-only commit under a .graphifyignored path correctly logged
1 file(s) changed → No tracked code files in change set - skipping rebuild
— but the filter keys on graph-tracked files, not on code files. A commit touching only tracked .md files therefore does rebuild, contrary to the documented intent, and that rebuild is exactly what degrades the graph. Closing #1018's point 2 properly would incidentally fix this.
Possibly also related: #857 (semantic references edges never re-inferred for files edited between passes).
Impact
Silent, cumulative loss of doc→doc navigation edges — the part of the graph that makes documentation traversable. Nothing warns; node counts even go up while edges go down.
For repos that track graphify-out/ in git, every doc commit additionally requires a manual --force plus a second repair commit, or the degraded graph gets committed.
Practical workaround we ended up with: disable the hook entirely via GRAPHIFY_SKIP_HOOK=1 and run graphify update . --force manually before each commit. That works, but it means the auto-rebuild feature is unusable for any repo with a meaningful amount of cross-linked documentation.
Suggested fixes (in order of preference)
On the incremental path, re-resolve a re-extracted file's outgoing cross-file references against the existing graph, instead of only linking to files that were re-extracted in the same run.
Implement #1018 point 2 as specified: skip the hook rebuild when no CODE_EXTENSIONS file changed.
Make GRAPHIFY_FORCE=1 widen the hook's scope (drop changed_paths), so the documented force-escape hatch also works from the hook.
Environment
graphifyy 0.9.26 (PyPI, installed via uv tool install graphifyy)
Windows 11, Python 3.12.10
44 tracked files (C#/XAML/Python code plus 12 Markdown documents), graphify-out/ tracked in git
.graphifyignore present (full copy of .gitignore patterns plus docs/reviews/)
Activity
fix(extract): stamp target_file on Python imports and markdown refs s…
chore: bump to 0.9.28; changelog for #2211/#2213/#2210/#2212/#2215/#2221
safishamsi commentedon Jul 27, 2026
Fixed in v0.9.28. Markdown reference links now stamp their resolved target, so an incremental rebuild canonicalizes the target to the root-relative node instead of dropping the md->md references edge. https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.28
Merge upstream/v8 (0.9.28) into feat/clusters
fix(extract): stamp target_file on Python imports and markdown refs s…
chore: bump to 0.9.28; changelog for Graphify-Labs#2211/Graphify-Labs…