@remnic/coding-graph 9.6.23 → 9.6.25
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.
- package/README.md +16 -19
- package/dist/{chunk-CPYJACC5.js → chunk-ABDBWCXU.js} +146 -8
- package/dist/chunk-ABDBWCXU.js.map +1 -0
- package/dist/{chunk-5I2DBHOQ.js → chunk-I4R6GAAA.js} +2 -2
- package/dist/cypher/query-parser.js +2 -2
- package/dist/graph-store.d.ts +47 -0
- package/dist/graph-store.js +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.js +318 -26
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/edge-provenance-scope.test.ts +457 -0
- package/src/engine/emit.ts +55 -8
- package/src/engine/extractors.ts +45 -11
- package/src/graph-store.ts +265 -9
- package/src/heuristic-resolution.test.ts +596 -0
- package/src/heuristic-resolution.ts +395 -0
- package/src/lsp/resolution.ts +52 -11
- package/src/lsp-reconciliation.test.ts +145 -0
- package/src/reindex.test.ts +57 -0
- package/src/reindex.ts +18 -6
- package/dist/chunk-CPYJACC5.js.map +0 -1
- /package/dist/{chunk-5I2DBHOQ.js.map → chunk-I4R6GAAA.js.map} +0 -0
package/README.md
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
engine: web-tree-sitter grammars, per-language symbol extractors, and the
|
|
5
5
|
neutral `FileIR` intermediate representation that the graph store consumes.
|
|
6
6
|
|
|
7
|
-
> Part of
|
|
8
|
-
> step 1 + step 2). The real backend lands in PR2 (#1551 step 3+).
|
|
7
|
+
> Part of Remnic's coding-knowledge layer (issue #1548).
|
|
9
8
|
|
|
10
9
|
## Install
|
|
11
10
|
|
|
@@ -15,23 +14,21 @@ npm install @remnic/coding-graph
|
|
|
15
14
|
pnpm add @remnic/coding-graph
|
|
16
15
|
```
|
|
17
16
|
|
|
18
|
-
##
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- `TIER_1_LANGUAGES`
|
|
24
|
-
- `
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
will ship in PR2 via the `grammars/` directory listed in the package's
|
|
34
|
-
`files` manifest.
|
|
17
|
+
## What it provides
|
|
18
|
+
|
|
19
|
+
- `createCodingGraphEngine()` — returns a working engine backed by a
|
|
20
|
+
WASM tree-sitter parser. It parses source files into the neutral `FileIR`
|
|
21
|
+
(symbols plus import edges) for the 15 tier-1 languages.
|
|
22
|
+
- `TIER_1_LANGUAGES` — the supported tier-1 language list.
|
|
23
|
+
- `ENGINE_VERSION` / `CODING_GRAPH_ENGINE_VERSION` — the engine version
|
|
24
|
+
string. The single source of truth lives in `@remnic/core`; this package
|
|
25
|
+
re-exports it so every consumer stays in lockstep.
|
|
26
|
+
- `CodingGraphEngine` interface and the `CodingGraphError` tagged error class.
|
|
27
|
+
|
|
28
|
+
A per-file parse failure surfaces as a tagged `{ ok: false, code: "parse_failed" }`
|
|
29
|
+
result rather than a thrown exception, so one unparseable file never bricks a
|
|
30
|
+
whole index run. Grammar `.wasm` assets ship in the `grammars/` directory listed
|
|
31
|
+
in the package's `files` manifest.
|
|
35
32
|
|
|
36
33
|
## How `@remnic/core` loads this
|
|
37
34
|
|
|
@@ -207,6 +207,80 @@ var GraphStore = class _GraphStore {
|
|
|
207
207
|
}
|
|
208
208
|
return this.queue.schedule(() => this.runUpsertEdges(edges));
|
|
209
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Retire stale LSP-provenance edges for a file (issue #1895).
|
|
212
|
+
*
|
|
213
|
+
* The LSP resolution pass re-derives edges from the CURRENT source on each
|
|
214
|
+
* run. After writing the new `lsp` edges for a file, this method deletes
|
|
215
|
+
* prior `lsp`-provenance edges owned by that file's nodes whose
|
|
216
|
+
* `(src, dst, type)` key is NOT in the asserted set. This is the LSP
|
|
217
|
+
* layer's side of the provenance-lifecycle contract: each layer owns its
|
|
218
|
+
* own stale-edge retirement (#1894 established that reindex's heuristic
|
|
219
|
+
* scope never touches `lsp` rows).
|
|
220
|
+
*
|
|
221
|
+
* Heuristic, trace, and semantic edges are never touched.
|
|
222
|
+
*
|
|
223
|
+
* @returns the number of retired edges.
|
|
224
|
+
*/
|
|
225
|
+
reconcileLspEdges(filePath, assertedEdges) {
|
|
226
|
+
if (this.closed) return 0;
|
|
227
|
+
try {
|
|
228
|
+
const fileRow = expectRow(
|
|
229
|
+
this.db.prepare("SELECT id FROM files WHERE path = ?").get(filePath),
|
|
230
|
+
["id"]
|
|
231
|
+
);
|
|
232
|
+
if (!fileRow) return 0;
|
|
233
|
+
const nodes = expectRows(
|
|
234
|
+
this.db.prepare("SELECT id, qualified_name FROM nodes WHERE file_id = ?").all(fileRow.id),
|
|
235
|
+
["id", "qualified_name"]
|
|
236
|
+
);
|
|
237
|
+
if (nodes.length === 0) return 0;
|
|
238
|
+
const nameCount = /* @__PURE__ */ new Map();
|
|
239
|
+
for (const n of nodes) nameCount.set(n.qualified_name, (nameCount.get(n.qualified_name) ?? 0) + 1);
|
|
240
|
+
const srcMap = /* @__PURE__ */ new Map();
|
|
241
|
+
for (const n of nodes) {
|
|
242
|
+
if (nameCount.get(n.qualified_name) === 1) srcMap.set(n.qualified_name, n.id);
|
|
243
|
+
}
|
|
244
|
+
const nodeIds = nodes.map((n) => n.id);
|
|
245
|
+
const assertedKeys = /* @__PURE__ */ new Set();
|
|
246
|
+
for (const e of assertedEdges) {
|
|
247
|
+
const srcId = srcMap.get(e.srcQualifiedName);
|
|
248
|
+
if (!srcId) continue;
|
|
249
|
+
const dstId = resolveNodeId(e.dstQualifiedName, /* @__PURE__ */ new Map(), this.db);
|
|
250
|
+
if (!dstId) continue;
|
|
251
|
+
assertedKeys.add(`${srcId}\0${dstId}\0${e.type}`);
|
|
252
|
+
}
|
|
253
|
+
const placeholders = nodeIds.map(() => "?").join(", ");
|
|
254
|
+
const priorEdges = expectRows(
|
|
255
|
+
this.db.prepare(
|
|
256
|
+
`SELECT src, dst, type FROM edges
|
|
257
|
+
WHERE src IN (${placeholders}) AND provenance = 'lsp'`
|
|
258
|
+
).all(...nodeIds),
|
|
259
|
+
["src", "dst", "type"]
|
|
260
|
+
);
|
|
261
|
+
let deleted = 0;
|
|
262
|
+
const toDelete = [];
|
|
263
|
+
for (const e of priorEdges) {
|
|
264
|
+
const key = `${e.src}\0${e.dst}\0${e.type}`;
|
|
265
|
+
if (!assertedKeys.has(key)) toDelete.push([e.src, e.dst, e.type]);
|
|
266
|
+
}
|
|
267
|
+
if (toDelete.length > 0) {
|
|
268
|
+
const SQLITE_VARIABLE_LIMIT2 = 32766;
|
|
269
|
+
const PARAMS_PER_TUPLE = 3;
|
|
270
|
+
const MAX_TUPLES = Math.floor(SQLITE_VARIABLE_LIMIT2 / PARAMS_PER_TUPLE);
|
|
271
|
+
for (let i = 0; i < toDelete.length; i += MAX_TUPLES) {
|
|
272
|
+
const chunk = toDelete.slice(i, i + MAX_TUPLES);
|
|
273
|
+
const ph = chunk.map(() => "(?, ?, ?)").join(", ");
|
|
274
|
+
const r = this.db.prepare(`DELETE FROM edges WHERE (src, dst, type) IN (${ph})`).run(...chunk.flat());
|
|
275
|
+
deleted += r.changes;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return deleted;
|
|
279
|
+
} catch (error) {
|
|
280
|
+
logWriteFailure(error);
|
|
281
|
+
return 0;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
210
284
|
/** Wait for pending writes to drain — test seam. */
|
|
211
285
|
async drain() {
|
|
212
286
|
await this.queue.drain();
|
|
@@ -748,11 +822,12 @@ var GraphStore = class _GraphStore {
|
|
|
748
822
|
}
|
|
749
823
|
const srcId = qualifiedNameToId.get(edge.srcQualifiedName);
|
|
750
824
|
if (!srcId) continue;
|
|
751
|
-
const dstId =
|
|
825
|
+
const dstId = edge.dstPathHint ? resolveNodeIdWithPathHint(
|
|
752
826
|
edge.dstQualifiedName,
|
|
753
|
-
|
|
754
|
-
this.db
|
|
755
|
-
|
|
827
|
+
edge.dstPathHint,
|
|
828
|
+
this.db,
|
|
829
|
+
edge.dstImporterLanguage
|
|
830
|
+
) : resolveNodeId(edge.dstQualifiedName, qualifiedNameToId, this.db);
|
|
756
831
|
if (!dstId) continue;
|
|
757
832
|
const key = `${srcId}\0${dstId}\0${edge.type}`;
|
|
758
833
|
assertedKeys.add(key);
|
|
@@ -769,12 +844,13 @@ var GraphStore = class _GraphStore {
|
|
|
769
844
|
);
|
|
770
845
|
const priorByKey = /* @__PURE__ */ new Map();
|
|
771
846
|
const staleSrcDstTypes = [];
|
|
847
|
+
const scoped = ir.assertedEdgeProvenances && ir.assertedEdgeProvenances.length > 0 ? ir.assertedEdgeProvenances : void 0;
|
|
772
848
|
for (const p of priorEdges) {
|
|
773
849
|
const key = `${p.src}\0${p.dst}\0${p.type}`;
|
|
774
850
|
priorByKey.set(key, { confidence: p.confidence, provenance: p.provenance });
|
|
775
|
-
if (
|
|
776
|
-
|
|
777
|
-
}
|
|
851
|
+
if (assertedKeys.has(key)) continue;
|
|
852
|
+
if (scoped && !scoped.includes(p.provenance)) continue;
|
|
853
|
+
staleSrcDstTypes.push({ src: p.src, dst: p.dst, type: p.type });
|
|
778
854
|
}
|
|
779
855
|
const SQLITE_VARIABLE_LIMIT2 = 32766;
|
|
780
856
|
const PARAMS_PER_TUPLE = 3;
|
|
@@ -805,6 +881,9 @@ var GraphStore = class _GraphStore {
|
|
|
805
881
|
if (prior && prior.confidence === edge.confidence && prior.provenance === edge.provenance) {
|
|
806
882
|
continue;
|
|
807
883
|
}
|
|
884
|
+
if (prior && scoped && (!scoped.includes(prior.provenance) || prior.provenance === "lsp" && edge.provenance === "heuristic")) {
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
808
887
|
const r = insertEdge.run(srcId, dstId, edge.type, edge.confidence, edge.provenance);
|
|
809
888
|
edgeCount += r.changes;
|
|
810
889
|
}
|
|
@@ -1734,6 +1813,65 @@ function resolveNodeId(qualifiedName, inBatch, db) {
|
|
|
1734
1813
|
}
|
|
1735
1814
|
return rows[0]?.id;
|
|
1736
1815
|
}
|
|
1816
|
+
var LANG_FAMILY_EXTENSIONS = {
|
|
1817
|
+
js: /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"]),
|
|
1818
|
+
python: /* @__PURE__ */ new Set(["py", "pyi"]),
|
|
1819
|
+
ruby: /* @__PURE__ */ new Set(["rb"]),
|
|
1820
|
+
go: /* @__PURE__ */ new Set(["go"]),
|
|
1821
|
+
rust: /* @__PURE__ */ new Set(["rs"]),
|
|
1822
|
+
php: /* @__PURE__ */ new Set(["php"]),
|
|
1823
|
+
java: /* @__PURE__ */ new Set(["java"]),
|
|
1824
|
+
csharp: /* @__PURE__ */ new Set(["cs"]),
|
|
1825
|
+
cpp: /* @__PURE__ */ new Set(["cc", "cpp", "cxx", "c", "hh", "hpp", "hxx", "h"]),
|
|
1826
|
+
kotlin: /* @__PURE__ */ new Set(["kt"]),
|
|
1827
|
+
swift: /* @__PURE__ */ new Set(["swift"]),
|
|
1828
|
+
bash: /* @__PURE__ */ new Set(["sh", "bash"])
|
|
1829
|
+
};
|
|
1830
|
+
var IMPORTER_LANG_FAMILY = {
|
|
1831
|
+
typescript: "js",
|
|
1832
|
+
tsx: "js",
|
|
1833
|
+
javascript: "js",
|
|
1834
|
+
python: "python",
|
|
1835
|
+
ruby: "ruby",
|
|
1836
|
+
go: "go",
|
|
1837
|
+
rust: "rust",
|
|
1838
|
+
php: "php",
|
|
1839
|
+
java: "java",
|
|
1840
|
+
csharp: "csharp",
|
|
1841
|
+
c: "cpp",
|
|
1842
|
+
cpp: "cpp",
|
|
1843
|
+
kotlin: "kotlin",
|
|
1844
|
+
swift: "swift",
|
|
1845
|
+
bash: "bash"
|
|
1846
|
+
};
|
|
1847
|
+
function resolveNodeIdWithPathHint(qualifiedName, pathHint, db, importerLanguage) {
|
|
1848
|
+
const family = importerLanguage ? IMPORTER_LANG_FAMILY[importerLanguage] : void 0;
|
|
1849
|
+
const allowedExts = family ? LANG_FAMILY_EXTENSIONS[family] : void 0;
|
|
1850
|
+
const rows = expectRows(
|
|
1851
|
+
db.prepare(
|
|
1852
|
+
`SELECT n.id, f.path FROM nodes n JOIN files f ON n.file_id = f.id
|
|
1853
|
+
WHERE n.qualified_name = ?
|
|
1854
|
+
ORDER BY f.path, n.id`
|
|
1855
|
+
).all(qualifiedName),
|
|
1856
|
+
["id", "path"]
|
|
1857
|
+
);
|
|
1858
|
+
const matches = rows.filter((row) => {
|
|
1859
|
+
if (row.path === pathHint) {
|
|
1860
|
+
if (allowedExts) {
|
|
1861
|
+
return false;
|
|
1862
|
+
}
|
|
1863
|
+
return true;
|
|
1864
|
+
}
|
|
1865
|
+
if (!row.path.startsWith(pathHint)) return false;
|
|
1866
|
+
const rest = row.path.slice(pathHint.length);
|
|
1867
|
+
const m = /^(?:\.([^./]+)|\/(?:index|__init__)\.([^./]+))$/.exec(rest);
|
|
1868
|
+
if (!m) return false;
|
|
1869
|
+
const ext = m[1] ?? m[2];
|
|
1870
|
+
return !allowedExts || allowedExts.has(ext);
|
|
1871
|
+
});
|
|
1872
|
+
if (matches.length !== 1) return void 0;
|
|
1873
|
+
return matches[0]?.id;
|
|
1874
|
+
}
|
|
1737
1875
|
function resolveByNodeId(stmt, nodeId, qualifiedName) {
|
|
1738
1876
|
const row = expectRow(stmt.get(nodeId), ["qualified_name"]);
|
|
1739
1877
|
if (!row) return void 0;
|
|
@@ -1835,4 +1973,4 @@ export {
|
|
|
1835
1973
|
GraphStore,
|
|
1836
1974
|
nodeIdFor
|
|
1837
1975
|
};
|
|
1838
|
-
//# sourceMappingURL=chunk-
|
|
1976
|
+
//# sourceMappingURL=chunk-ABDBWCXU.js.map
|