gitnexus 1.6.3-rc.15 → 1.6.3-rc.17

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.
@@ -61,5 +61,21 @@ export interface GraphRelationship {
61
61
  confidence: number;
62
62
  reason: string;
63
63
  step?: number;
64
+ /**
65
+ * Per-signal evidence trace for edges emitted by the scope-based
66
+ * resolution pipeline (RFC #909 Ring 2 PKG #925). Populated by
67
+ * `emit-references.ts` when draining `ReferenceIndex` into the graph
68
+ * so downstream query / audit tools can inspect *why* a given edge
69
+ * was emitted with its confidence value.
70
+ *
71
+ * Optional and additive — every existing edge emitter ignores this
72
+ * field, and every existing query continues to work whether or not
73
+ * an edge carries it.
74
+ */
75
+ evidence?: readonly {
76
+ readonly kind: string;
77
+ readonly weight: number;
78
+ readonly note?: string;
79
+ }[];
64
80
  }
65
81
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/graph/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/graph/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Phase 5 of the RFC #909 ingestion lifecycle: drain `ReferenceIndex`
3
+ * into the knowledge graph as labeled edges with `confidence` and
4
+ * `evidence` properties (Ring 2 PKG #925).
5
+ *
6
+ * The resolution phase (future PR) writes `Reference` records into
7
+ * `model.scopes.referenceSites`-derived `ReferenceIndex`; this module
8
+ * materializes those records as `GraphRelationship`s via
9
+ * `graph.addRelationship`. Every emitted edge carries:
10
+ *
11
+ * - `type`: one of `'CALLS' | 'ACCESSES' | 'INHERITS' | 'USES'`
12
+ * (mapped from `Reference.kind` — `'read'` and `'write'` both route
13
+ * to `ACCESSES`; `'type-reference'` and `'import-use'` route to
14
+ * `USES`; `'call'` stays `CALLS`; `'inherits'` stays `INHERITS`).
15
+ * - `confidence`: the pre-computed confidence from the Reference record.
16
+ * - `reason`: human-readable summary (`"scope-resolution: call | confidence 0.75"`).
17
+ * - `evidence`: the full `ResolutionEvidence[]` trace — additive graph
18
+ * property (see `GraphRelationship.evidence` in gitnexus-shared),
19
+ * so queries that don't know about it are unaffected.
20
+ * - `step`: carries the reference's access-kind discriminant when
21
+ * available (`1` for read, `2` for write) so `ACCESSES` edges retain
22
+ * the read/write distinction without forcing a new edge type.
23
+ *
24
+ * ## Optional scope-tree flush
25
+ *
26
+ * When `INGESTION_EMIT_SCOPES=1` is set, this module also emits:
27
+ *
28
+ * - `Scope` nodes for every `Scope` in the tree
29
+ * - `CONTAINS` edges from parent scope to child scope
30
+ * - `DEFINES` edges from scope to its `ownedDefs` members
31
+ * - `IMPORTS` edges from scope to `targetModuleScope` of each finalized
32
+ * `ImportEdge` that carries one
33
+ *
34
+ * Off by default — existing queries that don't know about `Scope` nodes
35
+ * continue to work, and the storage cost is opt-in.
36
+ *
37
+ * ## Source-of-truth: the caller def for a reference
38
+ *
39
+ * A `Reference` says "some code inside `fromScope` references `toDef`".
40
+ * The graph wants `(callerNodeId, calleeNodeId)`. We resolve the caller
41
+ * by walking up the scope tree from `fromScope` until we find a scope
42
+ * whose `ownedDefs` contains a Function-like def. If no such ancestor
43
+ * exists, the edge is attributed to the first def owned by the innermost
44
+ * ancestor scope, and if THAT produces nothing either the edge is
45
+ * skipped (with a count returned in `EmitStats.skippedNoCaller`).
46
+ */
47
+ import type { ReferenceIndex } from '../../_shared/index.js';
48
+ import type { KnowledgeGraph } from '../graph/types.js';
49
+ import type { ScopeResolutionIndexes } from './model/scope-resolution-indexes.js';
50
+ export interface EmitStats {
51
+ readonly edgesEmitted: number;
52
+ /** References dropped because no caller def could be resolved. */
53
+ readonly skippedNoCaller: number;
54
+ /** References dropped because `toDef` was not found in the DefIndex. */
55
+ readonly skippedMissingTarget: number;
56
+ /** Scope nodes emitted — `0` unless `INGESTION_EMIT_SCOPES=1`. */
57
+ readonly scopeNodesEmitted: number;
58
+ /** Scope-tree structural edges emitted — `0` unless `INGESTION_EMIT_SCOPES=1`. */
59
+ readonly scopeEdgesEmitted: number;
60
+ }
61
+ export interface EmitReferencesInput {
62
+ readonly graph: KnowledgeGraph;
63
+ readonly scopes: ScopeResolutionIndexes;
64
+ readonly referenceIndex: ReferenceIndex;
65
+ /** Human-consumable label for the `reason` prefix. Defaults to `'scope-resolution'`. */
66
+ readonly sourceLabel?: string;
67
+ }
68
+ /**
69
+ * Drain `referenceIndex.bySourceScope` into graph edges.
70
+ *
71
+ * The scope-tree flush is controlled separately by
72
+ * `INGESTION_EMIT_SCOPES` — callers can run `emitReferencesToGraph`
73
+ * without scope-node emission or layer the two calls as needed.
74
+ */
75
+ export declare function emitReferencesToGraph(input: EmitReferencesInput): EmitStats;
76
+ /**
77
+ * Emit `Scope` nodes + `CONTAINS`/`DEFINES`/`IMPORTS` edges representing
78
+ * the lexical scope tree itself. Skipped unless `INGESTION_EMIT_SCOPES=1`
79
+ * at the public entry point; exported here for tests that want to
80
+ * exercise the path directly.
81
+ */
82
+ export declare function emitScopeGraph(input: {
83
+ readonly graph: KnowledgeGraph;
84
+ readonly scopes: ScopeResolutionIndexes;
85
+ }): {
86
+ readonly scopeNodesEmitted: number;
87
+ readonly scopeEdgesEmitted: number;
88
+ };
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Phase 5 of the RFC #909 ingestion lifecycle: drain `ReferenceIndex`
3
+ * into the knowledge graph as labeled edges with `confidence` and
4
+ * `evidence` properties (Ring 2 PKG #925).
5
+ *
6
+ * The resolution phase (future PR) writes `Reference` records into
7
+ * `model.scopes.referenceSites`-derived `ReferenceIndex`; this module
8
+ * materializes those records as `GraphRelationship`s via
9
+ * `graph.addRelationship`. Every emitted edge carries:
10
+ *
11
+ * - `type`: one of `'CALLS' | 'ACCESSES' | 'INHERITS' | 'USES'`
12
+ * (mapped from `Reference.kind` — `'read'` and `'write'` both route
13
+ * to `ACCESSES`; `'type-reference'` and `'import-use'` route to
14
+ * `USES`; `'call'` stays `CALLS`; `'inherits'` stays `INHERITS`).
15
+ * - `confidence`: the pre-computed confidence from the Reference record.
16
+ * - `reason`: human-readable summary (`"scope-resolution: call | confidence 0.75"`).
17
+ * - `evidence`: the full `ResolutionEvidence[]` trace — additive graph
18
+ * property (see `GraphRelationship.evidence` in gitnexus-shared),
19
+ * so queries that don't know about it are unaffected.
20
+ * - `step`: carries the reference's access-kind discriminant when
21
+ * available (`1` for read, `2` for write) so `ACCESSES` edges retain
22
+ * the read/write distinction without forcing a new edge type.
23
+ *
24
+ * ## Optional scope-tree flush
25
+ *
26
+ * When `INGESTION_EMIT_SCOPES=1` is set, this module also emits:
27
+ *
28
+ * - `Scope` nodes for every `Scope` in the tree
29
+ * - `CONTAINS` edges from parent scope to child scope
30
+ * - `DEFINES` edges from scope to its `ownedDefs` members
31
+ * - `IMPORTS` edges from scope to `targetModuleScope` of each finalized
32
+ * `ImportEdge` that carries one
33
+ *
34
+ * Off by default — existing queries that don't know about `Scope` nodes
35
+ * continue to work, and the storage cost is opt-in.
36
+ *
37
+ * ## Source-of-truth: the caller def for a reference
38
+ *
39
+ * A `Reference` says "some code inside `fromScope` references `toDef`".
40
+ * The graph wants `(callerNodeId, calleeNodeId)`. We resolve the caller
41
+ * by walking up the scope tree from `fromScope` until we find a scope
42
+ * whose `ownedDefs` contains a Function-like def. If no such ancestor
43
+ * exists, the edge is attributed to the first def owned by the innermost
44
+ * ancestor scope, and if THAT produces nothing either the edge is
45
+ * skipped (with a count returned in `EmitStats.skippedNoCaller`).
46
+ */
47
+ /**
48
+ * Drain `referenceIndex.bySourceScope` into graph edges.
49
+ *
50
+ * The scope-tree flush is controlled separately by
51
+ * `INGESTION_EMIT_SCOPES` — callers can run `emitReferencesToGraph`
52
+ * without scope-node emission or layer the two calls as needed.
53
+ */
54
+ export function emitReferencesToGraph(input) {
55
+ const { graph, scopes, referenceIndex } = input;
56
+ const sourceLabel = input.sourceLabel ?? 'scope-resolution';
57
+ let edgesEmitted = 0;
58
+ let skippedNoCaller = 0;
59
+ let skippedMissingTarget = 0;
60
+ for (const [fromScope, refs] of referenceIndex.bySourceScope) {
61
+ for (const ref of refs) {
62
+ const targetDef = scopes.defs.get(ref.toDef);
63
+ if (targetDef === undefined) {
64
+ skippedMissingTarget++;
65
+ continue;
66
+ }
67
+ const callerId = resolveCallerNodeId(fromScope, scopes);
68
+ if (callerId === undefined) {
69
+ skippedNoCaller++;
70
+ continue;
71
+ }
72
+ graph.addRelationship(buildRelationship(ref, callerId, targetDef, sourceLabel));
73
+ edgesEmitted++;
74
+ }
75
+ }
76
+ const scopeStats = isScopeEmissionEnabled()
77
+ ? emitScopeGraph({ graph, scopes })
78
+ : { scopeNodesEmitted: 0, scopeEdgesEmitted: 0 };
79
+ return { edgesEmitted, skippedNoCaller, skippedMissingTarget, ...scopeStats };
80
+ }
81
+ /**
82
+ * Emit `Scope` nodes + `CONTAINS`/`DEFINES`/`IMPORTS` edges representing
83
+ * the lexical scope tree itself. Skipped unless `INGESTION_EMIT_SCOPES=1`
84
+ * at the public entry point; exported here for tests that want to
85
+ * exercise the path directly.
86
+ */
87
+ export function emitScopeGraph(input) {
88
+ const { graph, scopes } = input;
89
+ let scopeNodesEmitted = 0;
90
+ let scopeEdgesEmitted = 0;
91
+ for (const scope of scopes.scopeTree.byId.values()) {
92
+ graph.addNode({
93
+ id: scope.id,
94
+ label: 'CodeElement', // the generic bucket for non-symbol graph nodes
95
+ properties: {
96
+ name: scope.kind,
97
+ filePath: scope.filePath,
98
+ startLine: scope.range.startLine,
99
+ endLine: scope.range.endLine,
100
+ description: `Scope: ${scope.kind}`,
101
+ },
102
+ });
103
+ scopeNodesEmitted++;
104
+ if (scope.parent !== null) {
105
+ graph.addRelationship({
106
+ id: `rel:contains:${scope.parent}->${scope.id}`,
107
+ sourceId: scope.parent,
108
+ targetId: scope.id,
109
+ type: 'CONTAINS',
110
+ confidence: 1,
111
+ reason: 'scope-tree parent/child',
112
+ });
113
+ scopeEdgesEmitted++;
114
+ }
115
+ for (const def of scope.ownedDefs) {
116
+ graph.addRelationship({
117
+ id: `rel:defines:${scope.id}->${def.nodeId}`,
118
+ sourceId: scope.id,
119
+ targetId: def.nodeId,
120
+ type: 'DEFINES',
121
+ confidence: 1,
122
+ reason: 'scope.ownedDefs',
123
+ });
124
+ scopeEdgesEmitted++;
125
+ }
126
+ }
127
+ for (const [scopeId, edges] of scopes.imports) {
128
+ for (const edge of edges) {
129
+ if (edge.targetModuleScope === undefined)
130
+ continue;
131
+ graph.addRelationship({
132
+ id: `rel:imports:${scopeId}->${edge.targetModuleScope}:${edge.localName}`,
133
+ sourceId: scopeId,
134
+ targetId: edge.targetModuleScope,
135
+ type: 'IMPORTS',
136
+ confidence: edge.linkStatus === 'unresolved' ? 0.5 : 1,
137
+ reason: `import ${edge.kind} ${edge.localName}`,
138
+ });
139
+ scopeEdgesEmitted++;
140
+ }
141
+ }
142
+ return { scopeNodesEmitted, scopeEdgesEmitted };
143
+ }
144
+ // ─── Internal ───────────────────────────────────────────────────────────────
145
+ /** Accepted truthy values for `INGESTION_EMIT_SCOPES`. */
146
+ const TRUTHY = new Set(['true', '1', 'yes']);
147
+ function isScopeEmissionEnabled() {
148
+ const raw = process.env['INGESTION_EMIT_SCOPES'];
149
+ if (raw === undefined)
150
+ return false;
151
+ return TRUTHY.has(raw.trim().toLowerCase());
152
+ }
153
+ /**
154
+ * Walk up from `startScope` looking for the first ancestor scope whose
155
+ * `ownedDefs` contains a Function-like def (Function / Method /
156
+ * Constructor). Fall back to the innermost ancestor's first `ownedDef`
157
+ * if none is found; return `undefined` if all ancestors have no defs.
158
+ */
159
+ function resolveCallerNodeId(startScope, scopes) {
160
+ const tree = scopes.scopeTree;
161
+ let current = startScope;
162
+ const visited = new Set();
163
+ let firstOwnedFallback;
164
+ while (current !== null) {
165
+ if (visited.has(current))
166
+ break;
167
+ visited.add(current);
168
+ const scope = tree.getScope(current);
169
+ if (scope === undefined)
170
+ break;
171
+ // Prefer a Function-like owner.
172
+ const fnDef = scope.ownedDefs.find((d) => isFunctionLike(d.type));
173
+ if (fnDef !== undefined)
174
+ return fnDef.nodeId;
175
+ // Stash the first owned def we see as a conservative fallback.
176
+ if (firstOwnedFallback === undefined && scope.ownedDefs.length > 0) {
177
+ firstOwnedFallback = scope.ownedDefs[0].nodeId;
178
+ }
179
+ current = scope.parent;
180
+ }
181
+ return firstOwnedFallback;
182
+ }
183
+ function isFunctionLike(type) {
184
+ return type === 'Function' || type === 'Method' || type === 'Constructor';
185
+ }
186
+ function buildRelationship(ref, callerId, targetDef, sourceLabel) {
187
+ const type = mapKindToType(ref.kind);
188
+ const reason = `${sourceLabel}: ${ref.kind} | confidence ${ref.confidence.toFixed(3)}`;
189
+ // `step` encodes read/write discriminator for ACCESSES edges (1=read, 2=write).
190
+ // Other kinds omit `step`.
191
+ const step = ref.kind === 'read' ? 1 : ref.kind === 'write' ? 2 : undefined;
192
+ return {
193
+ id: `rel:${type}:${callerId}->${targetDef.nodeId}:${ref.atRange.startLine}:${ref.atRange.startCol}`,
194
+ sourceId: callerId,
195
+ targetId: targetDef.nodeId,
196
+ type,
197
+ confidence: ref.confidence,
198
+ reason,
199
+ evidence: ref.evidence.map(serializeEvidence),
200
+ ...(step !== undefined ? { step } : {}),
201
+ };
202
+ }
203
+ /**
204
+ * Map a `Reference.kind` to an existing `RelationshipType`. Read/write
205
+ * both fold into `ACCESSES`; `type-reference` + `import-use` both fold
206
+ * into `USES`. This keeps the graph schema additive — no new
207
+ * RelationshipType values are introduced by this module.
208
+ */
209
+ function mapKindToType(kind) {
210
+ switch (kind) {
211
+ case 'call':
212
+ return 'CALLS';
213
+ case 'read':
214
+ case 'write':
215
+ return 'ACCESSES';
216
+ case 'inherits':
217
+ return 'INHERITS';
218
+ case 'type-reference':
219
+ case 'import-use':
220
+ return 'USES';
221
+ }
222
+ }
223
+ function serializeEvidence(e) {
224
+ return {
225
+ kind: e.kind,
226
+ weight: e.weight,
227
+ ...(e.note !== undefined ? { note: e.note } : {}),
228
+ };
229
+ }
@@ -21,10 +21,14 @@ import { SupportedLanguages } from '../../_shared/index.js';
21
21
  */
22
22
  export function detectFrameworkFromPath(filePath) {
23
23
  // Normalize path separators and ensure leading slash for consistent matching
24
- let p = filePath.toLowerCase().replace(/\\/g, '/');
24
+ const originalPath = filePath.replace(/\\/g, '/');
25
+ let p = originalPath.toLowerCase();
25
26
  if (!p.startsWith('/')) {
26
27
  p = '/' + p; // Add leading slash so patterns like '/app/' match 'app/...'
27
28
  }
29
+ const originalPathWithLeadingSlash = originalPath.startsWith('/')
30
+ ? originalPath
31
+ : `/${originalPath}`;
28
32
  // ========== JAVASCRIPT / TYPESCRIPT FRAMEWORKS ==========
29
33
  // Next.js - Pages Router (high confidence)
30
34
  if (p.includes('/pages/') && !p.includes('/_') && !p.includes('/api/')) {
@@ -91,7 +95,7 @@ export function detectFrameworkFromPath(filePath) {
91
95
  if ((p.includes('/components/') || p.includes('/views/')) &&
92
96
  (p.endsWith('.tsx') || p.endsWith('.jsx'))) {
93
97
  // Only boost if PascalCase filename (likely a component, not util)
94
- const fileName = p.split('/').pop() || '';
98
+ const fileName = originalPathWithLeadingSlash.split('/').pop() || '';
95
99
  if (/^[A-Z]/.test(fileName)) {
96
100
  return { framework: 'react', entryPointMultiplier: 1.5, reason: 'react-component' };
97
101
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.3-rc.15",
3
+ "version": "1.6.3-rc.17",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",