import type { FileRankRow, SymbolRankRow } from './graph-rank.js'; import type { CallSite, CodeMapGraph, FileMeta, IndexStats, Symbol as IndexSymbol, Ref, SearchResult, SymbolKind, SymbolLang } from './schema.js'; import { type IndexSummary } from './writer-admin.js'; import type { ConceptCoverage, ConceptEdge, FileConcept, Subsystem } from './writer-concepts.js'; import type { RankedFileRow } from './writer-rank.js'; import type { WriterSearchFilter } from './writer-search-helpers.js'; import { StorePool } from './writer-store-pool.js'; import type { FileVectorRow, VectorHit } from './writer-vectors.js'; export { codebaseIndexDirOverride, resolveIndexDir } from './writer-helpers.js'; export { StorePool } from './writer-store-pool.js'; export declare class IndexStore { private db; private atomicIndexUpdateActive; private writeSavepointSequence; private readonly indexDir; private ftsAvailable; private vectorsAvailable; private readonly stmtCache; private bm25Cache; private bm25Dirty; /** `PRAGMA data_version` the BM25 cache was built at. */ private bm25DataVersion; private stmt; constructor(projectRoot: string, opts?: { indexDir?: string | undefined; }); runWithRetry(fn: () => T): T; runAtomicIndexUpdate(job: () => Promise): Promise; private beginWriteTransaction; private commitWriteTransaction; /** * Never throws: every caller is already propagating the failure that caused * the rollback. When SQLite has rolled the transaction back itself (disk * full, I/O error) the ROLLBACK fails with "no transaction is active", and * that message used to replace the real error. */ private rollbackWriteTransaction; private initSchema; private static readonly NEXT_SYMBOL_ID_KEY; private static readonly MAX_SQL_VARS; private allocateSymbolIds; private invalidateIncomingRefsForFiles; private resolveRefsForNamesUnsafe; insertSymbols(symbols: IndexSymbol[]): IndexSymbol[]; deleteSymbolsForFile(file: string): void; deleteFile(file: string): void; upsertFile(meta: FileMeta): void; getFileMeta(file: string): FileMeta | null; getAllFileMetas(): FileMeta[]; setFilePackages(entries: ReadonlyMap): void; getNamespaceDeclarations(): Array<{ name: string; file: string; }>; getFilePackages(): Map; getUnresolvedImports(onlyFiles?: readonly string[]): Array<{ fromFile: string; lang: string; module: string; }>; getFilesWithDanglingImports(): string[]; applyImportResolutions(resolutions: ReadonlyArray<{ fromFile: string; lang: string; module: string; toFile: string | null; }>): number; search(query: string, filter?: WriterSearchFilter, opts?: { limit?: number | undefined; }): SearchResult[]; countSearch(query: string, filter?: WriterSearchFilter | undefined): number; searchRanked(query: string, filter: WriterSearchFilter | undefined, limit: number): { results: SearchResult[]; total: number; }; private invalidateBm25; /** * The corpus is rebuilt when this connection wrote (bm25Dirty) OR another * connection committed since it was built. Pooled stores stay open for the * life of the host while the project daemon, an inline indexer or another * process writes the same database; the dirty flag alone never saw those * writes, so short-query results silently dropped every symbol added since. */ private getOrBuildBm25; /** Changes whenever ANOTHER connection commits to this database. */ private dataVersion; getAllIndexable(): Array<{ id: number; text: string; }>; getMaxSymbolId(): number; getStats(): IndexStats; /** P2.5: minimal summary for search-response piggyback (see writer-admin). */ getIndexSummary(): IndexSummary; setLastIndexed(ts: number): void; getMetadata(key: string): string | undefined; setMetadata(key: string, value: string): void; clearAll(): void; insertRefs(fromId: number, refs: Ref[]): void; insertRefsBatch(refs: Ref[]): void; commitBatch(entries: Array<{ file: string; lang: SymbolLang; symbols: IndexSymbol[]; refs: Ref[]; mtimeMs: number; symbolCount: number; contentHash?: string | undefined; }>, options?: { deleteForFiles?: string[] | undefined; }): IndexSymbol[]; deleteRefsForFile(file: string): void; resolveRefs(): number; resolveRefsForNames(names: Iterable): number; replaceEmptyFile(meta: FileMeta): void; optimize(): void; /** * P2: churn-gated FTS5 segment maintenance. * * FTS5 postings only compact when FTS5 itself merges them: every delete * leaves tombstone postings in the segments, and neither VACUUM nor * `PRAGMA optimize` reclaims them. Measured on the live index (2026-08-30): * one delete+reinsert cycle grew symbols_fts_data 174.9→212.8 MB, while the * FTS5 'optimize'/'rebuild' merge resets it (rebuild: 212.9→28.2 MB in * 1.26 s for 131k rows). 'optimize' is the recurring command — an * incremental merge that discards deleted docs — and only runs once churn * crosses the gate, so a clean index never pays for it. * * Follows the {@link checkpointWal} contract: best-effort, returns whether * it ran, safe to call from the daemon's single-threaded idle path. */ optimizeFtsIfNeeded(options?: { minChurnRatio?: number; minChurnRows?: number; }): boolean; /** * Best-effort churn bookkeeping for {@link optimizeFtsIfNeeded}. Counts the * FTS rows a mutation inserts or deletes so the maintenance gate can fire * after real churn, not on a timer. Persisted in metadata so the counter * survives store open/close cycles in the daemon pool. */ private recordFtsChurn; /** * P4.14: best-effort WAL checkpoint for idle-time maintenance. * * `wal_autocheckpoint` is PASSIVE and only attempts work after a COMMIT — * once writes stop, nothing fires again, so the WAL keeps whatever frames * the last burst left. This probes with PASSIVE first (never blocks; busy=1 * means readers still hold WAL snapshots) and only issues the TRUNCATE — * which resets index.db-wal to zero bytes — when the checkpointer can * proceed immediately. Callers run this on the daemon's single thread, so * never wait on readers here: busy means "retry at the next idle window". */ checkpointWal(): boolean; compactIfNeeded(options?: { minBytes?: number; minFreeRatio?: number; }): boolean; findIncomingCallsByName(symbolName: string, file?: string, limit?: number): { calls: CallSite[]; symbolFound: boolean; ambiguous: boolean; totalMatches: number; }; findOutgoingCallsByName(symbolName: string, file?: string, limit?: number): { calls: CallSite[]; symbolFound: boolean; unresolvedCount: number; totalMatches: number; }; findTransitiveIncomingCallsByName(symbolName: string, file?: string, limit?: number): { calls: CallSite[]; symbolFound: boolean; ambiguous: boolean; totalMatches: number; }; findTransitiveOutgoingCallsByName(symbolName: string, file?: string, limit?: number): { calls: CallSite[]; symbolFound: boolean; unresolvedCount: number; totalMatches: number; }; findReachableSymbolIds(seedIds: number[]): Set; findRefsTo(symbolId: number): Ref[]; findRefsFrom(symbolId: number): Ref[]; getPackageGraph(): CodeMapGraph; getFileGraph(packageFilter: string): CodeMapGraph; getSymbolGraph(fileFilter: string): CodeMapGraph; getAllSymbols(): Array<{ id: number; name: string; file: string; kind: SymbolKind; line: number; scope: string; }>; /** Declarations in one file, in source order. */ getFileSymbols(file: string, limit: number): Array<{ id: number; name: string; kind: string; line: number; signature: string; }>; /** Declarations behind an arbitrary id list, for the retrieval walk. */ getSymbolsByIds(ids: readonly number[]): Array<{ id: number; name: string; kind: string; lang: string; file: string; line: number; signature: string; scope: string; }>; getAllResolvedRefs(): Array<{ fromId: number; toId: number; callType: string; }>; /** * Replace both rank tables in one write. Called once per index run, after * ref resolution has settled — a rank computed against half-resolved refs * would describe a graph that never existed. */ replaceRanks(symbols: readonly SymbolRankRow[], files: readonly FileRankRow[]): void; /** Wipe stored vectors when the embedding model changed. */ reconcileVectorProvider(provider: string): boolean; getFileVectorStates(provider: string): Map; upsertFileVectors(rows: readonly FileVectorRow[]): void; pruneOrphanFileVectors(): number; countFileVectors(): number; searchFileVectors(query: Float32Array, limit: number, minScore: number): VectorHit[]; upsertFileConcept(concept: FileConcept): void; getFileConcept(file: string): FileConcept | undefined; getAllFileConcepts(): FileConcept[]; getReadyConceptSummaries(): Map; getConceptCoverage(): ConceptCoverage; /** Flag summaries whose file has changed since they were written. */ markStaleConcepts(): number; /** Drop summaries for files that are no longer indexed. */ pruneOrphanConcepts(): number; replaceSubsystems(subsystems: readonly Subsystem[], edges: readonly ConceptEdge[]): void; getSubsystems(): Subsystem[]; getConceptEdges(): ConceptEdge[]; getPackageFileCounts(): Map; getRankedFiles(limit: number): RankedFileRow[]; getTopFileRanks(limit: number): FileRankRow[]; getTopSymbolRanks(limit: number): SymbolRankRow[]; getFileRankMap(): Map; getRankCounts(): { symbols: number; files: number; }; getSymbolNameCandidates(): Map; getImportVisibility(): Map>; getAllImportRefs(): Array<{ sourceFile: string | null; toName: string; toId: number | null; callType: string; line: number; }>; close(): void; } export declare const indexStorePool: StorePool; //# sourceMappingURL=writer.d.ts.map