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:

  1. Full scan → Generate a fingerprint store containing SHA-256 hashes and structural signatures (functions, classes, imports/exports) for every file.
  2. Detect changes → Identify modified files using git diff or filesystem watchers.
  3. Re-fingerprint → Hash only the changed files and compare against stored fingerprints.
  4. Classify → Assign a changeLevel of NONE, COSMETIC, or STRUCTURAL based on the diff.
  5. Update → Mutate the graph only for STRUCTURAL changes, 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.
  • compareFingerprints classifies changes as NONE, COSMETIC, or STRUCTURAL, preventing unnecessary graph rebuilds during formatting changes.
  • analyzeChanges aggregates file-level results into a ChangeAnalysis that 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 STRUCTURAL changes, 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →