@remnic/coding-graph 9.6.36 → 9.6.38

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MAX_TRAVERSE_PATHS_HOPS
3
- } from "./chunk-ABDBWCXU.js";
3
+ } from "./chunk-I56SG643.js";
4
4
 
5
5
  // src/cypher/query-parser.ts
6
6
  var CYPHER_LABEL_TO_DB_LABEL = {
@@ -1039,4 +1039,4 @@ export {
1039
1039
  executeAst,
1040
1040
  executeCypher
1041
1041
  };
1042
- //# sourceMappingURL=chunk-I4R6GAAA.js.map
1042
+ //# sourceMappingURL=chunk-WOPWVODJ.js.map
@@ -4,8 +4,8 @@ import {
4
4
  executeAst,
5
5
  executeCypher,
6
6
  parseCypher
7
- } from "../chunk-I4R6GAAA.js";
8
- import "../chunk-ABDBWCXU.js";
7
+ } from "../chunk-WOPWVODJ.js";
8
+ import "../chunk-I56SG643.js";
9
9
  import "../chunk-ZVCMIM4T.js";
10
10
  export {
11
11
  CYPHER_LABEL_TO_DB_LABEL,
@@ -866,6 +866,36 @@ declare class GraphStore {
866
866
  * `exports` and `routes` arrays feed.
867
867
  */
868
868
  deadCode(): DeadCodeResult;
869
+ /**
870
+ * Find the innermost node whose span contains a byte offset in a file
871
+ * (issue #1917). Used by the LSP resolution pass's NodeLocator to map
872
+ * definition locations back to indexed nodes. Returns the node's
873
+ * qualified name, or null when no node contains the offset.
874
+ */
875
+ findNodeBySpan(filePath: string, byteOffset: number): string | null;
876
+ /**
877
+ * Callee NAMES already linked from a caller symbol via CALLS edges of a
878
+ * given provenance (issue #1917 wiring). The LSP pass uses this to skip
879
+ * call sites Phase A (provenance "heuristic") already resolved WITHOUT
880
+ * also skipping sites whose only edge is a prior "lsp" edge — those must
881
+ * be re-asserted each run or reconciliation would retire them.
882
+ */
883
+ resolvedCalleeNames(srcQualifiedName: string, provenance: string, filePath?: string): string[];
884
+ /**
885
+ * Current CALLS edges of a given provenance owned by a caller symbol in
886
+ * a file (#1923 review threads). The LSP pass uses this two ways:
887
+ * provenance "lsp" lists a filtered caller's existing lsp edges, and
888
+ * provenance "heuristic" lists its current Phase-A resolutions — an lsp
889
+ * edge is preserved at reconcile time only when it duplicates a current
890
+ * heuristic resolution (same dst), so removed member calls' edges retire
891
+ * while a filtered bare call's covering edge survives.
892
+ */
893
+ callEdgesForCaller(srcQualifiedName: string, filePath: string, provenance: string): Array<{
894
+ srcQualifiedName: string;
895
+ dstQualifiedName: string;
896
+ dstName: string;
897
+ type: string;
898
+ }>;
869
899
  /**
870
900
  * Read a symbol's source span from disk. The store NEVER persists
871
901
  * file contents (privacy + DB size — issue #1552 design); this
@@ -4,7 +4,7 @@ import {
4
4
  GraphStore,
5
5
  MAX_TRAVERSE_PATHS_HOPS,
6
6
  nodeIdFor
7
- } from "./chunk-ABDBWCXU.js";
7
+ } from "./chunk-I56SG643.js";
8
8
  import "./chunk-ZVCMIM4T.js";
9
9
  export {
10
10
  DEAD_CODE_EXCLUSION,
package/dist/index.js CHANGED
@@ -3,14 +3,14 @@ import {
3
3
  executeAst,
4
4
  executeCypher,
5
5
  parseCypher
6
- } from "./chunk-I4R6GAAA.js";
6
+ } from "./chunk-WOPWVODJ.js";
7
7
  import {
8
8
  DEAD_CODE_EXCLUSION,
9
9
  DEFAULT_TRAVERSE_PATHS_MAX,
10
10
  GraphStore,
11
11
  MAX_TRAVERSE_PATHS_HOPS,
12
12
  nodeIdFor
13
- } from "./chunk-ABDBWCXU.js";
13
+ } from "./chunk-I56SG643.js";
14
14
  import {
15
15
  CODING_GRAPH_SCHEMA_VERSION,
16
16
  EDGE_PROVENANCE_VALUES,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/coding-graph",
3
- "version": "9.6.36",
3
+ "version": "9.6.38",
4
4
  "description": "Web-tree-sitter powered symbol-extraction engine + SQLite knowledge-graph store for codebase memory (Tier 1: TypeScript, TSX, JavaScript, Python, Go, Rust, Java, C, C++, C#, Ruby, PHP, Kotlin, Swift, Bash). Optional companion of @remnic/core — install only when coding-graph features are needed.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -41,14 +41,14 @@
41
41
  "web-tree-sitter": "^0.25.0"
42
42
  },
43
43
  "peerDependencies": {
44
- "@remnic/core": "^9.6.36"
44
+ "@remnic/core": "^9.6.38"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^22.10.0",
48
48
  "tsup": "^8.5.1",
49
49
  "tsx": "^4.19.0",
50
50
  "typescript": "^5.7.0",
51
- "@remnic/core": "9.6.36"
51
+ "@remnic/core": "9.6.38"
52
52
  },
53
53
  "license": "MIT",
54
54
  "repository": {
@@ -2909,6 +2909,128 @@ export class GraphStore {
2909
2909
  }
2910
2910
  }
2911
2911
 
2912
+ /**
2913
+ * Find the innermost node whose span contains a byte offset in a file
2914
+ * (issue #1917). Used by the LSP resolution pass's NodeLocator to map
2915
+ * definition locations back to indexed nodes. Returns the node's
2916
+ * qualified name, or null when no node contains the offset.
2917
+ */
2918
+ findNodeBySpan(filePath: string, byteOffset: number): string | null {
2919
+ if (this.closed) return null;
2920
+ try {
2921
+ // Fetch the two smallest containing spans: nested containment is
2922
+ // normal (a method inside a class both contain the offset) and the
2923
+ // INNERMOST (smallest) wins — but a TIE at the smallest size means
2924
+ // the offset cannot be attributed to exactly one node, and the
2925
+ // NodeLocator contract requires null over an arbitrary pick
2926
+ // (review thread: a wrong winner persists incorrect CALLS edges).
2927
+ const rows = expectRows<{ qualified_name: string; size: number }>(
2928
+ this.db
2929
+ .prepare(
2930
+ `SELECT n.qualified_name, (n.span_end - n.span_start) AS size FROM nodes n
2931
+ JOIN files f ON n.file_id = f.id
2932
+ WHERE f.path = ? AND n.span_start <= ? AND n.span_end > ?
2933
+ ORDER BY size ASC
2934
+ LIMIT 2`,
2935
+ )
2936
+ .all(filePath, byteOffset, byteOffset),
2937
+ ["qualified_name", "size"],
2938
+ );
2939
+ if (rows.length === 0) return null;
2940
+ if (rows.length > 1 && rows[1] !== undefined && rows[0]!.size === rows[1].size) return null;
2941
+ const qualifiedName = rows[0]!.qualified_name;
2942
+ // The span lookup disambiguated by file, but the returned QUALIFIED
2943
+ // NAME is the edge key downstream (upsertEdges resolves names, not
2944
+ // ids). If another file defines the same qualified name (common for
2945
+ // top-level `main`/`handler`/`init`), an edge written against the
2946
+ // name could bind to the wrong node — return null and skip the
2947
+ // upgrade instead (conservative; id-carrying upgrades are the
2948
+ // follow-up, review thread on #1923).
2949
+ const dupRow = expectRow<{ n: number }>(
2950
+ this.db
2951
+ .prepare(`SELECT COUNT(*) AS n FROM nodes WHERE qualified_name = ?`)
2952
+ .get(qualifiedName),
2953
+ ["n"],
2954
+ );
2955
+ if (dupRow && dupRow.n > 1) return null;
2956
+ return qualifiedName;
2957
+ } catch {
2958
+ return null;
2959
+ }
2960
+ }
2961
+
2962
+ /**
2963
+ * Callee NAMES already linked from a caller symbol via CALLS edges of a
2964
+ * given provenance (issue #1917 wiring). The LSP pass uses this to skip
2965
+ * call sites Phase A (provenance "heuristic") already resolved WITHOUT
2966
+ * also skipping sites whose only edge is a prior "lsp" edge — those must
2967
+ * be re-asserted each run or reconciliation would retire them.
2968
+ */
2969
+ resolvedCalleeNames(srcQualifiedName: string, provenance: string, filePath?: string): string[] {
2970
+ if (this.closed) return [];
2971
+ try {
2972
+ // Scoped to the caller FILE when provided: two files defining the
2973
+ // same top-level qualified name (main/handler/init) must not leak
2974
+ // each other's resolutions (#1923 review thread).
2975
+ const fileClause = filePath !== undefined ? " AND f.path = ?" : "";
2976
+ const bind = filePath !== undefined ? [srcQualifiedName, provenance, filePath] : [srcQualifiedName, provenance];
2977
+ const rows = expectRows<{ name: string }>(
2978
+ this.db
2979
+ .prepare(
2980
+ `SELECT DISTINCT dn.name AS name FROM edges e
2981
+ JOIN nodes sn ON e.src = sn.id
2982
+ JOIN files f ON sn.file_id = f.id
2983
+ JOIN nodes dn ON e.dst = dn.id
2984
+ WHERE sn.qualified_name = ? AND e.type = 'CALLS' AND e.provenance = ?${fileClause}`,
2985
+ )
2986
+ .all(...bind),
2987
+ ["name"],
2988
+ );
2989
+ return rows.map((r) => r.name);
2990
+ } catch {
2991
+ return [];
2992
+ }
2993
+ }
2994
+
2995
+ /**
2996
+ * Current CALLS edges of a given provenance owned by a caller symbol in
2997
+ * a file (#1923 review threads). The LSP pass uses this two ways:
2998
+ * provenance "lsp" lists a filtered caller's existing lsp edges, and
2999
+ * provenance "heuristic" lists its current Phase-A resolutions — an lsp
3000
+ * edge is preserved at reconcile time only when it duplicates a current
3001
+ * heuristic resolution (same dst), so removed member calls' edges retire
3002
+ * while a filtered bare call's covering edge survives.
3003
+ */
3004
+ callEdgesForCaller(
3005
+ srcQualifiedName: string,
3006
+ filePath: string,
3007
+ provenance: string,
3008
+ ): Array<{ srcQualifiedName: string; dstQualifiedName: string; dstName: string; type: string }> {
3009
+ if (this.closed) return [];
3010
+ try {
3011
+ const rows = expectRows<{ src_q: string; dst_q: string; dst_n: string; type: string }>(
3012
+ this.db
3013
+ .prepare(
3014
+ `SELECT sn.qualified_name AS src_q, dn.qualified_name AS dst_q, dn.name AS dst_n, e.type AS type FROM edges e
3015
+ JOIN nodes sn ON e.src = sn.id
3016
+ JOIN files f ON sn.file_id = f.id
3017
+ JOIN nodes dn ON e.dst = dn.id
3018
+ WHERE sn.qualified_name = ? AND f.path = ? AND e.provenance = ? AND e.type = 'CALLS'`,
3019
+ )
3020
+ .all(srcQualifiedName, filePath, provenance),
3021
+ ["src_q", "dst_q", "dst_n", "type"],
3022
+ );
3023
+ return rows.map((r) => ({
3024
+ srcQualifiedName: r.src_q,
3025
+ dstQualifiedName: r.dst_q,
3026
+ dstName: r.dst_n,
3027
+ type: r.type,
3028
+ }));
3029
+ } catch {
3030
+ return [];
3031
+ }
3032
+ }
3033
+
2912
3034
  /**
2913
3035
  * Read a symbol's source span from disk. The store NEVER persists
2914
3036
  * file contents (privacy + DB size — issue #1552 design); this