@@ -39,6 +39,7 @@ import {
3939 INTELLIGENCE_FILENAME ,
4040 KEYWORD_INDEX_FILENAME ,
4141 MANIFEST_FILENAME ,
42+ RELATIONSHIPS_FILENAME ,
4243 VECTOR_DB_DIRNAME
4344} from '../constants/codebase-context.js' ;
4445
@@ -91,13 +92,15 @@ async function atomicSwapStagingToActive(
9192 const activeVectorDir = path . join ( contextDir , VECTOR_DB_DIRNAME ) ;
9293 const activeManifestPath = path . join ( contextDir , MANIFEST_FILENAME ) ;
9394 const activeStatsPath = path . join ( contextDir , INDEXING_STATS_FILENAME ) ;
95+ const activeRelationshipsPath = path . join ( contextDir , RELATIONSHIPS_FILENAME ) ;
9496
9597 const stagingMetaPath = path . join ( stagingDir , INDEX_META_FILENAME ) ;
9698 const stagingIndexPath = path . join ( stagingDir , KEYWORD_INDEX_FILENAME ) ;
9799 const stagingIntelligencePath = path . join ( stagingDir , INTELLIGENCE_FILENAME ) ;
98100 const stagingVectorDir = path . join ( stagingDir , VECTOR_DB_DIRNAME ) ;
99101 const stagingManifestPath = path . join ( stagingDir , MANIFEST_FILENAME ) ;
100102 const stagingStatsPath = path . join ( stagingDir , INDEXING_STATS_FILENAME ) ;
103+ const stagingRelationshipsPath = path . join ( stagingDir , RELATIONSHIPS_FILENAME ) ;
101104
102105 // Step 1: Create .previous directory and move current active there
103106 await fs . mkdir ( previousDir , { recursive : true } ) ;
@@ -134,6 +137,7 @@ async function atomicSwapStagingToActive(
134137 await moveIfExists ( activeIntelligencePath , path . join ( previousDir , INTELLIGENCE_FILENAME ) ) ;
135138 await moveIfExists ( activeManifestPath , path . join ( previousDir , MANIFEST_FILENAME ) ) ;
136139 await moveIfExists ( activeStatsPath , path . join ( previousDir , INDEXING_STATS_FILENAME ) ) ;
140+ await moveIfExists ( activeRelationshipsPath , path . join ( previousDir , RELATIONSHIPS_FILENAME ) ) ;
137141 await moveDirIfExists ( activeVectorDir , path . join ( previousDir , VECTOR_DB_DIRNAME ) ) ;
138142
139143 // Step 2: Move staging artifacts to active location
@@ -143,6 +147,7 @@ async function atomicSwapStagingToActive(
143147 await moveIfExists ( stagingIntelligencePath , activeIntelligencePath ) ;
144148 await moveIfExists ( stagingManifestPath , activeManifestPath ) ;
145149 await moveIfExists ( stagingStatsPath , activeStatsPath ) ;
150+ await moveIfExists ( stagingRelationshipsPath , activeRelationshipsPath ) ;
146151 await moveDirIfExists ( stagingVectorDir , activeVectorDir ) ;
147152
148153 // Step 3: Clean up .previous and staging directories
@@ -171,6 +176,7 @@ async function atomicSwapStagingToActive(
171176 await moveIfExists ( path . join ( previousDir , INTELLIGENCE_FILENAME ) , activeIntelligencePath ) ;
172177 await moveIfExists ( path . join ( previousDir , MANIFEST_FILENAME ) , activeManifestPath ) ;
173178 await moveIfExists ( path . join ( previousDir , INDEXING_STATS_FILENAME ) , activeStatsPath ) ;
179+ await moveIfExists ( path . join ( previousDir , RELATIONSHIPS_FILENAME ) , activeRelationshipsPath ) ;
174180 await moveDirIfExists ( path . join ( previousDir , VECTOR_DB_DIRNAME ) , activeVectorDir ) ;
175181 console . error ( 'Rollback successful' ) ;
176182 } catch ( rollbackError ) {
@@ -796,6 +802,51 @@ export class CodebaseIndexer {
796802 } ;
797803 await fs . writeFile ( intelligencePath , JSON . stringify ( intelligence , null , 2 ) ) ;
798804
805+ // Write relationships sidecar (versioned, for fast lookup)
806+ const relationshipsPath = path . join ( activeContextDir , RELATIONSHIPS_FILENAME ) ;
807+ const graphData = internalFileGraph . toJSON ( ) ;
808+
809+ // Build reverse import map (importedBy)
810+ const importedBy : Record < string , string [ ] > = { } ;
811+ if ( graphData . imports ) {
812+ for ( const [ file , deps ] of Object . entries ( graphData . imports ) ) {
813+ for ( const dep of deps as string [ ] ) {
814+ if ( ! importedBy [ dep ] ) importedBy [ dep ] = [ ] ;
815+ importedBy [ dep ] . push ( file ) ;
816+ }
817+ }
818+ }
819+
820+ // Build symbol export map (exportedBy)
821+ const exportedBy : Record < string , string [ ] > = { } ;
822+ if ( graphData . exports ) {
823+ for ( const [ file , exps ] of Object . entries ( graphData . exports ) ) {
824+ for ( const exp of exps as Array < { name : string ; type : string } > ) {
825+ if ( exp . name && exp . name !== 'default' ) {
826+ if ( ! exportedBy [ exp . name ] ) exportedBy [ exp . name ] = [ ] ;
827+ if ( ! exportedBy [ exp . name ] . includes ( file ) ) {
828+ exportedBy [ exp . name ] . push ( file ) ;
829+ }
830+ }
831+ }
832+ }
833+ }
834+
835+ const relationships = {
836+ header : { buildId, formatVersion : INDEX_FORMAT_VERSION } ,
837+ generatedAt,
838+ graph : {
839+ imports : graphData . imports || { } ,
840+ importedBy,
841+ exports : graphData . exports || { }
842+ } ,
843+ symbols : {
844+ exportedBy
845+ } ,
846+ stats : graphData . stats || internalFileGraph . getStats ( )
847+ } ;
848+ await fs . writeFile ( relationshipsPath , JSON . stringify ( relationships , null , 2 ) ) ;
849+
799850 // Write manifest (both full and incremental)
800851 // For full rebuild, write to staging; for incremental, write to active
801852 const activeManifestPath = path . join ( activeContextDir , MANIFEST_FILENAME ) ;
@@ -831,7 +882,8 @@ export class CodebaseIndexer {
831882 vectorDb : { path : VECTOR_DB_DIRNAME , provider : 'lancedb' } ,
832883 intelligence : { path : INTELLIGENCE_FILENAME } ,
833884 manifest : { path : MANIFEST_FILENAME } ,
834- indexingStats : { path : INDEXING_STATS_FILENAME }
885+ indexingStats : { path : INDEXING_STATS_FILENAME } ,
886+ relationships : { path : RELATIONSHIPS_FILENAME }
835887 }
836888 } ,
837889 null ,
0 commit comments