/** * Incremental Reindexing Engine (SMCP-098) * * Implements surgical chunk-level updates to avoid re-embedding entire files * when only a small portion has changed. This dramatically improves performance * for large files where most content remains unchanged. * * Key Features: * - Hash-based chunk change detection (position-independent) * - Moved chunk detection (same content, different position) * - Reuses existing embeddings for unchanged chunks * - Surgical deletion of removed chunks * * Performance Improvement: * - Before: Edit 1 line in 5000-line file = re-embed ~50 chunks = ~2.5 seconds * - After: Edit 1 line = re-embed ~2 affected chunks = ~100ms (25x faster) * * @module incrementalReindex */ import { Chunk } from './chunking.js'; import { ExistingChunk, ChunkRecord } from '../storage/lancedb.js'; /** * Result of comparing old and new chunks for a file */ export interface ChunkDiffResult { /** Chunks that need to be added (new content, need embedding) */ added: NewChunk[]; /** Chunks that were removed (delete from store) */ removed: ExistingChunk[]; /** Chunks that are unchanged (keep as-is) */ unchanged: ExistingChunk[]; /** Chunks that moved position but content is same (update metadata only) */ moved: MovedChunk[]; /** Statistics about the diff */ stats: ChunkDiffStats; } /** * A new chunk that needs embedding */ export interface NewChunk { /** Generated UUID for the chunk */ id: string; /** Chunk text content */ text: string; /** Start line in source file */ startLine: number; /** End line in source file */ endLine: number; /** Position-independent hash of chunk content */ chunkHash: string; } /** * A chunk that moved position but has the same content */ export interface MovedChunk { /** The existing chunk (with old position) */ existing: ExistingChunk; /** New line position */ newStartLine: number; /** New end line position */ newEndLine: number; } /** * Statistics about the chunk diff */ export interface ChunkDiffStats { /** Total old chunks */ oldChunkCount: number; /** Total new chunks */ newChunkCount: number; /** Number of chunks to add */ addedCount: number; /** Number of chunks to remove */ removedCount: number; /** Number of unchanged chunks */ unchangedCount: number; /** Number of moved chunks */ movedCount: number; /** Estimated embedding operations saved */ embeddingsSaved: number; /** Whether incremental approach was beneficial */ incrementalBeneficial: boolean; } /** * Result of an incremental reindex operation */ export interface IncrementalReindexResult { /** Whether the operation completed successfully */ success: boolean; /** Number of new chunks embedded */ chunksEmbedded: number; /** Number of chunks reused (embedding saved) */ chunksReused: number; /** Number of chunks deleted */ chunksDeleted: number; /** Number of chunks with updated metadata */ chunksUpdated: number; /** Total duration in milliseconds */ durationMs: number; /** Error message if failed */ error?: string; } /** * Compute a position-independent hash for a chunk * * This hash is based only on the normalized text content, not on * line numbers or position. This allows detecting moved chunks * that have the same content but different positions. * * @param text - Chunk text content * @returns SHA256 hash (first 32 characters) */ export declare function computeChunkHash(text: string): string; /** * Compare old chunks with new chunks to determine minimal update operations * * Algorithm: * 1. Build a map of old chunks by their content hash * 2. For each new chunk: * - If hash matches an old chunk at same position -> unchanged * - If hash matches an old chunk at different position -> moved * - If hash doesn't match any old chunk -> added * 3. Old chunks not matched by any new chunk -> removed * * @param oldChunks - Existing chunks from the database * @param newChunks - Newly generated chunks from the file * @returns ChunkDiffResult with categorized chunks */ export declare function diffChunks(oldChunks: ExistingChunk[], newChunks: Chunk[]): ChunkDiffResult; /** * Create ChunkRecords from moved chunks (reusing existing embeddings) * * @param movedChunks - Chunks that moved position * @param relativePath - File path for the records * @param contentHash - File content hash * @returns ChunkRecords ready for insertion */ export declare function createRecordsFromMovedChunks(movedChunks: MovedChunk[], relativePath: string, contentHash: string): ChunkRecord[]; /** * Create ChunkRecords from unchanged chunks (reusing everything) * * @param unchangedChunks - Chunks that haven't changed * @param relativePath - File path for the records * @param contentHash - File content hash * @returns ChunkRecords ready for insertion */ export declare function createRecordsFromUnchangedChunks(unchangedChunks: ExistingChunk[], relativePath: string, contentHash: string): ChunkRecord[]; /** * Create partial ChunkRecords from new chunks (need embedding) * * @param newChunks - New chunks that need embedding * @param relativePath - File path for the records * @param contentHash - File content hash * @returns Partial ChunkRecords (vector field will be empty) */ export declare function createPartialRecordsFromNewChunks(newChunks: NewChunk[], relativePath: string, contentHash: string): ChunkRecord[]; /** * Check if incremental reindexing should be used for this update * * Incremental reindexing has overhead (loading existing chunks, diffing). * It's only beneficial for larger files where we expect to save embeddings. * * @param oldChunkCount - Number of existing chunks for the file * @returns true if incremental approach should be used */ export declare function shouldUseIncremental(oldChunkCount: number): boolean; /** * Determine if the diff result justifies incremental approach * * If most chunks are new anyway, it might be faster to just re-embed everything. * * @param diff - The chunk diff result * @returns true if incremental was worthwhile */ export declare function wasIncrementalWorthwhile(diff: ChunkDiffResult): boolean; //# sourceMappingURL=incrementalReindex.d.ts.map