# How Graph Diff Compares Code Knowledge Graph Snapshots Over Time

> Learn how graph diff compares code knowledge graph snapshots over time. Our engine precisely identifies changes in nodes and edges between two moments.

- Repository: [Tirth Kanani/code-review-graph](https://github.com/tirth8205/code-review-graph)
- Tags: deep-dive
- Published: 2026-07-30

---

**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)](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](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/graph_diff.py#L46-L53)) 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](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/graph_diff.py#L56-L61)) 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](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/graph_diff.py#L64-L78)):**

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](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/graph_diff.py#L84-L99)):**

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](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/graph_diff.py#L100-L122)):**

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)](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:**

```python
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:**

```bash

# 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/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.