# How Understand Anything Implements Incremental Code Analysis with Fingerprint-Based Change Detection

> Understand Anything uses fingerprint-based change detection with SHA-256 hashes and structural signatures to optimize incremental code analysis, rebuilding only affected knowledge graph parts.

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

---

**Understand Anything uses SHA-256 content hashes combined with structural signatures to detect code changes and rebuild only the affected parts of its knowledge graph, skipping unchanged files entirely.**

Understand Anything is an open-source knowledge graph generator for codebases that avoids expensive full-project rescans. Instead, it implements incremental code analysis using fingerprint-based change detection to recompute only what actually changed structurally.

## Creating Structural Fingerprints

The incremental pipeline begins with **fingerprint creation**, which captures both content and structure for every source file.

When a project is scanned, Tree-Sitter parses each file and `extractFileFingerprint` (located in [`packages/core/src/fingerprint.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/packages/core/src/fingerprint.ts) at lines 79-122) transforms the parser's `StructuralAnalysis` into a `FileFingerprint` containing:

- **`contentHash`** – A fast SHA-256 hash of the raw source (implemented at lines 70-71)
- **Structural signatures** – Lists of `FunctionFingerprint`, `ClassFingerprint`, and `ImportFingerprint` that record only signatures (name, parameters, return type, export status, and line count) rather than full implementations

This lightweight representation allows the system to distinguish between cosmetic changes (whitespace, comments) and structural changes (API modifications).

## Building and Persisting the Fingerprint Store

The `buildFingerprintStore` function (lines 53-91 in [`fingerprint.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/fingerprint.ts)) initializes the baseline for comparison. It walks every file matching the project's glob pattern, reads contents, requests structural analysis from the `PluginRegistry`, and stores the resulting `FileFingerprint` in a JSON-serializable `FingerprintStore` alongside the current Git commit hash.

```typescript
import { buildFingerprintStore } from "@understand-anything/core";

const allFiles = await getAllSourceFiles(); // e.g., via glob
const store = buildFingerprintStore(
  "/my/project",
  allFiles,
  pluginRegistry,
  await getCurrentGitCommitHash(),
);
// Persist to .understand-anything/fingerprint.json

```

This store serves as the snapshot for subsequent incremental runs.

## Detecting Changes with Git Integration

On subsequent runs, only files reported by `git diff` (or another change detector) are inspected. The `analyzeChanges` function (lines 97-185 in [`fingerprint.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/fingerprint.ts)) recreates fresh fingerprints for each changed file, then delegates to `compareFingerprints` to determine the delta.

The system integrates with version control to minimize the file set requiring re-analysis, avoiding unnecessary parsing of unchanged modules.

## Fingerprint Comparison and Change Levels

The `compareFingerprints` function (lines 124-248 in [`fingerprint.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/fingerprint.ts)) determines the change level by comparing the previous and current fingerprints:

- **NONE** – Identical content hash; skip entirely
- **COSMETIC** – Content differs but all structural signatures match (only implementation details changed)
- **STRUCTURAL** – Any signature change such as added/removed functions or classes, parameter modifications, or import/export alterations

The function walks through function names, class names, method/property sets, imports, and exports, collecting human-readable `details` for every disparity.

## Update Classification Strategy

Once changes are detected, `classifyUpdate` (in [`packages/core/src/change-classifier.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/packages/core/src/change-classifier.ts) at lines 12-86) consumes the `ChangeAnalysis` result and returns a high-level decision:

- **SKIP** – All files are `NONE` or `COSMETIC`; no structural changes detected
- **PARTIAL_UPDATE** – Fewer than 10 files have structural changes; reanalyze only affected files
- **ARCHITECTURE_UPDATE** – Directory-level changes or moderate structural churn detected; rerun architecture detection (`detectLayers`) on changed files plus new top-level directories
- **FULL_UPDATE** – Extreme churn (>30 files or >50% of the graph); rebuild the entire graph from scratch

This decision matrix balances freshness against computational cost.

## Driving the Incremental Graph Rebuild

The consumer (typically the CLI command) orchestrates the workflow by loading the previous `FingerprintStore`, running `analyzeChanges` on the current diff, and passing the result to `classifyUpdate`:

```typescript
import { analyzeChanges, classifyUpdate } from "@understand-anything/core";

const changed = await getChangedFilesSinceLastRun();
const previousStore = loadFingerprintStore();
const changes = analyzeChanges(
  "/my/project",
  changed,
  previousStore,
  pluginRegistry,
);

const decision = classifyUpdate(
  changes,
  previousStore.files ? Object.keys(previousStore.files).length : 0,
  allFiles,
);

switch (decision.action) {
  case "SKIP":
    console.log("No structural changes – nothing to rebuild.");
    break;
  case "PARTIAL_UPDATE":
    console.log("Re-analyzing", decision.filesToReanalyze);
    // Re-run GraphBuilder only on affected files
    break;
  case "ARCHITECTURE_UPDATE":
    console.log("Directory structure changed – rerunning architecture analysis.");
    break;
  case "FULL_UPDATE":
    console.log("Massive churn – rebuilding the entire graph.");
    break;
}

```

For `PARTIAL_UPDATE`, only affected files are re-parsed and their graph nodes refreshed. `ARCHITECTURE_UPDATE` triggers layer detection and tour generation, while `FULL_UPDATE` reconstructs the entire knowledge graph.

## Summary

- **Fingerprint creation** in `extractFileFingerprint` combines SHA-256 content hashes with structural signatures (functions, classes, imports) to create lightweight file identifiers.
- **Change detection** via `analyzeChanges` compares current fingerprints against stored baselines to identify `NONE`, `COSMETIC`, or `STRUCTURAL` change levels.
- **Update classification** in `classifyUpdate` routes changes through optimized paths: `SKIP` for cosmetic changes, `PARTIAL_UPDATE` for limited structural changes, `ARCHITECTURE_UPDATE` for directory shifts, and `FULL_UPDATE` for massive churn.
- **Zero redraw** – Unchanged files are never reparsed, making the analysis truly incremental and performant on large codebases.

## Frequently Asked Questions

### How does Understand Anything distinguish between cosmetic and structural changes?

Understand Anything uses `compareFingerprints` (lines 124-248 in [`fingerprint.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/fingerprint.ts)) to check if the SHA-256 content hash differs while the structural signatures remain identical. If the hash changes but all function names, class definitions, parameter lists, and import/export statements match, the change is classified as **COSMETIC** (whitespace or comments only). Any signature mismatch triggers **STRUCTURAL**.

### What triggers a full rebuild versus a partial update?

According to the decision matrix in [`change-classifier.ts`](https://github.com/Lum1104/Understand-Anything/blob/main/change-classifier.ts), a **FULL_UPDATE** occurs when more than 30 files changed or when structural changes exceed 50% of the knowledge graph. A **PARTIAL_UPDATE** handles fewer than 10 files with structural changes. Intermediate churn triggers **ARCHITECTURE_UPDATE**, which re-examines layer relationships without rebuilding the entire graph.

### Can the fingerprint-based system work without Git?

Yes. While the implementation uses `git diff` to identify candidate files, the `analyzeChanges` function accepts any array of file paths. Alternative change detectors can feed the incremental pipeline as long as they provide the list of potentially modified files since the last run.

### Where are fingerprints stored between runs?

The `FingerprintStore` is serialized to JSON (typically at [`.understand-anything/fingerprint.json`](https://github.com/Lum1104/Understand-Anything/blob/main/.understand-anything/fingerprint.json)) by the consumer application. It persists content hashes, structural signatures, and the associated Git commit hash, enabling the `loadFingerprintStore` function to restore the previous state for comparison on subsequent runs.