How Understand Anything Implements Incremental Code Analysis with Fingerprint-Based Change Detection
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 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, andImportFingerprintthat 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) 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.
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) 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) 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 at lines 12-86) consumes the ChangeAnalysis result and returns a high-level decision:
- SKIP – All files are
NONEorCOSMETIC; 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:
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
extractFileFingerprintcombines SHA-256 content hashes with structural signatures (functions, classes, imports) to create lightweight file identifiers. - Change detection via
analyzeChangescompares current fingerprints against stored baselines to identifyNONE,COSMETIC, orSTRUCTURALchange levels. - Update classification in
classifyUpdateroutes changes through optimized paths:SKIPfor cosmetic changes,PARTIAL_UPDATEfor limited structural changes,ARCHITECTURE_UPDATEfor directory shifts, andFULL_UPDATEfor 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) 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, 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) 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.
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 →