How to Configure Incremental Code Updates with Fingerprinting in Egonex AI
Egonex AI uses SHA-256 content hashes and structural fingerprints to detect only modified files, classifying changes as COSMETIC or STRUCTURAL so the knowledge graph updates only when code semantics actually change.
The Understand-Anything plugin for Egonex AI constructs a comprehensive knowledge graph of your codebase. To maintain this graph efficiently without rescanning entire repositories on every save, the system implements a fingerprint-based incremental update pipeline. This approach leverages cryptographic hashing and tree-sitter structural analysis to distinguish between cosmetic formatting changes and meaningful code modifications.
How Fingerprint-Based Incremental Updates Work
The incremental update pipeline follows a deterministic five-step process to minimize graph rebuilds:
- Full scan → Generate a fingerprint store containing SHA-256 hashes and structural signatures (functions, classes, imports/exports) for every file.
- Detect changes → Identify modified files using
git diffor filesystem watchers. - Re-fingerprint → Hash only the changed files and compare against stored fingerprints.
- Classify → Assign a
changeLevelofNONE,COSMETIC, orSTRUCTURALbased on the diff. - Update → Mutate the graph only for
STRUCTURALchanges, new files, or deletions.
This workflow lives primarily in packages/core/src/fingerprint.ts and is orchestrated by the staleness module (packages/core/src/staleness.ts).
Core Fingerprinting Components
The fingerprinting system relies on several key functions exported from the core package:
| Function | Responsibility | Source Location |
|---|---|---|
contentHash |
Generates a SHA-256 hash of raw file text. | packages/core/src/fingerprint.ts |
extractFileFingerprint |
Converts tree-sitter StructuralAnalysis into a FileFingerprint containing function signatures, class definitions, imports, and exports. |
packages/core/src/fingerprint.ts |
compareFingerprints |
Diffs two FileFingerprint objects and returns a FileChangeResult with a changeLevel enum. |
packages/core/src/fingerprint.ts |
buildFingerprintStore |
Traverses file lists, runs the tree-sitter analyzer via PluginRegistry, and stores fingerprints with the current Git commit hash. |
packages/core/src/fingerprint.ts |
analyzeChanges |
Processes changed file paths, re-fingerprints each, and aggregates results into a ChangeAnalysis object. |
packages/core/src/fingerprint.ts |
PluginRegistry |
Supplies language-specific tree-sitter parsers for both full and incremental scans. | packages/core/src/plugins/registry.ts |
Implementing the Incremental Update Pipeline
Initial Fingerprint Store Creation
Begin by generating a baseline fingerprint store for your entire repository. The buildFingerprintStore function requires a PluginRegistry instance to handle language-specific parsing.
import { buildFingerprintStore, PluginRegistry } from '@understand-anything/core';
const registry = new PluginRegistry();
const allFiles = await getAllProjectFiles(); // e.g., via glob or git ls-files
const store = buildFingerprintStore(
projectRoot,
allFiles,
registry,
currentGitCommitHash,
);
// Persist to .understand-anything/fingerprint.json
Detecting and Analyzing Changes
When files change, use getChangedFiles to identify modifications via Git, then analyzeChanges to determine the impact:
import { getChangedFiles, analyzeChanges } from '@understand-anything/core';
const changed = await getChangedFiles(projectRoot); // Uses git diff --name-only
const analysis = analyzeChanges(
projectRoot,
changed,
previousStore,
registry,
);
The ChangeAnalysis result contains:
newFiles→ Treated as structural (new code must be added).deletedFiles→ Graph nodes are removed.structurallyChangedFiles→ Graph edges are rebuilt.cosmeticOnlyFiles→ No graph mutation required (only internal logic changed).unchangedFiles→ Skipped entirely.
Applying Graph Updates
Use mergeGraphUpdate to apply only the necessary mutations:
import { mergeGraphUpdate } from '@understand-anything/core';
const updatedGraph = mergeGraphUpdate(
oldGraph,
analysis.structurallyChangedFiles,
analysis.newFiles,
analysis.deletedFiles,
);
Complete Implementation Example
The following script demonstrates a minimal incremental update workflow that can be wired into CI hooks or file watchers:
#!/usr/bin/env node
import { resolve } from 'node:path';
import { readFileSync, writeFileSync } from 'node:fs';
import {
PluginRegistry,
buildFingerprintStore,
analyzeChanges,
getChangedFiles,
mergeGraphUpdate,
} from '@understand-anything/core';
// Configuration
const projectRoot = resolve(process.cwd());
const fingerprintPath = resolve(projectRoot, '.understand-anything/fingerprint.json');
const graphPath = resolve(projectRoot, '.understand-anything/knowledge-graph.json');
// Load previous state
const previousStore = JSON.parse(readFileSync(fingerprintPath, 'utf-8'));
const oldGraph = JSON.parse(readFileSync(graphPath, 'utf-8'));
const registry = new PluginRegistry();
(async () => {
// Detect changes
const changed = await getChangedFiles(projectRoot);
const analysis = analyzeChanges(projectRoot, changed, previousStore, registry);
console.log('Incremental analysis:');
console.log(' New files:', analysis.newFiles);
console.log(' Deleted files:', analysis.deletedFiles);
console.log(' Structural changes:', analysis.structurallyChangedFiles);
console.log(' Cosmetic only:', analysis.cosmeticOnlyFiles);
// Update graph
const updatedGraph = mergeGraphUpdate(
oldGraph,
analysis.structurallyChangedFiles,
analysis.newFiles,
analysis.deletedFiles,
);
// Rebuild fingerprints for touched files
const allFiles = [...analysis.structurallyChangedFiles, ...analysis.newFiles];
const newStore = buildFingerprintStore(
projectRoot,
allFiles,
registry,
await getCurrentCommitHash(),
);
// Persist
writeFileSync(graphPath, JSON.stringify(updatedGraph, null, 2));
writeFileSync(fingerprintPath, JSON.stringify(newStore, null, 2));
console.log('✅ Incremental update complete.');
})();
Key Source Files Reference
| File | Role |
|---|---|
packages/core/src/fingerprint.ts |
Core fingerprint generation, comparison, and analyzeChanges implementation. |
packages/core/src/staleness.ts |
Orchestrates getChangedFiles, isStale checks, and mergeGraphUpdate calls. |
packages/core/src/plugins/registry.ts |
Provides tree-sitter parsers via PluginRegistry. |
packages/core/src/index.ts |
Re-exports the public fingerprint API. |
Summary
- Fingerprinting combines SHA-256 content hashes with structural signatures (functions, classes, imports) to create deterministic file identifiers.
compareFingerprintsclassifies changes asNONE,COSMETIC, orSTRUCTURAL, preventing unnecessary graph rebuilds during formatting changes.analyzeChangesaggregates file-level results into aChangeAnalysisthat specifies exactly which files need graph mutations.- The staleness module (
staleness.ts) coordinates Git-based change detection with fingerprint comparison to drive the incremental update loop. - Only
STRUCTURALchanges, new files, and deletions trigger graph updates, keeping the system responsive even in large monorepos.
Frequently Asked Questions
What constitutes a file fingerprint in Egonex AI?
A file fingerprint is a FileFingerprint object generated by extractFileFingerprint that contains a SHA-256 content hash, plus structural signatures for every function, class, import, and export in the file. This composite hash allows the system to detect when code semantics change versus when only formatting or comments change.
How does the system distinguish between cosmetic and structural changes?
The compareFingerprints function in packages/core/src/fingerprint.ts returns a changeLevel enum. If the SHA-256 content hash differs but the structural signatures (function names, class hierarchies, import/export lists) remain identical, the change is classified as COSMETIC. If the structural signatures differ, it is marked STRUCTURAL, triggering a graph rebuild.
Where is the incremental update logic orchestrated?
The staleness module at packages/core/src/staleness.ts drives the incremental update loop. It calls getChangedFiles to identify modified paths via Git, delegates to analyzeChanges for fingerprint comparison, and invokes mergeGraphUpdate to apply only the necessary mutations to the knowledge graph.
Can I configure incremental updates without using Git?
Yes. While getChangedFiles defaults to Git-based detection (git diff --name-only), you can implement custom file watchers or CI diff tools. Pass any array of changed file paths directly to analyzeChanges; the fingerprinting logic itself is agnostic to how you detect changes, depending only on the file paths provided.
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 →