/** * Merkle DAG Change Detection Engine * * Implements Merkle tree-based change detection for efficient incremental indexing. * This enables chunk-level change detection instead of file-level, reducing reindex * time significantly for large codebases with small changes. * * Inspired by claude-context-local's Merkle DAG implementation. * * Features: * - Content-hash based change detection * - Hierarchical structure: Project -> Directory -> File -> Chunk * - Efficient diff algorithm to identify changed nodes only * - Snapshot persistence for fast startup * - Support for detecting moved/renamed chunks * * @module merkleTree */ /** * Type of Merkle node in the tree hierarchy */ export type MerkleNodeType = 'project' | 'directory' | 'file' | 'chunk'; /** * Base Merkle node structure */ export interface MerkleNode { /** Node type */ type: MerkleNodeType; /** Relative path from project root */ path: string; /** SHA256 hash of this node's content */ hash: string; /** Hash of children combined (for non-leaf nodes) */ childrenHash?: string; /** Child node hashes (for non-leaf nodes) */ children?: Map; } /** * Chunk node with additional metadata */ export interface ChunkNode extends MerkleNode { type: 'chunk'; /** Start line in source file (1-indexed) */ startLine: number; /** End line in source file (1-indexed) */ endLine: number; /** Hash of the chunk text content */ contentHash: string; /** Optional chunk type (function, class, etc.) */ chunkType?: string; /** Optional chunk name */ chunkName?: string; } /** * File node containing chunk children */ export interface FileNode extends MerkleNode { type: 'file'; /** File content hash */ contentHash: string; /** Size in bytes */ size: number; /** Last modified timestamp */ mtime: number; /** Map of chunk ID to chunk hash */ chunks: Map; /** Ordered list of chunk IDs for detecting reordering */ chunkOrder: string[]; } /** * Directory node containing file and subdirectory children */ export interface DirectoryNode extends MerkleNode { type: 'directory'; /** Map of child name to child hash */ children: Map; } /** * Project root node */ export interface ProjectNode extends MerkleNode { type: 'project'; /** Map of relative path to hash */ children: Map; /** Version for format migrations */ version: string; /** Timestamp of last update */ lastUpdated: string; } /** * Result of comparing two Merkle trees */ export interface MerkleDiff { /** Files that were added (new files) */ addedFiles: string[]; /** Files that were modified (content changed) */ modifiedFiles: string[]; /** Files that were removed */ removedFiles: string[]; /** Files with only chunk-level changes (for partial reindexing) */ chunkChanges: ChunkDiff[]; /** Total number of changes */ totalChanges: number; } /** * Chunk-level diff for a single file */ export interface ChunkDiff { /** File path */ filePath: string; /** Chunks that were added */ addedChunks: string[]; /** Chunks that were modified */ modifiedChunks: string[]; /** Chunks that were removed */ removedChunks: string[]; /** Chunks that appear to have moved (same hash, different position) */ movedChunks: Array<{ chunkId: string; from: number; to: number; }>; } /** Current Merkle tree format version */ export declare const MERKLE_TREE_VERSION = "1.0.0"; /** File name for persisted Merkle tree */ export declare const MERKLE_TREE_FILE = "merkle-tree.json"; /** * Compute SHA256 hash of a string */ export declare function computeHash(content: string): string; /** * Compute hash for a chunk node * * @param text - Chunk text content * @param startLine - Start line number * @param endLine - End line number * @returns SHA256 hash */ export declare function computeChunkHash(text: string, startLine: number, endLine: number): string; /** * Compute content-only hash for a chunk (position-independent) * * Used for detecting moved chunks that have the same content * * @param text - Chunk text content * @returns SHA256 hash */ export declare function computeChunkContentHash(text: string): string; /** * Compute hash for a file node based on its chunks * * The file hash is computed from the ordered list of chunk hashes, * making it sensitive to chunk reordering. * * @param chunkHashes - Ordered array of chunk hashes * @returns SHA256 hash */ export declare function computeFileHash(chunkHashes: string[]): string; /** * Compute hash for a directory node based on its children * * Children are sorted by name for deterministic hashing. * * @param children - Map of child name to child hash * @returns SHA256 hash */ export declare function computeDirectoryHash(children: Map): string; /** * Compute the root hash for the entire project * * @param fileHashes - Map of file path to file hash * @returns SHA256 hash */ export declare function computeProjectHash(fileHashes: Map): string; /** * Compare two file node maps to detect changes * * @param oldFiles - Previous state file map * @param newFiles - Current state file map * @returns MerkleDiff with categorized changes */ export declare function diffFileMaps(oldFiles: Map, newFiles: Map): MerkleDiff; /** * Merkle Tree Manager for tracking project state * * Maintains a Merkle tree structure for efficient change detection. * Supports chunk-level granularity for partial reindexing. * * @example * ```typescript * const manager = new MerkleTreeManager('/path/to/index'); * await manager.load(); * * // Add files and chunks * manager.addFile('src/index.ts', chunks, contentHash, stat); * * // Compute diff against previous state * const diff = manager.computeDiff(previousManager); * * // Save state * await manager.save(); * ``` */ export declare class MerkleTreeManager { private readonly indexPath; private files; private chunks; private rootHash; private lastUpdated; private isDirty; private isLoaded; /** * Create a new MerkleTreeManager * * @param indexPath - Path to the index directory */ constructor(indexPath: string); /** * Load Merkle tree state from disk * * Returns empty state if file doesn't exist. */ load(): Promise; /** * Save Merkle tree state to disk * * Uses atomic write to prevent corruption. */ save(): Promise; /** * Clear all state */ clear(): void; /** * Add or update a file in the tree * * @param filePath - Relative path to the file * @param chunks - Array of chunk info * @param contentHash - SHA256 hash of file content * @param stats - File stats (size, mtime) */ addFile(filePath: string, chunks: Array<{ id: string; text: string; startLine: number; endLine: number; chunkType?: string; chunkName?: string; }>, contentHash: string, stats: { size: number; mtime: number; }): void; /** * Remove a file from the tree * * @param filePath - Relative path to the file */ removeFile(filePath: string): void; /** * Check if a file exists in the tree */ hasFile(filePath: string): boolean; /** * Get a file node */ getFile(filePath: string): FileNode | undefined; /** * Get all file paths */ getFilePaths(): string[]; /** * Get the number of files */ getFileCount(): number; /** * Get the number of chunks */ getChunkCount(): number; /** * Compute the root hash * * Call this after making changes to update the root hash. */ computeRootHash(): string; /** * Get the current root hash */ getRootHash(): string; /** * Compute diff between this tree and another * * @param other - Other Merkle tree manager (typically the previous state) * @returns MerkleDiff with all changes */ computeDiff(other: MerkleTreeManager): MerkleDiff; /** * Quick check if the tree has changed from another * * Uses root hash comparison for O(1) check. * * @param other - Other Merkle tree manager * @returns true if trees are different */ hasChanged(other: MerkleTreeManager): boolean; /** * Get files that have changed compared to another tree * * Optimized version that only returns file paths without full diff details. * * @param other - Other Merkle tree manager * @returns Array of changed file paths */ getChangedFiles(other: MerkleTreeManager): string[]; /** * Get a chunk node by ID */ getChunk(chunkId: string): ChunkNode | undefined; /** * Get all chunks for a file */ getFileChunks(filePath: string): ChunkNode[]; /** * Find chunks by content hash (for detecting moved chunks) * * @param contentHash - Content hash to search for * @returns Array of chunk IDs with matching content */ findChunksByContentHash(contentHash: string): string[]; /** * Get the path to the Merkle tree file */ getTreePath(): string; /** * Check if the manager has been loaded */ get loaded(): boolean; /** * Check if there are unsaved changes */ get dirty(): boolean; /** * Get statistics about the tree */ getStats(): { fileCount: number; chunkCount: number; rootHash: string; lastUpdated: string; }; /** * Create a snapshot of the current state (for rollback) */ createSnapshot(): MerkleTreeManager; } /** * Create and load a MerkleTreeManager * * @param indexPath - Path to the index directory * @returns Loaded MerkleTreeManager */ export declare function createMerkleTreeManager(indexPath: string): Promise; /** * Build a Merkle tree from file and chunk data * * Helper function for building a tree from scratch during indexing. * * @param indexPath - Path to the index directory * @param files - Array of file data * @returns Populated MerkleTreeManager */ export declare function buildMerkleTree(indexPath: string, files: Array<{ path: string; contentHash: string; size: number; mtime: number; chunks: Array<{ id: string; text: string; startLine: number; endLine: number; chunkType?: string; chunkName?: string; }>; }>): Promise; //# sourceMappingURL=merkleTree.d.ts.map