@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,938 @@
1
+ import { EdgeProvenance } from './graph-schema.js';
2
+ import { CodingGraphLanguage, SymbolIR, ExportIR, RouteIR } from '@remnic/core/coding/coding-graph-types';
3
+ export { CallSiteIR, CodingGraphLanguage, ExportIR, FileIR, ImportIR, RouteIR, SymbolIR } from '@remnic/core/coding/coding-graph-types';
4
+ import '@remnic/core/runtime/better-sqlite';
5
+
6
+ /**
7
+ * Half-open byte span `[startByte, endByte)` — matches @remnic/core's
8
+ * inline span type. Kept as a named alias for API consumers that import
9
+ * `ByteSpan` from the store subpath (issue #1551 / rule 35).
10
+ */
11
+ type ByteSpan = {
12
+ readonly startByte: number;
13
+ readonly endByte: number;
14
+ };
15
+ /**
16
+ * Symbol kind union — matches @remnic/core's `SymbolIR["kind"]` exactly
17
+ * (core does not export this as a named type).
18
+ */
19
+ type SymbolKind = "function" | "class" | "method" | "interface" | "enum" | "type" | "module";
20
+ /**
21
+ * Store-specific edge — references nodes by `qualifiedName` so the store
22
+ * can resolve them against the same batch's symbol set plus the on-disk
23
+ * node table. PR1 only carries CALLS-style edges; PR2 adds the rest of
24
+ * #1552's edge types.
25
+ *
26
+ * Optional `srcNodeId` / `dstNodeId` (issue #1677) carry the content-
27
+ * derived node id (the same canonical hash form the store uses as
28
+ * `nodes.id`, see `nodeIdFor`). When present, the standalone
29
+ * `upsertEdges` path resolves the endpoint by `nodes.id` (unique) instead
30
+ * of by qualified name, so a SIMILAR_TO edge between two symbols that
31
+ * share a qualified name across files is persisted rather than dropped as
32
+ * ambiguous. The qname-keyed file-batch path and the existing
33
+ * `ambiguous … drops edges` behavior are unchanged. Only populated by
34
+ * callers that originate edges from node-id-keyed pairs (the semantic
35
+ * SIMILAR_TO pipeline); structural/trace edges keep the qname path.
36
+ */
37
+ interface EdgeIR {
38
+ /** Qualified name of the source node (caller / definition site). */
39
+ srcQualifiedName: string;
40
+ /** Qualified name of the destination node (callee / type used). */
41
+ dstQualifiedName: string;
42
+ type: string;
43
+ confidence: number;
44
+ provenance: EdgeProvenance;
45
+ /**
46
+ * Optional content-derived source node id (`nodes.id`). When present on
47
+ * a standalone-edge upsert, the store resolves the endpoint by id
48
+ * (unambiguous) instead of falling back to qualified-name resolution.
49
+ */
50
+ readonly srcNodeId?: string;
51
+ /** Optional content-derived destination node id — see {@link EdgeIR.srcNodeId}. */
52
+ readonly dstNodeId?: string;
53
+ }
54
+ /**
55
+ * Store input — the subset of @remnic/core's `FileIR` the store reads,
56
+ * plus the store-specific `edges` extension. A core `FileIR` (from
57
+ * `ParseResult.ir`) is structurally assignable here: all required fields
58
+ * (path, language, contentHash, symbols, imports, exports, callSites,
59
+ * routes) match by name and readonly-ness. PR2 callers pass
60
+ * `{ ...parseResult.ir, edges }` (or the bare IR when edges are absent)
61
+ * with zero casts or field-name translation.
62
+ *
63
+ * PR2 adds optional `exports` and `routes` consumption: when present,
64
+ * the write pipeline marks matching nodes in `node_attributes` so the
65
+ * `deadCode()` query can exclude them via the
66
+ * {@link DEAD_CODE_EXCLUSION} constant. Both fields are optional because
67
+ * a PR1-era caller (or a JSON-IR caller that strips them) still ingests
68
+ * cleanly — the dead-code query simply sees no exclusion flags.
69
+ */
70
+ interface StoreFileIR {
71
+ readonly path: string;
72
+ readonly language: CodingGraphLanguage;
73
+ readonly contentHash: string;
74
+ readonly symbols: readonly SymbolIR[];
75
+ /** Store-specific edges derived from the IR by the caller. */
76
+ readonly edges?: readonly EdgeIR[];
77
+ /**
78
+ * Per-file export list (mirrors core FileIR.exports). When present,
79
+ * the write pipeline marks every node in this file whose `name`
80
+ * matches an ExportIR.name as `is_exported=1` in `node_attributes`.
81
+ * Name-matching is the conventional pattern: a parser that emits a
82
+ * `export const foo` declaration also emits a SymbolIR named `foo`
83
+ * (or omits it if foo is a non-symbol like a plain variable); the
84
+ * dead-code query then excludes surviving exported symbols.
85
+ */
86
+ readonly exports?: readonly ExportIR[];
87
+ /**
88
+ * Per-file HTTP route declarations (mirrors core FileIR.routes). When
89
+ * present, the write pipeline marks the node whose `qualifiedName`
90
+ * equals `route.handlerQualifiedName` as `is_route_handler=1` in
91
+ * `node_attributes`. Route handlers are reachable from HTTP traffic
92
+ * regardless of whether any other indexed node CALLS them.
93
+ */
94
+ readonly routes?: readonly RouteIR[];
95
+ }
96
+ type GraphStoreFailureCode = "db_locked" | "db_corrupt" | "db_error" | "store_closed";
97
+ interface GraphStoreFailure {
98
+ ok: false;
99
+ code: GraphStoreFailureCode;
100
+ }
101
+ interface UpsertResult {
102
+ path: string;
103
+ fileId: number;
104
+ nodeCount: number;
105
+ edgeCount: number;
106
+ /**
107
+ * Dangling edges observed while deleting the file's prior subgraph
108
+ * (cross-file edges whose `dst` belonged to a node owned by this file).
109
+ * Per the PR1 dangling-edge policy in {@link graph-schema}, they are
110
+ * DROPPED, not kept with a marker. Surfaced here so callers can log
111
+ * the loss (rule 11, 40).
112
+ */
113
+ droppedDanglingEdges: number;
114
+ }
115
+ interface UpsertSuccess {
116
+ ok: true;
117
+ results: UpsertResult[];
118
+ }
119
+ type UpsertBatchResult = UpsertSuccess | GraphStoreFailure;
120
+ /**
121
+ * Result of {@link GraphStore.upsertEdges} — a standalone-edge write used by
122
+ * the codegraph ingest_traces surface (issue #1554). `persisted` counts
123
+ * edges actually inserted/updated; `skipped` counts edges whose src or dst
124
+ * did not resolve to exactly one node (dangling-edge policy).
125
+ */
126
+ interface UpsertEdgesSuccess {
127
+ ok: true;
128
+ persisted: number;
129
+ skipped: number;
130
+ }
131
+ type UpsertEdgesResult = UpsertEdgesSuccess | GraphStoreFailure;
132
+ /** Direction of traversal relative to the edge's src→dst orientation. */
133
+ type TraverseDirection = "outgoing" | "incoming" | "both";
134
+ /**
135
+ * Iterative frontier BFS over the edges table. Cycle-safe via a JS
136
+ * visited set keyed by node id; predictable memory regardless of graph
137
+ * shape (recursive CTEs are the documented fallback if benchmarks ever
138
+ * justify them — issue #1552 design section).
139
+ */
140
+ interface TraverseQuery {
141
+ /**
142
+ * Start node. Accepts either a node id or a qualified name — the
143
+ * store resolves a qualified name to its deterministic id via the
144
+ * same `(qualifiedName, filePath, label)` identity used at ingest
145
+ * time. When the qualified name is ambiguous (declared in more than
146
+ * one file), the query is rejected with `code: "ambiguous_start"`
147
+ * so the caller can pass an explicit node id instead.
148
+ */
149
+ start: string;
150
+ /** Default `"outgoing"`. */
151
+ direction?: TraverseDirection;
152
+ /**
153
+ * Edge types to follow (e.g. `["CALLS", "USES_TYPE"]`). When omitted
154
+ * or empty, every edge type in the table is followed. Unknown edge
155
+ * types simply contribute no rows — the query is not rejected
156
+ * because the schema places no CHECK constraint on `edges.type`.
157
+ */
158
+ edgeTypes?: readonly string[];
159
+ /**
160
+ * Maximum BFS depth. Half-open: a node at depth == maxDepth IS
161
+ * included; a node at depth maxDepth+1 is NOT (rule 35). The start
162
+ * node itself sits at depth 0 and is always included in the result
163
+ * set when it exists. A maxDepth of 0 returns just the start node.
164
+ * MUST be a non-negative integer — invalid values are rejected with
165
+ * `code: "invalid_query"` rather than silently clamped (rule 51).
166
+ */
167
+ maxDepth: number;
168
+ }
169
+ interface TraverseHit {
170
+ nodeId: string;
171
+ qualifiedName: string;
172
+ name: string;
173
+ label: string;
174
+ /** Repo-relative file path of the node (joined from files.path). */
175
+ filePath: string;
176
+ /** BFS depth from the start node (start = 0). */
177
+ depth: number;
178
+ }
179
+ type TraverseResult = {
180
+ ok: true;
181
+ hits: TraverseHit[];
182
+ } | ({
183
+ ok: false;
184
+ } & GraphStoreFailure) | {
185
+ ok: false;
186
+ code: "unknown_start" | "ambiguous_start" | "invalid_query";
187
+ };
188
+ /**
189
+ * Default cap on the number of concrete paths {@link GraphStore.traversePaths}
190
+ * enumerates before stopping and flagging `truncated`. Bounds the worst-case
191
+ * exponential blowup of relationship-simple path enumeration on dense
192
+ * subgraphs (issue #1650). Callers may override per-query via
193
+ * {@link TraversePathsQuery.maxPaths}.
194
+ */
195
+ declare const DEFAULT_TRAVERSE_PATHS_MAX = 10000;
196
+ /**
197
+ * Hard upper bound on {@link TraversePathsQuery.maxHops}. The DFS recurses
198
+ * once per hop; an unbounded depth (e.g. a Cypher `*15000`) would overflow
199
+ * the call stack before `maxPaths` could stop it. 1000 is ~100x any
200
+ * realistic code-graph depth and recurses safely (chatgpt-codex-connector
201
+ * P2: 'Avoid recursive DFS for deep bounded paths').
202
+ */
203
+ declare const MAX_TRAVERSE_PATHS_HOPS = 1000;
204
+ /**
205
+ * Path-enumerating traversal query (issue #1650). Mirrors {@link TraverseQuery}
206
+ * but yields CONCRETE paths rather than BFS-shortest-depth reachability, so an
207
+ * exact `*N` (N > 1) hop count is honored for nodes reachable at both a shorter
208
+ * and a length-N path.
209
+ */
210
+ interface TraversePathsQuery {
211
+ /** Start node id or qualified name (same resolution rules as {@link TraverseQuery.start}). */
212
+ start: string;
213
+ /** Default `"outgoing"`. */
214
+ direction?: TraverseDirection;
215
+ /** Edge types to follow; omitted/empty means every type. */
216
+ edgeTypes?: readonly string[];
217
+ /**
218
+ * Inclusive upper bound on enumerated path LENGTH (hop count). MUST be a
219
+ * non-negative integer. A `maxHops` of 0 yields no paths (every enumerated
220
+ * path has length >= 1); callers that need the length-0 trivial path add it
221
+ * themselves.
222
+ */
223
+ maxHops: number;
224
+ /**
225
+ * Inclusive LOWER bound on EMITTED path length (hop count). Defaults
226
+ * to 1. The DFS still EXPLORES shorter prefixes to reach longer paths,
227
+ * but only EMITS (and counts toward {@link maxPaths}) paths whose length
228
+ * is in `[minHops, maxHops]` -- so an exact `*N` cap is not consumed by
229
+ * the shorter prefixes (cursor Bugbot: 'Path cap ignores hop minimum').
230
+ * MUST be a positive integer (>= 1) when present.
231
+ */
232
+ minHops?: number;
233
+ /**
234
+ * Safety cap on total enumerated paths. Defaults to
235
+ * {@link DEFAULT_TRAVERSE_PATHS_MAX}. When the cap is reached, enumeration
236
+ * STOPS and the result carries `truncated: true` so callers can detect that
237
+ * the result is incomplete (e.g. to narrow the query or raise the cap).
238
+ */
239
+ maxPaths?: number;
240
+ }
241
+ /**
242
+ * One enumerated path. The endpoint node is fully resolved; the full node-id
243
+ * sequence lets callers reconstruct the path (issue #1650 acceptance).
244
+ */
245
+ interface TraversePathHit {
246
+ nodeId: string;
247
+ qualifiedName: string;
248
+ name: string;
249
+ label: string;
250
+ filePath: string;
251
+ /** Length of this path in hops (>= 1). */
252
+ length: number;
253
+ /** Full path as node ids, start-first (`length + 1` entries). */
254
+ nodeIds: string[];
255
+ /**
256
+ * Edge type per hop, parallel to {@link nodeIds} (`length` entries). Two
257
+ * distinct relationships can connect the same node pair with different
258
+ * types (the edges table is UNIQUE on `(src, dst, type)`); exposing the
259
+ * type per hop lets callers distinguish those otherwise-identical-node
260
+ * paths (chatgpt-codex-connector P2: 'Include edge identity in path
261
+ * hits').
262
+ */
263
+ edgeTypes: string[];
264
+ /**
265
+ * Per-hop edge endpoints, parallel to {@link nodeIds} (`length` entries).
266
+ * Under `direction: "both"` antiparallel same-type edges (A->B and B->A)
267
+ * yield distinct relationship-simple paths that share nodeIds + edgeTypes;
268
+ * the src/dst per hop disambiguates which edge was traversed and in which
269
+ * direction (chatgpt-codex-connector P2: 'Include edge endpoints in path
270
+ * hits').
271
+ */
272
+ edgeEndpoints: Array<{
273
+ src: string;
274
+ dst: string;
275
+ }>;
276
+ }
277
+ type TraversePathsResult = {
278
+ ok: true;
279
+ hits: TraversePathHit[];
280
+ truncated: boolean;
281
+ } | ({
282
+ ok: false;
283
+ } & GraphStoreFailure) | {
284
+ ok: false;
285
+ code: "unknown_start" | "ambiguous_start" | "invalid_query";
286
+ };
287
+ /**
288
+ * Structured node search. All filters are AND-combined; every filter
289
+ * is optional so the bare query `{}` returns the whole graph (capped
290
+ * by `limit`). Patterns use SQLite `LIKE` semantics — `%` matches any
291
+ * run, `_` matches one character — applied case-insensitively via
292
+ * `LIKE ... COLLATE NOCASE`. Patterns are parameter-bound, never
293
+ * string-interpolated, so a `%`/`_` in user input cannot inject SQL.
294
+ */
295
+ interface SearchQuery {
296
+ /** Filter by node label (the symbol kind, e.g. `"function"`). */
297
+ label?: string;
298
+ /** LIKE pattern on `nodes.name` (case-insensitive). */
299
+ namePattern?: string;
300
+ /** LIKE pattern on `files.path` (case-insensitive). */
301
+ filePattern?: string;
302
+ /**
303
+ * Inclusive lower bound on total degree (in + out edge count).
304
+ * Combined with {@link degreeMax} for a half-open? — no, inclusive
305
+ * on both ends by convention since degree is an integer count, not
306
+ * a span (rule 35 covers byte/time spans, not integer ranges).
307
+ */
308
+ degreeMin?: number;
309
+ /** Inclusive upper bound on total degree. */
310
+ degreeMax?: number;
311
+ /**
312
+ * Cap on returned rows. Default 100; clamped to [0, 1000]. A
313
+ * `limit: 0` returns an empty `hits` array (rule 27 — guard the
314
+ * slice/LIMIT against the zero case).
315
+ */
316
+ limit?: number;
317
+ }
318
+ interface SearchHit {
319
+ nodeId: string;
320
+ qualifiedName: string;
321
+ name: string;
322
+ label: string;
323
+ filePath: string;
324
+ /** Total in + out edge count for this node. */
325
+ degree: number;
326
+ }
327
+ type SearchResult = {
328
+ ok: true;
329
+ hits: SearchHit[];
330
+ } | ({
331
+ ok: false;
332
+ } & GraphStoreFailure) | {
333
+ ok: false;
334
+ code: "invalid_query";
335
+ };
336
+ /** Aggregate counts over the whole graph — single round-trip. */
337
+ interface SchemaStats {
338
+ files: number;
339
+ nodes: number;
340
+ edges: number;
341
+ /** Node count grouped by `label` (symbol kind). */
342
+ nodesByLabel: Record<string, number>;
343
+ /** Edge count grouped by `type`. */
344
+ edgesByType: Record<string, number>;
345
+ }
346
+ type SchemaStatsResult = {
347
+ ok: true;
348
+ stats: SchemaStats;
349
+ } | ({
350
+ ok: false;
351
+ } & GraphStoreFailure);
352
+ interface DeadCodeHit {
353
+ nodeId: string;
354
+ qualifiedName: string;
355
+ name: string;
356
+ label: string;
357
+ filePath: string;
358
+ }
359
+ type DeadCodeResult = {
360
+ ok: true;
361
+ hits: DeadCodeHit[];
362
+ } | ({
363
+ ok: false;
364
+ } & GraphStoreFailure);
365
+ /**
366
+ * Read a symbol's source span from disk. The store NEVER persists file
367
+ * contents (privacy + DB size — issue #1552 design); `snippetFor`
368
+ * resolves the node's `files.path` against {@link GraphStoreOptions.repoRoot}
369
+ * and slices `[span_start, span_end)` from the on-disk bytes.
370
+ */
371
+ interface SnippetQuery {
372
+ /**
373
+ * Qualified name to resolve. Optional when `nodeId` is supplied — the
374
+ * guard requires at least one of the two.
375
+ */
376
+ qualifiedName?: string;
377
+ /**
378
+ * Optional repo root override. When set, the snippet is read from this
379
+ * root instead of the root captured at GraphStore.open() time, so a
380
+ * caller that supplies its own repoRoot (e.g. semanticQuery) hydrates
381
+ * snippets even when the store was opened without one (chatgpt-codex-
382
+ * connector + cursor: 'Snippet hydration ignores query repoRoot').
383
+ */
384
+ repoRoot?: string;
385
+ /**
386
+ * Optional deterministic node id. When set, the lookup resolves by
387
+ * `nodes.id` (unique) instead of `qualified_name`, so a hit whose
388
+ * qualified name is duplicated across files still hydrates the exact
389
+ * node's snippet instead of failing with `ambiguous_name`
390
+ * (chatgpt-codex-connector P2: 'Hydrate snippets by node id as well').
391
+ */
392
+ nodeId?: string;
393
+ /**
394
+ * Optional lines of context to include before and after the span
395
+ * (default 0 — exact span only). Context is line-aligned: the slice
396
+ * expands to the nearest line boundary at each end.
397
+ */
398
+ contextLines?: number;
399
+ }
400
+ interface SnippetSuccess {
401
+ ok: true;
402
+ qualifiedName: string;
403
+ filePath: string;
404
+ /** Absolute path the bytes were read from (`repoRoot/files.path`). */
405
+ absolutePath: string;
406
+ startByte: number;
407
+ endByte: number;
408
+ /** The decoded source slice (UTF-8). */
409
+ text: string;
410
+ lang: string;
411
+ }
412
+ type SnippetFailureCode = "not_found" | "ambiguous_name" | "repo_root_unset" | "read_failed" | "invalid_query" | "store_closed" | "db_locked" | "db_corrupt" | "db_error";
413
+ type SnippetResult = SnippetSuccess | {
414
+ ok: false;
415
+ code: SnippetFailureCode;
416
+ };
417
+ /** Result of readMeta — `{ ok: true; value: null }` is a genuinely absent key;
418
+ * a tagged failure is a backend error (rule 22). */
419
+ type ReadMetaResult = {
420
+ ok: true;
421
+ value: string | null;
422
+ } | ({
423
+ ok: false;
424
+ } & GraphStoreFailure);
425
+ /** Result of readFileHashes — `{ ok: true; hashes: <empty> }` is an empty
426
+ * index; a tagged failure is a backend error (rule 22). */
427
+ type ReadFileHashesResult = {
428
+ ok: true;
429
+ hashes: Map<string, string>;
430
+ } | ({
431
+ ok: false;
432
+ } & GraphStoreFailure);
433
+ /** A co-change edge row returned by readCoChanges. */
434
+ interface ReadCoChangeEdge {
435
+ readonly fileA: string;
436
+ readonly fileB: string;
437
+ readonly support: number;
438
+ readonly confidence: number;
439
+ }
440
+ /** Result of readCoChanges — `{ ok: true; edges: [] }` means no edges
441
+ * recorded; a tagged failure is a backend error (rule 22). */
442
+ type ReadCoChangesResult = {
443
+ ok: true;
444
+ edges: readonly ReadCoChangeEdge[];
445
+ } | ({
446
+ ok: false;
447
+ } & GraphStoreFailure);
448
+ /**
449
+ * The single source of truth for what `deadCode()` EXCLUDES from the
450
+ * candidate set. Anything matched by these patterns or flags is treated
451
+ * as a non-dead surface even when it has zero inbound call/usage edges.
452
+ *
453
+ * This constant exists so the exclusion criteria are NAMED, DOCUMENTED,
454
+ * and auditable in one place — not scattered across ad-hoc `WHERE`
455
+ * clauses (rule 53 analog). Adding a new exclusion category means
456
+ * extending this constant plus the matching `node_attributes` column;
457
+ * the query then picks both up automatically.
458
+ *
459
+ * Categories:
460
+ * - {@link INBOUND_USAGE_EDGE_TYPES} — an inbound edge of any of these
461
+ * types disqualifies a node from being dead.
462
+ * - {@link TEST_PATH_PATTERNS} — a node whose `files.path` matches is
463
+ * in a test file; tests can call into private code without the
464
+ * production graph seeing the edge.
465
+ * - {@link ENTRY_POINT_PATH_PATTERNS} — process entry points (index,
466
+ * main, cli, bin/); these are reachable from outside the graph.
467
+ * - {@link EXCLUDED_ATTRIBUTE_FLAGS} — per-node flags stored in
468
+ * `node_attributes` (set at write time from FileIR.exports /
469
+ * FileIR.routes); `is_exported` and `is_route_handler`.
470
+ */
471
+ declare const DEAD_CODE_EXCLUSION: {
472
+ /**
473
+ * Edge types that — when pointing INTO a node — count as "this node
474
+ * is used". Mirrors the issue's `CALLS/USAGE` wording plus the four
475
+ * call-flavored edge types in the wider coding-graph vocabulary.
476
+ */
477
+ readonly INBOUND_USAGE_EDGE_TYPES: readonly ["CALLS", "USES_TYPE", "ASYNC_CALLS", "HTTP_CALLS", "DATA_FLOWS"];
478
+ /**
479
+ * File-path regexes identifying test files. Matched against
480
+ * `files.path` (repo-relative, forward slashes).
481
+ */
482
+ readonly TEST_PATH_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
483
+ /**
484
+ * File-path regexes identifying entry points (reachable from
485
+ * outside the indexed code). Matched against `files.path`. Kept
486
+ * deliberately narrow — `server.ts` / `app.ts` are intentionally
487
+ * NOT treated as entry points because they are common module
488
+ * names that may also contain dead helpers. The conservative
489
+ * direction is to report a symbol as dead rather than hide it.
490
+ */
491
+ readonly ENTRY_POINT_PATH_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp, RegExp];
492
+ /**
493
+ * Columns on `node_attributes` whose value being `1` excludes the
494
+ * node. Names mirror the schema so a future column add is a one-line
495
+ * constant extension + a query clause (no scattered edits).
496
+ */
497
+ readonly EXCLUDED_ATTRIBUTE_FLAGS: readonly ["is_exported", "is_route_handler"];
498
+ };
499
+ interface GraphStoreOptions {
500
+ /** Absolute path to the SQLite file. The caller resolves the namespace. */
501
+ dbPath: string;
502
+ /**
503
+ * Optional absolute path to the repo root. When set, `snippetFor()`
504
+ * resolves a node's repo-relative `files.path` against this root to
505
+ * read its source span from disk. When unset, `snippetFor()` returns
506
+ * `code: "repo_root_unset"` for every call. The store NEVER persists
507
+ * file contents (privacy + DB size — issue #1552 design); this is
508
+ * the only path the read-side uses.
509
+ */
510
+ repoRoot?: string;
511
+ }
512
+ /**
513
+ * One DB per instance. The store does NOT mutate its path or close the
514
+ * handle until {@link close} is called explicitly (rule 11).
515
+ */
516
+ declare class GraphStore {
517
+ private readonly db;
518
+ private readonly queue;
519
+ private readonly repoRoot;
520
+ private closed;
521
+ private closing;
522
+ /**
523
+ * True once close() has begun (closing) or completed (closed). Public so
524
+ * callers that hold a GraphStore reference can return the documented
525
+ * 'store_closed' degradation code instead of treating a closed store as
526
+ * an empty graph (cursor Bugbot: 'Closed store reports success'). The
527
+ * read primitives already short-circuit on this internally; this getter
528
+ * lets the semantic entry points do the same BEFORE calling a read that
529
+ * would return [].
530
+ */
531
+ get isClosed(): boolean;
532
+ private closePromise;
533
+ private constructor();
534
+ /**
535
+ * Open a store at the given dbPath. Creates parent directories and
536
+ * applies the schema (idempotent — also handles upgrade). The dbPath
537
+ * does no namespace resolution.
538
+ */
539
+ static open(options: GraphStoreOptions): Promise<GraphStore>;
540
+ /**
541
+ * The current schema_version row. Test seam — never expires, never
542
+ * cached so migrations land without a restart.
543
+ */
544
+ schemaVersion(): number;
545
+ /**
546
+ * Ingest a batch of IR files atomically. One transaction wraps every
547
+ * file's delete + insert; if any file throws, the whole batch rolls
548
+ * back (rule 34 — never partial-write a coding graph).
549
+ *
550
+ * Re-ingesting the same IR is a no-op once the rows are written
551
+ * (idempotency — node ids are deterministic so the second pass collides
552
+ * on PRIMARY KEY).
553
+ *
554
+ * Two-pass ordering: pass 1 upserts every file's nodes (so FTS stays
555
+ * in sync and cross-file edge targets exist by the time pass 2 runs),
556
+ * pass 2 resolves edges against the full batch's node map and deletes
557
+ * prior edges owned by these files so changed confidence/provenance
558
+ * values overwrite (chatgpt-codex-connector P1 + cursor medium + PR1
559
+ * design anchor in graph-schema).
560
+ *
561
+ * Tagging:
562
+ * - `{ok:true, results}` — every file's counts.
563
+ * - `{ok:false, code:"db_locked"}` — busy_timeout elapsed; caller may
564
+ * retry. NOT a thrown error so the agent can degrade gracefully.
565
+ * - `{ok:false, code:"db_corrupt"}` — SQLite reported
566
+ * `database disk image is malformed`; the caller must surface and
567
+ * stop trusting this DB.
568
+ */
569
+ upsertFileBatch(files: StoreFileIR[],
570
+ /**
571
+ * Optional paths to delete in the SAME transaction as the upsert
572
+ * (issue #1553 — the reindex executor prunes deleted files atomically
573
+ * with the changed-files upsert so a mid-batch failure cannot leave
574
+ * the graph with committed deletions but no re-ingested replacements).
575
+ * Cascades to nodes + edges + node_attributes via the schema's
576
+ * `ON DELETE CASCADE`. Empty/omitted = no deletions.
577
+ */
578
+ deletePaths?: readonly string[]): Promise<UpsertBatchResult>;
579
+ /**
580
+ * Upsert standalone edges whose endpoints are resolved from the FULL
581
+ * database (not just a per-file batch). Used by the codegraph
582
+ * ingest_traces surface (issue #1554) to persist runtime HTTP_CALLS
583
+ * observations as edges with `provenance: "trace"` — upgrading
584
+ * confidence on existing edges and inserting new ones.
585
+ *
586
+ * Endpoint resolution: when an edge carries `srcNodeId` / `dstNodeId`
587
+ * (issue #1677 — the SIMILAR_TO pipeline populates them from
588
+ * content-derived node ids), the endpoint is resolved by `nodes.id`
589
+ * (unique primary key), so an edge between two symbols that share a
590
+ * qualified name across files is persisted rather than dropped as
591
+ * ambiguous. Edges WITHOUT node ids fall back to qualified_name
592
+ * resolution via the global `resolveNodeId` (unambiguous single-match
593
+ * policy). Edges whose endpoints do not resolve (missing node id row OR
594
+ * an ambiguous/dangling qualified name) are skipped (and counted in
595
+ * `skipped`) rather than attached to the wrong node — the dangling-edge
596
+ * policy from `upsertFileBatch` applies.
597
+ *
598
+ * Serialized on the store's write queue like `upsertFileBatch` so a
599
+ * concurrent file-batch upsert and a trace upsert cannot interleave
600
+ * (rule 40).
601
+ */
602
+ upsertEdges(edges: readonly EdgeIR[]): Promise<UpsertEdgesResult>;
603
+ /** Wait for pending writes to drain — test seam. */
604
+ drain(): Promise<void>;
605
+ /**
606
+ * Read a value from the `meta` table. Returns `null` when the key is
607
+ * absent. Synchronous (like the other read primitives) so the reindex
608
+ * planner can read `last_indexed_head` without an await.
609
+ */
610
+ readMeta(key: string): ReadMetaResult;
611
+ /**
612
+ * Write a key/value pair to the `meta` table. Synchronous — runs in its
613
+ * own implicit transaction. The reindex executor calls this AFTER
614
+ * `upsertFileBatch` resolves (rule 25: head/state updates only after
615
+ * the data transaction commits). A crash between the two leaves the old
616
+ * head, and the next run re-ingests idempotently (deterministic node ids).
617
+ */
618
+ writeMeta(key: string, value: string): void;
619
+ /**
620
+ * Read every file row's path → content_hash. Used by hash_scan mode
621
+ * to detect content drift without a reachable base commit (issue #1553).
622
+ */
623
+ readFileHashes(): ReadFileHashesResult;
624
+ /**
625
+ * Drop file rows by path, cascading to their nodes + edges +
626
+ * node_attributes (the schema's `ON DELETE CASCADE` from `files(id)`
627
+ * handles the cascade — `foreign_keys = ON` is set in `open()`).
628
+ * Used by the reindex executor to prune deleted files.
629
+ *
630
+ * Paths are chunked under the SQLite variable limit (rule 23 pattern).
631
+ */
632
+ dropFiles(paths: readonly string[]): Promise<void>;
633
+ /**
634
+ * Chunk a parameterized DELETE-with-IN-list under SQLite's variable
635
+ * bind limit. Mirrors the chunking pattern used by `runChunkedUpdate`
636
+ * and the stale-edge deletes.
637
+ */
638
+ private runChunkedDelete;
639
+ /**
640
+ * PR3 (issue #1553): upsert co-change edges into the `co_changes`
641
+ * table. Clears existing edges then inserts the new set in one
642
+ * transaction (idempotent — re-running on unchanged history produces
643
+ * the same table). Serialized through the write queue.
644
+ */
645
+ /**
646
+ * PR3 (issue #1553): upsert co-change edges into the `co_changes`
647
+ * table. Clears existing edges then inserts the new set in one
648
+ * transaction (idempotent — re-running on unchanged history produces
649
+ * the same table). Serialized through the write queue.
650
+ *
651
+ * Returns `{ ok: false, code: "store_closed" }` when the store is
652
+ * closed/closing so the caller does NOT believe mining succeeded
653
+ * while nothing was persisted (cursor Bugbot: 'Co-change store
654
+ * reports false success').
655
+ */
656
+ upsertCoChanges(edges: readonly {
657
+ readonly fileA: string;
658
+ readonly fileB: string;
659
+ readonly support: number;
660
+ readonly confidence: number;
661
+ }[]): Promise<{
662
+ ok: true;
663
+ } | {
664
+ ok: false;
665
+ code: "store_closed";
666
+ } | {
667
+ ok: false;
668
+ code: "db_error";
669
+ }>;
670
+ /**
671
+ * PR3 (issue #1553): read co-change edges for a file. Returns edges
672
+ * where the file is either `file_a` or `file_b`. Synchronous read.
673
+ */
674
+ readCoChanges(filePath: string): ReadCoChangesResult;
675
+ /**
676
+ * Close the SQLite handle after draining the write queue. A batch
677
+ * that has already been scheduled on the queue would otherwise run
678
+ * against a closed DB and surface as `db_corrupt` — the caller
679
+ * would stop trusting the store for unrelated reasons. Drain first,
680
+ * then close (cursor Bugbot #09be5784).
681
+ */
682
+ close(): Promise<void>;
683
+ /** Drain queued writes then close the SQLite handle exactly once. */
684
+ private finishClose;
685
+ private runUpsert;
686
+ /**
687
+ * Standalone-edge upsert body (runs under the write queue). Resolves
688
+ * both endpoints from the full DB via the unambiguous single-match
689
+ * `resolveNodeId` fallback, then upserts each edge with the same
690
+ * ON CONFLICT(src,dst,type) policy as the file-batch path. Edges whose
691
+ * src or dst do not resolve to exactly one node are skipped (counted
692
+ * in `skipped`) per the dangling-edge policy.
693
+ */
694
+ private runUpsertEdges;
695
+ /**
696
+ * Pass 1a: upsert the file row and every symbol node, refreshing the
697
+ * contentless `nodes_fts` index in lockstep, and compute the set of
698
+ * stale node ids this file wants to prune (deterministic id, NOT
699
+ * qualified_name, so a kind change gets a new id and the OLD row is
700
+ * deleted). The prune itself — and the dangling-edge count that
701
+ * gates it — is deferred to {@link pruneFileNodes} so the whole batch
702
+ * shares one batch-wide view of what is being pruned before any
703
+ * cascade runs.
704
+ */
705
+ private upsertFileNodes;
706
+ /**
707
+ * Pass 1b: count the dangling edges this file's prune will drop and
708
+ * perform the cascade delete + FTS cleanup. A dangling edge is one
709
+ * whose dst is pruned by THIS file but whose src survives — and
710
+ * "survives" is judged against the BATCH-WIDE pruned set, so an edge
711
+ * whose both ends are pruned (possibly in different files) is
712
+ * cascade-deleted and never reported as dangling. This makes the
713
+ * reported loss independent of the order files are visited in
714
+ * (chatgpt-codex-connector P2: 'Count dangling edges against the
715
+ * whole batch').
716
+ */
717
+ private pruneFileNodes;
718
+ /**
719
+ * Pass 2: re-insert edges for one file. Runs AFTER every file's
720
+ * nodes are in place (the full batch is committed to nodes) so
721
+ * cross-file edges resolve regardless of input order. Stale edges
722
+ * for nodes owned by this file are deleted first so a changed
723
+ * `confidence` or `provenance` actually overwrites the prior row
724
+ * (chatgpt-codex-connector P1: ON CONFLICT DO NOTHING silently
725
+ * kept stale edges across re-ingests).
726
+ */
727
+ private upsertFileEdges;
728
+ /**
729
+ * Pass 3 (PR2): upsert `node_attributes` rows for this file's
730
+ * surviving nodes, derived from the IR's optional `exports` and
731
+ * `routes` arrays. Per-field preservation semantics (mirrors the
732
+ * edges pass, generalized to two independent flags):
733
+ * - `exports == null` (omitted) → preserve existing `is_exported`
734
+ * flags untouched (PR1-era IR has no exports field). The
735
+ * `is_route_handler` flag is rebuilt independently from
736
+ * `routes` — the two columns do NOT interact.
737
+ * - `exports === []` (explicit empty) → wipe the file's
738
+ * `is_exported` flags (the caller is asserting "this file
739
+ * exports nothing").
740
+ * - same rule for `routes` / `is_route_handler`.
741
+ *
742
+ * A symbol is `is_exported=1` when its `name` matches an entry in
743
+ * `ir.exports` (multiple symbols with the same name in one file all
744
+ * get the flag — the dead-code query treats this conservatively,
745
+ * never silently picking one). A symbol is `is_route_handler=1`
746
+ * when its `qualifiedName` equals a route's `handlerQualifiedName`.
747
+ *
748
+ * Implementation: per-flag UPDATE, not a delete-then-insert (the
749
+ * original PR2 implementation wiped both flags whenever either field
750
+ * was present, so a re-ingest with only `exports` silently dropped
751
+ * `is_route_handler` — cursor Bugbot + chatgpt-codex-connector P2).
752
+ * The two flags live in the same row keyed by node_id; INSERT OR
753
+ * IGNORE ensures a row exists, then UPDATE-per-flag changes only
754
+ * the column the IR is asserting.
755
+ */
756
+ private upsertFileAttributes;
757
+ /**
758
+ * Chunk a parameterized UPDATE-with-IN-list under SQLite's variable
759
+ * bind limit. The SQL template uses `%PH%` as a placeholder for the
760
+ * `?,?,…` list. Mirrors the chunking pattern PR1 uses for deletes.
761
+ */
762
+ private runChunkedUpdate;
763
+ /**
764
+ * Iterative frontier BFS over the edges table. Cycle-safe via a JS
765
+ * visited set keyed by node id; depth-capped by {@link TraverseQuery.maxDepth}
766
+ * (half-open — depth==maxDepth is INCLUDED, maxDepth+1 is NOT — rule 35).
767
+ * The start node is always included at depth 0 when it exists.
768
+ *
769
+ * Reads the edges table via a single prepared statement per
770
+ * direction; the frontier expands level-by-level so memory is
771
+ * bounded by the visited set's size, not the recursion depth.
772
+ */
773
+ traverse(query: TraverseQuery): TraverseResult;
774
+ /**
775
+ * Path-enumerating traversal (issue #1650). Unlike {@link traverse}'s BFS —
776
+ * which visits each node ONCE at its shortest-path depth and so cannot honor
777
+ * an exact `*N` (N > 1) hop count for nodes reachable at both a shorter and a
778
+ * length-N path — this primitive enumerates concrete relationship-simple
779
+ * paths from the start, yielding one hit per distinct (path, endpoint) pair
780
+ * up to {@link TraversePathsQuery.maxHops}.
781
+ *
782
+ * Cycle safety uses RELATIONSHIP UNIQUENESS (the real Cypher rule): a single
783
+ * path never traverses the same edge twice, keyed by the edge's canonical
784
+ * `(src, dst, type)` identity. A node MAY recur in a path via distinct edges
785
+ * (e.g. A->B->A over two different edges) — that is correct Cypher behavior.
786
+ * The {@link TraversePathsQuery.maxHops} cap bounds each path's length;
787
+ * {@link TraversePathsQuery.maxPaths} bounds the total enumerated count so a
788
+ * dense subgraph cannot blow enumeration up exponentially without notice
789
+ * (when hit, enumeration stops and the result carries `truncated: true`).
790
+ *
791
+ * Every yielded path has length >= 1 (at least one edge). A length-0 "path"
792
+ * (the trivial start->start) is NOT enumerated; callers that need the start
793
+ * node for a `*0..N` bound add it themselves.
794
+ */
795
+ traversePaths(query: TraversePathsQuery): TraversePathsResult;
796
+ /**
797
+ * Structured node search. All filters are AND-combined; patterns use
798
+ * SQLite LIKE (case-insensitive via COLLATE NOCASE). Patterns and
799
+ * limits are parameter-bound, never string-interpolated, so user
800
+ * input cannot inject SQL.
801
+ */
802
+ searchGraph(query: SearchQuery): SearchResult;
803
+ /**
804
+ * Aggregate counts over the whole graph. Single round-trip: one
805
+ * scalar per metric, two GROUP BY queries for the by-label /
806
+ * by-type histograms.
807
+ */
808
+ schemaStats(): SchemaStatsResult;
809
+ /**
810
+ * Dead-code candidates: nodes with zero inbound
811
+ * {@link DEAD_CODE_EXCLUSION.INBOUND_USAGE_EDGE_TYPES} edges, excluding
812
+ * nodes whose `node_attributes` row marks them exported / route-handler
813
+ * AND nodes whose file path matches the test / entry-point patterns
814
+ * in {@link DEAD_CODE_EXCLUSION}.
815
+ *
816
+ * The exclusion criteria live in the named constant — not in
817
+ * ad-hoc WHERE clauses (rule 53 analog). The stored flags come from
818
+ * the write pipeline's `upsertFileAttributes` pass, which the IR's
819
+ * `exports` and `routes` arrays feed.
820
+ */
821
+ deadCode(): DeadCodeResult;
822
+ /**
823
+ * Read a symbol's source span from disk. The store NEVER persists
824
+ * file contents (privacy + DB size — issue #1552 design); this
825
+ * method resolves `files.path` against {@link GraphStoreOptions.repoRoot}
826
+ * and slices the half-open `[startByte, endByte)` span from the
827
+ * on-disk bytes.
828
+ */
829
+ snippetFor(query: SnippetQuery): Promise<SnippetResult>;
830
+ /**
831
+ * Upsert one symbol vector. Idempotent on (node_id, model_id). The
832
+ * caller (the semantic indexer) has ALREADY decided to re-embed (the
833
+ * content_hash differs from the cached row); this method just persists.
834
+ */
835
+ writeSymbolVector(input: {
836
+ readonly nodeId: string;
837
+ readonly modelId: string;
838
+ readonly contentHash: string;
839
+ readonly dims: number;
840
+ readonly vector: Float32Array;
841
+ }): Promise<boolean>;
842
+ /**
843
+ * Read one vector row by (node_id, model_id). Returns null when absent.
844
+ * Used by the indexer's cache-check path (skip re-embed when content_hash
845
+ * matches) and by the cache-hit test.
846
+ */
847
+ readSymbolVector(nodeId: string, modelId: string): {
848
+ readonly contentHash: string;
849
+ readonly dims: number;
850
+ readonly vector: Float32Array;
851
+ } | null;
852
+ /**
853
+ * Read every vector row for a given model. Used by brute-force cosine
854
+ * retrieval (SIMILAR_TO confirmation + semantic_query). Returns node
855
+ * metadata alongside the vector so callers can hydrate hits without a
856
+ * second round-trip.
857
+ */
858
+ readAllSymbolVectors(modelId: string): readonly {
859
+ readonly nodeId: string;
860
+ readonly qualifiedName: string;
861
+ readonly filePath: string;
862
+ readonly kind: string;
863
+ readonly dims: number;
864
+ readonly vector: Float32Array;
865
+ readonly contentHash: string;
866
+ }[];
867
+ /**
868
+ * Delete vector rows for a set of node ids (all models). Used by the
869
+ * cache-invalidation path when a symbol's canonical text changed AND
870
+ * it could not be re-embedded (provider gone) — the stale vector must
871
+ * not survive to pollute cosine retrieval. Cascades via the schema's
872
+ * ON DELETE CASCADE on nodes(id) when a node is pruned, so this method
873
+ * is only for the targeted-invalidation path.
874
+ */
875
+ deleteSymbolVectors(nodeIds: readonly string[]): Promise<void>;
876
+ /**
877
+ * Remove every SIMILAR_TO edge written by the semantic similarity
878
+ * pipeline (type 'SIMILAR_TO', provenance 'semantic'). The pipeline
879
+ * recomputes the FULL near-clone edge set on each run, so callers MUST
880
+ * clear the prior set before upserting the new one — otherwise an edge
881
+ * between two symbols that stopped being similar survives indefinitely
882
+ * and graph traversal keeps reporting a stale clone relationship
883
+ * (chatgpt-codex-connector P2: 'Replace old SIMILAR_TO edges on
884
+ * recompute'). Scoped to provenance 'semantic' so non-semantic edges
885
+ * are untouched. Serialized via the write queue so it cannot interleave
886
+ * a concurrent file-batch edge upsert.
887
+ */
888
+ clearSemanticSimilarToEdges(): Promise<void>;
889
+ /**
890
+ * Read every node with its file path + span, for the semantic indexer.
891
+ * The indexer reads source text from disk (via repoRoot) and builds
892
+ * canonical text per node. Returns kind + qualified_name + span so the
893
+ * indexer can reconstruct the SymbolIR-equivalent without a second
894
+ * join. Ordered by qualified_name for deterministic processing order.
895
+ */
896
+ readNodesForSemantic(): readonly {
897
+ readonly nodeId: string;
898
+ readonly qualifiedName: string;
899
+ readonly kind: string;
900
+ readonly filePath: string;
901
+ readonly startByte: number;
902
+ readonly endByte: number;
903
+ readonly lang: string;
904
+ }[];
905
+ /**
906
+ * Read the callers and callees of a node by qualified name, for
907
+ * semantic_query hydration (the issue: hydrate each hit with graph
908
+ * context — defining file, direct callers/callees).
909
+ */
910
+ readNeighbors(qualifiedName: string): {
911
+ readonly callers: readonly string[];
912
+ readonly callees: readonly string[];
913
+ };
914
+ /**
915
+ * Read callers/callees by node id directly (avoids the qualified-name
916
+ * ambiguity when duplicate names exist across files). Used by
917
+ * semantic_query hydration (chatgpt-codex-connector: 'Use the hit node
918
+ * id when hydrating neighbors').
919
+ */
920
+ readNeighborsByNodeId(nodeId: string): {
921
+ readonly callers: readonly string[];
922
+ readonly callees: readonly string[];
923
+ };
924
+ }
925
+ interface NodeIdInput {
926
+ qualifiedName: string;
927
+ filePath: string;
928
+ label: string;
929
+ }
930
+ /**
931
+ * sha256 over the sorted key material. The exact form MUST match between
932
+ * ingest and lookup; tests assert this. Sort is stable (string compare),
933
+ * no separators needed — the three fields are concatenated with a length
934
+ * prefix so collision space is unambiguous.
935
+ */
936
+ declare function nodeIdFor(input: NodeIdInput): string;
937
+
938
+ export { type ByteSpan, DEAD_CODE_EXCLUSION, DEFAULT_TRAVERSE_PATHS_MAX, type DeadCodeHit, type DeadCodeResult, type EdgeIR, GraphStore, type GraphStoreFailure, type GraphStoreFailureCode, type GraphStoreOptions, MAX_TRAVERSE_PATHS_HOPS, type NodeIdInput, type ReadCoChangeEdge, type ReadCoChangesResult, type ReadFileHashesResult, type ReadMetaResult, type SchemaStats, type SchemaStatsResult, type SearchHit, type SearchQuery, type SearchResult, type SnippetFailureCode, type SnippetQuery, type SnippetResult, type SnippetSuccess, type StoreFileIR, type SymbolKind, type TraverseDirection, type TraverseHit, type TraversePathHit, type TraversePathsQuery, type TraversePathsResult, type TraverseQuery, type TraverseResult, type UpsertBatchResult, type UpsertEdgesResult, type UpsertEdgesSuccess, type UpsertResult, type UpsertSuccess, nodeIdFor };