# How Understand Anything Implements Incremental Updates and Fingerprint-Based Change Detection

> Learn how Understand Anything uses fingerprinting and incremental updates to efficiently detect changes in source code, minimizing LLM token usage. Explore Lum1104/Understand-Anything.

- Repository: [Yuxiang Lin/Understand-Anything](https://github.com/Lum1104/Understand-Anything)
- Tags: internals
- Published: 2026-06-02

---

**Understand Anything uses deterministic tree-sitter parsing to generate structural fingerprints for each source file, compares them against a stored baseline to classify changes as `NONE`, `COSMETIC`, or `STRUCTURAL`, and updates only the affected subgraph to minimize LLM token consumption.**

Understand Anything (Lum1104/Understand-Anything) is an open-source knowledge graph builder for codebases that avoids expensive full rebuilds when repositories change. Instead of re-analyzing every file after each commit, the tool implements **incremental updates and fingerprint-based change detection** to identify precisely what changed and update only the necessary portions of the graph.

## Building the Baseline Fingerprint Store

When a project is first scanned, the core module in [`src/fingerprint.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/src/fingerprint.ts) establishes a persistent baseline. The function `buildFingerprintStore` walks every source file and invokes `PluginRegistry.analyzeFile` to extract **structural facts**—including imports, exports, function and class definitions, call sites, and inheritance relationships—using deterministic tree-sitter parsing.

For each file, the system creates a fingerprint object containing these structural elements. Files lacking a tree-sitter parser receive a **content-hash-only** fingerprint instead. The resulting map (file path → fingerprint) is written to [`.understand-anything/fingerprints.json`](https://github.com/Lum1104/Understand-Anything/blob/main/.understand-anything/fingerprints.json), defined by the constant `FINGERPRINT_FILE = "fingerprints.json"` (lines 125–142, 249–294).

```typescript
import { buildFingerprintStore } from "./fingerprint.js";

// Generates fingerprints for all files in the scan list
const store = await buildFingerprintStore(fileList);

```

## Detecting Changes on New Commits

The **auto-update hook** located at [`hooks/auto-update-prompt.md`](https://github.com/Lum1104/Understand-Anything/blob/main/hooks/auto-update-prompt.md) orchestrates change detection after every commit when `autoUpdate` is enabled. The hook executes a temporary Node script (`fingerprint-check.mjs`) that performs a three-step patch operation:

1. **Load** – Reads the existing [`fingerprints.json`](https://github.com/Lum1104/Understand-Anything/blob/main/fingerprints.json) store.
2. **Patch** – Re-computes fingerprints **only for files reported by `git diff --name-status`**, handling new files and deletions.
3. **Save** – Merges fresh fingerprints into the existing store without overwriting the entire file.

If the script fails, the system safely falls back to treating all changed files as `STRUCTURAL` (lines 88–115, 242–250).

```bash

# The post-commit hook runs automatically after:

git commit -am "refactor authentication"

# Output: [auto-update] 3 structural changes, 12 cosmetic changes → incremental update applied.

```

## How Fingerprint Comparison Works

Inside [`src/fingerprint.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/src/fingerprint.ts), the `compareFingerprints` function determines the nature of changes between the stored baseline and the current file state:

```typescript
export function compareFingerprints(
  oldFp: Fingerprint,
  newFp: Fingerprint
): ChangeLevel { … }

```

The comparison logic (hinted at line 339) applies these rules:

- **Structural comparison** – If both fingerprints contain structural element lists, the function checks for set equality. Differences indicate `STRUCTURAL` changes.
- **Content-hash comparison** – If only content hashes are present (no tree-sitter parser available), a simple hash comparison returns `NONE` or `COSMETIC`.
- **Lifecycle changes** – Added or deleted files automatically receive `STRUCTURAL` classification.

The `ChangeLevel` type is defined as `"NONE" | "COSMETIC" | "STRUCTURAL"` (line 48 in [`index.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/index.ts)), distinguishing between unchanged files, whitespace or comment modifications, and API-altering edits.

## Classification Logic: Deciding the Update Strategy

The [`src/change-classifier.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/src/change-classifier.ts) module consumes fingerprint comparisons and decides whether to proceed incrementally or fall back to a full rebuild. Its primary export, `classifyUpdate(analysis, tokenBudget, allKnownFiles?)`, evaluates the `ChangeAnalysis` object containing per-file change levels.

The classifier applies two critical thresholds:

- **Structural change limit** – If structural changes exceed a configurable percentage (e.g., 50%) of changed files, the system escalates to `FULL_UPDATE`.
- **Token budget enforcement** – When all changes are `COSMETIC` and the projected LLM token usage stays within budget, the system permits a cosmetic-only update path.

This decision logic ensures that minor formatting edits never trigger expensive re-analysis, while significant refactoring cascades correctly through the dependency graph.

## The Incremental Graph Update Flow

When the classifier returns an incremental decision, the pipeline executes three phases:

1. **Phase 1: Fingerprint Check** – Zero LLM cost; purely local computation.
2. **Phase 2: Selective Re-analysis** – Only files marked `STRUCTURAL` are fed to the core analyzers in [`graph-builder.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/graph-builder.ts).
3. **Phase 3: Graph Stitching** – The new sub-graph merges into the existing knowledge graph, preserving unchanged nodes and relationships.

If the decision is `FULL_UPDATE`, the pipeline reverts to the original seven-phase analysis, rebuilding the entire graph from scratch.

For debugging or custom tooling, you can invoke the classifier directly:

```typescript
import { classifyUpdate } from "@understand-anything/core/change-classifier.js";
import { readFingerprints, compareFingerprints } from "@understand-anything/core/fingerprint.js";

const oldStore = await readFingerprints();
const newStore = await buildFingerprintStore(changedFiles);
const analysis = compareFingerprints(oldStore, newStore);
const decision = classifyUpdate(analysis, 50); // 50% structural threshold

console.log(decision);
// { mode: "INCREMENTAL", structuralCount: 3 }

```

## Summary

- **Fingerprint generation** in [`src/fingerprint.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/src/fingerprint.ts) creates deterministic structural hashes using tree-sitter parsing, stored in [`.understand-anything/fingerprints.json`](https://github.com/Lum1104/Understand-Anything/blob/main/.understand-anything/fingerprints.json).
- **Change detection** occurs via the post-commit hook in [`hooks/auto-update-prompt.md`](https://github.com/Lum1104/Understand-Anything/blob/main/hooks/auto-update-prompt.md), which patches fingerprints using `git diff --name-status` output.
- **Comparison logic** classifies every change as `NONE`, `COSMETIC`, or `STRUCTURAL` based on AST element equality or content hashing.
- **Classification** in [`src/change-classifier.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/src/change-classifier.ts) applies structural-change thresholds to decide between incremental patching and full rebuilds.
- **Incremental updates** reduce LLM token consumption by re-analyzing only modified structural units and stitching results into the existing knowledge graph.

## Frequently Asked Questions

### What is a structural fingerprint?

A structural fingerprint is a deterministic representation of a source file's AST-extracted elements—such as function signatures, class hierarchies, and import/export statements—generated by tree-sitter. It excludes whitespace and comments, ensuring that purely cosmetic edits produce identical fingerprints to their predecessors.

### How does the system handle files without tree-sitter support?

When a file lacks an available tree-sitter parser, `buildFingerprintStore` falls back to a **content-hash-only** fingerprint. The comparison logic in `compareFingerprints` then relies strictly on cryptographic hash equality, classifying changes as either `NONE` (identical) or `COSMETIC` (different bytes but potentially just formatting), never `STRUCTURAL` unless the file is added or deleted.

### What is the difference between COSMETIC and STRUCTURAL changes?

`COSMETIC` changes affect non-functional elements—whitespace, comments, or formatting—leaving the AST-derived structural elements unchanged. `STRUCTURAL` changes alter the code's shape, such as modifying function signatures, adding methods, or changing inheritance relationships. Only `STRUCTURAL` changes trigger re-analysis of dependencies and graph updates.

### When does the system fall back to a full rebuild?

The classifier escalates to `FULL_UPDATE` when structural changes exceed the configured threshold (typically 50% of modified files) or when the `fingerprint-check.mjs` script fails to execute. This safety mechanism ensures graph integrity when the incremental delta becomes too large to process efficiently or when the fingerprint store is potentially corrupted.