Summary
generateNodeId derives a node's identity from filePath:kind:name:line without any column/offset. Two symbols that share a file, kind, name, and line therefore collapse to the same id, and since nodes are persisted with INSERT OR REPLACE INTO nodes on a PRIMARY KEY (id), the second silently overwrites the first — the earlier node's row (and anything an edge pointed at via that id) is lost. The canonical trigger is a one-line getter/setter pair (get x() {...} set x() {...}), a common real-world pattern. Verified end-to-end against a real indexed project (built dist, real SQLite DB).
Root cause
// src/extraction/tree-sitter-helpers.ts:18-30
export function generateNodeId(
filePath: string,
kind: NodeKind,
name: string,
line: number
): string {
const hash = crypto
.createHash('sha256')
.update(`${filePath}:${kind}:${name}:${line}`) // <- no column / startIndex
.digest('hex')
.substring(0, 32);
return `${kind}:${hash}`;
}
A getter and setter for the same property are both kind: 'method', both name: 'x', and when written on one line both line: N → identical id. On write (INSERT OR REPLACE, keyed on id TEXT PRIMARY KEY) the setter clobbers the getter's row.
Repro (executed end-to-end against a real indexed project)
Fixture point.ts:
export class Point {
get x() { return this._x; } set x(v: number) { this._x = v; }
_x = 0;
}
After CodeGraph.init + indexAll, querying persisted nodes named x:
rows persisted for name='x': 1 (source declares 2: getter at col 2, setter at col 30)
method L2 col30 method:7450f331e3093417b66c24dfd082db8b
Only the setter survives; the getter node is gone. Extracting the same source in isolation confirms two distinct method nodes are produced (getter startColumn=2, setter startColumn=30) with the same computed id, so the collision happens at id-generation time and the loss happens at DB write time.
Scope / honest severity note
This is structural — generateNodeId is the shared id function for all languages, so any two same-kind same-name symbols on one line collide (one-line getter/setter pairs are the most common instance; minified/transpiled single-line output is another). Multi-line getter/setter pairs — the more common formatting — do not collide, since the line differs, so the everyday blast radius is bounded. Flagging it because the failure is silent (no error, no diagnostic) and the fix is small.
Suggested direction
Fold node.startPosition.column (or startIndex) into the hash input, e.g. `${filePath}:${kind}:${name}:${line}:${column}`. Ids are recomputed per-extraction and edges are built in the same pass, so adding column stays internally consistent within a re-index.
Environment
main @ 2d72891, package 1.4.1, Node 22.23.1 (Linux). Reproduces on a clean npm ci && npm run build.
Summary
generateNodeIdderives a node's identity fromfilePath:kind:name:linewithout any column/offset. Two symbols that share a file, kind, name, and line therefore collapse to the same id, and since nodes are persisted withINSERT OR REPLACE INTO nodeson aPRIMARY KEY (id), the second silently overwrites the first — the earlier node's row (and anything an edge pointed at via that id) is lost. The canonical trigger is a one-line getter/setter pair (get x() {...} set x() {...}), a common real-world pattern. Verified end-to-end against a real indexed project (built dist, real SQLite DB).Root cause
A getter and setter for the same property are both
kind: 'method', bothname: 'x', and when written on one line bothline: N→ identical id. On write (INSERT OR REPLACE, keyed onid TEXT PRIMARY KEY) the setter clobbers the getter's row.Repro (executed end-to-end against a real indexed project)
Fixture
point.ts:After
CodeGraph.init+indexAll, querying persisted nodes namedx:Only the setter survives; the getter node is gone. Extracting the same source in isolation confirms two distinct method nodes are produced (getter
startColumn=2, setterstartColumn=30) with the same computed id, so the collision happens at id-generation time and the loss happens at DB write time.Scope / honest severity note
This is structural —
generateNodeIdis the shared id function for all languages, so any two same-kind same-name symbols on one line collide (one-line getter/setter pairs are the most common instance; minified/transpiled single-line output is another). Multi-line getter/setter pairs — the more common formatting — do not collide, since the line differs, so the everyday blast radius is bounded. Flagging it because the failure is silent (no error, no diagnostic) and the fix is small.Suggested direction
Fold
node.startPosition.column(orstartIndex) into the hash input, e.g.`${filePath}:${kind}:${name}:${line}:${column}`. Ids are recomputed per-extraction and edges are built in the same pass, so adding column stays internally consistent within a re-index.Environment
main@2d72891, package1.4.1, Node 22.23.1 (Linux). Reproduces on a cleannpm ci && npm run build.