import { CreateCodingGraphEngineOptions, CodingGraphEngine, CodingGraphLanguage, ParseFileInput, ParseResult, FileIR, SymbolIR, CodingGraphErrorCode } from '@remnic/core'; export { CODING_GRAPH_ENGINE_VERSION, CodingGraphEngine, CodingGraphErrorCode, CodingGraphLanguage, CreateCodingGraphEngineOptions, FileIR, ParseFileInput, ParseResult, SymbolIR, TIER_1_LANGUAGES } from '@remnic/core'; import { Tree, Language } from 'web-tree-sitter'; import { EdgeProvenance } from './graph-schema.js'; export { CODING_GRAPH_SCHEMA_VERSION, EDGE_PROVENANCE_VALUES, applyCodingGraphSchema, isEdgeProvenance, readSchemaVersion } from './graph-schema.js'; import { GraphStore, ReadFileHashesResult, ReadMetaResult, EdgeIR } from './graph-store.js'; export { ByteSpan, DEAD_CODE_EXCLUSION, DEFAULT_TRAVERSE_PATHS_MAX, DeadCodeHit, DeadCodeResult, GraphStoreFailure, GraphStoreFailureCode, GraphStoreOptions, MAX_TRAVERSE_PATHS_HOPS, NodeIdInput, ReadCoChangeEdge, ReadCoChangesResult, SchemaStats, SchemaStatsResult, SearchHit, SearchQuery, SearchResult, SnippetFailureCode, SnippetQuery, SnippetResult, SnippetSuccess, StoreFileIR, SymbolKind, TraverseDirection, TraverseHit, TraversePathHit, TraversePathsQuery, TraversePathsResult, TraverseQuery, TraverseResult, UpsertBatchResult, UpsertEdgesResult, UpsertEdgesSuccess, UpsertResult, UpsertSuccess, nodeIdFor } from './graph-store.js'; export { CypherAst, CypherFailure, CypherFailureCode, CypherNodeValue, CypherParseResult, CypherResult, CypherRow, CypherScalar, CypherSuccess, CypherValue, VALID_CYPHER_LABELS, executeAst, executeCypher, parseCypher } from './cypher/query-parser.js'; import { spawn } from 'node:child_process'; import { HostEmbeddingProvider } from '@remnic/core/host-embedding-provider'; export { ExportIR, ImportIR } from '@remnic/core/coding/coding-graph-types'; import '@remnic/core/runtime/better-sqlite'; /** * Engine factory — constructs a `CodingGraphEngine` backed by the WASM * tree-sitter parser + tier-1 extractors. * * Rule 11 (no module-level state): the backend, query caches, and parser * instances all live on the returned engine object. Two `createCodingGraphEngine` * calls in the same process have fully isolated state. * * Rule 30/48: `codingGraph.enabled` defaults `false` in @remnic/core; the * loader is never invoked when disabled, so this factory is only reached when * the user has explicitly opted in. */ /** * Construct a coding-graph engine. PR2 implementation: real WASM tree-sitter * parser with tier-1 extractors. No longer throws `not_implemented`. * * @throws never — load failures surface as `{ ok: false, code: "parse_failed" }` * per-file, not as constructor exceptions. */ declare function createCodingGraphEngine(_options?: CreateCodingGraphEngineOptions): CodingGraphEngine; /** * ParserBackend — the abstraction layer between the tree-sitter parsing * engine and the per-language extractors. * * Design rationale (issue #1551): everything goes through this interface so * that a native `node-tree-sitter` backend (or the external C binary via the * subprocess provider) can slot in later without touching extractors. The WASM * backend (`WasmTreeSitterBackend`) is the default; it costs ~2–3× parse speed * vs native but has no per-platform build toolchain — the class of pain * documented in #1518 and #1538. * * Rule 11 (no module-level caches): every parser instance, grammar cache, and * initialization flag lives on the *engine instance*, never at module scope. * This means two engines in the same process have fully isolated state. */ /** * The opaque interface a backend exposes to extractors. Each method is keyed * per-instance so the engine lifecycle (`dispose()`) can clean everything up. */ interface ParserBackend { /** Initialize the backend runtime (e.g. load the WASM core). Idempotent per instance. */ init(): Promise; /** Ensure the grammar for `lang` is loaded and a parser is ready. Idempotent per instance. */ ensureLanguage(lang: CodingGraphLanguage): Promise; /** * Parse `content` (a UTF-8 string) using the grammar for `lang`. Returns the * tree-sitter Tree on success, or `null` if the grammar is not loaded / the * parser cannot produce a tree. Node offsets are UTF-16 code-unit offsets; * callers must convert to UTF-8 byte offsets for multibyte content (#1659). */ parse(lang: CodingGraphLanguage, content: string): Tree | null; /** Return the loaded Language for `lang`, or null if not loaded. */ getLanguage(lang: CodingGraphLanguage): Language | null; /** Release all parsers, grammars, and the WASM runtime. Safe to call multiple times. */ dispose(): Promise; } /** * web-tree-sitter 0.25 WASM backend. * * Per rule 11, all mutable state (init flag, parser instance, grammar cache) * lives on the instance — never at module scope. The backend is constructed by * `createCodingGraphEngine` and disposed alongside the engine. */ declare class WasmTreeSitterBackend implements ParserBackend { private initialized; private initializing; private parser; private readonly languages; private readonly loadingLanguages; private grammarDir; private grammarDirResolved; private readonly grammarDirHint; constructor(grammarDir?: string); private getGrammarDir; init(): Promise; ensureLanguage(lang: CodingGraphLanguage): Promise; parse(lang: CodingGraphLanguage, content: string): Tree | null; /** Return the loaded Language object for `lang`, or null if not loaded. */ getLanguage(lang: CodingGraphLanguage): Language | null; dispose(): Promise; } declare function hashContent(content: Uint8Array): string; /** A line from `git diff --name-status `. */ interface NameStatusEntry { /** * `git diff --name-status` status code: `A` (added), `M` (modified), * `D` (deleted), `R100` (renamed, 100% similarity), `C75` (copied, 75%), * etc. Renames carry the NEW path in `path` and the OLD path in * `oldPath`. */ readonly status: string; /** Repo-relative forward-slash path of the file in the NEW tree. */ readonly path: string; /** For renames/copies (`R*`/`C*`), the source path. Otherwise `undefined`. */ readonly oldPath?: string; } /** A hunk header from `git diff --unified=0` (`@@ -a,b +c,d @@`). */ interface DiffHunk { /** File path (repo-relative, forward slashes). */ readonly path: string; /** * Half-open `[startLine, endLine)` line range in the NEW (post-change) * version of the file. `startLine` is 1-based; `endLine` is exclusive. * A single-line change has `endLine === startLine + 1`. */ readonly newRange: { readonly startLine: number; readonly endLine: number; }; } /** A co-change commit entry from `git log --name-only`. */ interface LogFilesEntry { /** Full commit SHA (40 hex chars). */ readonly sha: string; /** Files changed in this commit (repo-relative, forward slashes, deduped). */ readonly files: readonly string[]; } /** * Tagged failure — `code` is the load-bearing signal for programmatic * detection (rule 34). Never carries `error.message` (which may contain * absolute paths — rule 11). */ interface GitFailure { readonly ok: false; readonly code: "git_unavailable" | "unreachable_head" | "git_error"; } /** * Injectable git-invocation surface. Each method runs a specific git * subcommand with argv arrays (never string interpolation — rule 10) and * returns parsed output or a tagged failure. Tests inject a mock to avoid * spawning a real git process against synthetic fixture repos. * * The default implementation uses `launchProcessSync` with a 2-second * timeout (matching `git-context.ts`'s `DEFAULT_GIT_TIMEOUT_MS`). */ interface CodingGitInvoker { /** * `git rev-parse HEAD` — the current HEAD SHA. Returns `null` when HEAD * does not exist (empty repo / no commits yet). */ revParseHead(cwd: string): { ok: true; head: string | null; } | GitFailure; /** * `git rev-parse --verify ^{commit}` — check whether a prior head * is still reachable. Used to detect rebase/force-push scenarios * (issue #1553 pitfall 34: `unreachable_head`). */ isReachable(cwd: string, ref: string): { ok: true; reachable: boolean; } | GitFailure; /** * `git diff --name-status ` — the changed files between two * commits (or between a commit and HEAD). Returns one entry per file. */ diffNameStatus(cwd: string, range: string): { ok: true; entries: readonly NameStatusEntry[]; } | GitFailure; /** * Working-tree diff hunks with zero context lines. Captures BOTH staged * and unstaged changes (compared against HEAD) so `detect_changes` maps * every uncommitted edit to a symbol span. Each hunk carries its * new-version line range. */ diffHunks(cwd: string, paths: readonly string[]): { ok: true; hunks: readonly DiffHunk[]; } | GitFailure; /** * `git log --name-only --format=%H -n ` — commit SHAs + their * changed files over a bounded window. Used by co-change mining. */ logFiles(cwd: string, limit: number): { ok: true; entries: readonly LogFilesEntry[]; } | GitFailure; /** * `git ls-files` — the repo's tracked files as repo-relative forward-slash * paths. Used by the codegraph runtime to source `candidatePaths` for a * full reindex (issue #1554: the executor treats an omitted candidate list * as non-authoritative and no-ops, so the runtime must supply one). * Returns an empty path list for a repo with no tracked files; a git * failure degrades to `{ ok: false, code: "git_unavailable" | "git_error" }`. */ listTrackedFiles(cwd: string): { ok: true; paths: readonly string[]; } | GitFailure; } /** * Construct the default git invoker that spawns real `git` via * `launchProcessSync`. Tests inject a mock instead. */ declare function defaultCodingGitInvoker(): CodingGitInvoker; /** * Parse `git diff --name-status` output. Each line is `\t` * for add/modify/delete, or `\t\t` for rename/copy. * * Empty lines are skipped (trailing newline). Lines that do not parse * are skipped rather than crashing the whole diff (rule 44 — a single * bad line should not make the index go stale silently; but a partial * parse is better than a total failure because the executor will fall * back to hash-scan if the file count looks wrong). */ declare function parseNameStatus(stdout: string): NameStatusEntry[]; /** * Parse `git diff --unified=0` output into per-file hunks. Each hunk * header is `@@ -a,b +c,d @@ `. With `--unified=0` * the OLD range `(a,b)` is not useful for blast-radius (we want the * NEW range). We extract `+c,d` → `{ startLine: c, endLine: c + d }`. * * The file path comes from `diff --git a/path b/path` or * `+++ b/path` lines. We track the current file as we walk. */ declare function parseHunks(stdout: string): DiffHunk[]; /** * Parse `git log --format=%H --name-only` output into one entry per * commit. Real git emits a blank line AFTER each SHA (before the file * names) AND between commits, so a naive `\n\n` block split puts the * SHA alone in one block and the file names in the next — the SHA-only * block yields a commit with zero files and the file-name block fails * SHA validation and is dropped. Walk line-by-line instead: a 40-hex * line starts a new commit; subsequent non-empty lines are its files * until the next SHA (chatgpt-codex-connector: 'Parse real git log * records before mining co-changes'). Robust to both the synthetic * (no blank-after-SHA) and real (blank-after-SHA) layouts. */ declare function parseLogFiles(stdout: string): LogFilesEntry[]; /** * The persisted index state the planner reasons about. * `lastHead: null` means "never indexed" → full reindex. */ interface ReindexState { /** The HEAD SHA at the time of the last successful reindex, or `null`. */ readonly lastHead: string | null; /** * Per-file content hashes as of the last index. Used by hash_scan mode * to detect content drift without a reachable base commit. * Keyed by repo-relative forward-slash path. */ readonly fileHashes: ReadonlyMap; } /** Git facts the planner needs — gathered BEFORE the plan is computed. */ interface ReindexGitFacts { /** Current `git rev-parse HEAD`. `null` when the repo has no commits. */ readonly currentHead: string | null; /** * Whether `lastHead` (when non-null) is still reachable in the repo. * `false` after a rebase/force-push that rewrote history past the old * head. `true` when `lastHead` is null (no prior state to reach). */ readonly lastHeadReachable: boolean; /** * Files changed between `lastHead` and `currentHead` (when both are * non-null and reachable). One entry per file from * `git diff --name-status`. Empty array for a noop or fresh repo. */ readonly changedFiles: readonly NameStatusEntry[]; } /** What the planner decided to do. */ type ReindexPlan = { readonly mode: "full"; readonly reason: string; } | { readonly mode: "noop"; readonly reason: string; } | { readonly mode: "incremental"; readonly changedPaths: readonly string[]; } | { readonly mode: "hash_scan"; readonly reason: string; /** Paths whose on-disk hash differs from the stored hash. */ readonly mismatchedPaths: readonly string[]; }; /** Result of executing a reindex plan. */ type ReindexResult = { readonly ok: true; readonly mode: "full" | "noop" | "incremental" | "hash_scan"; /** Number of files actually parsed + ingested. */ readonly filesIngested: number; /** The new `last_indexed_head` persisted to meta (null on noop). */ readonly head: string | null; } | ({ readonly ok: false; } & GitFailure) | { readonly ok: false; readonly code: "parse_failed"; readonly path: string; readonly message: string; } | { readonly ok: false; readonly code: "store_error"; readonly message: string; }; declare const META_KEY_LAST_HEAD: "last_indexed_head"; /** * Meta key holding a JSON array of repo-relative paths that failed to * parse on the last run (rule 44: files that fail to parse do not update * their stored content hash and MUST retry on the next run). Because a * HEAD-unchanged run would otherwise plan `noop` and skip them, the * executor consults this set at the top of every run and re-ingests any * pending paths even when the plan is noop (cursor Bugbot: 'Parse skips * block future reindex'). */ declare const META_KEY_PENDING_PARSE_FAILURES: "pending_parse_failures"; /** * Decide what to do given the last state and current git facts. * * Decision tree: * 1. `currentHead === null` → noop (nothing to index; empty repo). * 2. `lastHead === null` → full (first index). * 3. `lastHead === currentHead` → noop (HEAD unchanged). * 4. `!lastHeadReachable` → hash_scan (rebase/force-push lost the base). * 5. Otherwise → incremental (re-parse changedFiles paths). * * Deleted files (status `D`) are included in `changedPaths` so the executor * can prune them from the store. Renames include both old and new paths. */ declare function planReindex(lastState: ReindexState, facts: ReindexGitFacts): ReindexPlan; /** * A parse function — the engine seam. The executor calls this for each * file the plan says to (re)ingest. In production the orchestrator injects * the real `CodingGraphEngine.parseFile`; in tests a synthetic parser is * injected. This keeps the package decoupled from the engine implementation * (which is still a placeholder in #1551 PR1). */ type ParseFileFn = (input: ParseFileInput) => Promise; /** Injectable file reader — defaults to `node:fs/promises`.readFile. */ type ReadFileFn = (absPath: string) => Promise; /** * Read the persisted `last_indexed_head` from the store's meta table. * Returns `null` when the key is absent (fresh DB). */ declare function readLastIndexedHead(store: GraphStore): ReadMetaResult; /** * Read every file row's path → content_hash from the store. Used by * hash_scan to detect content drift without a reachable base commit. */ declare function readFileHashes(store: GraphStore): ReadFileHashesResult; /** * Execute a reindex against a store + git repo. * * Steps: * 1. Gather git facts (currentHead, reachable, changedFiles). * 2. Plan via {@link planReindex}. * 3. For full/incremental/hash_scan: parse each file, build a batch, * call `store.upsertFileBatch`. * 4. Persist `last_indexed_head` ONLY after the batch commits (rule 25). * * The reindex is serialized per-store via the store's own write queue. * A session-start trigger racing a manual CLI trigger coalesces (rule 40). * * `candidatePaths` is needed for full and hash_scan modes (the set of * files to consider). Typically from `git ls-files` or a glob. When * omitted, full/hash_scan operate over the union of stored files and * git-tracked files. */ declare function executeReindex(options: { readonly store: GraphStore; readonly git: CodingGitInvoker; readonly repoRoot: string; readonly parseFile: ParseFileFn; readonly candidatePaths?: readonly string[]; readonly readFile?: ReadFileFn; }): Promise; /** * detect_changes + blast radius for the coding-graph (issue #1553). * * Maps the current diff (staged + unstaged + committed-since-last-index) * hunks to symbols whose spans overlap the changed line ranges — against * a FRESH parse of the changed files, never against stale stored spans. * * Blast radius: reverse BFS from affected symbols over inbound * CALLS / IMPORTS / USES_TYPE edges with a depth cap. Risk classification * is a deterministic rubric (no LLM in the loop): * * ┌──────────────┬──────────────────────────────────────────────────┐ * │ Risk │ Criterion │ * ├──────────────┼──────────────────────────────────────────────────┤ * │ "direct" │ The symbol itself is in a changed hunk (depth 0).│ * │ "near" │ 1 hop away from a directly-changed symbol. │ * │ "transitive" │ 2+ hops away. │ * └──────────────┴──────────────────────────────────────────────────┘ * * Fan-in escalation: when an affected symbol has ≥ FAN_IN_ESCALATION * inbound edges, its risk is escalated one level (near→direct, * transitive→near) because high-fan-in symbols concentrate blast radius. * * Half-open interval semantics (rule 35): a hunk range `[startLine, endLine)` * overlaps a symbol span `[symStartLine, symEndLine)` iff * `startLine < symEndLine && symStartLine < endLine`. * Boundary lines (exact hit on startLine or endLine-1) are tested. */ /** Risk classification rubric — deterministic, no LLM. */ type RiskLevel = "direct" | "near" | "transitive"; /** A symbol affected by the current diff, with its blast-radius classification. */ interface AffectedSymbol { /** Qualified name of the symbol. */ readonly qualifiedName: string; /** Simple name of the symbol. */ readonly name: string; /** Symbol kind (function, class, method, ...). */ readonly label: string; /** Repo-relative file path. */ readonly filePath: string; /** Risk level from the deterministic rubric. */ readonly risk: RiskLevel; /** BFS depth from the nearest directly-changed symbol (0 = direct). */ readonly depth: number; /** Total inbound edge count (fan-in) at the time of classification. */ readonly fanIn: number; } /** Result of detect_changes. */ type DetectChangesResult = { readonly ok: true; readonly affected: readonly AffectedSymbol[]; } | { readonly ok: false; readonly code: "git_error" | "store_error"; }; /** * Result of computeBlastRadius. A backend/store failure during traversal is * surfaced as `{ ok: false; code: "store_error" }` rather than masked as an * empty result — the blast-radius computation is unreliable when the store * cannot be read (rule 22; cursor Bugbot: 'computeBlastRadius masks traverse * failures'). `{ ok: true; affected: [] }` is a genuinely empty blast radius. */ type BlastRadiusResult = { ok: true; affected: readonly AffectedSymbol[]; } | { ok: false; code: "store_error"; }; /** * Edge types traversed for blast-radius computation. These represent * "depends on" relationships — an inbound edge of any of these types * means the source symbol is affected when the destination changes. */ declare const BLAST_RADIUS_EDGE_TYPES: readonly ["CALLS", "IMPORTS", "USES_TYPE"]; /** * Fan-in threshold for risk escalation. When an affected symbol has this * many or more inbound edges, its risk is escalated one level because * high-fan-in symbols concentrate blast radius. */ declare const FAN_IN_ESCALATION_THRESHOLD = 5; /** Maximum BFS depth for blast-radius traversal. */ declare const DEFAULT_BLAST_RADIUS_DEPTH = 3; /** * Byte offset → 1-based line number. Walks the content counting newlines * up to (but not including) the byte offset. Used to convert symbol spans * (byte offsets) to line ranges for hunk-overlap comparison. * * Returns `{ startLine, endLine }` where both are 1-based and `endLine` * is exclusive (half-open — rule 35). A symbol that starts at byte 0 on * line 1 and ends at byte 10 (still line 1) has `{ 1, 2 }`. */ declare function byteSpanToLines(content: Uint8Array, startByte: number, endByte: number): { startLine: number; endLine: number; }; /** * Half-open overlap test (rule 35): two ranges `[aStart, aEnd)` and * `[bStart, bEnd)` overlap iff `aStart < bEnd && bStart < aEnd`. * * Boundary case: a hunk starting exactly at `symEndLine` does NOT overlap * (the symbol ends before the hunk starts). A hunk ending exactly at * `symStartLine` does NOT overlap (the hunk ends before the symbol starts). */ declare function rangesOverlap(a: { startLine: number; endLine: number; }, b: { startLine: number; endLine: number; }): boolean; /** * Classify risk from BFS depth + fan-in. Deterministic rubric: * - depth 0 → "direct" * - depth 1 → "near" * - depth 2+ → "transitive" * - fanIn ≥ threshold escalates one level (capped at "direct") */ declare function classifyRisk(depth: number, fanIn: number): RiskLevel; /** * Find symbols whose line ranges overlap any hunk in the given file. * Uses a FRESH parse (never stale stored spans). Returns the set of * directly-affected qualified names. */ declare function findDirectlyAffectedSymbols(hunksByPath: ReadonlyMap, freshIRs: ReadonlyMap, contentsByPath: ReadonlyMap): Set; /** * Compute blast radius from a set of directly-affected symbols using the * store's `traverse` primitive (direction: incoming). Reuses the existing * BFS — does NOT write a second traversal (rule 22 spirit). * * Returns affected symbols with their risk classification. Byte-stable: * the same diff + graph always produces the same output. */ declare function computeBlastRadius(store: GraphStore, directlyAffected: ReadonlySet, maxDepth?: number): BlastRadiusResult; /** * FILE_CHANGES_WITH co-change edge mining (issue #1553). * * Mines co-change relationships from `git log --name-only` over a bounded * window (default 500 commits). An edge between two files is written when: * - support (co-change count) ≥ `minSupport` * - confidence (co-change / total changes of the less-changed file) ≥ * `minConfidence` * * Deterministic given the same history — re-running on unchanged history * is idempotent (same edges, same confidence). * * Co-change edges are stored in a dedicated `co_changes` table (file-level, * not symbol-level — the existing `edges` table is between symbol nodes). * The table is additive to schema v1 (CREATE TABLE IF NOT EXISTS — same * pattern as `node_attributes` in PR2). */ /** A mined co-change edge between two files. */ interface CoChangeEdge { readonly fileA: string; readonly fileB: string; /** Number of commits where both files changed together. */ readonly support: number; /** Confidence: support / min(totalChangesA, totalChangesB). */ readonly confidence: number; } /** Result of co-change mining. */ type MineCoChangesResult = { readonly ok: true; readonly edges: readonly CoChangeEdge[]; } | GitFailure | { readonly ok: false; readonly code: "store_closed" | "db_error"; }; /** Configuration for co-change mining. */ interface CoChangeConfig { /** Maximum commits to scan (default 500). */ readonly maxCommits: number; /** Minimum co-change count to write an edge (default 3). */ readonly minSupport: number; /** Minimum confidence to write an edge (default 0.3). */ readonly minConfidence: number; } declare const DEFAULT_CO_CHANGE_CONFIG: CoChangeConfig; /** * Compute co-change edges from a list of commit→files entries. * * Algorithm: * 1. Count total changes per file (how many commits touched it). * 2. For each unordered file pair, count how many commits changed both. * 3. Confidence = coChangeCount / min(totalA, totalB). * 4. Keep edges where support ≥ minSupport AND confidence ≥ minConfidence. * * Deterministic: the same input always produces the same output, sorted * by (fileA, fileB) for byte-stability. * * Half-open time windows (rule 35): each commit is a discrete point; a * pair is "co-changed" in a commit iff both files are in that commit's * file list. No off-by-one — a file that appears once in a commit counts * once (deduped by the git-invoker's parser). */ declare function mineCoChangeEdges(entries: readonly LogFilesEntry[], config?: CoChangeConfig): CoChangeEdge[]; /** * Mine co-change edges from git history and persist them to the store's * `co_changes` table. Idempotent: re-running on unchanged history produces * the same edges (the table is cleared + repopulated each run, so stale * edges from history changes are pruned automatically). */ declare function mineAndStoreCoChanges(options: { readonly store: GraphStore; readonly git: CodingGitInvoker; readonly repoRoot: string; readonly config?: CoChangeConfig; }): Promise; /** * Index staleness reporting (issue #1553 done-when: "a deliberately stale * index with `autoIndex: "manual"` reports its staleness via `index_status` * rather than pretending freshness"). * * Reports: * - `lastIndexedHead` — the HEAD at the last successful reindex. * - `currentHead` — the current repo HEAD (null when git unavailable). * - `dirty` — true when `lastIndexedHead !== currentHead` (the index * is behind the repo and needs a reindex). * - `mode` — "fresh" | "stale" | "empty" | "git_unavailable". * * Never throws — a git failure degrades to `mode: "git_unavailable"` * so callers (remnic doctor / xray) can surface it without crashing. */ type IndexStatusMode = "fresh" | "stale" | "empty" | "git_unavailable"; interface IndexStatus { readonly lastIndexedHead: string | null; readonly currentHead: string | null; readonly dirty: boolean; readonly mode: IndexStatusMode; readonly fileCount: number; readonly nodeCount: number; } /** * Report the current index status. Pure over the store + git facts — no * side effects, never throws. */ declare function getIndexStatus(store: GraphStore, git: CodingGitInvoker, repoRoot: string): IndexStatus; /** * Minimal LSP protocol types — only the subset the resolution pass needs. * * These are NOT a full LSP type system. They mirror the wire shapes from * the LSP specification (v3.17 — https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/) * for the methods we implement: initialize, initialized, shutdown, exit, * textDocument/didOpen, textDocument/definition. * * Keeping a local subset avoids pulling in `vscode-languageserver-protocol` * (a dependency the issue explicitly rejects — "No npm LSP framework"). */ /** * LSP Position — zero-based line and character offsets (LSP 3.17 §3.17). * `character` is a UTF-16 code-unit offset, matching what tree-sitter * byte offsets are converted FROM during the location→node mapping. */ interface LspPosition { readonly line: number; readonly character: number; } /** * LSP Range — half-open `[start, end)` (LSP 3.17 §3.18). * Matches the coding-graph's half-open byte-span convention (rule 35). */ interface LspRange { readonly start: LspPosition; readonly end: LspPosition; } /** * LSP Location — a URI + Range (LSP 3.17 §3.19). * The resolution pass maps these back to graph nodes by file + span * containment. */ interface LspLocation { readonly uri: string; readonly range: LspRange; } /** * LSP TextDocumentIdentifier — a URI identifying a document (LSP 3.17 §3.16). */ interface LspTextDocumentIdentifier { readonly uri: string; } /** * LSP TextDocumentItem — the full open-document payload for didOpen * (LSP 3.17 §3.17). Carries the file content so the server can analyze * without reading from disk. */ interface LspTextDocumentItem { readonly uri: string; readonly languageId: string; /** Schema version — we always send 1. */ readonly version: number; readonly text: string; } /** * LSP TextDocumentPositionParams — the request payload for definition * (LSP 3.17 §3.18). */ interface LspTextDocumentPositionParams { readonly textDocument: LspTextDocumentIdentifier; readonly position: LspPosition; } /** * Client capabilities — we send an empty object because the resolution * pass uses no client-side features. The server still needs the field * present per the spec. */ interface LspClientCapabilities { } /** * Initialize params sent by the client (LSP 3.17 §3.17). * `processId` is our PID so a server can track us; `rootUri` is the * workspace root for multi-file definition resolution. */ interface LspInitializeParams { readonly processId: number | null; readonly rootUri: string | null; readonly capabilities: LspClientCapabilities; } /** * Server capabilities — the subset we inspect. `definitionProvider` * must be truthy for the resolution pass to work. */ interface LspServerCapabilities { readonly definitionProvider?: boolean | object; readonly referencesProvider?: boolean | object; } /** * Initialize result returned by the server (LSP 3.17 §3.17). */ interface LspInitializeResult { readonly capabilities: LspServerCapabilities; readonly serverInfo?: { readonly name?: string; readonly version?: string; }; } /** * LSP degradation codes — shaped like {@link SearchDegradation} from * @remnic/core/search/port (issue #1536, CLAUDE.md rule 34). * * Every failure mode produces a DISTINCT code so callers (index_status, * remnic doctor) can render per-language LSP state without conflating * "server not installed" with "server timed out" or "protocol violation". * * The codes are the load-bearing signal — `detail` is optional and never * carries `error.message` (which may contain absolute paths — rule 11). */ /** * Backend identifier — always `"lsp"` so callers can distinguish LSP * degradations from QMD/search degradations in a unified handler. */ type LspBackend = "lsp"; /** * Distinct failure codes (rule 34 — never `[]`-on-error). * * - `server_missing` — the configured server binary is not on PATH * or is not executable (probe phase). * - `handshake_timeout` — `initialize` did not complete within * `lsp.timeoutMs`. * - `handshake_error` — server responded to `initialize` with an * error or a malformed response. * - `request_timeout` — a `textDocument/definition` request did * not receive a response within the * per-request deadline. * - `request_error` — server returned a JSON-RPC error response * for a resolution request. * - `protocol_error` — malformed JSON-RPC frame (bad header, * unparseable JSON, unknown method). * - `server_crashed` — the child process exited unexpectedly * mid-run. * - `budget_exhausted` — `lsp.maxRequestsPerRun` reached; remaining * call sites keep their Phase A resolution. * - `not_enabled` — `lsp.enabled` is `false`; no probe attempted. * - `unknown_language` — the file's language has no registered * server spec and none was overridden in * `lsp.servers`. */ type LspDegradationCode = "server_missing" | "handshake_timeout" | "handshake_error" | "request_timeout" | "request_error" | "protocol_error" | "server_crashed" | "budget_exhausted" | "not_enabled" | "unknown_language"; /** * Tagged degradation — mirrors the shape of {@link SearchDegradation}. * `ok: false` results from the client/registry/resolution surface carry * this object so consumers can switch on `code` programmatically. */ interface LspDegradation { readonly backend: LspBackend; readonly code: LspDegradationCode; readonly detail?: string; } /** * Tagged-result helpers — the shared discriminated-union pattern used * throughout the coding-graph package (rule 34). Every LSP surface * returns `{ ok: true; … }` on success or `{ ok: false; degradation }` * on failure — never throws, never returns `[]` to mean "error". */ type LspResult = ({ readonly ok: true; } & T) | { readonly ok: false; readonly degradation: LspDegradation; }; /** * Construct a degradation object. Kept as a factory rather than a class * so callers can spread it into a result without `new`. */ declare function lspDegradation(code: LspDegradationCode, detail?: string): LspDegradation; /** * How to launch a language server: command + argv. Always an array of * strings — never a shell string — so injection via config is impossible * (rule 10: argv arrays end-to-end). */ interface LspServerLaunchSpec { readonly command: string; readonly args: readonly string[]; } /** * Per-language server overrides. Keys are language identifiers (matching * {@link CodingGraphLanguage}); values are launch specs that REPLACE the * default. An unknown language key is an error (rule 51 — list supported * languages). */ type LspServerOverrides = Partial>; interface LspConfig { /** Master switch — default false (rule 30/48). */ readonly enabled: boolean; /** Per-language server overrides (default: empty — use registry defaults). */ readonly servers: LspServerOverrides; /** Handshake + per-request timeout in ms (default 3000). */ readonly timeoutMs: number; /** Max definition requests per index run (default 500). */ readonly maxRequestsPerRun: number; } declare const DEFAULT_LSP_TIMEOUT_MS = 3000; declare const DEFAULT_LSP_MAX_REQUESTS_PER_RUN = 500; declare const DEFAULT_LSP_CONFIG: LspConfig; /** * Parse and validate user-supplied LSP config. Unknown keys in `servers` * are rejected with a degradation listing supported languages (rule 51). * Non-executable absolute paths are rejected (rule 24 analog). * * Returns `{ ok: true, config }` or `{ ok: false, degradation }` — never * throws (rule 13). */ type LspConfigParseResult = { readonly ok: true; readonly config: LspConfig; } | { readonly ok: false; readonly degradation: LspDegradation; }; /** * Parse and validate user-supplied LSP config. Unknown keys in `servers` * are rejected with a degradation listing supported languages (rule 51). * Non-executable absolute paths are rejected (rule 24 analog). * * Returns `{ ok: true, config }` or `{ ok: false, degradation }` — never * throws (rule 13). */ declare function parseLspConfig(raw: unknown, knownLanguages: readonly CodingGraphLanguage[]): LspConfigParseResult; /** * Read the env-var override for `lsp.enabled`. Returns the raw string * value or null. The caller decides how to interpret it (gotcha 9: * `REMNIC_` primary, `ENGRAM_` fallback). */ declare function readLspEnabledEnv(): string | null; /** * Minimal JSON-RPC-over-stdio LSP client. * * Implements exactly the protocol subset the resolution pass needs * (LSP 3.17): initialize → initialized → didOpen → definition → * shutdown → exit. No npm LSP framework — the protocol subset is small * and a dependency here would bloat the optional package (issue #1555). * * Failure discipline (rule 13): every operation degrades to a tagged * `LspDegradation` — the client NEVER throws to a caller. Server crashes * mid-run, protocol errors, and timeouts all surface as distinct codes. * * Lifecycle: the client owns the child process. `dispose()` sends * shutdown + exit, then hard-kills (SIGKILL) any lingering process — * no zombie children survive (tested). */ interface LspClientOptions { readonly launchSpec: LspServerLaunchSpec; readonly rootUri: string | null; readonly timeoutMs: number; /** * Optional spawn override — test seam. When provided, the client calls * this instead of `child_process.spawn`. Must return a ChildProcess- * compatible object with stdin/stdout streams. */ readonly spawnFn?: typeof spawn; } declare class LspClient { private readonly child; private readonly decoder; private readonly rootUri; private readonly timeoutMs; private nextId; private readonly pending; private disposed; private crashed; private crashCode; private serverCapabilities; private constructor(); /** * Spawn the server and perform the initialize handshake. Returns a * tagged result — `{ ok: true, client }` on success, or a degradation * on failure (server_missing, handshake_timeout, handshake_error). */ static connect(options: LspClientOptions): Promise<{ ok: true; client: LspClient; } | { ok: false; degradation: LspDegradation; }>; /** * Send `textDocument/didOpen` — notifies the server about an open * document with its full content. No response expected. */ didOpen(item: LspTextDocumentItem): void; /** * Send `textDocument/definition` for a position in a document. Returns * the definition locations (may be empty, a single location, or an * array). Degrades on timeout/error/crash — never throws. */ definition(params: LspTextDocumentPositionParams): Promise>; /** * Send `shutdown`, then `exit`, then SIGKILL if the process lingers. * Idempotent — safe to call multiple times. After dispose, no child * process remains (tested — zombie cleanup). */ dispose(): Promise; /** Returns the pid of the child process (for zombie-cleanup tests). */ get pid(): number | undefined; /** True if the server reported definitionProvider capability. */ get supportsDefinition(): boolean; /** * Send a request and await its response. Returns the `result` field * on success, or a degradation on timeout/error/crash/protocol-error. */ private request; /** * Send a notification (no response expected). Best-effort — if the * write fails, the next request will surface the crash. */ private notify; /** * Dispatch a decoded JSON-RPC message. Correlates responses to pending * requests by id; ignores server-initiated notifications (we don't * need them for the resolution pass). */ /** * Dispatch a decoded JSON-RPC message. Correlates responses to pending * requests by id; ignores server-initiated notifications. */ private dispatchMessage; /** * stdout data handler — feed the decoder, dispatch complete messages, * detect protocol errors. */ private onStdoutData; /** * Handle an unexpected child exit. All pending requests are rejected * with server_crashed. */ private onChildExit; /** * Handle a spawn error (ENOENT etc). Marks the server as missing. */ private onChildError; /** * Protocol error — the stream produced a malformed frame. Reject all * pending and mark disposed so no further requests can be sent. */ private handleProtocolError; /** * Hard-kill the child process: SIGKILL. Called by dispose() as a * final cleanup guarantee. Also called if the graceful shutdown path * fails. Uses `kill` which is a no-op if the process already exited. */ private hardKill; } /** * Convert a repo-relative or absolute file path to a `file://` URI. * Handles Windows drive letters (C:\ → file:///C:/). */ declare function pathToUri(filePath: string): string; /** * Convert a `file://` URI back to an absolute file path. */ declare function uriToPath(uri: string): string; /** * Length-prefixed LSP framing — `Content-Length: \r\n\r\n` headers * followed by exactly `` bytes of JSON body (LSP 3.17 §6.1 — Base Protocol). * * This is the one module where off-by-one and split-buffer bugs hide. * The parser tracks a RUNNING OFFSET — it never re-scans bytes a * previous scan already confirmed separator-free (rule 32). */ /** * Encode a JSON-RPC message as a Content-Length-prefixed frame ready to * write to the server's stdin. Uses UTF-8 — the LSP base protocol * mandates UTF-8 content encoding (§6.1). */ declare function encodeLspFrame(message: unknown): string; /** * Reason for a decode failure. A `protocol_error` degradation surfaces * the specific reason so the caller can distinguish "bad header" from * "unparseable JSON body". */ type FrameDecodeErrorKind = "malformed_header" | "json_parse_error"; interface FrameDecodeError { readonly kind: FrameDecodeErrorKind; readonly detail: string; } interface FrameDecodeSuccess { readonly ok: true; readonly messages: unknown[]; } type FrameDecodeResult = FrameDecodeSuccess | { readonly ok: false; readonly error: FrameDecodeError; }; /** * Streaming LSP frame decoder. Feed raw Buffer or string chunks via * {@link feed}; each call returns the complete JSON messages parsed * since the last call, plus any error if a frame was malformed. * * Works with BYTES internally because Content-Length counts UTF-8 bytes * (LSP 3.17 §6.1), not UTF-16 code units. A string-based buffer would * mis-slice any body containing multi-byte characters (𝕏, emoji, CJK). * * The decoder maintains a running byte-buffer and a scan offset. After * each feed, consumed bytes are sliced away so the buffer never grows * unbounded across a long session (rule 11 — no unbounded state). */ declare class LspFrameDecoder { private buffer; /** * Scan offset into {@link buffer}. The header scan resumes here on * the next feed() — never re-scans bytes already confirmed to not * contain the separator (rule 32). Reset to 0 after each consumed * frame because slicing the buffer discards those bytes. */ private scanOffset; /** * Feed a raw chunk (Buffer or string) from the server's stdout. * Returns all complete messages parsed from the accumulated buffer * since the last call, or the first decode error encountered (the * decoder stops on error — a protocol violation means the stream is * corrupt and further parsing is undefined). */ feed(chunk: Buffer | string): FrameDecodeResult; /** True if there is un-consumed residual data in the buffer. */ get hasResidual(): boolean; /** Reset the decoder to a clean state (test seam). */ reset(): void; } /** * LSP resolution pass — upgrades Phase A heuristic edges with real * definition-lookup results from language servers. * * Two halves (issue #1555 step 4): * * 1. **Planner** (pure): `planLspUpgrades(unresolvedCallSites, budget)` * decides which call sites to query and in what order. Pure function — * no side effects, no I/O. Deterministic output for deterministic input. * * 2. **Executor**: sends `textDocument/didOpen` + `textDocument/definition` * for each planned request via the LSP client, maps returned locations * back to graph nodes by file + half-open span containment (rule 35), * and applies edge upgrades transactionally per batch. A mid-batch * failure leaves zero partial upgrades (rule 25 — tested). * * Budgets (issue #1555): `maxRequestsPerRun` caps the worst case. Remaining * call sites keep their Phase A resolution. Everything degrades to Phase A * results with a tagged degradation surfaced in index_status. * * Files whose ingest failed are excluded (rule 44 — the executor never * queries for a file that isn't in the store). */ /** * A call site that Phase A left unresolved or at low confidence. The * resolution pass will query the LSP server for its definition. */ interface UnresolvedCallSite { /** Repo-relative file path of the CALLER (the file containing the call). */ readonly filePath: string; readonly language: CodingGraphLanguage; /** Full file content — needed for byte↔position conversion. */ readonly content: string; /** Byte offset of the callee name in the source (for the definition query position). */ readonly calleeByteOffset: number; /** The callee name as extracted by Phase A (for logging/debugging). */ readonly calleeName: string; /** The caller's qualified name (source node for the edge). */ readonly srcQualifiedName: string; } /** * A planned LSP definition request — the planner's output. Each request * targets one call site at one position in one file. */ interface PlannedLspRequest { readonly filePath: string; readonly language: CodingGraphLanguage; readonly content: string; readonly calleeName: string; readonly srcQualifiedName: string; /** LSP position derived from calleeByteOffset via line-offset map. */ readonly position: { readonly line: number; readonly character: number; }; } /** * The planner's budget — how many requests can be sent this run. */ interface LspBudget { readonly maxRequests: number; } /** * Result of the planner — the requests to send, plus how many call sites * were deferred due to budget exhaustion. */ interface PlanResult { readonly requests: readonly PlannedLspRequest[]; readonly budgetExhausted: number; } /** * Result of the resolution pass. */ interface ResolutionResult { /** Edges upgraded from heuristic → lsp with resolved dst node. */ readonly upgraded: number; /** LSP returned no location or the location didn't map to an indexed node. */ readonly unresolved: number; /** Call sites skipped because maxRequestsPerRun was reached. */ readonly budgetExhausted: number; /** Degradation if the pass could not run (server crashed, protocol error). */ readonly degradation?: LspDegradation; } /** * Look up a graph node by file path + byte span containment (rule 35 — * half-open). Returns the node's qualified name if exactly one node's * span contains the byte offset, or null if none/ambiguous. * * The executor provides this closure backed by the GraphStore; tests * inject a mock. */ type NodeLocator = (filePath: string, byteOffset: number) => string | null; /** * Context for mapping an LSP location to a graph node. Provides the * caller's file path and content (for same-file definitions), plus * optional workspace-root normalization and cross-file content resolution. */ interface MapLocationContext { /** Repo-relative path of the caller file. */ readonly callerFilePath: string; /** Full content of the caller file. */ readonly callerContent: string; /** Workspace root for normalizing absolute LSP URIs to repo-relative paths. */ readonly workspaceRoot?: string; /** Resolve content for a target file path (repo-relative). */ readonly resolveContent?: (filePath: string) => string | null; } /** * Plan which call sites to resolve via LSP. Pure — deterministic output * for deterministic input. Orders requests by file path then byte offset * for predictable, reviewable batches (rule 38 — byte-stable ordering). * * Budget enforcement: at most `budget.maxRequests` requests are planned. * Excess call sites are counted in `budgetExhausted` so the caller can * surface the degradation. */ declare function planLspUpgrades(callSites: readonly UnresolvedCallSite[], budget: LspBudget): PlanResult; /** * Options for the resolution executor. */ interface ResolveOptions { readonly client: LspClient; readonly nodeLocator: NodeLocator; /** * Warm-up retry delay in ms (issue #1933). Language servers (tsserver * in particular) load projects ASYNCHRONOUSLY after `didOpen`; a * definition request that arrives too early returns an EMPTY location * array — indistinguishable from "definitely no definition". Until the * server has proven warm (any non-empty response), an empty result is * retried ONCE after this delay. Default 2500. Set 0 to disable. */ readonly warmupRetryDelayMs?: number; /** * Apply a batch of edge upgrades atomically. Called once per file batch. * MUST be transactional — if it throws, zero upgrades from this batch * persist (rule 25). Each upgrade is an edge `{srcQualifiedName, * dstQualifiedName, type: "CALLS", confidence, provenance: "lsp"}`. */ readonly applyUpgrades: (upgrades: readonly EdgeUpgrade[]) => Promise; /** * Optional stale-edge reconciliation (issue #1895): after applying a * file batch's upgrades, the caller retires prior `lsp`-provenance * edges owned by that file whose `(src, dst, type)` keys the current * batch does NOT assert. When absent, stale lsp edges persist until * node pruning — the soft-fail path documented in #1894. */ readonly reconcileLspEdges?: (filePath: string, assertedEdges: ReadonlyArray<{ srcQualifiedName: string; dstQualifiedName: string; type: string; }>) => void; /** * Workspace root for resolving repo-relative file paths to absolute LSP * URIs and normalizing returned URIs back to repo-relative paths. */ readonly workspaceRoot?: string; /** * Resolve the content of a target file by repo-relative path. Used for * cross-file definition positions — without this, cross-file byte-offset * conversion falls back to the caller's content (best-effort). */ readonly resolveContent?: (filePath: string) => string | null; } /** * A single edge upgrade — the output of a successful definition lookup. */ interface EdgeUpgrade { readonly srcQualifiedName: string; readonly dstQualifiedName: string; readonly type: string; readonly confidence: number; readonly provenance: "lsp"; } /** * Execute the resolution pass: for each planned request, send a * `textDocument/didOpen` + `textDocument/definition` query, map the * returned location to a graph node, and collect edge upgrades. Upgrades * are applied in file-batched transactions — a mid-batch failure leaves * zero partial upgrades. * * Never throws — degrades to Phase A results with a tagged degradation. */ declare function executeLspResolution(requests: readonly PlannedLspRequest[], options: ResolveOptions): Promise; /** * Map an LSP definition location to a graph node's qualified name. * * Uses half-open span containment (rule 35): the location's start byte * must be within `[node.spanStart, node.spanEnd)` for the node to match. * If multiple locations are returned, the FIRST one that maps to a node * wins (LSP servers typically return the most relevant definition first). * * For same-file definitions, the caller's content is used for byte-offset * conversion (exact). For cross-file definitions, `context.resolveContent` * is used to fetch the target file's content; if unavailable, the caller's * content is used as a best-effort fallback. * * Returns null if no location maps to an indexed node. */ declare function mapLocationToNode(locations: readonly LspLocation[], context: MapLocationContext, nodeLocator: NodeLocator): string | null; /** * LSP status surfacing — per-language LSP state for index_status and * `remnic doctor` (issue #1555 step 5). * * Reports per-language: enabled / probed / degraded(code) / requests_used. * A missing server is normal, not an error — the status entry shows * `probed: false` with a degradation code so the operator knows LSP * resolution is available but inactive for that language. */ /** * Per-language LSP status. Surfaced in `index_status` and rendered as a * single line by `remnic doctor`. */ interface LspStatusEntry { readonly language: CodingGraphLanguage; /** Master switch state (codingGraph.lsp.enabled). */ readonly enabled: boolean; /** Server binary found and initialize handshake succeeded. */ readonly probed: boolean; /** True if the last resolution pass degraded (timeout/crash/protocol). */ readonly degraded: boolean; /** Specific degradation code if `degraded` is true. */ readonly degradationCode?: LspDegradationCode; /** Definition requests sent in the last index run. */ readonly requestsUsed: number; } /** * Input for LSP status computation — the probe results and resolution * results from the last index run. The caller (the index pipeline) * collects these during the run and passes them here. */ interface LspStatusInput { readonly config: LspConfig; /** * Per-language probe results from the last run. `true` = server found * and handshake succeeded. */ readonly probeResults: ReadonlyMap; /** * Per-language degradation codes from the last run. A language in this * map with a code means the resolution pass degraded for that language. */ readonly degradations: ReadonlyMap; /** * Per-language definition request counts from the last run. */ readonly requestCounts: ReadonlyMap; /** * The languages configured for resolution (from the index run's * candidate set). Only these languages get status entries. */ readonly languages: readonly CodingGraphLanguage[]; } /** * Compute per-language LSP status entries. Pure — no side effects. * Returns one entry per language in `languages`. */ declare function getLspStatus(input: LspStatusInput): readonly LspStatusEntry[]; /** * Render a single LSP status line for `remnic doctor`. Format: * * typescript: lsp [probed] 12 requests * python: lsp [degraded:request_timeout] 3 requests * go: lsp [not_probed] 0 requests * rust: lsp [disabled] */ declare function formatLspStatusLine(entry: LspStatusEntry): string; /** * Adapter: convert a {@link ResolutionResult} into the per-language * degradation + request-count maps that {@link getLspStatus} consumes. * * The resolution pass runs once per language (each language has its own * server). This helper is called once per language to populate the maps. */ declare function resolutionResultToStatusMaps(language: CodingGraphLanguage, result: ResolutionResult, degradations: Map, requestCounts: Map): void; /** * Byte-offset ↔ LSP position conversion. * * LSP positions are zero-based {line, character} where `character` is a * UTF-16 code-unit offset within the line (LSP 3.17 §3.17). The coding- * graph store uses UTF-8 byte spans. This module converts between the two. * * A {@link LineOffsetMap} pre-computes the UTF-8 BYTE offset of each line * start, making both directions O(log n) via binary search for the line, * then O(line length) for the character within the line. */ /** * Pre-computed line-start byte offsets for a single file. Built once per * file from its content; reused for all position conversions in that file. * * `lineStarts[i]` = UTF-8 byte offset of the first character on line `i`. * Line 0 always starts at byte 0. */ interface LineOffsetMap { readonly lineStarts: readonly number[]; } /** * Build a line-offset map from file content (as a UTF-8 string or Buffer). * Records UTF-8 BYTE offsets — not UTF-16 string indices — because * Content-Length and the store's span_start/span_end count bytes. * Handles `\n`, `\r\n`, and `\r` line endings. */ declare function buildLineOffsetMap(content: string | Buffer): LineOffsetMap; /** * Convert a UTF-8 byte offset to an LSP position {line, character}. * `character` is a UTF-16 code-unit count from the line start (surrogates * count as 2, matching LSP §3.17). */ declare function byteOffsetToPosition(content: string, byteOffset: number, map: LineOffsetMap): { line: number; character: number; }; /** * Convert an LSP position {line, character} to a UTF-8 byte offset. * `character` is a UTF-16 code-unit count from the line start. */ declare function positionToByteOffset(content: string, position: { line: number; character: number; }, map: LineOffsetMap): number; /** * Semantic-layer configuration for @remnic/coding-graph (issue #1556). * * Rule 30/48: the semantic layer is OFF by default. Embedding costs compute * and possibly tokens, so nothing in this module touches a provider, writes * a vector, or sends symbol text off-machine unless `enabled` is explicitly * true. The gate-off characterization test (`gate-off.test.ts`) asserts * this end to end. * * The config object is intentionally self-contained in this package rather * than wired into @remnic/core/config.ts — the coding-graph package is an * optional peer dep and must compile standalone. Host integrations * (core/config.ts + openclaw.plugin.json schema) resolve to this same * shape via `resolveSemanticConfig()`, which reads the documented env vars * with the `ENGRAM_` fallback (gotcha 9). */ /** * Default SIMILAR_TO cosine confirmation threshold (issue #1556 design). * 0.92 is the codebase-memory-mcp precedent — high enough to avoid * false near-clone pairs across structurally-similar but logically-distinct * functions, low enough to catch genuine copy-paste with a renamed * variable. */ declare const DEFAULT_SIMILAR_TO_THRESHOLD = 0.92; /** * Default maximum symbols embedded per indexing run. Bounds per-run * provider cost. 0 means unlimited (the host budget is the only cap). */ declare const DEFAULT_MAX_SYMBOLS_PER_RUN = 0; /** * Confidence band assigned to MinHash-only SIMILAR_TO edges when no * embedding provider is available (deterministic, local). Kept below the * embedding-confirmed band so consumers can distinguish provenance quality. * The issue designates this a distinct, documented lower band. */ declare const MINHASH_ONLY_CONFIDENCE = 0.5; /** * Edge type emitted by the SIMILAR_TO pipeline. Lives in the `edges` * table with `provenance: "semantic"` (already in EDGE_PROVENANCE_VALUES). */ declare const SIMILAR_TO_EDGE_TYPE = "SIMILAR_TO"; /** * The single provenance tag for every edge this module writes. */ declare const SEMANTIC_PROVENANCE: EdgeProvenance; /** * Canonical-text body line budget. `signature + doc comment + first N lines * of body` per the issue design. N is bounded so a 5k-line function does * not dominate the embedded string (and the provider token budget). */ declare const DEFAULT_CANONICAL_BODY_LINES = 16; /** * Self-contained semantic config. Resolved from host config + env. * * `enabled` is the single gate for the whole layer. When false, every * semantic entry point (index-time vector writes, SIMILAR_TO edges, * semantic_query) returns a tagged `{ ok: false, code: "semantic_disabled" }` * WITHOUT touching the provider or the vectors table (gate-off parity). */ interface SemanticConfig { /** Master gate. Default false (rule 30/48). */ readonly enabled: boolean; /** Cosine threshold for SIMILAR_TO confirmation. Default 0.92. */ readonly similarToThreshold: number; /** Per-run embedding budget (0 = unlimited). Default 0. */ readonly maxSymbolsPerRun: number; /** Canonical-text body line budget. Default 16. */ readonly canonicalBodyLines: number; } /** * Resolve the semantic config from an optional host-provided partial plus * the environment. Explicit host values win; env vars fill the gaps; * documented defaults apply last. * * `env` defaults to `process.env` but is a parameter so tests can pin the * environment deterministically (rule 38 — no implicit process state). */ declare function resolveSemanticConfig(host?: Partial, env?: NodeJS.ProcessEnv): SemanticConfig; /** * Input to the canonical-text builder. `rawText` is the symbol's on-disk * source slice `[startByte, endByte)`. `docComment` is the leading * comment block immediately above the symbol, if the caller extracted one * (the parser does not currently emit doc comments on SymbolIR, so this * is optional and may be empty/undefined — the canonical form degrades * gracefully to `signature + body`). */ interface CanonicalTextInput { readonly symbol: SymbolIR; /** Raw source text of the symbol span. */ readonly rawText: string; /** Optional leading doc comment (/** … *\/ or // … lines). */ readonly docComment?: string; /** Body token budget (default {@link DEFAULT_CANONICAL_BODY_LINES}). */ readonly maxBodyLines?: number; } /** * Collapse ALL whitespace runs (including newlines) to single spaces and * trim. This is the universal normalization pass: it absorbs indentation, * brace placement, trailing whitespace, tabs vs spaces, and line-ending * differences. After this pass, two formatting variants of the same * function are byte-identical. */ declare function collapseWhitespace(text: string): string; /** * Extract a coarse signature string from raw symbol text. The text is * fully whitespace-normalized first, then split at the first body-open * marker. The returned signature is stable across all formatting variants * of the same function. */ declare function extractSignatureLine(rawText: string, _kind: SymbolIR["kind"]): string; /** * Extract the body (everything after the signature/header marker) from * raw symbol text, truncated to the first `maxBodyLines` tokens. Tokens * are whitespace-delimited words/operators in the normalized body text — * this is a STABLE budget (formatting-independent) unlike line-based * truncation. `maxBodyLines` is the config field name; it functions as a * token budget here (each "line" ≈ one significant token). * * `maxBodyLines <= 0` means unlimited (return the full normalized body). */ declare function extractBodyText(rawText: string, maxBodyLines: number): string; /** * Build the canonical embedding text for a symbol. * * The form (rule 23/38 — ONE form, every consumer): * KIND:\nQNAME:\nSIG:\n[DOC:]\nBODY: * * `kind` and `qualifiedName` are included as stable prefix lines so two * functions with identical bodies but different names (the "renamed * variable" clone fixture) embed close but not identically — the qualified * name differentiates them at the embedding level while the body dominates * the similarity. The cache hash, by contrast, is over the FULL canonical * text including the name, so a rename invalidates the cache (rule 37). * * Normalization: * - ALL whitespace collapsed (indentation/brace-style/newlines absorbed) * - body truncated to maxBodyLines tokens (stable budget) */ declare function buildCanonicalText(input: CanonicalTextInput): string; /** * The cache key for a canonical text. This is sha256 over the EXACT * canonical text string (rule 23 — the embedded string equals the hashed * string). When the canonical text changes (e.g. a rename edits the * qualified name, or the body changes), the hash changes, and: * 1. the cached vector is invalidated (re-embedded), and * 2. any SIMILAR_TO edge derived from it is recomputed. * * This is the single chokepoint for cache invalidation (rule 37). Every * layer that persists a vector persists THIS hash alongside it; every * re-index compares THIS hash to decide whether to re-embed. */ declare function canonicalTextHash(canonicalText: string): string; /** * Convenience: build canonical text AND its hash in one call. The hash is * over the returned text — callers that store both MUST store the exact * `text` alongside the `hash` (never re-derive the text from disk and hash * separately, or a formatter run between the two would silently * re-embed — rule 37). */ declare function buildCanonicalTextAndHash(input: CanonicalTextInput): { readonly text: string; readonly hash: string; }; declare const MINHASH_SEEDS: readonly bigint[]; /** * Normalize symbol body text into a token stream for shingling. * * Normalization: * - lowercase (case-insensitive clone detection — `MyFunc` vs `myfunc`) * - split on non-alphanumeric (identifiers, numbers, operators become tokens) * - drop empty tokens * * This is deliberately coarse: the goal is Jaccard over token sets, not * semantic parsing. A renamed variable changes exactly one token per * occurrence, so Jaccard stays high for genuine clones. */ declare function tokenizeForShingling(body: string): string[]; /** * Build the set of shingles (n-grams of width MINHASH_SHINGLE_WIDTH) from * a token stream. Returns a Set so duplicate shingles collapse (Jaccard * is over the SET of shingles, not a multiset). */ declare function shingleSet(tokens: readonly string[]): Set; /** * Compute the MinHash signature (array of permutation minima) for a set * of shingles. Signature length = MINHASH_NUM_PERMUTATIONS. The estimated * Jaccard similarity between two signatures is the fraction of matching * positions. */ declare function minHashSignature(shingles: Set): bigint[]; /** * LSH band key — the concatenation of one band's rows from the signature. * Two signatures that share at least one band key are a candidate pair. */ declare function lshBandKeys(signature: readonly bigint[]): string[]; /** * An indexed symbol body ready for LSH bucketing. */ interface LshIndexEntry { readonly nodeId: string; readonly qualifiedName: string; readonly body: string; } /** * A MinHash/LSH indexer instance. Owns the band→node-id bucket map on the * instance (rule 11). `findCandidates` returns the deduplicated candidate * pair set with estimated Jaccard for each pair. */ declare class MinHasher { /** band key → set of node ids in that band. */ private readonly buckets; /** node id → signature (for Jaccard estimation on candidate pairs). */ private readonly signatures; /** node id → qualified name (for readable candidate output). */ private readonly qnames; /** * Add a symbol body to the LSH index. Idempotent — re-adding the same * (nodeId, body) is a no-op. */ add(entry: LshIndexEntry): void; /** * Find all candidate pairs (pairs sharing at least one LSH band) with * their estimated Jaccard similarity. Returns a stable-sorted array * (by aNodeId then bNodeId) so the determinism test can compare runs * byte-for-byte. */ findCandidates(): { readonly aNodeId: string; readonly bNodeId: string; readonly aQualifiedName: string; readonly bQualifiedName: string; readonly jaccard: number; }[]; } /** * Construct a fresh MinHasher (rule 11 — state on the instance). */ declare function createMinHasher(): MinHasher; /** * Cosine similarity between two equal-length float vectors. Returns 0 for * zero-norm vectors (no division-by-zero). This is the brute-force * retrieval primitive shared by SIMILAR_TO confirmation and semantic_query. */ declare function cosineSimilarity(a: Float32Array | number[], b: Float32Array | number[]): number; /** * Shared types for the semantic layer (issue #1556). * * Tagged failures follow rule 34 — every entry point returns a * discriminated union so a caller that switches on `result.code` never * observes a thrown error from the semantic layer. */ /** * A persisted symbol vector. `vector` is the float32 embedding; `dims` * is its dimensionality; `modelId` identifies the provider+model that * produced it (so a provider swap invalidates the cache); `contentHash` * is the canonical-text hash (so a canonical-text change invalidates the * cache — rule 37). */ interface SymbolVector { readonly nodeId: string; readonly qualifiedName: string; readonly vector: Float32Array; readonly dims: number; readonly modelId: string; readonly contentHash: string; } /** * A row read back from the vectors table for brute-force cosine. */ interface SymbolVectorRow { readonly nodeId: string; readonly qualifiedName: string; readonly vector: Float32Array; readonly dims: number; readonly modelId: string; readonly contentHash: string; readonly filePath: string; } /** * Tagged-failure codes shared across the semantic layer. * * - `semantic_disabled`: the master gate is off (rule 30/48). No provider * call, no vectors-table write, no edge emitted. * - `provider_unavailable`: no host embedding provider registered for the * given scope. * - `provider_timeout`: the provider exceeded the lookup budget. * - `malformed_vector`: `normalizeHostEmbeddingVector` returned null. * - `repo_root_unset`: the store was opened without a repoRoot, so source * text cannot be read. * - `store_closed`: the store is closed. * - `db_error`: an underlying SQLite error. * - `no_vectors`: semantic_query ran but the vectors table is empty. * - `invalid_query`: malformed query input. */ type SemanticFailureCode = "semantic_disabled" | "provider_unavailable" | "provider_timeout" | "malformed_vector" | "repo_root_unset" | "store_closed" | "db_error" | "no_vectors" | "invalid_query"; interface SemanticFailure { readonly ok: false; readonly code: SemanticFailureCode; readonly message?: string; } /** * Result of indexing vectors for a batch of symbols. */ interface IndexVectorsResult { readonly ok: true; readonly embedded: number; readonly cached: number; readonly skipped: number; } /** * A SIMILAR_TO candidate pair from the MinHash/LSH pass. */ interface SimilarCandidate { readonly aNodeId: string; readonly bNodeId: string; readonly aQualifiedName: string; readonly bQualifiedName: string; readonly jaccard: number; } /** * A confirmed SIMILAR_TO edge (after cosine confirmation when available). * * Carries the content-derived node ids (`nodes.id`) of both endpoints so * the persisted edge resolves unambiguously even when the two symbols share * a qualified name across files (issue #1677). The qualified-name fields * remain for diagnostics / stable-sort tie-breaking. */ interface SimilarEdge { readonly srcNodeId: string; readonly dstNodeId: string; readonly srcQualifiedName: string; readonly dstQualifiedName: string; readonly confidence: number; readonly confirmed: boolean; } /** * Result of the SIMILAR_TO pipeline. */ interface SimilarToResult { readonly ok: true; readonly edges: readonly SimilarEdge[]; readonly candidates: number; readonly confirmed: number; readonly minhashOnly: number; } /** * A hydrated semantic_query hit — graph context attached so the agent * gets structure, not just a snippet. */ interface SemanticQueryHit { readonly qualifiedName: string; readonly filePath: string; readonly kind: string; readonly score: number; readonly snippet: string; readonly callers: readonly string[]; readonly callees: readonly string[]; } /** * Result of semantic_query. When degraded, `ok: true` still carries the * (possibly empty) hits plus a `degraded` tag so the caller never * mistakes "no matches" for "backend broken" (rule 34). */ interface SemanticQuerySuccess { readonly ok: true; readonly hits: readonly SemanticQueryHit[]; readonly degraded?: "provider_unavailable" | "provider_timeout" | "malformed_vector"; } type SemanticQueryOutcome = SemanticQuerySuccess | SemanticFailure; /** * Input to {@link indexSymbolVectors}. The store provides node metadata + * the vectors table; the provider embeds; repoRoot resolves file paths. */ interface IndexVectorsInput { readonly store: GraphStore; readonly provider: HostEmbeddingProvider | undefined; readonly repoRoot: string; readonly config: SemanticConfig; /** * Optional abort signal forwarded to the provider. The indexer does not * impose its own timeout (the provider's embed() contract handles that). */ readonly signal?: AbortSignal; } /** * The model id used for cache keying. Derives from the provider's `model` * (falling back to `id`) so a provider/model swap produces a distinct * cache namespace and does not overwrite the prior vectors. */ declare function modelIdFor(provider: HostEmbeddingProvider): string; /** * Index symbol vectors for every persisted node in the store. * * Flow: * 1. Gate: if !config.enabled → tagged semantic_disabled (no work). * 2. Provider check: if no provider → tagged provider_unavailable. * 3. Read all nodes from the store (persisted only — rule 44). * 4. For each node (within maxSymbolsPerRun budget): * a. Read source text from disk. * b. Build canonical text + hash. * c. Cache check: skip if cached row's content_hash matches. * d. Embed via provider. * e. Normalize (reject malformed → counted as skipped). * f. Persist vector. * 5. Return counts. * * Budget order (rule 27): recently-changed symbols first. The store's * readNodesForSemantic returns nodes ordered by qualified_name; the * indexer applies the caller-supplied priority before slicing. When no * priority is given, all nodes are eligible (budget 0 = unlimited). */ declare function indexSymbolVectors(input: IndexVectorsInput): Promise; /** * Input to {@link computeSimilarTo}. */ interface SimilarToInput { readonly store: GraphStore; readonly provider: HostEmbeddingProvider | undefined; readonly config: SemanticConfig; /** * Repo root for reading source text from disk when bodies are not * supplied. Required when bodies is absent. */ readonly repoRoot?: string; /** * Symbol bodies keyed by nodeId. The caller (the indexer or a * standalone pass) reads source text and builds canonical bodies. When * absent, the pipeline reads nodes from the store + disk itself. */ readonly bodies?: ReadonlyMap; /** * Vectors keyed by nodeId (the persisted embedding). When absent, the * pipeline reads them from the store via readAllSymbolVectors. */ readonly vectors?: ReadonlyMap; } /** * The comparison operator for cosine confirmation. Decided ONCE (rule 35 * spirit): `>= threshold`. A pair at EXACTLY the threshold confirms. The * boundary test asserts this. */ declare const CONFIRM_OPERATOR: ">="; /** * Compute SIMILAR_TO edges. * * Returns the edges (for the caller to persist via store.upsertEdges) plus * counts. The caller persists; this function is pure over its inputs * (rule 38 — deterministic given seeds + bodies + vectors). * * When `config.enabled` is false → tagged semantic_disabled (no work, no * candidate generation — gate-off parity). */ declare function computeSimilarTo(input: SimilarToInput): SimilarToResult | SemanticFailure; /** * Convert SimilarEdge[] to the store's EdgeIR[] for persistence via * upsertEdges. Provenance is always "semantic"; type is SIMILAR_TO. * * Carries the content-derived node ids onto the EdgeIR (issue #1677) so * the store resolves each endpoint by `nodes.id` (unique) instead of by * qualified name — two symbols that share a qualified name across files * get distinct, non-colliding SIMILAR_TO edges instead of being dropped * as ambiguous. */ declare function similarEdgesToEdgeIR(edges: readonly SimilarEdge[]): EdgeIR[]; /** * Estimate Jaccard similarity between two bodies directly (no LSH). Used * by the hard-negative test to assert two bodies are NOT similar. */ declare function estimateJaccard(bodyA: string, bodyB: string): number; /** * semantic_query — natural-language retrieval over the symbol graph * (issue #1556 PR3 component). * * Embed the query via the host provider / EmbeddingFallback * (`mode: "lookup"`), top-k symbols by cosine, hydrate each hit with * graph context (defining file, direct callers/callees). * * Rule 34 — degradation matrix. Provider missing / timeout / malformed * vector (`normalizeHostEmbeddingVector` returns null) → three distinct * `{ok:false}` codes, never an empty result masquerading as "no matches". * When the provider is available but returns zero hits, `ok:true` with * empty hits is the honest answer. */ /** * Input to {@link semanticQuery}. */ interface SemanticQueryInput { readonly store: GraphStore; readonly provider: HostEmbeddingProvider | undefined; readonly repoRoot: string; readonly config: SemanticConfig; readonly query: string; readonly limit?: number; readonly signal?: AbortSignal; } /** * Default top-k for semantic_query. */ declare const DEFAULT_SEMANTIC_QUERY_LIMIT = 10; /** * Run a semantic query: embed → top-k → hydrate. * * Degradation matrix (rule 34): * - !config.enabled → { ok:false, code:"semantic_disabled" } * - no provider → { ok:false, code:"provider_unavailable" } * - provider throws timeout → { ok:false, code:"provider_timeout" } * - provider returns null/malformed → { ok:false, code:"malformed_vector" } * - no vectors in table → { ok:false, code:"no_vectors" } * - ok but zero hits → { ok:true, hits:[] } */ declare function semanticQuery(input: SemanticQueryInput): Promise; /** * @remnic/coding-graph — symbol-extraction engine + SQLite knowledge-graph * store for codebase memory. * * À-la-carte optional companion of @remnic/core (CLAUDE.md rule 57). * * This package unifies two PR1 surfaces: * - The web-tree-sitter engine scaffold (#1551 step 1): the package and * its build wiring exist; the engine public surface is declared and * the placeholder factory throws a tagged * `CodingGraphError("not_implemented", …)`. The real backend lands in * #1551 PR2. * - The SQLite knowledge-graph store (#1552 PR1): versioned schema + the * write pipeline (upsert/drop file batches, node-id derivation, * dangling-edge accounting). Traversal, search, dead-code, and the * openCypher subset land in #1552 PR2/PR3. * * Type-source direction: * The contract types (CodingGraphEngine, FileIR, etc.) and the * TIER_1_LANGUAGES / CODING_GRAPH_ENGINE_VERSION constants live in * @remnic/core (packages/remnic-core/src/coding/coding-graph-types.ts, * re-exported from the main index). This package imports them and * implements against them; it does NOT redefine them. That keeps a * single source of truth so updating the engine version in one place * keeps every consumer in lockstep (Cursor Bugbot low-severity on * PR #1588 round 2: "ENGINE_VERSION duplicated not imported"). * * @remnic/coding-graph declares @remnic/core as both `peerDependencies` * and `devDependencies: "workspace:*"` in its package.json, so the * pnpm workspace link exists and the `import from "@remnic/core"` * below resolves in development. * * The store modules (graph-schema, graph-store, row-types) are local to * this package; they import `openBetterSqlite3` from * `@remnic/core/runtime/better-sqlite` so the native-binding lifecycle * is paid for once there (rule 23/38: do not invent a new pattern). * * IR-type re-export policy: * graph-store.ts imports the core IR contract types (`FileIR`, * `SymbolIR`, etc.) from `@remnic/core/coding/coding-graph-types` * and re-exports them so existing `import { type FileIR } from * "./graph-store.js"` call-sites continue to resolve. The store * does NOT redefine these types — it derives from the core contract * so PR2 callers can pass `ParseResult.ir` directly * (chatgpt-codex-connector P2: 'Derive store FileIR from the core * parser contract'). At the package root, `FileIR`/`SymbolIR` * resolve to the @remnic/core contract types re-exported below; * the store-specific `StoreFileIR` (FileIR + edges extension) and * `EdgeIR` are re-exported from the root via graph-store. */ /** Public engine version. Imported from @remnic/core (single source of truth). */ declare const ENGINE_VERSION: "0.1.0-pr1"; /** * Thrown by `createCodingGraphEngine` while the real implementation is * being landed. It is *not* a generic Error — the `code` field is the * load-bearing signal for programmatic detection (see PR2 contract). */ declare class CodingGraphError extends Error { readonly code: CodingGraphErrorCode; readonly engineVersion: string; constructor(code: CodingGraphErrorCode, message: string, engineVersion?: string); } export { type AffectedSymbol, BLAST_RADIUS_EDGE_TYPES, type BlastRadiusResult, CONFIRM_OPERATOR, type CanonicalTextInput, type CoChangeConfig, type CoChangeEdge, type CodingGitInvoker, CodingGraphError, DEFAULT_BLAST_RADIUS_DEPTH, DEFAULT_CANONICAL_BODY_LINES, DEFAULT_CO_CHANGE_CONFIG, DEFAULT_LSP_CONFIG, DEFAULT_LSP_MAX_REQUESTS_PER_RUN, DEFAULT_LSP_TIMEOUT_MS, DEFAULT_MAX_SYMBOLS_PER_RUN, DEFAULT_SEMANTIC_QUERY_LIMIT, DEFAULT_SIMILAR_TO_THRESHOLD, type DetectChangesResult, type DiffHunk, ENGINE_VERSION, EdgeIR, EdgeProvenance, type EdgeUpgrade, FAN_IN_ESCALATION_THRESHOLD, type FrameDecodeError, type FrameDecodeErrorKind, type FrameDecodeResult, type GitFailure, GraphStore, type IndexStatus, type IndexStatusMode, type IndexVectorsInput, type IndexVectorsResult, type LineOffsetMap, type LogFilesEntry, type LshIndexEntry, type LspBackend, type LspBudget, LspClient, type LspClientOptions, type LspConfig, type LspConfigParseResult, type LspDegradation, type LspDegradationCode, LspFrameDecoder, type LspInitializeParams, type LspInitializeResult, type LspLocation, type LspPosition, type LspRange, type LspResult, type LspServerCapabilities, type LspServerLaunchSpec, type LspServerOverrides, type LspStatusEntry, type LspStatusInput, type LspTextDocumentItem, type LspTextDocumentPositionParams, META_KEY_LAST_HEAD, META_KEY_PENDING_PARSE_FAILURES, MINHASH_ONLY_CONFIDENCE, MINHASH_SEEDS, MinHasher, type MineCoChangesResult, type NameStatusEntry, type NodeLocator, type ParseFileFn, type ParserBackend, type PlanResult, type PlannedLspRequest, type ReadFileFn, ReadFileHashesResult, ReadMetaResult, type ReindexGitFacts, type ReindexPlan, type ReindexResult, type ReindexState, type ResolutionResult, type ResolveOptions, type RiskLevel, SEMANTIC_PROVENANCE, SIMILAR_TO_EDGE_TYPE, type SemanticConfig, type SemanticFailure, type SemanticFailureCode, type SemanticQueryHit, type SemanticQueryInput, type SemanticQueryOutcome, type SemanticQuerySuccess, type SimilarCandidate, type SimilarEdge, type SimilarToInput, type SimilarToResult, type SymbolVector, type SymbolVectorRow, type UnresolvedCallSite, WasmTreeSitterBackend, buildCanonicalText, buildCanonicalTextAndHash, buildLineOffsetMap, byteOffsetToPosition, byteSpanToLines, canonicalTextHash, classifyRisk, collapseWhitespace, computeBlastRadius, computeSimilarTo, cosineSimilarity, createCodingGraphEngine, createMinHasher, defaultCodingGitInvoker, encodeLspFrame, estimateJaccard, executeLspResolution, executeReindex, extractBodyText, extractSignatureLine, findDirectlyAffectedSymbols, formatLspStatusLine, getIndexStatus, getLspStatus, hashContent, indexSymbolVectors, lshBandKeys, lspDegradation, mapLocationToNode, minHashSignature, mineAndStoreCoChanges, mineCoChangeEdges, modelIdFor, parseHunks, parseLogFiles, parseLspConfig, parseNameStatus, pathToUri, planLspUpgrades, planReindex, positionToByteOffset, rangesOverlap, readFileHashes, readLastIndexedHead, readLspEnabledEnv, resolutionResultToStatusMaps, resolveSemanticConfig, semanticQuery, shingleSet, similarEdgesToEdgeIR, tokenizeForShingling, uriToPath };