@remnic/coding-graph 9.3.759

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +130 -0
  2. package/dist/chunk-5I2DBHOQ.js +1042 -0
  3. package/dist/chunk-5I2DBHOQ.js.map +1 -0
  4. package/dist/chunk-CPYJACC5.js +1838 -0
  5. package/dist/chunk-CPYJACC5.js.map +1 -0
  6. package/dist/chunk-ZVCMIM4T.js +216 -0
  7. package/dist/chunk-ZVCMIM4T.js.map +1 -0
  8. package/dist/cypher/query-parser.d.ts +253 -0
  9. package/dist/cypher/query-parser.js +17 -0
  10. package/dist/cypher/query-parser.js.map +1 -0
  11. package/dist/graph-schema.d.ts +84 -0
  12. package/dist/graph-schema.js +17 -0
  13. package/dist/graph-schema.js.map +1 -0
  14. package/dist/graph-store.d.ts +938 -0
  15. package/dist/graph-store.js +16 -0
  16. package/dist/graph-store.js.map +1 -0
  17. package/dist/index.d.ts +1953 -0
  18. package/dist/index.js +3509 -0
  19. package/dist/index.js.map +1 -0
  20. package/grammars/tree-sitter-bash.wasm +0 -0
  21. package/grammars/tree-sitter-c.wasm +0 -0
  22. package/grammars/tree-sitter-c_sharp.wasm +0 -0
  23. package/grammars/tree-sitter-cpp.wasm +0 -0
  24. package/grammars/tree-sitter-go.wasm +0 -0
  25. package/grammars/tree-sitter-java.wasm +0 -0
  26. package/grammars/tree-sitter-javascript.wasm +0 -0
  27. package/grammars/tree-sitter-kotlin.wasm +0 -0
  28. package/grammars/tree-sitter-php.wasm +0 -0
  29. package/grammars/tree-sitter-python.wasm +0 -0
  30. package/grammars/tree-sitter-ruby.wasm +0 -0
  31. package/grammars/tree-sitter-rust.wasm +0 -0
  32. package/grammars/tree-sitter-swift.wasm +0 -0
  33. package/grammars/tree-sitter-tsx.wasm +0 -0
  34. package/grammars/tree-sitter-typescript.wasm +0 -0
  35. package/package.json +79 -0
  36. package/src/co-change.test.ts +175 -0
  37. package/src/co-change.ts +167 -0
  38. package/src/cypher/query-parser.test.ts +1107 -0
  39. package/src/cypher/query-parser.ts +1692 -0
  40. package/src/detect-changes.test.ts +533 -0
  41. package/src/detect-changes.ts +367 -0
  42. package/src/engine/emit.ts +556 -0
  43. package/src/engine/engine.test.ts +1417 -0
  44. package/src/engine/engine.ts +182 -0
  45. package/src/engine/extractors.ts +486 -0
  46. package/src/engine/fixtures.ts +364 -0
  47. package/src/engine/language-sniff.ts +56 -0
  48. package/src/engine/parser-backend.ts +206 -0
  49. package/src/engine/utf16-offsets.ts +68 -0
  50. package/src/git-invoker.test.ts +116 -0
  51. package/src/git-invoker.ts +426 -0
  52. package/src/graph-schema.test.ts +541 -0
  53. package/src/graph-schema.ts +383 -0
  54. package/src/graph-store-pr2.test.ts +1879 -0
  55. package/src/graph-store.test.ts +1420 -0
  56. package/src/graph-store.ts +3489 -0
  57. package/src/index-status.test.ts +303 -0
  58. package/src/index-status.ts +135 -0
  59. package/src/index.ts +384 -0
  60. package/src/lsp/byte-position.ts +173 -0
  61. package/src/lsp/characterization.test.ts +174 -0
  62. package/src/lsp/client.test.ts +275 -0
  63. package/src/lsp/client.ts +484 -0
  64. package/src/lsp/config.ts +219 -0
  65. package/src/lsp/degradation.ts +86 -0
  66. package/src/lsp/fixtures/fake-server.mjs +198 -0
  67. package/src/lsp/framing.test.ts +180 -0
  68. package/src/lsp/framing.ts +177 -0
  69. package/src/lsp/resolution.test.ts +497 -0
  70. package/src/lsp/resolution.ts +483 -0
  71. package/src/lsp/status.ts +140 -0
  72. package/src/lsp/types.ts +167 -0
  73. package/src/reindex.test.ts +1038 -0
  74. package/src/reindex.ts +908 -0
  75. package/src/row-types.ts +45 -0
  76. package/src/semantic/canonical-text.test.ts +150 -0
  77. package/src/semantic/canonical-text.ts +219 -0
  78. package/src/semantic/config.ts +235 -0
  79. package/src/semantic/index.ts +78 -0
  80. package/src/semantic/minhash.test.ts +197 -0
  81. package/src/semantic/minhash.ts +261 -0
  82. package/src/semantic/semantic-query.ts +173 -0
  83. package/src/semantic/semantic.test.ts +1315 -0
  84. package/src/semantic/similarity.ts +268 -0
  85. package/src/semantic/types.ts +145 -0
  86. package/src/semantic/vectors.ts +235 -0
@@ -0,0 +1,1953 @@
1
+ import { CreateCodingGraphEngineOptions, CodingGraphEngine, CodingGraphLanguage, ParseFileInput, ParseResult, FileIR, SymbolIR, CodingGraphErrorCode } from '@remnic/core';
2
+ export { CODING_GRAPH_ENGINE_VERSION, CodingGraphEngine, CodingGraphErrorCode, CodingGraphLanguage, CreateCodingGraphEngineOptions, FileIR, ParseFileInput, ParseResult, SymbolIR, TIER_1_LANGUAGES } from '@remnic/core';
3
+ import { Tree, Language } from 'web-tree-sitter';
4
+ import { EdgeProvenance } from './graph-schema.js';
5
+ export { CODING_GRAPH_SCHEMA_VERSION, EDGE_PROVENANCE_VALUES, applyCodingGraphSchema, isEdgeProvenance, readSchemaVersion } from './graph-schema.js';
6
+ import { GraphStore, ReadFileHashesResult, ReadMetaResult, EdgeIR } from './graph-store.js';
7
+ 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';
8
+ export { CypherAst, CypherFailure, CypherFailureCode, CypherNodeValue, CypherParseResult, CypherResult, CypherRow, CypherScalar, CypherSuccess, CypherValue, VALID_CYPHER_LABELS, executeAst, executeCypher, parseCypher } from './cypher/query-parser.js';
9
+ import { spawn } from 'node:child_process';
10
+ import { HostEmbeddingProvider } from '@remnic/core/host-embedding-provider';
11
+ export { ExportIR, ImportIR } from '@remnic/core/coding/coding-graph-types';
12
+ import '@remnic/core/runtime/better-sqlite';
13
+
14
+ /**
15
+ * Engine factory — constructs a `CodingGraphEngine` backed by the WASM
16
+ * tree-sitter parser + tier-1 extractors.
17
+ *
18
+ * Rule 11 (no module-level state): the backend, query caches, and parser
19
+ * instances all live on the returned engine object. Two `createCodingGraphEngine`
20
+ * calls in the same process have fully isolated state.
21
+ *
22
+ * Rule 30/48: `codingGraph.enabled` defaults `false` in @remnic/core; the
23
+ * loader is never invoked when disabled, so this factory is only reached when
24
+ * the user has explicitly opted in.
25
+ */
26
+
27
+ /**
28
+ * Construct a coding-graph engine. PR2 implementation: real WASM tree-sitter
29
+ * parser with tier-1 extractors. No longer throws `not_implemented`.
30
+ *
31
+ * @throws never — load failures surface as `{ ok: false, code: "parse_failed" }`
32
+ * per-file, not as constructor exceptions.
33
+ */
34
+ declare function createCodingGraphEngine(_options?: CreateCodingGraphEngineOptions): CodingGraphEngine;
35
+
36
+ /**
37
+ * ParserBackend — the abstraction layer between the tree-sitter parsing
38
+ * engine and the per-language extractors.
39
+ *
40
+ * Design rationale (issue #1551): everything goes through this interface so
41
+ * that a native `node-tree-sitter` backend (or the external C binary via the
42
+ * subprocess provider) can slot in later without touching extractors. The WASM
43
+ * backend (`WasmTreeSitterBackend`) is the default; it costs ~2–3× parse speed
44
+ * vs native but has no per-platform build toolchain — the class of pain
45
+ * documented in #1518 and #1538.
46
+ *
47
+ * Rule 11 (no module-level caches): every parser instance, grammar cache, and
48
+ * initialization flag lives on the *engine instance*, never at module scope.
49
+ * This means two engines in the same process have fully isolated state.
50
+ */
51
+
52
+ /**
53
+ * The opaque interface a backend exposes to extractors. Each method is keyed
54
+ * per-instance so the engine lifecycle (`dispose()`) can clean everything up.
55
+ */
56
+ interface ParserBackend {
57
+ /** Initialize the backend runtime (e.g. load the WASM core). Idempotent per instance. */
58
+ init(): Promise<void>;
59
+ /** Ensure the grammar for `lang` is loaded and a parser is ready. Idempotent per instance. */
60
+ ensureLanguage(lang: CodingGraphLanguage): Promise<void>;
61
+ /**
62
+ * Parse `content` (a UTF-8 string) using the grammar for `lang`. Returns the
63
+ * tree-sitter Tree on success, or `null` if the grammar is not loaded / the
64
+ * parser cannot produce a tree. Node offsets are UTF-16 code-unit offsets;
65
+ * callers must convert to UTF-8 byte offsets for multibyte content (#1659).
66
+ */
67
+ parse(lang: CodingGraphLanguage, content: string): Tree | null;
68
+ /** Return the loaded Language for `lang`, or null if not loaded. */
69
+ getLanguage(lang: CodingGraphLanguage): Language | null;
70
+ /** Release all parsers, grammars, and the WASM runtime. Safe to call multiple times. */
71
+ dispose(): Promise<void>;
72
+ }
73
+ /**
74
+ * web-tree-sitter 0.25 WASM backend.
75
+ *
76
+ * Per rule 11, all mutable state (init flag, parser instance, grammar cache)
77
+ * lives on the instance — never at module scope. The backend is constructed by
78
+ * `createCodingGraphEngine` and disposed alongside the engine.
79
+ */
80
+ declare class WasmTreeSitterBackend implements ParserBackend {
81
+ private initialized;
82
+ private initializing;
83
+ private parser;
84
+ private readonly languages;
85
+ private readonly loadingLanguages;
86
+ private grammarDir;
87
+ private grammarDirResolved;
88
+ private readonly grammarDirHint;
89
+ constructor(grammarDir?: string);
90
+ private getGrammarDir;
91
+ init(): Promise<void>;
92
+ ensureLanguage(lang: CodingGraphLanguage): Promise<void>;
93
+ parse(lang: CodingGraphLanguage, content: string): Tree | null;
94
+ /** Return the loaded Language object for `lang`, or null if not loaded. */
95
+ getLanguage(lang: CodingGraphLanguage): Language | null;
96
+ dispose(): Promise<void>;
97
+ }
98
+
99
+ declare function hashContent(content: Uint8Array): string;
100
+
101
+ /** A line from `git diff --name-status <range>`. */
102
+ interface NameStatusEntry {
103
+ /**
104
+ * `git diff --name-status` status code: `A` (added), `M` (modified),
105
+ * `D` (deleted), `R100` (renamed, 100% similarity), `C75` (copied, 75%),
106
+ * etc. Renames carry the NEW path in `path` and the OLD path in
107
+ * `oldPath`.
108
+ */
109
+ readonly status: string;
110
+ /** Repo-relative forward-slash path of the file in the NEW tree. */
111
+ readonly path: string;
112
+ /** For renames/copies (`R*`/`C*`), the source path. Otherwise `undefined`. */
113
+ readonly oldPath?: string;
114
+ }
115
+ /** A hunk header from `git diff --unified=0` (`@@ -a,b +c,d @@`). */
116
+ interface DiffHunk {
117
+ /** File path (repo-relative, forward slashes). */
118
+ readonly path: string;
119
+ /**
120
+ * Half-open `[startLine, endLine)` line range in the NEW (post-change)
121
+ * version of the file. `startLine` is 1-based; `endLine` is exclusive.
122
+ * A single-line change has `endLine === startLine + 1`.
123
+ */
124
+ readonly newRange: {
125
+ readonly startLine: number;
126
+ readonly endLine: number;
127
+ };
128
+ }
129
+ /** A co-change commit entry from `git log --name-only`. */
130
+ interface LogFilesEntry {
131
+ /** Full commit SHA (40 hex chars). */
132
+ readonly sha: string;
133
+ /** Files changed in this commit (repo-relative, forward slashes, deduped). */
134
+ readonly files: readonly string[];
135
+ }
136
+ /**
137
+ * Tagged failure — `code` is the load-bearing signal for programmatic
138
+ * detection (rule 34). Never carries `error.message` (which may contain
139
+ * absolute paths — rule 11).
140
+ */
141
+ interface GitFailure {
142
+ readonly ok: false;
143
+ readonly code: "git_unavailable" | "unreachable_head" | "git_error";
144
+ }
145
+ /**
146
+ * Injectable git-invocation surface. Each method runs a specific git
147
+ * subcommand with argv arrays (never string interpolation — rule 10) and
148
+ * returns parsed output or a tagged failure. Tests inject a mock to avoid
149
+ * spawning a real git process against synthetic fixture repos.
150
+ *
151
+ * The default implementation uses `launchProcessSync` with a 2-second
152
+ * timeout (matching `git-context.ts`'s `DEFAULT_GIT_TIMEOUT_MS`).
153
+ */
154
+ interface CodingGitInvoker {
155
+ /**
156
+ * `git rev-parse HEAD` — the current HEAD SHA. Returns `null` when HEAD
157
+ * does not exist (empty repo / no commits yet).
158
+ */
159
+ revParseHead(cwd: string): {
160
+ ok: true;
161
+ head: string | null;
162
+ } | GitFailure;
163
+ /**
164
+ * `git rev-parse --verify <ref>^{commit}` — check whether a prior head
165
+ * is still reachable. Used to detect rebase/force-push scenarios
166
+ * (issue #1553 pitfall 34: `unreachable_head`).
167
+ */
168
+ isReachable(cwd: string, ref: string): {
169
+ ok: true;
170
+ reachable: boolean;
171
+ } | GitFailure;
172
+ /**
173
+ * `git diff --name-status <range>` — the changed files between two
174
+ * commits (or between a commit and HEAD). Returns one entry per file.
175
+ */
176
+ diffNameStatus(cwd: string, range: string): {
177
+ ok: true;
178
+ entries: readonly NameStatusEntry[];
179
+ } | GitFailure;
180
+ /**
181
+ * Working-tree diff hunks with zero context lines. Captures BOTH staged
182
+ * and unstaged changes (compared against HEAD) so `detect_changes` maps
183
+ * every uncommitted edit to a symbol span. Each hunk carries its
184
+ * new-version line range.
185
+ */
186
+ diffHunks(cwd: string, paths: readonly string[]): {
187
+ ok: true;
188
+ hunks: readonly DiffHunk[];
189
+ } | GitFailure;
190
+ /**
191
+ * `git log --name-only --format=%H -n <limit>` — commit SHAs + their
192
+ * changed files over a bounded window. Used by co-change mining.
193
+ */
194
+ logFiles(cwd: string, limit: number): {
195
+ ok: true;
196
+ entries: readonly LogFilesEntry[];
197
+ } | GitFailure;
198
+ /**
199
+ * `git ls-files` — the repo's tracked files as repo-relative forward-slash
200
+ * paths. Used by the codegraph runtime to source `candidatePaths` for a
201
+ * full reindex (issue #1554: the executor treats an omitted candidate list
202
+ * as non-authoritative and no-ops, so the runtime must supply one).
203
+ * Returns an empty path list for a repo with no tracked files; a git
204
+ * failure degrades to `{ ok: false, code: "git_unavailable" | "git_error" }`.
205
+ */
206
+ listTrackedFiles(cwd: string): {
207
+ ok: true;
208
+ paths: readonly string[];
209
+ } | GitFailure;
210
+ }
211
+ /**
212
+ * Construct the default git invoker that spawns real `git` via
213
+ * `launchProcessSync`. Tests inject a mock instead.
214
+ */
215
+ declare function defaultCodingGitInvoker(): CodingGitInvoker;
216
+ /**
217
+ * Parse `git diff --name-status` output. Each line is `<status>\t<path>`
218
+ * for add/modify/delete, or `<status>\t<old>\t<new>` for rename/copy.
219
+ *
220
+ * Empty lines are skipped (trailing newline). Lines that do not parse
221
+ * are skipped rather than crashing the whole diff (rule 44 — a single
222
+ * bad line should not make the index go stale silently; but a partial
223
+ * parse is better than a total failure because the executor will fall
224
+ * back to hash-scan if the file count looks wrong).
225
+ */
226
+ declare function parseNameStatus(stdout: string): NameStatusEntry[];
227
+ /**
228
+ * Parse `git diff --unified=0` output into per-file hunks. Each hunk
229
+ * header is `@@ -a,b +c,d @@ <optional section>`. With `--unified=0`
230
+ * the OLD range `(a,b)` is not useful for blast-radius (we want the
231
+ * NEW range). We extract `+c,d` → `{ startLine: c, endLine: c + d }`.
232
+ *
233
+ * The file path comes from `diff --git a/path b/path` or
234
+ * `+++ b/path` lines. We track the current file as we walk.
235
+ */
236
+ declare function parseHunks(stdout: string): DiffHunk[];
237
+ /**
238
+ * Parse `git log --format=%H --name-only` output into one entry per
239
+ * commit. Real git emits a blank line AFTER each SHA (before the file
240
+ * names) AND between commits, so a naive `\n\n` block split puts the
241
+ * SHA alone in one block and the file names in the next — the SHA-only
242
+ * block yields a commit with zero files and the file-name block fails
243
+ * SHA validation and is dropped. Walk line-by-line instead: a 40-hex
244
+ * line starts a new commit; subsequent non-empty lines are its files
245
+ * until the next SHA (chatgpt-codex-connector: 'Parse real git log
246
+ * records before mining co-changes'). Robust to both the synthetic
247
+ * (no blank-after-SHA) and real (blank-after-SHA) layouts.
248
+ */
249
+ declare function parseLogFiles(stdout: string): LogFilesEntry[];
250
+
251
+ /**
252
+ * The persisted index state the planner reasons about.
253
+ * `lastHead: null` means "never indexed" → full reindex.
254
+ */
255
+ interface ReindexState {
256
+ /** The HEAD SHA at the time of the last successful reindex, or `null`. */
257
+ readonly lastHead: string | null;
258
+ /**
259
+ * Per-file content hashes as of the last index. Used by hash_scan mode
260
+ * to detect content drift without a reachable base commit.
261
+ * Keyed by repo-relative forward-slash path.
262
+ */
263
+ readonly fileHashes: ReadonlyMap<string, string>;
264
+ }
265
+ /** Git facts the planner needs — gathered BEFORE the plan is computed. */
266
+ interface ReindexGitFacts {
267
+ /** Current `git rev-parse HEAD`. `null` when the repo has no commits. */
268
+ readonly currentHead: string | null;
269
+ /**
270
+ * Whether `lastHead` (when non-null) is still reachable in the repo.
271
+ * `false` after a rebase/force-push that rewrote history past the old
272
+ * head. `true` when `lastHead` is null (no prior state to reach).
273
+ */
274
+ readonly lastHeadReachable: boolean;
275
+ /**
276
+ * Files changed between `lastHead` and `currentHead` (when both are
277
+ * non-null and reachable). One entry per file from
278
+ * `git diff --name-status`. Empty array for a noop or fresh repo.
279
+ */
280
+ readonly changedFiles: readonly NameStatusEntry[];
281
+ }
282
+ /** What the planner decided to do. */
283
+ type ReindexPlan = {
284
+ readonly mode: "full";
285
+ readonly reason: string;
286
+ } | {
287
+ readonly mode: "noop";
288
+ readonly reason: string;
289
+ } | {
290
+ readonly mode: "incremental";
291
+ readonly changedPaths: readonly string[];
292
+ } | {
293
+ readonly mode: "hash_scan";
294
+ readonly reason: string;
295
+ /** Paths whose on-disk hash differs from the stored hash. */
296
+ readonly mismatchedPaths: readonly string[];
297
+ };
298
+ /** Result of executing a reindex plan. */
299
+ type ReindexResult = {
300
+ readonly ok: true;
301
+ readonly mode: "full" | "noop" | "incremental" | "hash_scan";
302
+ /** Number of files actually parsed + ingested. */
303
+ readonly filesIngested: number;
304
+ /** The new `last_indexed_head` persisted to meta (null on noop). */
305
+ readonly head: string | null;
306
+ } | ({
307
+ readonly ok: false;
308
+ } & GitFailure) | {
309
+ readonly ok: false;
310
+ readonly code: "parse_failed";
311
+ readonly path: string;
312
+ readonly message: string;
313
+ } | {
314
+ readonly ok: false;
315
+ readonly code: "store_error";
316
+ readonly message: string;
317
+ };
318
+ declare const META_KEY_LAST_HEAD: "last_indexed_head";
319
+ /**
320
+ * Meta key holding a JSON array of repo-relative paths that failed to
321
+ * parse on the last run (rule 44: files that fail to parse do not update
322
+ * their stored content hash and MUST retry on the next run). Because a
323
+ * HEAD-unchanged run would otherwise plan `noop` and skip them, the
324
+ * executor consults this set at the top of every run and re-ingests any
325
+ * pending paths even when the plan is noop (cursor Bugbot: 'Parse skips
326
+ * block future reindex').
327
+ */
328
+ declare const META_KEY_PENDING_PARSE_FAILURES: "pending_parse_failures";
329
+ /**
330
+ * Decide what to do given the last state and current git facts.
331
+ *
332
+ * Decision tree:
333
+ * 1. `currentHead === null` → noop (nothing to index; empty repo).
334
+ * 2. `lastHead === null` → full (first index).
335
+ * 3. `lastHead === currentHead` → noop (HEAD unchanged).
336
+ * 4. `!lastHeadReachable` → hash_scan (rebase/force-push lost the base).
337
+ * 5. Otherwise → incremental (re-parse changedFiles paths).
338
+ *
339
+ * Deleted files (status `D`) are included in `changedPaths` so the executor
340
+ * can prune them from the store. Renames include both old and new paths.
341
+ */
342
+ declare function planReindex(lastState: ReindexState, facts: ReindexGitFacts): ReindexPlan;
343
+ /**
344
+ * A parse function — the engine seam. The executor calls this for each
345
+ * file the plan says to (re)ingest. In production the orchestrator injects
346
+ * the real `CodingGraphEngine.parseFile`; in tests a synthetic parser is
347
+ * injected. This keeps the package decoupled from the engine implementation
348
+ * (which is still a placeholder in #1551 PR1).
349
+ */
350
+ type ParseFileFn = (input: ParseFileInput) => Promise<ParseResult>;
351
+ /** Injectable file reader — defaults to `node:fs/promises`.readFile. */
352
+ type ReadFileFn = (absPath: string) => Promise<Uint8Array>;
353
+ /**
354
+ * Read the persisted `last_indexed_head` from the store's meta table.
355
+ * Returns `null` when the key is absent (fresh DB).
356
+ */
357
+ declare function readLastIndexedHead(store: GraphStore): ReadMetaResult;
358
+ /**
359
+ * Read every file row's path → content_hash from the store. Used by
360
+ * hash_scan to detect content drift without a reachable base commit.
361
+ */
362
+ declare function readFileHashes(store: GraphStore): ReadFileHashesResult;
363
+
364
+ /**
365
+ * Execute a reindex against a store + git repo.
366
+ *
367
+ * Steps:
368
+ * 1. Gather git facts (currentHead, reachable, changedFiles).
369
+ * 2. Plan via {@link planReindex}.
370
+ * 3. For full/incremental/hash_scan: parse each file, build a batch,
371
+ * call `store.upsertFileBatch`.
372
+ * 4. Persist `last_indexed_head` ONLY after the batch commits (rule 25).
373
+ *
374
+ * The reindex is serialized per-store via the store's own write queue.
375
+ * A session-start trigger racing a manual CLI trigger coalesces (rule 40).
376
+ *
377
+ * `candidatePaths` is needed for full and hash_scan modes (the set of
378
+ * files to consider). Typically from `git ls-files` or a glob. When
379
+ * omitted, full/hash_scan operate over the union of stored files and
380
+ * git-tracked files.
381
+ */
382
+ declare function executeReindex(options: {
383
+ readonly store: GraphStore;
384
+ readonly git: CodingGitInvoker;
385
+ readonly repoRoot: string;
386
+ readonly parseFile: ParseFileFn;
387
+ readonly candidatePaths?: readonly string[];
388
+ readonly readFile?: ReadFileFn;
389
+ }): Promise<ReindexResult>;
390
+
391
+ /**
392
+ * detect_changes + blast radius for the coding-graph (issue #1553).
393
+ *
394
+ * Maps the current diff (staged + unstaged + committed-since-last-index)
395
+ * hunks to symbols whose spans overlap the changed line ranges — against
396
+ * a FRESH parse of the changed files, never against stale stored spans.
397
+ *
398
+ * Blast radius: reverse BFS from affected symbols over inbound
399
+ * CALLS / IMPORTS / USES_TYPE edges with a depth cap. Risk classification
400
+ * is a deterministic rubric (no LLM in the loop):
401
+ *
402
+ * ┌──────────────┬──────────────────────────────────────────────────┐
403
+ * │ Risk │ Criterion │
404
+ * ├──────────────┼──────────────────────────────────────────────────┤
405
+ * │ "direct" │ The symbol itself is in a changed hunk (depth 0).│
406
+ * │ "near" │ 1 hop away from a directly-changed symbol. │
407
+ * │ "transitive" │ 2+ hops away. │
408
+ * └──────────────┴──────────────────────────────────────────────────┘
409
+ *
410
+ * Fan-in escalation: when an affected symbol has ≥ FAN_IN_ESCALATION
411
+ * inbound edges, its risk is escalated one level (near→direct,
412
+ * transitive→near) because high-fan-in symbols concentrate blast radius.
413
+ *
414
+ * Half-open interval semantics (rule 35): a hunk range `[startLine, endLine)`
415
+ * overlaps a symbol span `[symStartLine, symEndLine)` iff
416
+ * `startLine < symEndLine && symStartLine < endLine`.
417
+ * Boundary lines (exact hit on startLine or endLine-1) are tested.
418
+ */
419
+
420
+ /** Risk classification rubric — deterministic, no LLM. */
421
+ type RiskLevel = "direct" | "near" | "transitive";
422
+ /** A symbol affected by the current diff, with its blast-radius classification. */
423
+ interface AffectedSymbol {
424
+ /** Qualified name of the symbol. */
425
+ readonly qualifiedName: string;
426
+ /** Simple name of the symbol. */
427
+ readonly name: string;
428
+ /** Symbol kind (function, class, method, ...). */
429
+ readonly label: string;
430
+ /** Repo-relative file path. */
431
+ readonly filePath: string;
432
+ /** Risk level from the deterministic rubric. */
433
+ readonly risk: RiskLevel;
434
+ /** BFS depth from the nearest directly-changed symbol (0 = direct). */
435
+ readonly depth: number;
436
+ /** Total inbound edge count (fan-in) at the time of classification. */
437
+ readonly fanIn: number;
438
+ }
439
+ /** Result of detect_changes. */
440
+ type DetectChangesResult = {
441
+ readonly ok: true;
442
+ readonly affected: readonly AffectedSymbol[];
443
+ } | {
444
+ readonly ok: false;
445
+ readonly code: "git_error" | "store_error";
446
+ };
447
+ /**
448
+ * Result of computeBlastRadius. A backend/store failure during traversal is
449
+ * surfaced as `{ ok: false; code: "store_error" }` rather than masked as an
450
+ * empty result — the blast-radius computation is unreliable when the store
451
+ * cannot be read (rule 22; cursor Bugbot: 'computeBlastRadius masks traverse
452
+ * failures'). `{ ok: true; affected: [] }` is a genuinely empty blast radius.
453
+ */
454
+ type BlastRadiusResult = {
455
+ ok: true;
456
+ affected: readonly AffectedSymbol[];
457
+ } | {
458
+ ok: false;
459
+ code: "store_error";
460
+ };
461
+ /**
462
+ * Edge types traversed for blast-radius computation. These represent
463
+ * "depends on" relationships — an inbound edge of any of these types
464
+ * means the source symbol is affected when the destination changes.
465
+ */
466
+ declare const BLAST_RADIUS_EDGE_TYPES: readonly ["CALLS", "IMPORTS", "USES_TYPE"];
467
+ /**
468
+ * Fan-in threshold for risk escalation. When an affected symbol has this
469
+ * many or more inbound edges, its risk is escalated one level because
470
+ * high-fan-in symbols concentrate blast radius.
471
+ */
472
+ declare const FAN_IN_ESCALATION_THRESHOLD = 5;
473
+ /** Maximum BFS depth for blast-radius traversal. */
474
+ declare const DEFAULT_BLAST_RADIUS_DEPTH = 3;
475
+ /**
476
+ * Byte offset → 1-based line number. Walks the content counting newlines
477
+ * up to (but not including) the byte offset. Used to convert symbol spans
478
+ * (byte offsets) to line ranges for hunk-overlap comparison.
479
+ *
480
+ * Returns `{ startLine, endLine }` where both are 1-based and `endLine`
481
+ * is exclusive (half-open — rule 35). A symbol that starts at byte 0 on
482
+ * line 1 and ends at byte 10 (still line 1) has `{ 1, 2 }`.
483
+ */
484
+ declare function byteSpanToLines(content: Uint8Array, startByte: number, endByte: number): {
485
+ startLine: number;
486
+ endLine: number;
487
+ };
488
+ /**
489
+ * Half-open overlap test (rule 35): two ranges `[aStart, aEnd)` and
490
+ * `[bStart, bEnd)` overlap iff `aStart < bEnd && bStart < aEnd`.
491
+ *
492
+ * Boundary case: a hunk starting exactly at `symEndLine` does NOT overlap
493
+ * (the symbol ends before the hunk starts). A hunk ending exactly at
494
+ * `symStartLine` does NOT overlap (the hunk ends before the symbol starts).
495
+ */
496
+ declare function rangesOverlap(a: {
497
+ startLine: number;
498
+ endLine: number;
499
+ }, b: {
500
+ startLine: number;
501
+ endLine: number;
502
+ }): boolean;
503
+ /**
504
+ * Classify risk from BFS depth + fan-in. Deterministic rubric:
505
+ * - depth 0 → "direct"
506
+ * - depth 1 → "near"
507
+ * - depth 2+ → "transitive"
508
+ * - fanIn ≥ threshold escalates one level (capped at "direct")
509
+ */
510
+ declare function classifyRisk(depth: number, fanIn: number): RiskLevel;
511
+ /**
512
+ * Find symbols whose line ranges overlap any hunk in the given file.
513
+ * Uses a FRESH parse (never stale stored spans). Returns the set of
514
+ * directly-affected qualified names.
515
+ */
516
+ declare function findDirectlyAffectedSymbols(hunksByPath: ReadonlyMap<string, readonly DiffHunk[]>, freshIRs: ReadonlyMap<string, FileIR>, contentsByPath: ReadonlyMap<string, Uint8Array>): Set<string>;
517
+ /**
518
+ * Compute blast radius from a set of directly-affected symbols using the
519
+ * store's `traverse` primitive (direction: incoming). Reuses the existing
520
+ * BFS — does NOT write a second traversal (rule 22 spirit).
521
+ *
522
+ * Returns affected symbols with their risk classification. Byte-stable:
523
+ * the same diff + graph always produces the same output.
524
+ */
525
+ declare function computeBlastRadius(store: GraphStore, directlyAffected: ReadonlySet<string>, maxDepth?: number): BlastRadiusResult;
526
+
527
+ /**
528
+ * FILE_CHANGES_WITH co-change edge mining (issue #1553).
529
+ *
530
+ * Mines co-change relationships from `git log --name-only` over a bounded
531
+ * window (default 500 commits). An edge between two files is written when:
532
+ * - support (co-change count) ≥ `minSupport`
533
+ * - confidence (co-change / total changes of the less-changed file) ≥
534
+ * `minConfidence`
535
+ *
536
+ * Deterministic given the same history — re-running on unchanged history
537
+ * is idempotent (same edges, same confidence).
538
+ *
539
+ * Co-change edges are stored in a dedicated `co_changes` table (file-level,
540
+ * not symbol-level — the existing `edges` table is between symbol nodes).
541
+ * The table is additive to schema v1 (CREATE TABLE IF NOT EXISTS — same
542
+ * pattern as `node_attributes` in PR2).
543
+ */
544
+
545
+ /** A mined co-change edge between two files. */
546
+ interface CoChangeEdge {
547
+ readonly fileA: string;
548
+ readonly fileB: string;
549
+ /** Number of commits where both files changed together. */
550
+ readonly support: number;
551
+ /** Confidence: support / min(totalChangesA, totalChangesB). */
552
+ readonly confidence: number;
553
+ }
554
+ /** Result of co-change mining. */
555
+ type MineCoChangesResult = {
556
+ readonly ok: true;
557
+ readonly edges: readonly CoChangeEdge[];
558
+ } | GitFailure | {
559
+ readonly ok: false;
560
+ readonly code: "store_closed" | "db_error";
561
+ };
562
+ /** Configuration for co-change mining. */
563
+ interface CoChangeConfig {
564
+ /** Maximum commits to scan (default 500). */
565
+ readonly maxCommits: number;
566
+ /** Minimum co-change count to write an edge (default 3). */
567
+ readonly minSupport: number;
568
+ /** Minimum confidence to write an edge (default 0.3). */
569
+ readonly minConfidence: number;
570
+ }
571
+ declare const DEFAULT_CO_CHANGE_CONFIG: CoChangeConfig;
572
+ /**
573
+ * Compute co-change edges from a list of commit→files entries.
574
+ *
575
+ * Algorithm:
576
+ * 1. Count total changes per file (how many commits touched it).
577
+ * 2. For each unordered file pair, count how many commits changed both.
578
+ * 3. Confidence = coChangeCount / min(totalA, totalB).
579
+ * 4. Keep edges where support ≥ minSupport AND confidence ≥ minConfidence.
580
+ *
581
+ * Deterministic: the same input always produces the same output, sorted
582
+ * by (fileA, fileB) for byte-stability.
583
+ *
584
+ * Half-open time windows (rule 35): each commit is a discrete point; a
585
+ * pair is "co-changed" in a commit iff both files are in that commit's
586
+ * file list. No off-by-one — a file that appears once in a commit counts
587
+ * once (deduped by the git-invoker's parser).
588
+ */
589
+ declare function mineCoChangeEdges(entries: readonly LogFilesEntry[], config?: CoChangeConfig): CoChangeEdge[];
590
+ /**
591
+ * Mine co-change edges from git history and persist them to the store's
592
+ * `co_changes` table. Idempotent: re-running on unchanged history produces
593
+ * the same edges (the table is cleared + repopulated each run, so stale
594
+ * edges from history changes are pruned automatically).
595
+ */
596
+ declare function mineAndStoreCoChanges(options: {
597
+ readonly store: GraphStore;
598
+ readonly git: CodingGitInvoker;
599
+ readonly repoRoot: string;
600
+ readonly config?: CoChangeConfig;
601
+ }): Promise<MineCoChangesResult>;
602
+
603
+ /**
604
+ * Index staleness reporting (issue #1553 done-when: "a deliberately stale
605
+ * index with `autoIndex: "manual"` reports its staleness via `index_status`
606
+ * rather than pretending freshness").
607
+ *
608
+ * Reports:
609
+ * - `lastIndexedHead` — the HEAD at the last successful reindex.
610
+ * - `currentHead` — the current repo HEAD (null when git unavailable).
611
+ * - `dirty` — true when `lastIndexedHead !== currentHead` (the index
612
+ * is behind the repo and needs a reindex).
613
+ * - `mode` — "fresh" | "stale" | "empty" | "git_unavailable".
614
+ *
615
+ * Never throws — a git failure degrades to `mode: "git_unavailable"`
616
+ * so callers (remnic doctor / xray) can surface it without crashing.
617
+ */
618
+
619
+ type IndexStatusMode = "fresh" | "stale" | "empty" | "git_unavailable";
620
+ interface IndexStatus {
621
+ readonly lastIndexedHead: string | null;
622
+ readonly currentHead: string | null;
623
+ readonly dirty: boolean;
624
+ readonly mode: IndexStatusMode;
625
+ readonly fileCount: number;
626
+ readonly nodeCount: number;
627
+ }
628
+ /**
629
+ * Report the current index status. Pure over the store + git facts — no
630
+ * side effects, never throws.
631
+ */
632
+ declare function getIndexStatus(store: GraphStore, git: CodingGitInvoker, repoRoot: string): IndexStatus;
633
+
634
+ /**
635
+ * Minimal LSP protocol types — only the subset the resolution pass needs.
636
+ *
637
+ * These are NOT a full LSP type system. They mirror the wire shapes from
638
+ * the LSP specification (v3.17 — https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/)
639
+ * for the methods we implement: initialize, initialized, shutdown, exit,
640
+ * textDocument/didOpen, textDocument/definition.
641
+ *
642
+ * Keeping a local subset avoids pulling in `vscode-languageserver-protocol`
643
+ * (a dependency the issue explicitly rejects — "No npm LSP framework").
644
+ */
645
+ /**
646
+ * LSP Position — zero-based line and character offsets (LSP 3.17 §3.17).
647
+ * `character` is a UTF-16 code-unit offset, matching what tree-sitter
648
+ * byte offsets are converted FROM during the location→node mapping.
649
+ */
650
+ interface LspPosition {
651
+ readonly line: number;
652
+ readonly character: number;
653
+ }
654
+ /**
655
+ * LSP Range — half-open `[start, end)` (LSP 3.17 §3.18).
656
+ * Matches the coding-graph's half-open byte-span convention (rule 35).
657
+ */
658
+ interface LspRange {
659
+ readonly start: LspPosition;
660
+ readonly end: LspPosition;
661
+ }
662
+ /**
663
+ * LSP Location — a URI + Range (LSP 3.17 §3.19).
664
+ * The resolution pass maps these back to graph nodes by file + span
665
+ * containment.
666
+ */
667
+ interface LspLocation {
668
+ readonly uri: string;
669
+ readonly range: LspRange;
670
+ }
671
+ /**
672
+ * LSP TextDocumentIdentifier — a URI identifying a document (LSP 3.17 §3.16).
673
+ */
674
+ interface LspTextDocumentIdentifier {
675
+ readonly uri: string;
676
+ }
677
+ /**
678
+ * LSP TextDocumentItem — the full open-document payload for didOpen
679
+ * (LSP 3.17 §3.17). Carries the file content so the server can analyze
680
+ * without reading from disk.
681
+ */
682
+ interface LspTextDocumentItem {
683
+ readonly uri: string;
684
+ readonly languageId: string;
685
+ /** Schema version — we always send 1. */
686
+ readonly version: number;
687
+ readonly text: string;
688
+ }
689
+ /**
690
+ * LSP TextDocumentPositionParams — the request payload for definition
691
+ * (LSP 3.17 §3.18).
692
+ */
693
+ interface LspTextDocumentPositionParams {
694
+ readonly textDocument: LspTextDocumentIdentifier;
695
+ readonly position: LspPosition;
696
+ }
697
+ /**
698
+ * Client capabilities — we send an empty object because the resolution
699
+ * pass uses no client-side features. The server still needs the field
700
+ * present per the spec.
701
+ */
702
+ interface LspClientCapabilities {
703
+ }
704
+ /**
705
+ * Initialize params sent by the client (LSP 3.17 §3.17).
706
+ * `processId` is our PID so a server can track us; `rootUri` is the
707
+ * workspace root for multi-file definition resolution.
708
+ */
709
+ interface LspInitializeParams {
710
+ readonly processId: number | null;
711
+ readonly rootUri: string | null;
712
+ readonly capabilities: LspClientCapabilities;
713
+ }
714
+ /**
715
+ * Server capabilities — the subset we inspect. `definitionProvider`
716
+ * must be truthy for the resolution pass to work.
717
+ */
718
+ interface LspServerCapabilities {
719
+ readonly definitionProvider?: boolean | object;
720
+ readonly referencesProvider?: boolean | object;
721
+ }
722
+ /**
723
+ * Initialize result returned by the server (LSP 3.17 §3.17).
724
+ */
725
+ interface LspInitializeResult {
726
+ readonly capabilities: LspServerCapabilities;
727
+ readonly serverInfo?: {
728
+ readonly name?: string;
729
+ readonly version?: string;
730
+ };
731
+ }
732
+
733
+ /**
734
+ * LSP degradation codes — shaped like {@link SearchDegradation} from
735
+ * @remnic/core/search/port (issue #1536, CLAUDE.md rule 34).
736
+ *
737
+ * Every failure mode produces a DISTINCT code so callers (index_status,
738
+ * remnic doctor) can render per-language LSP state without conflating
739
+ * "server not installed" with "server timed out" or "protocol violation".
740
+ *
741
+ * The codes are the load-bearing signal — `detail` is optional and never
742
+ * carries `error.message` (which may contain absolute paths — rule 11).
743
+ */
744
+ /**
745
+ * Backend identifier — always `"lsp"` so callers can distinguish LSP
746
+ * degradations from QMD/search degradations in a unified handler.
747
+ */
748
+ type LspBackend = "lsp";
749
+ /**
750
+ * Distinct failure codes (rule 34 — never `[]`-on-error).
751
+ *
752
+ * - `server_missing` — the configured server binary is not on PATH
753
+ * or is not executable (probe phase).
754
+ * - `handshake_timeout` — `initialize` did not complete within
755
+ * `lsp.timeoutMs`.
756
+ * - `handshake_error` — server responded to `initialize` with an
757
+ * error or a malformed response.
758
+ * - `request_timeout` — a `textDocument/definition` request did
759
+ * not receive a response within the
760
+ * per-request deadline.
761
+ * - `request_error` — server returned a JSON-RPC error response
762
+ * for a resolution request.
763
+ * - `protocol_error` — malformed JSON-RPC frame (bad header,
764
+ * unparseable JSON, unknown method).
765
+ * - `server_crashed` — the child process exited unexpectedly
766
+ * mid-run.
767
+ * - `budget_exhausted` — `lsp.maxRequestsPerRun` reached; remaining
768
+ * call sites keep their Phase A resolution.
769
+ * - `not_enabled` — `lsp.enabled` is `false`; no probe attempted.
770
+ * - `unknown_language` — the file's language has no registered
771
+ * server spec and none was overridden in
772
+ * `lsp.servers`.
773
+ */
774
+ type LspDegradationCode = "server_missing" | "handshake_timeout" | "handshake_error" | "request_timeout" | "request_error" | "protocol_error" | "server_crashed" | "budget_exhausted" | "not_enabled" | "unknown_language";
775
+ /**
776
+ * Tagged degradation — mirrors the shape of {@link SearchDegradation}.
777
+ * `ok: false` results from the client/registry/resolution surface carry
778
+ * this object so consumers can switch on `code` programmatically.
779
+ */
780
+ interface LspDegradation {
781
+ readonly backend: LspBackend;
782
+ readonly code: LspDegradationCode;
783
+ readonly detail?: string;
784
+ }
785
+ /**
786
+ * Tagged-result helpers — the shared discriminated-union pattern used
787
+ * throughout the coding-graph package (rule 34). Every LSP surface
788
+ * returns `{ ok: true; … }` on success or `{ ok: false; degradation }`
789
+ * on failure — never throws, never returns `[]` to mean "error".
790
+ */
791
+ type LspResult<T> = ({
792
+ readonly ok: true;
793
+ } & T) | {
794
+ readonly ok: false;
795
+ readonly degradation: LspDegradation;
796
+ };
797
+ /**
798
+ * Construct a degradation object. Kept as a factory rather than a class
799
+ * so callers can spread it into a result without `new`.
800
+ */
801
+ declare function lspDegradation(code: LspDegradationCode, detail?: string): LspDegradation;
802
+
803
+ /**
804
+ * How to launch a language server: command + argv. Always an array of
805
+ * strings — never a shell string — so injection via config is impossible
806
+ * (rule 10: argv arrays end-to-end).
807
+ */
808
+ interface LspServerLaunchSpec {
809
+ readonly command: string;
810
+ readonly args: readonly string[];
811
+ }
812
+ /**
813
+ * Per-language server overrides. Keys are language identifiers (matching
814
+ * {@link CodingGraphLanguage}); values are launch specs that REPLACE the
815
+ * default. An unknown language key is an error (rule 51 — list supported
816
+ * languages).
817
+ */
818
+ type LspServerOverrides = Partial<Record<CodingGraphLanguage, LspServerLaunchSpec>>;
819
+ interface LspConfig {
820
+ /** Master switch — default false (rule 30/48). */
821
+ readonly enabled: boolean;
822
+ /** Per-language server overrides (default: empty — use registry defaults). */
823
+ readonly servers: LspServerOverrides;
824
+ /** Handshake + per-request timeout in ms (default 3000). */
825
+ readonly timeoutMs: number;
826
+ /** Max definition requests per index run (default 500). */
827
+ readonly maxRequestsPerRun: number;
828
+ }
829
+ declare const DEFAULT_LSP_TIMEOUT_MS = 3000;
830
+ declare const DEFAULT_LSP_MAX_REQUESTS_PER_RUN = 500;
831
+ declare const DEFAULT_LSP_CONFIG: LspConfig;
832
+ /**
833
+ * Parse and validate user-supplied LSP config. Unknown keys in `servers`
834
+ * are rejected with a degradation listing supported languages (rule 51).
835
+ * Non-executable absolute paths are rejected (rule 24 analog).
836
+ *
837
+ * Returns `{ ok: true, config }` or `{ ok: false, degradation }` — never
838
+ * throws (rule 13).
839
+ */
840
+ type LspConfigParseResult = {
841
+ readonly ok: true;
842
+ readonly config: LspConfig;
843
+ } | {
844
+ readonly ok: false;
845
+ readonly degradation: LspDegradation;
846
+ };
847
+ /**
848
+ * Parse and validate user-supplied LSP config. Unknown keys in `servers`
849
+ * are rejected with a degradation listing supported languages (rule 51).
850
+ * Non-executable absolute paths are rejected (rule 24 analog).
851
+ *
852
+ * Returns `{ ok: true, config }` or `{ ok: false, degradation }` — never
853
+ * throws (rule 13).
854
+ */
855
+ declare function parseLspConfig(raw: unknown, knownLanguages: readonly CodingGraphLanguage[]): LspConfigParseResult;
856
+ /**
857
+ * Read the env-var override for `lsp.enabled`. Returns the raw string
858
+ * value or null. The caller decides how to interpret it (gotcha 9:
859
+ * `REMNIC_` primary, `ENGRAM_` fallback).
860
+ */
861
+ declare function readLspEnabledEnv(): string | null;
862
+
863
+ /**
864
+ * Minimal JSON-RPC-over-stdio LSP client.
865
+ *
866
+ * Implements exactly the protocol subset the resolution pass needs
867
+ * (LSP 3.17): initialize → initialized → didOpen → definition →
868
+ * shutdown → exit. No npm LSP framework — the protocol subset is small
869
+ * and a dependency here would bloat the optional package (issue #1555).
870
+ *
871
+ * Failure discipline (rule 13): every operation degrades to a tagged
872
+ * `LspDegradation` — the client NEVER throws to a caller. Server crashes
873
+ * mid-run, protocol errors, and timeouts all surface as distinct codes.
874
+ *
875
+ * Lifecycle: the client owns the child process. `dispose()` sends
876
+ * shutdown + exit, then hard-kills (SIGKILL) any lingering process —
877
+ * no zombie children survive (tested).
878
+ */
879
+
880
+ interface LspClientOptions {
881
+ readonly launchSpec: LspServerLaunchSpec;
882
+ readonly rootUri: string | null;
883
+ readonly timeoutMs: number;
884
+ /**
885
+ * Optional spawn override — test seam. When provided, the client calls
886
+ * this instead of `child_process.spawn`. Must return a ChildProcess-
887
+ * compatible object with stdin/stdout streams.
888
+ */
889
+ readonly spawnFn?: typeof spawn;
890
+ }
891
+ declare class LspClient {
892
+ private readonly child;
893
+ private readonly decoder;
894
+ private readonly rootUri;
895
+ private readonly timeoutMs;
896
+ private nextId;
897
+ private readonly pending;
898
+ private disposed;
899
+ private crashed;
900
+ private crashCode;
901
+ private serverCapabilities;
902
+ private constructor();
903
+ /**
904
+ * Spawn the server and perform the initialize handshake. Returns a
905
+ * tagged result — `{ ok: true, client }` on success, or a degradation
906
+ * on failure (server_missing, handshake_timeout, handshake_error).
907
+ */
908
+ static connect(options: LspClientOptions): Promise<{
909
+ ok: true;
910
+ client: LspClient;
911
+ } | {
912
+ ok: false;
913
+ degradation: LspDegradation;
914
+ }>;
915
+ /**
916
+ * Send `textDocument/didOpen` — notifies the server about an open
917
+ * document with its full content. No response expected.
918
+ */
919
+ didOpen(item: LspTextDocumentItem): void;
920
+ /**
921
+ * Send `textDocument/definition` for a position in a document. Returns
922
+ * the definition locations (may be empty, a single location, or an
923
+ * array). Degrades on timeout/error/crash — never throws.
924
+ */
925
+ definition(params: LspTextDocumentPositionParams): Promise<LspResult<{
926
+ locations: LspLocation[];
927
+ }>>;
928
+ /**
929
+ * Send `shutdown`, then `exit`, then SIGKILL if the process lingers.
930
+ * Idempotent — safe to call multiple times. After dispose, no child
931
+ * process remains (tested — zombie cleanup).
932
+ */
933
+ dispose(): Promise<void>;
934
+ /** Returns the pid of the child process (for zombie-cleanup tests). */
935
+ get pid(): number | undefined;
936
+ /** True if the server reported definitionProvider capability. */
937
+ get supportsDefinition(): boolean;
938
+ /**
939
+ * Send a request and await its response. Returns the `result` field
940
+ * on success, or a degradation on timeout/error/crash/protocol-error.
941
+ */
942
+ private request;
943
+ /**
944
+ * Send a notification (no response expected). Best-effort — if the
945
+ * write fails, the next request will surface the crash.
946
+ */
947
+ private notify;
948
+ /**
949
+ * Dispatch a decoded JSON-RPC message. Correlates responses to pending
950
+ * requests by id; ignores server-initiated notifications (we don't
951
+ * need them for the resolution pass).
952
+ */
953
+ /**
954
+ * Dispatch a decoded JSON-RPC message. Correlates responses to pending
955
+ * requests by id; ignores server-initiated notifications.
956
+ */
957
+ private dispatchMessage;
958
+ /**
959
+ * stdout data handler — feed the decoder, dispatch complete messages,
960
+ * detect protocol errors.
961
+ */
962
+ private onStdoutData;
963
+ /**
964
+ * Handle an unexpected child exit. All pending requests are rejected
965
+ * with server_crashed.
966
+ */
967
+ private onChildExit;
968
+ /**
969
+ * Handle a spawn error (ENOENT etc). Marks the server as missing.
970
+ */
971
+ private onChildError;
972
+ /**
973
+ * Protocol error — the stream produced a malformed frame. Reject all
974
+ * pending and mark disposed so no further requests can be sent.
975
+ */
976
+ private handleProtocolError;
977
+ /**
978
+ * Hard-kill the child process: SIGKILL. Called by dispose() as a
979
+ * final cleanup guarantee. Also called if the graceful shutdown path
980
+ * fails. Uses `kill` which is a no-op if the process already exited.
981
+ */
982
+ private hardKill;
983
+ }
984
+ /**
985
+ * Convert a repo-relative or absolute file path to a `file://` URI.
986
+ * Handles Windows drive letters (C:\ → file:///C:/).
987
+ */
988
+ declare function pathToUri(filePath: string): string;
989
+ /**
990
+ * Convert a `file://` URI back to an absolute file path.
991
+ */
992
+ declare function uriToPath(uri: string): string;
993
+
994
+ /**
995
+ * Length-prefixed LSP framing — `Content-Length: <N>\r\n\r\n` headers
996
+ * followed by exactly `<N>` bytes of JSON body (LSP 3.17 §6.1 — Base Protocol).
997
+ *
998
+ * This is the one module where off-by-one and split-buffer bugs hide.
999
+ * The parser tracks a RUNNING OFFSET — it never re-scans bytes a
1000
+ * previous scan already confirmed separator-free (rule 32).
1001
+ */
1002
+ /**
1003
+ * Encode a JSON-RPC message as a Content-Length-prefixed frame ready to
1004
+ * write to the server's stdin. Uses UTF-8 — the LSP base protocol
1005
+ * mandates UTF-8 content encoding (§6.1).
1006
+ */
1007
+ declare function encodeLspFrame(message: unknown): string;
1008
+ /**
1009
+ * Reason for a decode failure. A `protocol_error` degradation surfaces
1010
+ * the specific reason so the caller can distinguish "bad header" from
1011
+ * "unparseable JSON body".
1012
+ */
1013
+ type FrameDecodeErrorKind = "malformed_header" | "json_parse_error";
1014
+ interface FrameDecodeError {
1015
+ readonly kind: FrameDecodeErrorKind;
1016
+ readonly detail: string;
1017
+ }
1018
+ interface FrameDecodeSuccess {
1019
+ readonly ok: true;
1020
+ readonly messages: unknown[];
1021
+ }
1022
+ type FrameDecodeResult = FrameDecodeSuccess | {
1023
+ readonly ok: false;
1024
+ readonly error: FrameDecodeError;
1025
+ };
1026
+ /**
1027
+ * Streaming LSP frame decoder. Feed raw Buffer or string chunks via
1028
+ * {@link feed}; each call returns the complete JSON messages parsed
1029
+ * since the last call, plus any error if a frame was malformed.
1030
+ *
1031
+ * Works with BYTES internally because Content-Length counts UTF-8 bytes
1032
+ * (LSP 3.17 §6.1), not UTF-16 code units. A string-based buffer would
1033
+ * mis-slice any body containing multi-byte characters (𝕏, emoji, CJK).
1034
+ *
1035
+ * The decoder maintains a running byte-buffer and a scan offset. After
1036
+ * each feed, consumed bytes are sliced away so the buffer never grows
1037
+ * unbounded across a long session (rule 11 — no unbounded state).
1038
+ */
1039
+ declare class LspFrameDecoder {
1040
+ private buffer;
1041
+ /**
1042
+ * Scan offset into {@link buffer}. The header scan resumes here on
1043
+ * the next feed() — never re-scans bytes already confirmed to not
1044
+ * contain the separator (rule 32). Reset to 0 after each consumed
1045
+ * frame because slicing the buffer discards those bytes.
1046
+ */
1047
+ private scanOffset;
1048
+ /**
1049
+ * Feed a raw chunk (Buffer or string) from the server's stdout.
1050
+ * Returns all complete messages parsed from the accumulated buffer
1051
+ * since the last call, or the first decode error encountered (the
1052
+ * decoder stops on error — a protocol violation means the stream is
1053
+ * corrupt and further parsing is undefined).
1054
+ */
1055
+ feed(chunk: Buffer | string): FrameDecodeResult;
1056
+ /** True if there is un-consumed residual data in the buffer. */
1057
+ get hasResidual(): boolean;
1058
+ /** Reset the decoder to a clean state (test seam). */
1059
+ reset(): void;
1060
+ }
1061
+
1062
+ /**
1063
+ * LSP resolution pass — upgrades Phase A heuristic edges with real
1064
+ * definition-lookup results from language servers.
1065
+ *
1066
+ * Two halves (issue #1555 step 4):
1067
+ *
1068
+ * 1. **Planner** (pure): `planLspUpgrades(unresolvedCallSites, budget)`
1069
+ * decides which call sites to query and in what order. Pure function —
1070
+ * no side effects, no I/O. Deterministic output for deterministic input.
1071
+ *
1072
+ * 2. **Executor**: sends `textDocument/didOpen` + `textDocument/definition`
1073
+ * for each planned request via the LSP client, maps returned locations
1074
+ * back to graph nodes by file + half-open span containment (rule 35),
1075
+ * and applies edge upgrades transactionally per batch. A mid-batch
1076
+ * failure leaves zero partial upgrades (rule 25 — tested).
1077
+ *
1078
+ * Budgets (issue #1555): `maxRequestsPerRun` caps the worst case. Remaining
1079
+ * call sites keep their Phase A resolution. Everything degrades to Phase A
1080
+ * results with a tagged degradation surfaced in index_status.
1081
+ *
1082
+ * Files whose ingest failed are excluded (rule 44 — the executor never
1083
+ * queries for a file that isn't in the store).
1084
+ */
1085
+
1086
+ /**
1087
+ * A call site that Phase A left unresolved or at low confidence. The
1088
+ * resolution pass will query the LSP server for its definition.
1089
+ */
1090
+ interface UnresolvedCallSite {
1091
+ /** Repo-relative file path of the CALLER (the file containing the call). */
1092
+ readonly filePath: string;
1093
+ readonly language: CodingGraphLanguage;
1094
+ /** Full file content — needed for byte↔position conversion. */
1095
+ readonly content: string;
1096
+ /** Byte offset of the callee name in the source (for the definition query position). */
1097
+ readonly calleeByteOffset: number;
1098
+ /** The callee name as extracted by Phase A (for logging/debugging). */
1099
+ readonly calleeName: string;
1100
+ /** The caller's qualified name (source node for the edge). */
1101
+ readonly srcQualifiedName: string;
1102
+ }
1103
+ /**
1104
+ * A planned LSP definition request — the planner's output. Each request
1105
+ * targets one call site at one position in one file.
1106
+ */
1107
+ interface PlannedLspRequest {
1108
+ readonly filePath: string;
1109
+ readonly language: CodingGraphLanguage;
1110
+ readonly content: string;
1111
+ readonly calleeName: string;
1112
+ readonly srcQualifiedName: string;
1113
+ /** LSP position derived from calleeByteOffset via line-offset map. */
1114
+ readonly position: {
1115
+ readonly line: number;
1116
+ readonly character: number;
1117
+ };
1118
+ }
1119
+ /**
1120
+ * The planner's budget — how many requests can be sent this run.
1121
+ */
1122
+ interface LspBudget {
1123
+ readonly maxRequests: number;
1124
+ }
1125
+ /**
1126
+ * Result of the planner — the requests to send, plus how many call sites
1127
+ * were deferred due to budget exhaustion.
1128
+ */
1129
+ interface PlanResult {
1130
+ readonly requests: readonly PlannedLspRequest[];
1131
+ readonly budgetExhausted: number;
1132
+ }
1133
+ /**
1134
+ * Result of the resolution pass.
1135
+ */
1136
+ interface ResolutionResult {
1137
+ /** Edges upgraded from heuristic → lsp with resolved dst node. */
1138
+ readonly upgraded: number;
1139
+ /** LSP returned no location or the location didn't map to an indexed node. */
1140
+ readonly unresolved: number;
1141
+ /** Call sites skipped because maxRequestsPerRun was reached. */
1142
+ readonly budgetExhausted: number;
1143
+ /** Degradation if the pass could not run (server crashed, protocol error). */
1144
+ readonly degradation?: LspDegradation;
1145
+ }
1146
+ /**
1147
+ * Look up a graph node by file path + byte span containment (rule 35 —
1148
+ * half-open). Returns the node's qualified name if exactly one node's
1149
+ * span contains the byte offset, or null if none/ambiguous.
1150
+ *
1151
+ * The executor provides this closure backed by the GraphStore; tests
1152
+ * inject a mock.
1153
+ */
1154
+ type NodeLocator = (filePath: string, byteOffset: number) => string | null;
1155
+ /**
1156
+ * Context for mapping an LSP location to a graph node. Provides the
1157
+ * caller's file path and content (for same-file definitions), plus
1158
+ * optional workspace-root normalization and cross-file content resolution.
1159
+ */
1160
+ interface MapLocationContext {
1161
+ /** Repo-relative path of the caller file. */
1162
+ readonly callerFilePath: string;
1163
+ /** Full content of the caller file. */
1164
+ readonly callerContent: string;
1165
+ /** Workspace root for normalizing absolute LSP URIs to repo-relative paths. */
1166
+ readonly workspaceRoot?: string;
1167
+ /** Resolve content for a target file path (repo-relative). */
1168
+ readonly resolveContent?: (filePath: string) => string | null;
1169
+ }
1170
+ /**
1171
+ * Plan which call sites to resolve via LSP. Pure — deterministic output
1172
+ * for deterministic input. Orders requests by file path then byte offset
1173
+ * for predictable, reviewable batches (rule 38 — byte-stable ordering).
1174
+ *
1175
+ * Budget enforcement: at most `budget.maxRequests` requests are planned.
1176
+ * Excess call sites are counted in `budgetExhausted` so the caller can
1177
+ * surface the degradation.
1178
+ */
1179
+ declare function planLspUpgrades(callSites: readonly UnresolvedCallSite[], budget: LspBudget): PlanResult;
1180
+ /**
1181
+ * Options for the resolution executor.
1182
+ */
1183
+ interface ResolveOptions {
1184
+ readonly client: LspClient;
1185
+ readonly nodeLocator: NodeLocator;
1186
+ /**
1187
+ * Apply a batch of edge upgrades atomically. Called once per file batch.
1188
+ * MUST be transactional — if it throws, zero upgrades from this batch
1189
+ * persist (rule 25). Each upgrade is an edge `{srcQualifiedName,
1190
+ * dstQualifiedName, type: "CALLS", confidence, provenance: "lsp"}`.
1191
+ */
1192
+ readonly applyUpgrades: (upgrades: readonly EdgeUpgrade[]) => Promise<void>;
1193
+ /**
1194
+ * Workspace root for resolving repo-relative file paths to absolute LSP
1195
+ * URIs and normalizing returned URIs back to repo-relative paths.
1196
+ */
1197
+ readonly workspaceRoot?: string;
1198
+ /**
1199
+ * Resolve the content of a target file by repo-relative path. Used for
1200
+ * cross-file definition positions — without this, cross-file byte-offset
1201
+ * conversion falls back to the caller's content (best-effort).
1202
+ */
1203
+ readonly resolveContent?: (filePath: string) => string | null;
1204
+ }
1205
+ /**
1206
+ * A single edge upgrade — the output of a successful definition lookup.
1207
+ */
1208
+ interface EdgeUpgrade {
1209
+ readonly srcQualifiedName: string;
1210
+ readonly dstQualifiedName: string;
1211
+ readonly type: string;
1212
+ readonly confidence: number;
1213
+ readonly provenance: "lsp";
1214
+ }
1215
+ /**
1216
+ * Execute the resolution pass: for each planned request, send a
1217
+ * `textDocument/didOpen` + `textDocument/definition` query, map the
1218
+ * returned location to a graph node, and collect edge upgrades. Upgrades
1219
+ * are applied in file-batched transactions — a mid-batch failure leaves
1220
+ * zero partial upgrades.
1221
+ *
1222
+ * Never throws — degrades to Phase A results with a tagged degradation.
1223
+ */
1224
+ declare function executeLspResolution(requests: readonly PlannedLspRequest[], options: ResolveOptions): Promise<ResolutionResult>;
1225
+ /**
1226
+ * Map an LSP definition location to a graph node's qualified name.
1227
+ *
1228
+ * Uses half-open span containment (rule 35): the location's start byte
1229
+ * must be within `[node.spanStart, node.spanEnd)` for the node to match.
1230
+ * If multiple locations are returned, the FIRST one that maps to a node
1231
+ * wins (LSP servers typically return the most relevant definition first).
1232
+ *
1233
+ * For same-file definitions, the caller's content is used for byte-offset
1234
+ * conversion (exact). For cross-file definitions, `context.resolveContent`
1235
+ * is used to fetch the target file's content; if unavailable, the caller's
1236
+ * content is used as a best-effort fallback.
1237
+ *
1238
+ * Returns null if no location maps to an indexed node.
1239
+ */
1240
+ declare function mapLocationToNode(locations: readonly LspLocation[], context: MapLocationContext, nodeLocator: NodeLocator): string | null;
1241
+
1242
+ /**
1243
+ * LSP status surfacing — per-language LSP state for index_status and
1244
+ * `remnic doctor` (issue #1555 step 5).
1245
+ *
1246
+ * Reports per-language: enabled / probed / degraded(code) / requests_used.
1247
+ * A missing server is normal, not an error — the status entry shows
1248
+ * `probed: false` with a degradation code so the operator knows LSP
1249
+ * resolution is available but inactive for that language.
1250
+ */
1251
+
1252
+ /**
1253
+ * Per-language LSP status. Surfaced in `index_status` and rendered as a
1254
+ * single line by `remnic doctor`.
1255
+ */
1256
+ interface LspStatusEntry {
1257
+ readonly language: CodingGraphLanguage;
1258
+ /** Master switch state (codingGraph.lsp.enabled). */
1259
+ readonly enabled: boolean;
1260
+ /** Server binary found and initialize handshake succeeded. */
1261
+ readonly probed: boolean;
1262
+ /** True if the last resolution pass degraded (timeout/crash/protocol). */
1263
+ readonly degraded: boolean;
1264
+ /** Specific degradation code if `degraded` is true. */
1265
+ readonly degradationCode?: LspDegradationCode;
1266
+ /** Definition requests sent in the last index run. */
1267
+ readonly requestsUsed: number;
1268
+ }
1269
+ /**
1270
+ * Input for LSP status computation — the probe results and resolution
1271
+ * results from the last index run. The caller (the index pipeline)
1272
+ * collects these during the run and passes them here.
1273
+ */
1274
+ interface LspStatusInput {
1275
+ readonly config: LspConfig;
1276
+ /**
1277
+ * Per-language probe results from the last run. `true` = server found
1278
+ * and handshake succeeded.
1279
+ */
1280
+ readonly probeResults: ReadonlyMap<CodingGraphLanguage, boolean>;
1281
+ /**
1282
+ * Per-language degradation codes from the last run. A language in this
1283
+ * map with a code means the resolution pass degraded for that language.
1284
+ */
1285
+ readonly degradations: ReadonlyMap<CodingGraphLanguage, LspDegradationCode>;
1286
+ /**
1287
+ * Per-language definition request counts from the last run.
1288
+ */
1289
+ readonly requestCounts: ReadonlyMap<CodingGraphLanguage, number>;
1290
+ /**
1291
+ * The languages configured for resolution (from the index run's
1292
+ * candidate set). Only these languages get status entries.
1293
+ */
1294
+ readonly languages: readonly CodingGraphLanguage[];
1295
+ }
1296
+ /**
1297
+ * Compute per-language LSP status entries. Pure — no side effects.
1298
+ * Returns one entry per language in `languages`.
1299
+ */
1300
+ declare function getLspStatus(input: LspStatusInput): readonly LspStatusEntry[];
1301
+ /**
1302
+ * Render a single LSP status line for `remnic doctor`. Format:
1303
+ *
1304
+ * typescript: lsp [probed] 12 requests
1305
+ * python: lsp [degraded:request_timeout] 3 requests
1306
+ * go: lsp [not_probed] 0 requests
1307
+ * rust: lsp [disabled]
1308
+ */
1309
+ declare function formatLspStatusLine(entry: LspStatusEntry): string;
1310
+ /**
1311
+ * Adapter: convert a {@link ResolutionResult} into the per-language
1312
+ * degradation + request-count maps that {@link getLspStatus} consumes.
1313
+ *
1314
+ * The resolution pass runs once per language (each language has its own
1315
+ * server). This helper is called once per language to populate the maps.
1316
+ */
1317
+ declare function resolutionResultToStatusMaps(language: CodingGraphLanguage, result: ResolutionResult, degradations: Map<CodingGraphLanguage, LspDegradationCode>, requestCounts: Map<CodingGraphLanguage, number>): void;
1318
+
1319
+ /**
1320
+ * Byte-offset ↔ LSP position conversion.
1321
+ *
1322
+ * LSP positions are zero-based {line, character} where `character` is a
1323
+ * UTF-16 code-unit offset within the line (LSP 3.17 §3.17). The coding-
1324
+ * graph store uses UTF-8 byte spans. This module converts between the two.
1325
+ *
1326
+ * A {@link LineOffsetMap} pre-computes the UTF-8 BYTE offset of each line
1327
+ * start, making both directions O(log n) via binary search for the line,
1328
+ * then O(line length) for the character within the line.
1329
+ */
1330
+ /**
1331
+ * Pre-computed line-start byte offsets for a single file. Built once per
1332
+ * file from its content; reused for all position conversions in that file.
1333
+ *
1334
+ * `lineStarts[i]` = UTF-8 byte offset of the first character on line `i`.
1335
+ * Line 0 always starts at byte 0.
1336
+ */
1337
+ interface LineOffsetMap {
1338
+ readonly lineStarts: readonly number[];
1339
+ }
1340
+ /**
1341
+ * Build a line-offset map from file content (as a UTF-8 string or Buffer).
1342
+ * Records UTF-8 BYTE offsets — not UTF-16 string indices — because
1343
+ * Content-Length and the store's span_start/span_end count bytes.
1344
+ * Handles `\n`, `\r\n`, and `\r` line endings.
1345
+ */
1346
+ declare function buildLineOffsetMap(content: string | Buffer): LineOffsetMap;
1347
+ /**
1348
+ * Convert a UTF-8 byte offset to an LSP position {line, character}.
1349
+ * `character` is a UTF-16 code-unit count from the line start (surrogates
1350
+ * count as 2, matching LSP §3.17).
1351
+ */
1352
+ declare function byteOffsetToPosition(content: string, byteOffset: number, map: LineOffsetMap): {
1353
+ line: number;
1354
+ character: number;
1355
+ };
1356
+ /**
1357
+ * Convert an LSP position {line, character} to a UTF-8 byte offset.
1358
+ * `character` is a UTF-16 code-unit count from the line start.
1359
+ */
1360
+ declare function positionToByteOffset(content: string, position: {
1361
+ line: number;
1362
+ character: number;
1363
+ }, map: LineOffsetMap): number;
1364
+
1365
+ /**
1366
+ * Semantic-layer configuration for @remnic/coding-graph (issue #1556).
1367
+ *
1368
+ * Rule 30/48: the semantic layer is OFF by default. Embedding costs compute
1369
+ * and possibly tokens, so nothing in this module touches a provider, writes
1370
+ * a vector, or sends symbol text off-machine unless `enabled` is explicitly
1371
+ * true. The gate-off characterization test (`gate-off.test.ts`) asserts
1372
+ * this end to end.
1373
+ *
1374
+ * The config object is intentionally self-contained in this package rather
1375
+ * than wired into @remnic/core/config.ts — the coding-graph package is an
1376
+ * optional peer dep and must compile standalone. Host integrations
1377
+ * (core/config.ts + openclaw.plugin.json schema) resolve to this same
1378
+ * shape via `resolveSemanticConfig()`, which reads the documented env vars
1379
+ * with the `ENGRAM_` fallback (gotcha 9).
1380
+ */
1381
+
1382
+ /**
1383
+ * Default SIMILAR_TO cosine confirmation threshold (issue #1556 design).
1384
+ * 0.92 is the codebase-memory-mcp precedent — high enough to avoid
1385
+ * false near-clone pairs across structurally-similar but logically-distinct
1386
+ * functions, low enough to catch genuine copy-paste with a renamed
1387
+ * variable.
1388
+ */
1389
+ declare const DEFAULT_SIMILAR_TO_THRESHOLD = 0.92;
1390
+ /**
1391
+ * Default maximum symbols embedded per indexing run. Bounds per-run
1392
+ * provider cost. 0 means unlimited (the host budget is the only cap).
1393
+ */
1394
+ declare const DEFAULT_MAX_SYMBOLS_PER_RUN = 0;
1395
+ /**
1396
+ * Confidence band assigned to MinHash-only SIMILAR_TO edges when no
1397
+ * embedding provider is available (deterministic, local). Kept below the
1398
+ * embedding-confirmed band so consumers can distinguish provenance quality.
1399
+ * The issue designates this a distinct, documented lower band.
1400
+ */
1401
+ declare const MINHASH_ONLY_CONFIDENCE = 0.5;
1402
+ /**
1403
+ * Edge type emitted by the SIMILAR_TO pipeline. Lives in the `edges`
1404
+ * table with `provenance: "semantic"` (already in EDGE_PROVENANCE_VALUES).
1405
+ */
1406
+ declare const SIMILAR_TO_EDGE_TYPE = "SIMILAR_TO";
1407
+ /**
1408
+ * The single provenance tag for every edge this module writes.
1409
+ */
1410
+ declare const SEMANTIC_PROVENANCE: EdgeProvenance;
1411
+ /**
1412
+ * Canonical-text body line budget. `signature + doc comment + first N lines
1413
+ * of body` per the issue design. N is bounded so a 5k-line function does
1414
+ * not dominate the embedded string (and the provider token budget).
1415
+ */
1416
+ declare const DEFAULT_CANONICAL_BODY_LINES = 16;
1417
+ /**
1418
+ * Self-contained semantic config. Resolved from host config + env.
1419
+ *
1420
+ * `enabled` is the single gate for the whole layer. When false, every
1421
+ * semantic entry point (index-time vector writes, SIMILAR_TO edges,
1422
+ * semantic_query) returns a tagged `{ ok: false, code: "semantic_disabled" }`
1423
+ * WITHOUT touching the provider or the vectors table (gate-off parity).
1424
+ */
1425
+ interface SemanticConfig {
1426
+ /** Master gate. Default false (rule 30/48). */
1427
+ readonly enabled: boolean;
1428
+ /** Cosine threshold for SIMILAR_TO confirmation. Default 0.92. */
1429
+ readonly similarToThreshold: number;
1430
+ /** Per-run embedding budget (0 = unlimited). Default 0. */
1431
+ readonly maxSymbolsPerRun: number;
1432
+ /** Canonical-text body line budget. Default 16. */
1433
+ readonly canonicalBodyLines: number;
1434
+ }
1435
+ /**
1436
+ * Resolve the semantic config from an optional host-provided partial plus
1437
+ * the environment. Explicit host values win; env vars fill the gaps;
1438
+ * documented defaults apply last.
1439
+ *
1440
+ * `env` defaults to `process.env` but is a parameter so tests can pin the
1441
+ * environment deterministically (rule 38 — no implicit process state).
1442
+ */
1443
+ declare function resolveSemanticConfig(host?: Partial<SemanticConfig>, env?: NodeJS.ProcessEnv): SemanticConfig;
1444
+
1445
+ /**
1446
+ * Input to the canonical-text builder. `rawText` is the symbol's on-disk
1447
+ * source slice `[startByte, endByte)`. `docComment` is the leading
1448
+ * comment block immediately above the symbol, if the caller extracted one
1449
+ * (the parser does not currently emit doc comments on SymbolIR, so this
1450
+ * is optional and may be empty/undefined — the canonical form degrades
1451
+ * gracefully to `signature + body`).
1452
+ */
1453
+ interface CanonicalTextInput {
1454
+ readonly symbol: SymbolIR;
1455
+ /** Raw source text of the symbol span. */
1456
+ readonly rawText: string;
1457
+ /** Optional leading doc comment (/** … *\/ or // … lines). */
1458
+ readonly docComment?: string;
1459
+ /** Body token budget (default {@link DEFAULT_CANONICAL_BODY_LINES}). */
1460
+ readonly maxBodyLines?: number;
1461
+ }
1462
+ /**
1463
+ * Collapse ALL whitespace runs (including newlines) to single spaces and
1464
+ * trim. This is the universal normalization pass: it absorbs indentation,
1465
+ * brace placement, trailing whitespace, tabs vs spaces, and line-ending
1466
+ * differences. After this pass, two formatting variants of the same
1467
+ * function are byte-identical.
1468
+ */
1469
+ declare function collapseWhitespace(text: string): string;
1470
+ /**
1471
+ * Extract a coarse signature string from raw symbol text. The text is
1472
+ * fully whitespace-normalized first, then split at the first body-open
1473
+ * marker. The returned signature is stable across all formatting variants
1474
+ * of the same function.
1475
+ */
1476
+ declare function extractSignatureLine(rawText: string, _kind: SymbolIR["kind"]): string;
1477
+ /**
1478
+ * Extract the body (everything after the signature/header marker) from
1479
+ * raw symbol text, truncated to the first `maxBodyLines` tokens. Tokens
1480
+ * are whitespace-delimited words/operators in the normalized body text —
1481
+ * this is a STABLE budget (formatting-independent) unlike line-based
1482
+ * truncation. `maxBodyLines` is the config field name; it functions as a
1483
+ * token budget here (each "line" ≈ one significant token).
1484
+ *
1485
+ * `maxBodyLines <= 0` means unlimited (return the full normalized body).
1486
+ */
1487
+ declare function extractBodyText(rawText: string, maxBodyLines: number): string;
1488
+ /**
1489
+ * Build the canonical embedding text for a symbol.
1490
+ *
1491
+ * The form (rule 23/38 — ONE form, every consumer):
1492
+ * KIND:<kind>\nQNAME:<qualifiedName>\nSIG:<signature>\n[DOC:<doc>]\nBODY:<body>
1493
+ *
1494
+ * `kind` and `qualifiedName` are included as stable prefix lines so two
1495
+ * functions with identical bodies but different names (the "renamed
1496
+ * variable" clone fixture) embed close but not identically — the qualified
1497
+ * name differentiates them at the embedding level while the body dominates
1498
+ * the similarity. The cache hash, by contrast, is over the FULL canonical
1499
+ * text including the name, so a rename invalidates the cache (rule 37).
1500
+ *
1501
+ * Normalization:
1502
+ * - ALL whitespace collapsed (indentation/brace-style/newlines absorbed)
1503
+ * - body truncated to maxBodyLines tokens (stable budget)
1504
+ */
1505
+ declare function buildCanonicalText(input: CanonicalTextInput): string;
1506
+ /**
1507
+ * The cache key for a canonical text. This is sha256 over the EXACT
1508
+ * canonical text string (rule 23 — the embedded string equals the hashed
1509
+ * string). When the canonical text changes (e.g. a rename edits the
1510
+ * qualified name, or the body changes), the hash changes, and:
1511
+ * 1. the cached vector is invalidated (re-embedded), and
1512
+ * 2. any SIMILAR_TO edge derived from it is recomputed.
1513
+ *
1514
+ * This is the single chokepoint for cache invalidation (rule 37). Every
1515
+ * layer that persists a vector persists THIS hash alongside it; every
1516
+ * re-index compares THIS hash to decide whether to re-embed.
1517
+ */
1518
+ declare function canonicalTextHash(canonicalText: string): string;
1519
+ /**
1520
+ * Convenience: build canonical text AND its hash in one call. The hash is
1521
+ * over the returned text — callers that store both MUST store the exact
1522
+ * `text` alongside the `hash` (never re-derive the text from disk and hash
1523
+ * separately, or a formatter run between the two would silently
1524
+ * re-embed — rule 37).
1525
+ */
1526
+ declare function buildCanonicalTextAndHash(input: CanonicalTextInput): {
1527
+ readonly text: string;
1528
+ readonly hash: string;
1529
+ };
1530
+
1531
+ declare const MINHASH_SEEDS: readonly bigint[];
1532
+ /**
1533
+ * Normalize symbol body text into a token stream for shingling.
1534
+ *
1535
+ * Normalization:
1536
+ * - lowercase (case-insensitive clone detection — `MyFunc` vs `myfunc`)
1537
+ * - split on non-alphanumeric (identifiers, numbers, operators become tokens)
1538
+ * - drop empty tokens
1539
+ *
1540
+ * This is deliberately coarse: the goal is Jaccard over token sets, not
1541
+ * semantic parsing. A renamed variable changes exactly one token per
1542
+ * occurrence, so Jaccard stays high for genuine clones.
1543
+ */
1544
+ declare function tokenizeForShingling(body: string): string[];
1545
+ /**
1546
+ * Build the set of shingles (n-grams of width MINHASH_SHINGLE_WIDTH) from
1547
+ * a token stream. Returns a Set so duplicate shingles collapse (Jaccard
1548
+ * is over the SET of shingles, not a multiset).
1549
+ */
1550
+ declare function shingleSet(tokens: readonly string[]): Set<string>;
1551
+ /**
1552
+ * Compute the MinHash signature (array of permutation minima) for a set
1553
+ * of shingles. Signature length = MINHASH_NUM_PERMUTATIONS. The estimated
1554
+ * Jaccard similarity between two signatures is the fraction of matching
1555
+ * positions.
1556
+ */
1557
+ declare function minHashSignature(shingles: Set<string>): bigint[];
1558
+ /**
1559
+ * LSH band key — the concatenation of one band's rows from the signature.
1560
+ * Two signatures that share at least one band key are a candidate pair.
1561
+ */
1562
+ declare function lshBandKeys(signature: readonly bigint[]): string[];
1563
+ /**
1564
+ * An indexed symbol body ready for LSH bucketing.
1565
+ */
1566
+ interface LshIndexEntry {
1567
+ readonly nodeId: string;
1568
+ readonly qualifiedName: string;
1569
+ readonly body: string;
1570
+ }
1571
+ /**
1572
+ * A MinHash/LSH indexer instance. Owns the band→node-id bucket map on the
1573
+ * instance (rule 11). `findCandidates` returns the deduplicated candidate
1574
+ * pair set with estimated Jaccard for each pair.
1575
+ */
1576
+ declare class MinHasher {
1577
+ /** band key → set of node ids in that band. */
1578
+ private readonly buckets;
1579
+ /** node id → signature (for Jaccard estimation on candidate pairs). */
1580
+ private readonly signatures;
1581
+ /** node id → qualified name (for readable candidate output). */
1582
+ private readonly qnames;
1583
+ /**
1584
+ * Add a symbol body to the LSH index. Idempotent — re-adding the same
1585
+ * (nodeId, body) is a no-op.
1586
+ */
1587
+ add(entry: LshIndexEntry): void;
1588
+ /**
1589
+ * Find all candidate pairs (pairs sharing at least one LSH band) with
1590
+ * their estimated Jaccard similarity. Returns a stable-sorted array
1591
+ * (by aNodeId then bNodeId) so the determinism test can compare runs
1592
+ * byte-for-byte.
1593
+ */
1594
+ findCandidates(): {
1595
+ readonly aNodeId: string;
1596
+ readonly bNodeId: string;
1597
+ readonly aQualifiedName: string;
1598
+ readonly bQualifiedName: string;
1599
+ readonly jaccard: number;
1600
+ }[];
1601
+ }
1602
+ /**
1603
+ * Construct a fresh MinHasher (rule 11 — state on the instance).
1604
+ */
1605
+ declare function createMinHasher(): MinHasher;
1606
+ /**
1607
+ * Cosine similarity between two equal-length float vectors. Returns 0 for
1608
+ * zero-norm vectors (no division-by-zero). This is the brute-force
1609
+ * retrieval primitive shared by SIMILAR_TO confirmation and semantic_query.
1610
+ */
1611
+ declare function cosineSimilarity(a: Float32Array | number[], b: Float32Array | number[]): number;
1612
+
1613
+ /**
1614
+ * Shared types for the semantic layer (issue #1556).
1615
+ *
1616
+ * Tagged failures follow rule 34 — every entry point returns a
1617
+ * discriminated union so a caller that switches on `result.code` never
1618
+ * observes a thrown error from the semantic layer.
1619
+ */
1620
+ /**
1621
+ * A persisted symbol vector. `vector` is the float32 embedding; `dims`
1622
+ * is its dimensionality; `modelId` identifies the provider+model that
1623
+ * produced it (so a provider swap invalidates the cache); `contentHash`
1624
+ * is the canonical-text hash (so a canonical-text change invalidates the
1625
+ * cache — rule 37).
1626
+ */
1627
+ interface SymbolVector {
1628
+ readonly nodeId: string;
1629
+ readonly qualifiedName: string;
1630
+ readonly vector: Float32Array;
1631
+ readonly dims: number;
1632
+ readonly modelId: string;
1633
+ readonly contentHash: string;
1634
+ }
1635
+ /**
1636
+ * A row read back from the vectors table for brute-force cosine.
1637
+ */
1638
+ interface SymbolVectorRow {
1639
+ readonly nodeId: string;
1640
+ readonly qualifiedName: string;
1641
+ readonly vector: Float32Array;
1642
+ readonly dims: number;
1643
+ readonly modelId: string;
1644
+ readonly contentHash: string;
1645
+ readonly filePath: string;
1646
+ }
1647
+ /**
1648
+ * Tagged-failure codes shared across the semantic layer.
1649
+ *
1650
+ * - `semantic_disabled`: the master gate is off (rule 30/48). No provider
1651
+ * call, no vectors-table write, no edge emitted.
1652
+ * - `provider_unavailable`: no host embedding provider registered for the
1653
+ * given scope.
1654
+ * - `provider_timeout`: the provider exceeded the lookup budget.
1655
+ * - `malformed_vector`: `normalizeHostEmbeddingVector` returned null.
1656
+ * - `repo_root_unset`: the store was opened without a repoRoot, so source
1657
+ * text cannot be read.
1658
+ * - `store_closed`: the store is closed.
1659
+ * - `db_error`: an underlying SQLite error.
1660
+ * - `no_vectors`: semantic_query ran but the vectors table is empty.
1661
+ * - `invalid_query`: malformed query input.
1662
+ */
1663
+ type SemanticFailureCode = "semantic_disabled" | "provider_unavailable" | "provider_timeout" | "malformed_vector" | "repo_root_unset" | "store_closed" | "db_error" | "no_vectors" | "invalid_query";
1664
+ interface SemanticFailure {
1665
+ readonly ok: false;
1666
+ readonly code: SemanticFailureCode;
1667
+ readonly message?: string;
1668
+ }
1669
+ /**
1670
+ * Result of indexing vectors for a batch of symbols.
1671
+ */
1672
+ interface IndexVectorsResult {
1673
+ readonly ok: true;
1674
+ readonly embedded: number;
1675
+ readonly cached: number;
1676
+ readonly skipped: number;
1677
+ }
1678
+ /**
1679
+ * A SIMILAR_TO candidate pair from the MinHash/LSH pass.
1680
+ */
1681
+ interface SimilarCandidate {
1682
+ readonly aNodeId: string;
1683
+ readonly bNodeId: string;
1684
+ readonly aQualifiedName: string;
1685
+ readonly bQualifiedName: string;
1686
+ readonly jaccard: number;
1687
+ }
1688
+ /**
1689
+ * A confirmed SIMILAR_TO edge (after cosine confirmation when available).
1690
+ *
1691
+ * Carries the content-derived node ids (`nodes.id`) of both endpoints so
1692
+ * the persisted edge resolves unambiguously even when the two symbols share
1693
+ * a qualified name across files (issue #1677). The qualified-name fields
1694
+ * remain for diagnostics / stable-sort tie-breaking.
1695
+ */
1696
+ interface SimilarEdge {
1697
+ readonly srcNodeId: string;
1698
+ readonly dstNodeId: string;
1699
+ readonly srcQualifiedName: string;
1700
+ readonly dstQualifiedName: string;
1701
+ readonly confidence: number;
1702
+ readonly confirmed: boolean;
1703
+ }
1704
+ /**
1705
+ * Result of the SIMILAR_TO pipeline.
1706
+ */
1707
+ interface SimilarToResult {
1708
+ readonly ok: true;
1709
+ readonly edges: readonly SimilarEdge[];
1710
+ readonly candidates: number;
1711
+ readonly confirmed: number;
1712
+ readonly minhashOnly: number;
1713
+ }
1714
+ /**
1715
+ * A hydrated semantic_query hit — graph context attached so the agent
1716
+ * gets structure, not just a snippet.
1717
+ */
1718
+ interface SemanticQueryHit {
1719
+ readonly qualifiedName: string;
1720
+ readonly filePath: string;
1721
+ readonly kind: string;
1722
+ readonly score: number;
1723
+ readonly snippet: string;
1724
+ readonly callers: readonly string[];
1725
+ readonly callees: readonly string[];
1726
+ }
1727
+ /**
1728
+ * Result of semantic_query. When degraded, `ok: true` still carries the
1729
+ * (possibly empty) hits plus a `degraded` tag so the caller never
1730
+ * mistakes "no matches" for "backend broken" (rule 34).
1731
+ */
1732
+ interface SemanticQuerySuccess {
1733
+ readonly ok: true;
1734
+ readonly hits: readonly SemanticQueryHit[];
1735
+ readonly degraded?: "provider_unavailable" | "provider_timeout" | "malformed_vector";
1736
+ }
1737
+ type SemanticQueryOutcome = SemanticQuerySuccess | SemanticFailure;
1738
+
1739
+ /**
1740
+ * Input to {@link indexSymbolVectors}. The store provides node metadata +
1741
+ * the vectors table; the provider embeds; repoRoot resolves file paths.
1742
+ */
1743
+ interface IndexVectorsInput {
1744
+ readonly store: GraphStore;
1745
+ readonly provider: HostEmbeddingProvider | undefined;
1746
+ readonly repoRoot: string;
1747
+ readonly config: SemanticConfig;
1748
+ /**
1749
+ * Optional abort signal forwarded to the provider. The indexer does not
1750
+ * impose its own timeout (the provider's embed() contract handles that).
1751
+ */
1752
+ readonly signal?: AbortSignal;
1753
+ }
1754
+ /**
1755
+ * The model id used for cache keying. Derives from the provider's `model`
1756
+ * (falling back to `id`) so a provider/model swap produces a distinct
1757
+ * cache namespace and does not overwrite the prior vectors.
1758
+ */
1759
+ declare function modelIdFor(provider: HostEmbeddingProvider): string;
1760
+ /**
1761
+ * Index symbol vectors for every persisted node in the store.
1762
+ *
1763
+ * Flow:
1764
+ * 1. Gate: if !config.enabled → tagged semantic_disabled (no work).
1765
+ * 2. Provider check: if no provider → tagged provider_unavailable.
1766
+ * 3. Read all nodes from the store (persisted only — rule 44).
1767
+ * 4. For each node (within maxSymbolsPerRun budget):
1768
+ * a. Read source text from disk.
1769
+ * b. Build canonical text + hash.
1770
+ * c. Cache check: skip if cached row's content_hash matches.
1771
+ * d. Embed via provider.
1772
+ * e. Normalize (reject malformed → counted as skipped).
1773
+ * f. Persist vector.
1774
+ * 5. Return counts.
1775
+ *
1776
+ * Budget order (rule 27): recently-changed symbols first. The store's
1777
+ * readNodesForSemantic returns nodes ordered by qualified_name; the
1778
+ * indexer applies the caller-supplied priority before slicing. When no
1779
+ * priority is given, all nodes are eligible (budget 0 = unlimited).
1780
+ */
1781
+ declare function indexSymbolVectors(input: IndexVectorsInput): Promise<IndexVectorsResult | SemanticFailure>;
1782
+
1783
+ /**
1784
+ * Input to {@link computeSimilarTo}.
1785
+ */
1786
+ interface SimilarToInput {
1787
+ readonly store: GraphStore;
1788
+ readonly provider: HostEmbeddingProvider | undefined;
1789
+ readonly config: SemanticConfig;
1790
+ /**
1791
+ * Repo root for reading source text from disk when bodies are not
1792
+ * supplied. Required when bodies is absent.
1793
+ */
1794
+ readonly repoRoot?: string;
1795
+ /**
1796
+ * Symbol bodies keyed by nodeId. The caller (the indexer or a
1797
+ * standalone pass) reads source text and builds canonical bodies. When
1798
+ * absent, the pipeline reads nodes from the store + disk itself.
1799
+ */
1800
+ readonly bodies?: ReadonlyMap<string, {
1801
+ readonly qualifiedName: string;
1802
+ readonly body: string;
1803
+ }>;
1804
+ /**
1805
+ * Vectors keyed by nodeId (the persisted embedding). When absent, the
1806
+ * pipeline reads them from the store via readAllSymbolVectors.
1807
+ */
1808
+ readonly vectors?: ReadonlyMap<string, Float32Array>;
1809
+ }
1810
+ /**
1811
+ * The comparison operator for cosine confirmation. Decided ONCE (rule 35
1812
+ * spirit): `>= threshold`. A pair at EXACTLY the threshold confirms. The
1813
+ * boundary test asserts this.
1814
+ */
1815
+ declare const CONFIRM_OPERATOR: ">=";
1816
+ /**
1817
+ * Compute SIMILAR_TO edges.
1818
+ *
1819
+ * Returns the edges (for the caller to persist via store.upsertEdges) plus
1820
+ * counts. The caller persists; this function is pure over its inputs
1821
+ * (rule 38 — deterministic given seeds + bodies + vectors).
1822
+ *
1823
+ * When `config.enabled` is false → tagged semantic_disabled (no work, no
1824
+ * candidate generation — gate-off parity).
1825
+ */
1826
+ declare function computeSimilarTo(input: SimilarToInput): SimilarToResult | SemanticFailure;
1827
+ /**
1828
+ * Convert SimilarEdge[] to the store's EdgeIR[] for persistence via
1829
+ * upsertEdges. Provenance is always "semantic"; type is SIMILAR_TO.
1830
+ *
1831
+ * Carries the content-derived node ids onto the EdgeIR (issue #1677) so
1832
+ * the store resolves each endpoint by `nodes.id` (unique) instead of by
1833
+ * qualified name — two symbols that share a qualified name across files
1834
+ * get distinct, non-colliding SIMILAR_TO edges instead of being dropped
1835
+ * as ambiguous.
1836
+ */
1837
+ declare function similarEdgesToEdgeIR(edges: readonly SimilarEdge[]): EdgeIR[];
1838
+ /**
1839
+ * Estimate Jaccard similarity between two bodies directly (no LSH). Used
1840
+ * by the hard-negative test to assert two bodies are NOT similar.
1841
+ */
1842
+ declare function estimateJaccard(bodyA: string, bodyB: string): number;
1843
+
1844
+ /**
1845
+ * semantic_query — natural-language retrieval over the symbol graph
1846
+ * (issue #1556 PR3 component).
1847
+ *
1848
+ * Embed the query via the host provider / EmbeddingFallback
1849
+ * (`mode: "lookup"`), top-k symbols by cosine, hydrate each hit with
1850
+ * graph context (defining file, direct callers/callees).
1851
+ *
1852
+ * Rule 34 — degradation matrix. Provider missing / timeout / malformed
1853
+ * vector (`normalizeHostEmbeddingVector` returns null) → three distinct
1854
+ * `{ok:false}` codes, never an empty result masquerading as "no matches".
1855
+ * When the provider is available but returns zero hits, `ok:true` with
1856
+ * empty hits is the honest answer.
1857
+ */
1858
+
1859
+ /**
1860
+ * Input to {@link semanticQuery}.
1861
+ */
1862
+ interface SemanticQueryInput {
1863
+ readonly store: GraphStore;
1864
+ readonly provider: HostEmbeddingProvider | undefined;
1865
+ readonly repoRoot: string;
1866
+ readonly config: SemanticConfig;
1867
+ readonly query: string;
1868
+ readonly limit?: number;
1869
+ readonly signal?: AbortSignal;
1870
+ }
1871
+ /**
1872
+ * Default top-k for semantic_query.
1873
+ */
1874
+ declare const DEFAULT_SEMANTIC_QUERY_LIMIT = 10;
1875
+ /**
1876
+ * Run a semantic query: embed → top-k → hydrate.
1877
+ *
1878
+ * Degradation matrix (rule 34):
1879
+ * - !config.enabled → { ok:false, code:"semantic_disabled" }
1880
+ * - no provider → { ok:false, code:"provider_unavailable" }
1881
+ * - provider throws timeout → { ok:false, code:"provider_timeout" }
1882
+ * - provider returns null/malformed → { ok:false, code:"malformed_vector" }
1883
+ * - no vectors in table → { ok:false, code:"no_vectors" }
1884
+ * - ok but zero hits → { ok:true, hits:[] }
1885
+ */
1886
+ declare function semanticQuery(input: SemanticQueryInput): Promise<SemanticQueryOutcome>;
1887
+
1888
+ /**
1889
+ * @remnic/coding-graph — symbol-extraction engine + SQLite knowledge-graph
1890
+ * store for codebase memory.
1891
+ *
1892
+ * À-la-carte optional companion of @remnic/core (CLAUDE.md rule 57).
1893
+ *
1894
+ * This package unifies two PR1 surfaces:
1895
+ * - The web-tree-sitter engine scaffold (#1551 step 1): the package and
1896
+ * its build wiring exist; the engine public surface is declared and
1897
+ * the placeholder factory throws a tagged
1898
+ * `CodingGraphError("not_implemented", …)`. The real backend lands in
1899
+ * #1551 PR2.
1900
+ * - The SQLite knowledge-graph store (#1552 PR1): versioned schema + the
1901
+ * write pipeline (upsert/drop file batches, node-id derivation,
1902
+ * dangling-edge accounting). Traversal, search, dead-code, and the
1903
+ * openCypher subset land in #1552 PR2/PR3.
1904
+ *
1905
+ * Type-source direction:
1906
+ * The contract types (CodingGraphEngine, FileIR, etc.) and the
1907
+ * TIER_1_LANGUAGES / CODING_GRAPH_ENGINE_VERSION constants live in
1908
+ * @remnic/core (packages/remnic-core/src/coding/coding-graph-types.ts,
1909
+ * re-exported from the main index). This package imports them and
1910
+ * implements against them; it does NOT redefine them. That keeps a
1911
+ * single source of truth so updating the engine version in one place
1912
+ * keeps every consumer in lockstep (Cursor Bugbot low-severity on
1913
+ * PR #1588 round 2: "ENGINE_VERSION duplicated not imported").
1914
+ *
1915
+ * @remnic/coding-graph declares @remnic/core as both `peerDependencies`
1916
+ * and `devDependencies: "workspace:*"` in its package.json, so the
1917
+ * pnpm workspace link exists and the `import from "@remnic/core"`
1918
+ * below resolves in development.
1919
+ *
1920
+ * The store modules (graph-schema, graph-store, row-types) are local to
1921
+ * this package; they import `openBetterSqlite3` from
1922
+ * `@remnic/core/runtime/better-sqlite` so the native-binding lifecycle
1923
+ * is paid for once there (rule 23/38: do not invent a new pattern).
1924
+ *
1925
+ * IR-type re-export policy:
1926
+ * graph-store.ts imports the core IR contract types (`FileIR`,
1927
+ * `SymbolIR`, etc.) from `@remnic/core/coding/coding-graph-types`
1928
+ * and re-exports them so existing `import { type FileIR } from
1929
+ * "./graph-store.js"` call-sites continue to resolve. The store
1930
+ * does NOT redefine these types — it derives from the core contract
1931
+ * so PR2 callers can pass `ParseResult.ir` directly
1932
+ * (chatgpt-codex-connector P2: 'Derive store FileIR from the core
1933
+ * parser contract'). At the package root, `FileIR`/`SymbolIR`
1934
+ * resolve to the @remnic/core contract types re-exported below;
1935
+ * the store-specific `StoreFileIR` (FileIR + edges extension) and
1936
+ * `EdgeIR` are re-exported from the root via graph-store.
1937
+ */
1938
+
1939
+ /** Public engine version. Imported from @remnic/core (single source of truth). */
1940
+ declare const ENGINE_VERSION: "0.1.0-pr1";
1941
+
1942
+ /**
1943
+ * Thrown by `createCodingGraphEngine` while the real implementation is
1944
+ * being landed. It is *not* a generic Error — the `code` field is the
1945
+ * load-bearing signal for programmatic detection (see PR2 contract).
1946
+ */
1947
+ declare class CodingGraphError extends Error {
1948
+ readonly code: CodingGraphErrorCode;
1949
+ readonly engineVersion: string;
1950
+ constructor(code: CodingGraphErrorCode, message: string, engineVersion?: string);
1951
+ }
1952
+
1953
+ 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 };