Skip to content

_rebuild_code (post-commit hook) unconditionally overwrites graph.json, destroying semantic nodes #2251

Description

@gq5957

Bug: _rebuild_code (post-commit/post-checkout hook path) silently discards the semantic graph

Summary

graphify.watch._rebuild_code() — the function the installed post-commit/post-checkout git hooks call — builds a graph from only the currently-detected code files via AST extraction, then unconditionally overwrites graphify-out/graph.json with it. If a full /graphify run has already populated the graph with semantic doc/paper/image nodes (from the LLM extraction path), a subsequent code-only hook rebuild destroys all of that, silently, with no warning.

This makes the hook actively dangerous on any repo that mixes code and docs (e.g. a markdown-heavy knowledge base with a handful of scripts): every commit that touches a code file wipes the semantic graph back down to code-AST-only.

Reproduction

  1. Run a full /graphify pass on a mixed corpus (docs + a few code files) — confirm graph.json has both code and semantic (document-type) nodes.
  2. Install the git hooks: graphify hook install.
  3. Commit a change to any code file (.py, .ts, etc. — anything in CODE_EXTENSIONS).
  4. Inspect graphify-out/graph.json — the semantic nodes are gone. Only the AST-extracted code nodes remain.

Root cause

In graphify/watch.py::_rebuild_code:

G = build_from_json(result)   # built from code_files ONLY
communities = cluster(G)
...
to_json(G, communities, str(out / "graph.json"))   # unconditional overwrite

There's no attempt to load the existing graph.json and merge; G is built from scratch every time.

Suggested fix

Load the existing graph (if present) and merge the freshly-extracted code subgraph into it, rather than replacing it wholesale:

G = build_from_json(result)

existing_path = watch_path / "graphify-out" / "graph.json"
if existing_path.exists():
    try:
        from networkx.readwrite import json_graph
        existing_data = json.loads(existing_path.read_text())
        G_existing = json_graph.node_link_graph(existing_data, edges="links")
        G_existing.update(G)
        G = G_existing
    except Exception as merge_exc:
        print(f"[graphify watch] Could not merge into existing graph.json ({merge_exc}); writing code-only graph instead.")

communities = cluster(G)

I've applied and tested this patch locally (verified: a 337-node graph with semantic content survives a simulated hook rebuild with the patch, vs. collapsing to 82 nodes without it). Happy to open a PR with this change if useful — let me know.

Secondary note (not a bug, just a heads-up)

The post-commit hook template in hooks.py only triggers on real code extensions, which is correct. Some users may have manually added .md/.mdx to their local hook's trigger list (I found this in the wild) hoping it would "refresh" the doc graph too — worth noting in docs that this doesn't do anything useful today, since _rebuild_code only ever reads code files regardless of what triggered it. Doc/paper/image extraction requires the LLM-driven /graphify --update path, which the hook correctly doesn't attempt.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions