@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 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 #1548 (Track B, Phase 1). Package and core wiring for PR1 (#1551
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
- ## Status (PR1)
19
-
20
- This package's public surface is **scaffolded only**:
21
-
22
- - `ENGINE_VERSION = "0.1.0-pr1"` constant
23
- - `TIER_1_LANGUAGES` (15 languages)
24
- - `CodingGraphError` tagged error class (`code: "not_implemented" | "module_load_failed"`)
25
- - `CodingGraphEngine` interface the full PR2 contract, no implementation
26
- - `createCodingGraphEngine()` throws `CodingGraphError("not_implemented", …)`
27
-
28
- Calling `createCodingGraphEngine()` *always* throws a tagged error. There
29
- is no silent stub and there is no half-working fallback. The runtime
30
- contract is observable today; PR2 fills the implementation.
31
-
32
- `web-tree-sitter` is declared as the parser engine; grammar `.wasm` assets
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 = resolveNodeId(
825
+ const dstId = edge.dstPathHint ? resolveNodeIdWithPathHint(
752
826
  edge.dstQualifiedName,
753
- qualifiedNameToId,
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 (!assertedKeys.has(key)) {
776
- staleSrcDstTypes.push({ src: p.src, dst: p.dst, type: p.type });
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-CPYJACC5.js.map
1976
+ //# sourceMappingURL=chunk-ABDBWCXU.js.map