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

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 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, defined by the constant FINGERPRINT_FILE = "fingerprints.json" (lines 125–142, 249–294).

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 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 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).


# 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, the compareFingerprints function determines the nature of changes between the stored baseline and the current file state:

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), distinguishing between unchanged files, whitespace or comment modifications, and API-altering edits.

Classification Logic: Deciding the Update Strategy

The 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.
  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:

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 creates deterministic structural hashes using tree-sitter parsing, stored in .understand-anything/fingerprints.json.
  • Change detection occurs via the post-commit hook in 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 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.

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 →