@@ -62,6 +62,13 @@ export interface IndexerEvents {
6262
6363export class Indexer extends EventEmitter {
6464 private contextIgnore : ContextIgnore ;
65+ private pendingReferences : Array < {
66+ calls : Array < { callerNodeId : string ; calleeName : string } > ;
67+ imports : import ( './parser.js' ) . ParsedImport [ ] ;
68+ inheritance : import ( './parser.js' ) . ParsedInheritance [ ] ;
69+ repositoryId : string ;
70+ filePath : string ;
71+ } > = [ ] ;
6572
6673 constructor (
6774 public storage : StorageProvider ,
@@ -116,6 +123,8 @@ export class Indexer extends EventEmitter {
116123 const files = await this . discoverFiles ( absolutePath , options . respectIgnore ) ;
117124 job . filesTotal = files . length ;
118125
126+ this . pendingReferences = [ ] ;
127+
119128 const BATCH_SIZE = 50 ;
120129 for ( let i = 0 ; i < files . length ; i += BATCH_SIZE ) {
121130 const batch = files . slice ( i , i + BATCH_SIZE ) ;
@@ -134,6 +143,12 @@ export class Indexer extends EventEmitter {
134143 }
135144 }
136145
146+ const forwardEdges = this . resolveForwardEdges ( repositoryId ) ;
147+ if ( forwardEdges > 0 ) {
148+ console . log ( `Resolved ${ forwardEdges } forward-reference edges` ) ;
149+ }
150+ this . pendingReferences = [ ] ;
151+
137152 const stats = this . graph . getStats ( ) ;
138153 job . nodesCreated = stats . nodeCount ;
139154 job . edgesCreated = stats . edgeCount ;
@@ -217,6 +232,14 @@ export class Indexer extends EventEmitter {
217232 }
218233 this . storage . upsertEdges ( edges ) ;
219234
235+ this . pendingReferences . push ( {
236+ calls : parsed . calls ,
237+ imports : parsed . imports ,
238+ inheritance : parsed . inheritance ,
239+ repositoryId,
240+ filePath : parsed . filePath ,
241+ } ) ;
242+
220243 fileMetadata . hash = parsed . hash ;
221244 fileMetadata . language = parsed . language ;
222245 fileMetadata . nodeCount = parsed . nodes . length ;
@@ -278,6 +301,110 @@ export class Indexer extends EventEmitter {
278301 }
279302 }
280303
304+ private resolveForwardEdges ( repositoryId : string ) : number {
305+ let created = 0 ;
306+ const now = new Date ( ) ;
307+
308+ for ( const ref of this . pendingReferences ) {
309+ if ( ref . repositoryId !== repositoryId ) continue ;
310+
311+ for ( const call of ref . calls ) {
312+ const targets = this . graph . findByName ( call . calleeName ) ;
313+ for ( const target of targets ) {
314+ if ( target . repositoryId !== repositoryId ) continue ;
315+ const edgeId = this . generateEdgeId ( call . callerNodeId , target . id , 'calls' ) ;
316+ try {
317+ if ( ! this . graph . getNode ( call . callerNodeId ) ) continue ;
318+ this . graph . addEdge ( {
319+ id : edgeId ,
320+ sourceId : call . callerNodeId ,
321+ targetId : target . id ,
322+ kind : 'calls' ,
323+ confidence : 0.9 ,
324+ repositoryId,
325+ createdAt : now ,
326+ updatedAt : now ,
327+ } ) ;
328+ created ++ ;
329+ } catch {
330+ // Already exists or source/target missing
331+ }
332+ }
333+ }
334+
335+ for ( const inh of ref . inheritance ) {
336+ const parents = this . graph . findByName ( inh . parentName ) ;
337+ for ( const parent of parents ) {
338+ if ( parent . repositoryId !== repositoryId ) continue ;
339+ const edgeId = this . generateEdgeId ( inh . childNodeId , parent . id , inh . kind ) ;
340+ try {
341+ if ( ! this . graph . getNode ( inh . childNodeId ) ) continue ;
342+ this . graph . addEdge ( {
343+ id : edgeId ,
344+ sourceId : inh . childNodeId ,
345+ targetId : parent . id ,
346+ kind : inh . kind ,
347+ confidence : 1.0 ,
348+ repositoryId,
349+ createdAt : now ,
350+ updatedAt : now ,
351+ } ) ;
352+ created ++ ;
353+ } catch {
354+ // Already exists or source/target missing
355+ }
356+ }
357+ }
358+
359+ for ( const imp of ref . imports ) {
360+ if ( ! imp . source || ! imp . source . startsWith ( '.' ) ) continue ;
361+ const importDir = dirname ( ref . filePath ) ;
362+ const resolvedBase = join ( importDir , imp . source ) . replace ( / \\ / g, '/' ) ;
363+ const extensions = [ '' , '.ts' , '.tsx' , '.js' , '.jsx' , '/index.ts' , '/index.tsx' , '/index.js' ] ;
364+
365+ for ( const importedName of imp . imported ) {
366+ const targets = this . graph . findByName ( importedName ) ;
367+ for ( const target of targets ) {
368+ if ( target . repositoryId !== repositoryId ) continue ;
369+ if ( target . filePath === ref . filePath ) continue ;
370+
371+ const targetFileBase = target . filePath . replace ( / \. ( t s | t s x | j s | j s x ) $ / , '' ) . replace ( / \/ i n d e x $ / , '' ) ;
372+ const matchesPath = extensions . some ( ext => {
373+ const candidate = resolvedBase + ext ;
374+ return target . filePath === candidate || targetFileBase === resolvedBase ;
375+ } ) ;
376+ if ( ! matchesPath ) continue ;
377+
378+ const nodesInFile = this . graph . getNodesInFile ( ref . filePath ) ;
379+ const sourceNode = nodesInFile . find ( n =>
380+ n . kind === 'function' || n . kind === 'class' || n . kind === 'method'
381+ ) || nodesInFile [ 0 ] ;
382+ if ( ! sourceNode ) continue ;
383+
384+ const edgeId = this . generateEdgeId ( sourceNode . id , target . id , 'imports' ) ;
385+ try {
386+ this . graph . addEdge ( {
387+ id : edgeId ,
388+ sourceId : sourceNode . id ,
389+ targetId : target . id ,
390+ kind : 'imports' ,
391+ confidence : 1.0 ,
392+ repositoryId,
393+ createdAt : now ,
394+ updatedAt : now ,
395+ } ) ;
396+ created ++ ;
397+ } catch {
398+ // Already exists
399+ }
400+ }
401+ }
402+ }
403+ }
404+
405+ return created ;
406+ }
407+
281408 private static readonly SKIP_DIRS = new Set ( [
282409 'node_modules' , '.git' , '.svn' , '.hg' , 'vendor' , '__pycache__' ,
283410 '.venv' , 'venv' , 'dist' , 'build' , 'out' , 'target' , '.next' ,
0 commit comments