/** * Corpus refresh — reconcile the corpus (reality) against its manifest * (intent) and report the drift in operational terms. * * Laws: * * Manifest is the source of truth. `paths`, `embed_model`, `chunk_*` * come from the manifest; refresh doesn't accept them as input. The * only argument is the corpus name. * * Deletes are real. Any path present in the corpus but absent from * the manifest has its chunks removed. Any path present in the * manifest but missing from disk also has its chunks removed — and * the caller gets a separate `missing` list so they can tell whether * the delete was intentional (manifest edit) or a disk gap. * * Drift is legible. Every category of change surfaces by name: * added / changed / unchanged / deleted / missing, plus chunk-level * counts (reused / reembedded / dropped). No generic "out of date". * * Idempotence is sacred. A no-change refresh is a no-op: no embed * calls, no disk writes, no manifest bump. `no_op: true` in the * report. * * Phase 7 / FT-001 event-emission policy: this module does NOT call * the logger directly — the report return value is the operator-facing * surface. Tool handlers (src/tools/corpusRefresh.ts) build their own * `kind: "call"` event from the report; if a future call site needs to * emit a corpus-step event, use `buildCorpusRefreshStepEvent` below * (op: 'pack_step', rule: 'corpus_refresh_step') so the closed * CorrelationOp enum stays tight. */ import type { OllamaClient } from "../ollama.js"; export interface RefreshReport { name: string; embed_model: string; /** Paths indexed for the first time this refresh. */ added: string[]; /** Paths whose sha256 changed since the last index and were re-embedded. */ changed: string[]; /** Paths present in manifest and on disk with matching sha256 — reused. */ unchanged: string[]; /** Paths whose chunks left the corpus (union of manifest-removed and disk-missing). */ deleted: string[]; /** Subset of `deleted`: paths the manifest still declares but disk does not have. */ missing: string[]; /** Chunk-level counts. */ reused_chunks: number; reembedded_chunks: number; dropped_chunks: number; elapsed_ms: number; /** True iff the refresh made no changes at all. */ no_op: boolean; /** * Silent :latest drift: present when the resolved tag captured during this * refresh differs from the one stored in the manifest at refresh start. * Reuse chunks are from the OLD resolved model; re-index the corpus if you * want uniform vector space. Null or absent when no drift (or when the * manifest had no prior resolved tag — e.g. migrated v1 manifest or * no-op refresh). */ embed_model_resolved_drift?: { prior: string; current: string; }; /** * Drift observed WITHIN this single refresh run — set only when the * indexer saw more than one resolved tag across its batches (Ollama * bumped `:latest` mid-stream). The resulting corpus has vectors from * two different models; re-index for a clean baseline. Absent on the * happy path. */ embed_model_resolved_drift_within_refresh?: string[]; /** * Paths retried this run because the prior index/refresh recorded them * as failed. Empty when retry_failed was false or the prior manifest had * no failed_paths. Paths that succeed this run leave failed_paths; paths * that fail again end up in `still_failed` and the manifest preserves * them for the next retry attempt. */ retried_failed: string[]; /** Paths that failed again this run (subset of retried_failed + fresh failures on the normal path set). */ still_failed: { path: string; reason: string; }[]; } export interface RefreshParams { name: string; /** Active embed model. Must match the manifest or refresh refuses. */ model: string; client: OllamaClient; /** * When true, re-attempt any paths that the previous index/refresh * recorded as failed (persisted in manifest.failed_paths). Default false * — a normal refresh honors the manifest's declared paths and nothing * else. Useful after the user fixes permissions / removes a stale * symlink / resizes a file below the cap and wants to retry without * re-indexing the entire corpus. */ retry_failed?: boolean; } export declare function refreshCorpus(params: RefreshParams): Promise; /** * Detail payload for an operator-facing structured event a tool handler * can emit when it wraps a corpus refresh in a pack-step pipeline. * * Phase 7 / FT-001: tagged `op: 'pack_step'` so it slots into the * existing closed CorrelationOp enum (corpus refresh is a structured * multi-step operation; a separate corpus_step op would be redundant). * The NDJSON logger auto-merges `run_id` from ALS at write time. * * Carries the same drift signals the report exposes so a log-only * consumer (one that doesn't capture envelopes) can still answer "did * this refresh re-embed anything? did Ollama bump :latest mid-stream?" */ export interface CorpusRefreshStepEventDetail { /** Closed-enum op tag from observability.CorrelationOp. */ op: "pack_step"; /** Stable rule identifier — greppable. */ rule: "corpus_refresh_step"; /** Corpus name. */ name: string; /** True iff nothing changed and the refresh was a no-op. */ no_op: boolean; /** Chunks reused from the existing corpus. */ reused_chunks: number; /** Chunks newly embedded this refresh. */ reembedded_chunks: number; /** Chunks dropped this refresh (old chunks that didn't survive). */ dropped_chunks: number; /** True when Ollama silently bumped `:latest` between this refresh and the prior one. */ embed_model_drift_detected: boolean; /** True when Ollama bumped `:latest` mid-refresh (within a single run). */ embed_model_drift_within_refresh: boolean; /** Number of paths that failed this run (read errors, size cap, symlink, etc.). */ failed_path_count: number; } /** * Build the pack-step event detail for a completed refresh. Pure * shaping — does NOT call the logger itself. Use from tool handlers * that wrap a `refreshCorpus` call inside a pack-style pipeline. */ export declare function buildCorpusRefreshStepEvent(report: RefreshReport): CorpusRefreshStepEventDetail; //# sourceMappingURL=refresh.d.ts.map