import type { ScanEntryKind } from '../utils/scan.js'; /** Path-granular facts about the regions a directory scan could not observe. */ export interface SyncScanCoverage { unreadableDirs: { dirPath: string; code: string; }[]; depthLimitedDirs: string[]; skippedSymlinks: string[]; } /** * Everything one run could not observe: the scan facts plus the files whose * bytes were never read because they exceed the configured size limit. Every * path here is an unobserved prefix, so the rows under it are protected from * prune. */ export interface SyncCoverage extends SyncScanCoverage { /** Files skipped by `hashFile` for exceeding the configured size limit. */ oversizedFiles: string[]; } /** One bounded directory scan: the supported files found plus its coverage facts. */ export interface SyncScanResult extends SyncScanCoverage { files: string[]; } /** * One stored chunk row (or row group) of the database manifest. `filePath` is * the verbatim stored spelling — the only value valid for deletion. A `null` or * absent `contentHash` marks the row hashless, which makes its file dirty. */ export interface SyncManifestRow { filePath: string; contentHash?: string | null; } /** One supported file found on disk, with the hash of its current bytes. */ export interface SyncDiskFile { filePath: string; contentHash: string; } /** How a sync invocation was addressed, after validation and classification. */ export type SyncRequest = { kind: 'roots'; } | { kind: 'directory'; path: string; } | { kind: 'file'; path: string; }; /** * Result of classifying a requested path on disk. Anything other than * `directory` or `file` is refused before the path is read: the union is the * walker's own {@link ScanEntryKind} plus `missing`, so a path a caller names and * a path the walk discovers pass the identical predicates. */ export type SyncPathKind = ScanEntryKind | 'missing'; /** Re-ingest one disk file, then drop the other stored spellings of its key. */ export interface SyncUpsertAction { /** Verbatim disk path handed to `ingestFile`. */ filePath: string; /** Verbatim stored spellings of the same comparison key, excluding `filePath`. */ staleStoredPaths: string[]; } /** Remove every stored spelling of one comparison key that left the disk. */ export interface SyncPruneAction { storedPaths: string[]; } export interface SyncPlan { upserts: SyncUpsertAction[]; /** Files whose stored content identity already matches the disk bytes. */ skipped: number; prunes: SyncPruneAction[]; } export interface SyncPlanInput { roots: readonly string[]; dbPath: string; /** Configured database/cache prefixes that must never be pruned. */ excludePaths: readonly string[]; platform: NodeJS.Platform; request: SyncRequest; diskFiles: readonly SyncDiskFile[]; dbRows: readonly SyncManifestRow[]; coverage: SyncCoverage; } /** The one controlled error a failed run exposes. */ export interface SyncError { message: string; /** The file, root, or requested path a failure is attributable to. */ filePath: string | null; } /** Append an attributable path only when the underlying message does not already contain it. */ export declare function formatSyncError({ message, filePath }: SyncError): string; export interface SyncCounters { upserted: number; skipped: number; empty: number; /** Comparison keys removed from the index. Counts files, not rows, and is not part of `completed`. */ pruned: number; } /** One stored spelling per pruned comparison key, so a count of N reports N paths. */ interface PrunedPaths { prunedPaths: string[]; } export interface SyncExecutionResult extends SyncCounters, PrunedPaths { error: SyncError | null; } export interface SyncResult extends SyncCounters, PrunedPaths { /** Scanner facts as data. Formatting and reporting belong to the adapters. */ coverage: SyncCoverage; error: SyncError | null; } /** Mutating collaborators, injected by the CLI and MCP adapters. */ export interface SyncExecutor { /** * Parse, chunk, embed, build vectors, then delete-and-insert for this one * file, returning the inserted chunk count. Returning `0` must leave the * store untouched: the executor relies on that to keep a zero-chunk file's * prior rows and hash intact. */ ingestFile(filePath: string, images: boolean): Promise; /** Delete the rows of exactly one stored path spelling. */ deleteExactPath(filePath: string): Promise; optimize(): Promise; } /** Everything {@link runSync} needs from the outside world. */ export interface SyncCollaborators extends SyncExecutor { /** * Canonical form of the requested path — its parent chain resolved through * symbolic links, the final component verbatim — or `null` when that chain * cannot be resolved. Injected because this module performs no filesystem * access; both adapters supply `canonicalizeRequestedPath` from `utils/scan.ts`. */ canonicalizeRequestedPath(path: string): Promise; /** * Classify the requested path WITHOUT reading it, applying the walker's collect * predicates (`classifyRequestedPath` in `utils/scan.ts`) so both surfaces * refuse the same paths. */ classifyPath(path: string): Promise; /** * Bounded scan of one root. Deliberately takes no scope predicate: a * scope-pruned directory is reported in none of the coverage arrays, so a * scope filter here would make unobserved regions invisible and prune unsafe. */ scanDir(rootPath: string): Promise; /** * Hash the file's current bytes, or return `null` to decline reading it because * it exceeds the configured size limit. A declined file is left out of the disk * manifest and recorded in `coverage.oversizedFiles`, which protects its stored * rows from prune — omitting it without that record would make it look deleted. */ hashFile(filePath: string): Promise; /** Every stored chunk row's verbatim path and hash, for the whole table. */ loadDbManifest(): Promise; } export interface RunSyncInput { /** Configured roots in the `resolve()`-only spelling the DB keys live in. */ roots: readonly string[]; /** * The same configured roots, canonicalized (realpath'd) — the security domain * the requested path's canonical form is checked against. Both adapters already * hold this list: the MCP server as `baseDirs`, the CLI as * `config.baseDirs.baseDirs`, each the counterpart of its `roots` entry. */ canonicalRoots: readonly string[]; dbPath: string; excludePaths: readonly string[]; platform: NodeJS.Platform; /** Omitted means "every configured root". */ requestedPath?: string | undefined; /** `true` stores images for files already selected as new or changed. */ images?: boolean | undefined; collaborators: SyncCollaborators; } /** * Decide the skip, upsert, and prune actions for one sync run. Pure: all * filesystem and database facts arrive pre-fetched. * * A prune action is emitted only when all four conditions hold for the key: it * is inside the requested scope, absent from the disk manifest, outside the * configured excluded and managed paths, and outside every unobserved prefix — * unreadable, depth-limited, symlinked, or too large to have been read. * Dropping any one of them protects the rows. */ export declare function planSync(input: SyncPlanInput): SyncPlan; /** * Apply a plan: upserts first, then prune, then a single `optimize()`. * * The first failure anywhere stops the run. Earlier successful mutations stay, * every remaining upsert and the entire prune phase are abandoned, and exactly * one error is returned. No rollback, retry, or failure classification is * attempted — an interrupted run is recovered by rerunning sync. * * A zero-chunk ingest counts as `empty` and mutates nothing for that file, so * its prior rows stay searchable and the next run plans it again. */ export declare function executeSyncPlan(plan: SyncPlan, executor: SyncExecutor, images?: boolean): Promise; /** * Run one full sync: gather, plan, execute. * * Returned counters and coverage facts are plain data; the caller decides how to * print them and what exit status or job state they imply. A run with nothing to * do calls neither `ingestFile` nor `optimize`, so a true no-op never pays for * loading the embedding model or compacting the table. */ export declare function runSync(input: RunSyncInput): Promise; export {}; //# sourceMappingURL=sync.d.ts.map