How Does Incremental Analysis with Fingerprint-Based Change Detection Work in Understand Anything?
Understand Anything avoids re-scanning entire projects by storing SHA-256 fingerprints and structural signatures for each file, comparing them on subsequent runs to detect only meaningful changes and skip expensive LLM re-analysis when possible.
The Egonex-AI/Understand-Anything repository implements a sophisticated incremental analysis system that eliminates redundant processing in large codebases. By combining cryptographic hashing with language-aware structural analysis, this fingerprint-based change detection mechanism determines exactly which files require re-processing and which can be safely ignored. The system operates through a three-stage pipeline that balances accuracy with performance.
The Three-Stage Incremental Analysis Pipeline
Stage 1: Fingerprint Generation with buildFingerprintStore
The process begins in packages/core/src/fingerprint.ts where the buildFingerprintStore function generates a comprehensive fingerprint for every source file. For each file, the system invokes the language-specific tree-sitter parser via the PluginRegistry to extract structural elements—function signatures, class definitions, imports, and exports—while simultaneously computing a SHA-256 content hash of the entire file.
The resulting FingerprintStore contains both the structural description and the content hash, persisted typically as .understand-anything/fingerprint.json. This dual-layer approach allows the system to distinguish between purely cosmetic whitespace changes and modifications that actually affect the knowledge graph.
Stage 2: Change Detection via analyzeChanges and compareFingerprints
On subsequent runs, the engine receives a list of files from Git and executes analyzeChanges to recompute fingerprints for only those modified paths. The compareFingerprints function then categorizes each change into one of three levels:
- NONE: The SHA-256 content hash matches exactly, indicating no changes whatsoever.
- COSMETIC: The content hash differs, but structural signatures (functions, classes, imports/exports) remain identical—typically representing formatting or comment changes.
- STRUCTURAL: Any difference in signatures, missing structural analysis, new or deleted files, or changes to import/export statements that affect the dependency graph.
This analysis returns a ChangeAnalysis object that groups files by their change level, providing the granular data needed for intelligent update decisions.
Stage 3: Update Classification with classifyUpdate
Located in packages/core/src/change-classifier.ts, the classifyUpdate routine transforms the raw ChangeAnalysis into actionable pipeline decisions. The function evaluates the scope and magnitude of changes to determine one of four update strategies:
- SKIP: Triggered when all changes are
NONEorCOSMETIC, requiring no re-analysis. - PARTIAL_UPDATE: Used for structural changes confined to specific directories, limiting re-analysis to affected areas.
- ARCHITECTURE_UPDATE: Triggered by new or removed top-level directories, or when exceeding 10 structural changes.
- FULL_UPDATE: Reserved for massive refactorings (>30 structural changes or >50% of the project), necessitating a complete regeneration of the knowledge graph.
The returned UpdateDecision specifies exactly which files to re-analyze, whether to rebuild the architecture graph, and whether to regenerate the guided tour.
Implementation Example: From Fingerprint to Update Decision
The following TypeScript implementation demonstrates the complete incremental analysis workflow:
import { buildFingerprintStore, analyzeChanges } from "./fingerprint.js";
import { classifyUpdate } from "./change-classifier.js";
import { pluginRegistry } from "./plugins/registry.js";
// 1️⃣ First run – create a full fingerprint store
const allFiles = await glob("**/*.{ts,js,tsx,jsx}", { path: projectRoot });
const fingerprintStore = buildFingerprintStore(
projectRoot,
allFiles,
pluginRegistry,
gitCommitHash,
);
// Persist the store (e.g. .understand-anything/fingerprint.json)
// ---------------------------------------------------------------
// 2️⃣ Subsequent run – ask Git for changed paths
const changed = await getGitChangedFiles(); // ["src/utils.ts", "src/newFeature.ts"]
// 3️⃣ Detect what actually changed
const changeAnalysis = analyzeChanges(
projectRoot,
changed,
fingerprintStore,
pluginRegistry,
);
// 4️⃣ Decide the scope of the re‑analysis
const decision = classifyUpdate(
changeAnalysis,
allFiles.length,
allFiles,
);
// 5️⃣ Feed the decision back to the core pipeline
if (decision.action !== "SKIP") {
await reanalyzeFiles(decision.filesToReanalyze);
if (decision.rerunArchitecture) await rebuildArchitectureGraph();
if (decision.rerunTour) await regenerateTour();
}
This pipeline minimizes computational overhead by ensuring that only structurally modified files trigger expensive LLM-driven analysis, while cosmetic changes are processed instantly without re-building the knowledge graph.
Summary
- Fingerprint-based change detection in Understand Anything combines SHA-256 content hashing with tree-sitter structural analysis to avoid redundant re-scanning.
- The
buildFingerprintStorefunction inpackages/core/src/fingerprint.tscreates dual-layer fingerprints capturing both content hashes and code signatures. - Change levels (
NONE,COSMETIC,STRUCTURAL) determined bycompareFingerprintsallow the system to distinguish between formatting changes and meaningful code modifications. - Update decisions (
SKIP,PARTIAL_UPDATE,ARCHITECTURE_UPDATE,FULL_UPDATE) generated byclassifyUpdateinpackages/core/src/change-classifier.tsoptimize pipeline execution based on change magnitude. - Only files with structural changes trigger LLM re-analysis, dramatically improving performance on repeated runs of large projects.
Frequently Asked Questions
What is a fingerprint in Understand Anything?
A fingerprint is a persistent data structure created by the extractFileFingerprint function that contains two critical components: a SHA-256 content hash of the entire file source and a structural description of function signatures, class definitions, and import/export statements. These fingerprints are stored in the FingerprintStore and serve as the baseline for detecting changes between analysis runs.
How does Understand Anything distinguish between cosmetic and structural changes?
The system uses compareFingerprints to perform a two-tier comparison. First, it checks the SHA-256 content hash; if matched, the change is NONE. If the hash differs, it compares the structural signatures extracted by tree-sitter parsers. Identical structures with different hashes result in a COSMETIC classification (formatting/comments), while any difference in signatures triggers a STRUCTURAL classification requiring re-analysis.
When does the system trigger a full update versus a partial update?
According to the classifyUpdate implementation in packages/core/src/change-classifier.ts, a FULL_UPDATE occurs when there are more than 30 structural changes or when over 50% of the project files change structurally. A PARTIAL_UPDATE handles smaller structural changes confined to specific directories, while an ARCHITECTURE_UPDATE triggers for new or removed top-level directories or when exceeding 10 structural changes.
Which tree-sitter parsers are used for fingerprint generation?
The PluginRegistry in packages/core/src/plugins/registry.ts provides the analyzeFile function that invokes language-specific tree-sitter parsers based on file extension. The buildFingerprintStore function passes each file through this registry to extract language-agnostic structural signatures (functions, classes, imports) regardless of whether the source is TypeScript, JavaScript, or other supported languages.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →