/** * McpWatcher — incremental re-indexer for the MCP server's --watch mode. * * Watches source files for changes and incrementally updates: * 1. signatures in llm-context.json (always) * 2. vector index (only when embed: true and an embedding server is reachable) * * The call graph is deliberately excluded — rebuilding it requires full * tree-sitter analysis of all call sites and is too expensive for a watch loop. * It stays current via the post-commit hook (openlore analyze --force --embed). * * Spec 13.1 (watch-mode performance): freshness is O(change), not O(repo). * • Per-file events COALESCE into one batched flush (single debounce timer + * hard max-batch ceiling), so a burst / branch-switch runs the pipeline once, * not once per file. * • The patched llm-context is handed to the MCP read cache in place * (primeContextCache), so the next tool call is a cache HIT — no 2.1 MB * cold re-parse — even after the disk write. * • Vector updates are row-level (VectorIndex.updateFiles), not a full-corpus * read+overwrite, and run on a separate lower-priority lane so signature * freshness never blocks on embedding. * • VCS-flood / bulk batches are detected and collapsed to a single refresh. * • stderr emits one summary line per batch by default (per-file detail behind * OPENLORE_WATCH_DEBUG). */ export interface McpWatcherOptions { /** Absolute path to the project root being watched */ rootPath: string; /** Absolute path to .openlore/analysis/ — where llm-context.json lives */ outputPath?: string; /** Milliseconds to debounce file-change events (default: WATCH_DEBOUNCE_MS) */ debounceMs?: number; /** Hard flush ceiling under a continuous change stream (default: WATCH_MAX_BATCH_MS) */ maxBatchMs?: number; /** Batch size that trips VCS-flood handling (default: WATCH_BULK_THRESHOLD) */ bulkThreshold?: number; /** Run the live vector update; false = signatures-only (default: true) */ embed?: boolean; /** Above this many watched source files, auto-degrade to signatures-only */ embedFileCeiling?: number; /** Extra glob patterns to ignore in addition to defaults */ ignore?: string[]; /** * Fired after each coalesced batch is flushed to disk (signatures + vector). * Lets a host — e.g. the `openlore serve` daemon — schedule heavier work, such * as a debounced full call-graph re-analyze, off the watcher's own lane. The * watcher deliberately excludes the call graph (too expensive synchronously), * so this is the seam where continuous call-graph freshness is layered on. */ onBatchFlushed?: (changedAbsPaths: string[]) => void; } /** * True if a root-relative path should never be watched. Evaluated as a cheap * segment scan before any FD is opened, so it stays allocation-light. A path is * ignored if ANY of its segments is a known build/dependency/VCS directory * name, or it has a test-file suffix. Exported for testing. * * @param relPath path relative to the watch root (forward- or back-slashed) */ export declare function isIgnoredRelPath(relPath: string): boolean; export declare class McpWatcher { private readonly rootPath; private readonly outputPath; private readonly contextPath; private readonly debounceMs; private readonly maxBatchMs; private readonly bulkThreshold; private readonly embedFileCeiling; private readonly extraIgnore; private readonly debug; private readonly onBatchFlushed?; private fsWatcher?; private gitWatcher?; private pending; private debounceTimer?; private maxBatchTimer?; private running; private vcsBulkFlag; private embed; private embedDegraded; private embedFiles; private embedNodes; private embedTimer?; private embedRunning; private lastEmbedContext?; constructor(options: McpWatcherOptions); start(): Promise; stop(): Promise; /** * Add a changed path to the pending set and (re)arm a single debounce timer, * plus a one-shot hard ceiling so a continuous stream still flushes. */ private enqueue; /** A .git ref changed — settle, then flush whatever changed as one bulk batch. */ private onVcsEvent; /** * Drain the pending set into a single batch. Single-flight: if a flush is * already running, leave the new paths in `pending` and reschedule once it * finishes — never interleave two flushes. */ private flush; /** * Re-index a single changed file. Exposed for unit testing without needing a * real file watcher; flushes synchronously so callers observe the update on * disk immediately. Internally this is just a batch of one. */ handleChange(absPath: string): Promise; /** * Process a coalesced batch of changed files as ONE pipeline pass: * • per-file incremental edge update (content-hash skip), all under one open * EdgeStore; * • ONE signature patch + ONE llm-context persist + ONE read-cache handoff; * • ONE vector update (inline when syncFlush, else on the embed lane). */ private handleBatch; /** * Self-heal a schema-reset graph by spawning one detached `analyze --force` * (BM25-only, no network). Runs at most once per process (`backgroundRebuild * Triggered`); a spawn failure logs and falls back to the existing "run * analyze" note rather than retrying — no thundering herd, no loop (B10). */ private scheduleBackgroundRebuild; /** * True when this watcher writes to the canonical `/.openlore/analysis` * layout that the MCP read handlers cache against. Only then is the shared * in-memory read cache (primeContextCache) the right channel to prime; a custom * `outputPath` (tests / non-standard installs) writes only to disk. */ private get usesStandardLayout(); /** * Load the context the watcher is about to patch. This ALWAYS reads fresh from * disk — never through the shared read cache — because the cache is a read-path * (tool-call) optimization, and patching a possibly-stale cached object could * silently drop signatures written by a concurrent `analyze` between events. * The writer reads ground truth; persistContext then primes the read cache with * the result so the next tool call is still a hit (Step 2a, G1). */ private loadContext; private persistContext; private scheduleEmbed; private runEmbedLane; /** * Row-level vector update for the changed files only (Step 3). Falls back to a * silent no-op when no embedding service and no index are available. */ private updateVectors; /** Bounded count of watched source files; stops early once `cap` is exceeded. */ private countSourceFiles; } //# sourceMappingURL=mcp-watcher.d.ts.map