# How Incremental Analysis Works with Structural Fingerprints in Understand Anything

> Discover how Understand Anything uses incremental analysis and structural fingerprints to efficiently update knowledge graphs, skipping cosmetic code changes and focusing on impactful edits.

- Repository: [Egonex/Understand-Anything](https://github.com/Egonex-AI/Understand-Anything)
- Tags: internals
- Published: 2026-06-27

---

**Understand Anything combines SHA-256 content hashing with AST-derived structural fingerprints to detect whether code changes affect the knowledge graph's topology, enabling targeted incremental updates that skip cosmetic edits.**

Understand Anything, from the Egonex-AI repository, builds a comprehensive knowledge graph mapping functions, classes, imports, and exports across your codebase. When files change, the tool leverages **incremental analysis with structural fingerprints** to avoid rebuilding the entire graph. By generating deterministic signatures for each file's structural elements and comparing them between Git commits, the system precisely isolates which modifications require graph updates versus those that merely alter implementation details.

## Creating Structural Fingerprints from Source Code

The fingerprinting engine resides in [`understand-anything-plugin/packages/core/src/fingerprint.ts`](https://github.com/Egonex-AI/Understand-Anything/blob/main/understand-anything-plugin/packages/core/src/fingerprint.ts), where each source file is converted into a compact, deterministic representation of its graph-relevant elements.

### Extracting Signatures with extractFileFingerprint

The `extractFileFingerprint` function processes Tree-sitter parse results to build a **FileFingerprint** object containing function signatures, class definitions, imports, exports, and a SHA-256 hash of the raw content:

```ts
export function extractFileFingerprint(
  filePath: string,
  content: string,
  analysis: StructuralAnalysis,
): FileFingerprint {
  const hash = contentHash(content);                               // ← content hash
  const exportedNames = new Set(analysis.exports.map(e => e.name));

  const functions = analysis.functions.map(fn => ({
    name: fn.name,
    params: [...fn.params],
    returnType: fn.returnType,
    exported: exportedNames.has(fn.name),
    lineCount: fn.lineRange[1] - fn.lineRange[0] + 1,
  }));

  const classes = analysis.classes.map(cls => ({
    name: cls.name,
    methods: [...cls.methods],
    properties: [...cls.properties],
    exported: exportedNames.has(cls.name),
    lineCount: cls.lineRange[1] - cls.lineRange[0] + 1,
  }));

  const imports = analysis.imports.map(imp => ({
    source: imp.source,
    specifiers: [...imp.specifiers],
  }));

  return {
    filePath,
    contentHash: hash,
    functions,
    classes,
    imports,
    exports: analysis.exports.map(e => e.name),
    totalLines: content.split("\n").length,
    hasStructuralAnalysis: true,
  };
}

```

The **Tree-sitter** parser, registered in [`understand-anything-plugin/packages/core/src/plugins/registry.ts`](https://github.com/Egonex-AI/Understand-Anything/blob/main/understand-anything-plugin/packages/core/src/plugins/registry.ts), supplies the `StructuralAnalysis` object containing AST-extracted elements. For unsupported languages, the system stores only the content hash and sets `hasStructuralAnalysis: false`, triggering a conservative full re-analysis on any change.

### Persisting Fingerprints in the FingerprintStore

All fingerprints for a project scan are aggregated via `buildFingerprintStore` in [`fingerprint.ts`](https://github.com/Egonex-AI/Understand-Anything/blob/main/fingerprint.ts):

```ts
export function buildFingerprintStore(
  projectDir: string,
  filePaths: string[],
  registry: PluginRegistry,
  gitCommitHash: string,
): FingerprintStore { … }

```

This store is serialized alongside the knowledge graph (typically in [`.understand-anything/knowledge-graph.json`](https://github.com/Egonex-AI/Understand-Anything/blob/main/.understand-anything/knowledge-graph.json)), preserving the state of the codebase at a specific Git commit.

## Detecting Changes Between Analysis Runs

When a new analysis begins, the system determines repository staleness through Git operations and fingerprint comparison logic located in [`understand-anything-plugin/packages/core/src/staleness.ts`](https://github.com/Egonex-AI/Understand-Anything/blob/main/understand-anything-plugin/packages/core/src/staleness.ts).

### Identifying Modified Files via Git

The `getChangedFiles` function calculates the delta between the stored Git commit and the current HEAD:

```ts
export function getChangedFiles(projectDir: string, lastCommitHash: string): string[] {
  // runs `git diff <last>..HEAD --name-only`
}

```

This returns a list of file paths modified since the last analysis, providing the input for the incremental comparison stage.

### Classifying Changes with compareFingerprints

For each modified file, `analyzeChanges` (also in [`staleness.ts`](https://github.com/Egonex-AI/Understand-Anything/blob/main/staleness.ts)) generates a fresh fingerprint and invokes `compareFingerprints` to categorize the change:

```ts
export function compareFingerprints(
  oldFp: FileFingerprint,
  newFp: FileFingerprint,
): FileChangeResult {
  // ① identical content → NONE
  // ② missing structural analysis → STRUCTURAL (conservative)
  // ③ otherwise compare function & class signatures, imports & exports
  //      – any signature difference → STRUCTURAL
  //      – only content diff, signatures identical → COSMETIC
}

```

The function returns one of three **change levels**:

- **NONE**: The file is byte-for-byte identical.
- **COSMETIC**: Only implementation details changed (comments, internal logic); the knowledge graph requires no updates.
- **STRUCTURAL**: Signatures changed (new parameters, added exports, modified imports); the graph must be refreshed for this file.

The `analyzeChanges` function aggregates these results into a `ChangeAnalysis` object containing arrays for `newFiles`, `deletedFiles`, `structurallyChangedFiles`, `cosmeticOnlyFiles`, and `unchangedFiles`.

## Incrementally Updating the Knowledge Graph

After change detection, `mergeGraphUpdate` (implemented in [`staleness.ts`](https://github.com/Egonex-AI/Understand-Anything/blob/main/staleness.ts)) performs a targeted merge:

- **Removes** nodes and edges associated with `structurallyChangedFiles`, `newFiles`, or `deletedFiles`.
- **Adds** fresh nodes and edges generated from re-parsing the structurally changed files.
- **Preserves** all existing graph elements linked to unchanged or cosmetically modified files.

This selective update strategy ensures that incremental analysis touches only the minimal subgraph affected by structural changes, dramatically reducing analysis time for large codebases.

## Practical Implementation Examples

### Running an Incremental Scan

```ts
import { readFileSync } from "fs";
import {
  buildFingerprintStore,
  analyzeChanges,
  compareFingerprints,
} from "./packages/core/src/fingerprint.js";
import { getChangedFiles } from "./packages/core/src/staleness.js";
import { registry } from "./packages/core/src/plugins/registry.js";

const projectDir = "/my/project";
const lastStore = JSON.parse(readFileSync("./.understand-anything/fingerprint-store.json", "utf-8"));
const changed = getChangedFiles(projectDir, lastStore.gitCommitHash);

const changeReport = analyzeChanges(projectDir, changed, lastStore, registry);
console.log("Structural changes:", changeReport.structurallyChangedFiles);

```

### Manually Comparing Two Fingerprints

```ts
import { compareFingerprints } from "./packages/core/src/fingerprint.js";

const oldFp = …; // loaded from previous store
const newFp = …; // freshly extracted

const result = compareFingerprints(oldFp, newFp);
console.log(`File ${result.filePath} → ${result.changeLevel}`);

```

### Building a Fresh Fingerprint Store

```ts
import { glob } from "fast-glob";
import { buildFingerprintStore } from "./packages/core/src/fingerprint.js";
import { registry } from "./packages/core/src/plugins/registry.js";

const allTs = await glob("src/**/*.ts", { cwd: projectDir });
const store = buildFingerprintStore(projectDir, allTs, registry, "currentGitHash");
await writeFile("./.understand-anything/fingerprint-store.json", JSON.stringify(store));

```

## Summary

- **Structural fingerprints** in Understand Anything capture SHA-256 content hashes alongside AST-derived signatures for functions, classes, imports, and exports.
- The `extractFileFingerprint` function in [`fingerprint.ts`](https://github.com/Egonex-AI/Understand-Anything/blob/main/fingerprint.ts) generates these deterministic snapshots, while `compareFingerprints` classifies changes as NONE, COSMETIC, or STRUCTURAL.
- Git integration via `getChangedFiles` identifies modified files, triggering targeted re-analysis only for structurally changed components.
- The `mergeGraphUpdate` process preserves existing knowledge graph nodes for cosmetic changes while rebuilding only affected subgraphs, optimizing performance for incremental runs.

## Frequently Asked Questions

### What constitutes a structural fingerprint in Understand Anything?

A structural fingerprint is a deterministic object generated by `extractFileFingerprint` in [`packages/core/src/fingerprint.ts`](https://github.com/Egonex-AI/Understand-Anything/blob/main/packages/core/src/fingerprint.ts) that combines a SHA-256 hash of the file's content with serialized signatures of its functions (names, parameters, return types), classes (methods, properties), imports, and exports. This compact representation allows the system to detect whether changes affect the knowledge graph's topology without re-parsing unchanged files.

### How does the system distinguish between cosmetic and structural code changes?

The `compareFingerprints` function compares old and new fingerprints: if the content hashes differ but all function signatures, class definitions, import statements, and export declarations remain identical, the change is classified as **COSMETIC**. If any structural element differs, or if the file lacks parser support (triggering conservative analysis), the change is marked **STRUCTURAL**, requiring a knowledge graph update.

### What happens when Understand Anything encounters an unsupported file type?

According to the fallback logic in `extractFileFingerprint`, files without Tree-sitter parser support store only the SHA-256 content hash and set `hasStructuralAnalysis: false`. During comparison, these files automatically trigger a **STRUCTURAL** classification on any content change, ensuring the knowledge graph remains accurate even when detailed AST analysis is unavailable.

### Where are fingerprints stored between analysis runs?

The `buildFingerprintStore` function aggregates all file fingerprints into a `FingerprintStore` object, which is serialized alongside the knowledge graph (typically in [`.understand-anything/knowledge-graph.json`](https://github.com/Egonex-AI/Understand-Anything/blob/main/.understand-anything/knowledge-graph.json)). This store includes the Git commit hash, enabling `getChangedFiles` to calculate deltas between the stored state and the current repository HEAD during subsequent incremental analysis runs.