How Graph Diff Compares Code Knowledge Graph Snapshots Over Time

The graph diff engine captures compact structural snapshots of the knowledge graph and computes precise set-based differences to identify added, removed, or reassigned nodes and edges between any two moments in time.

The code-review-graph repository provides a specialized graph diff system that tracks evolution in code knowledge graphs without requiring database-level versioning. By extracting complete node and edge states into portable snapshots, the system enables deterministic comparison of graph topology across CI builds, manual inspections, or temporal analytics.

Capturing Graph Snapshots with take_snapshot()

The snapshot acquisition process begins with the take_snapshot() function defined in [code_review_graph/graph_diff.py](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/graph_diff.py#L15-L38). This method queries the underlying GraphStore to assemble a complete, serializable representation of the current graph state.

Key data collected:

  • Total statistics via GraphStore.get_stats(), capturing aggregate node and edge counts
  • Complete node inventory via GraphStore.get_all_nodes(), mapping each qualified name to its kind, source file, and community ID
  • Community assignments via GraphStore.get_all_community_ids() for later change detection
  • Full edge collection representing every relationship in the graph

The resulting dictionary provides a pure-data snapshot that is independent of the live SQLite database, allowing comparisons without locking or interfering with ongoing graph operations.

Persisting and Loading Snapshots

Once captured, snapshots transition between memory and disk through deterministic serialization helpers.

save_snapshot() (lines 46‑53) writes the snapshot dictionary to a JSON file. The implementation normalizes the edge collection to a sorted list, ensuring deterministic output that produces identical file hashes for identical graph states. This stability is critical for version control integration and CI caching.

load_snapshot() (lines 56‑61) performs the inverse operation, reading the JSON file and converting the edge list back into a Python set. This restoration enables fast O(1) membership testing and set arithmetic required for efficient diff computation.

Computing Structural Differences with diff_snapshots()

The core comparison logic resides in diff_snapshots(), which accepts two snapshot dictionaries (before and after) and returns a detailed structural delta.

Set construction (lines 64‑78):

The function first reconstructs four critical sets:

  • before_nodes and after_nodes: Sets of qualified names present in each snapshot
  • before_edges and after_edges: Sets of edge tuples describing relationships

Change detection via set operations:

  • New entities: Computed as after_nodes - before_nodes and after_edges - before_edges
  • Removed entities: Computed as before_nodes - after_nodes and before_edges - after_edges

Community reassignment tracking (lines 84‑99):

For nodes surviving in both snapshots, the function examines whether the community_id changed. Any reassignment is recorded in community_changes, tracking which symbols moved between detected communities.

Result packaging with safety limits (lines 100‑122):

The output dictionary structures the delta for immediate consumption:

  • new_nodes and removed_nodes (capped at 100 entries each)
  • new_edges and removed_edges (capped at 100 entries each)
  • community_changes (capped at 50 entries)
  • A comprehensive summary containing exact counts for all categories plus total node counts before and after

Integration with the SQLite GraphStore

All snapshot operations consume data from GraphStore, the SQLite-backed graph engine implemented in [code_review_graph/graph.py](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/graph.py#L72-L98). The store exposes three critical methods that feed the diff logic:

  • get_stats() for aggregate metrics
  • get_all_nodes() for entity metadata
  • get_all_community_ids() for community topology

This architecture decouples the diff computation from storage internals, allowing the same snapshot logic to work with future storage backends provided they implement the same interface.

Practical Implementation Examples

Basic snapshot and diff workflow:

from pathlib import Path
from code_review_graph.graph import GraphStore
from code_review_graph.graph_diff import (
    take_snapshot,
    save_snapshot,
    load_snapshot,
    diff_snapshots,
)

# Initialize the graph store

store = GraphStore("/tmp/graph.db")

# Capture initial state

snapshot_before = take_snapshot(store)

# ... perform graph mutations (add nodes, edges, etc.) ...

# Capture post-mutation state  

snapshot_after = take_snapshot(store)

# Persist for later analysis

save_snapshot(snapshot_before, Path("snap_before.json"))
save_snapshot(snapshot_after, Path("snap_after.json"))

# Compute structural diff

diff = diff_snapshots(snapshot_before, snapshot_after)

print("Summary:", diff["summary"])
print("New nodes:", diff["new_nodes"])
print("Community changes:", diff["community_changes"])

CI/CD integration pattern:


# Crawl source and capture timestamped snapshot

python -m code_review_graph.main crawl src/
python - <<'PY'
import json, pathlib
from code_review_graph.graph import GraphStore
from code_review_graph.graph_diff import take_snapshot, save_snapshot

store = GraphStore("graph.db")
snap = take_snapshot(store)
path = pathlib.Path(f"snapshots/{int(time.time())}.json")
save_snapshot(snap, path)
PY

# Later, compare two builds

python - <<'PY'
from code_review_graph.graph_diff import load_snapshot, diff_snapshots
import pathlib

before = load_snapshot(pathlib.Path("snapshots/1620000000.json"))
after = load_snapshot(pathlib.Path("snapshots/1620003600.json"))
result = diff_snapshots(before, after)

# Fail build if critical symbols changed

if result["summary"]["removed_nodes_count"] > 0:
    raise SystemExit("Graph regression detected")
PY

Summary

  • take_snapshot() extracts complete node, edge, and community data from GraphStore into a portable dictionary format.
  • save_snapshot() and load_snapshot() handle deterministic JSON serialization, normalizing edges to sorted lists for storage and restoring them to sets for analysis.
  • diff_snapshots() performs set arithmetic to identify added and removed entities, detects community reassignments for surviving nodes, and returns capped result lists with precise summary statistics.
  • The entire pipeline operates on pure Python data structures, making graph diff operations storage-engine agnostic and suitable for CI/CD integration.
  • Underlying data providers in [graph.py](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/graph.py) supply the raw graph topology via SQLite queries.

Frequently Asked Questions

How does the graph diff handle large graphs with millions of edges?

The diff_snapshots() function loads the complete node and edge sets into memory as Python sets, which requires RAM proportional to graph size. However, the output automatically caps displayed results to 100 nodes, 100 edges, and 50 community changes, while the summary dictionary always reports exact total counts. For extremely large graphs, consider sampling or partitioning strategies outside the core diff logic.

Can snapshots be compared across different database files?

Yes. Because take_snapshot() extracts a pure-data representation containing qualified names, file paths, and community IDs without database-specific identifiers, you can save snapshots from GraphStore instances pointing to different SQLite files and compare them using diff_snapshots(). The JSON format makes snapshots portable across environments.

What constitutes a "community change" in the diff output?

A community change occurs when a node (identified by its qualified name) exists in both the before and after snapshots but its assigned community_id differs. This indicates the graph's community detection algorithm reassigned the symbol to a different cluster between captures, often reflecting structural changes in the codebase's modular organization.

Is the snapshot format stable for long-term storage?

The JSON snapshot schema used by save_snapshot() is designed for stability and human readability. Edges are normalized to sorted lists to ensure deterministic output, and the structure uses standard Python dictionaries and lists. While the repository does not guarantee backward compatibility across major versions, the simple key-value format minimizes migration complexity.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →