@@ -24,7 +24,11 @@ import type {
2424 PatternsData ,
2525 CodeChunk
2626} from '../types/index.js' ;
27- import { RELATIONSHIPS_FILENAME , KEYWORD_INDEX_FILENAME } from '../constants/codebase-context.js' ;
27+ import {
28+ EXCLUDED_DIRECTORY_NAMES ,
29+ RELATIONSHIPS_FILENAME ,
30+ KEYWORD_INDEX_FILENAME
31+ } from '../constants/codebase-context.js' ;
2832
2933// ---------------------------------------------------------------------------
3034// Internal types for relationships.json
@@ -50,12 +54,37 @@ interface RelationshipsData {
5054 } ;
5155}
5256
53- // ---------------------------------------------------------------------------
54- // Entrypoint exclusion pattern
55- // ---------------------------------------------------------------------------
56-
57- const ENTRYPOINT_EXCLUSION_RE =
58- / (?: ^ | \/ ) (?: t e s t s ? | _ _ t e s t s _ _ | f i x t u r e s ? | s c r i p t s ? ) \/ | \. t e s t \. | \. s p e c \. / ;
57+ type CodebaseMapMode = 'bounded' | 'full' ;
58+
59+ type BuildCodebaseMapOptions = {
60+ mode ?: CodebaseMapMode ;
61+ } ;
62+
63+ const BOUNDED_SECTION_LIMITS = {
64+ entrypoints : 8 ,
65+ hubFiles : 5 ,
66+ keyInterfaces : 8 ,
67+ apiSurfaceFiles : 8 ,
68+ apiSurfaceExports : 3 ,
69+ hotspots : 5 ,
70+ bestExamples : 3
71+ } as const ;
72+
73+ const MAP_EXCLUDED_PATH_PATTERNS = [
74+ / ^ r e p o s \/ [ ^ / ] + (?: \/ | $ ) / i,
75+ / (?: ^ | \/ ) (?: t e s t s ? | _ _ t e s t s _ _ | s p e c s ? | _ _ s p e c s _ _ ) (?: \/ | $ ) / i,
76+ / \. (?: t e s t | s p e c ) \. [ ^ / ] + $ / i,
77+ / (?: ^ | \/ ) (?: f i x t u r e s ? | _ _ f i x t u r e s _ _ ) (?: \/ | $ ) / i,
78+ / (?: ^ | \/ ) (?: g e n e r a t e d | _ _ g e n e r a t e d _ _ ) (?: \/ | $ ) / i,
79+ / (?: ^ | \/ ) [ ^ / ] * \. (?: g e n e r a t e d | g e n | m i n ) \. [ ^ / ] + $ / i,
80+ / \. s n a p $ / i
81+ ] as const ;
82+
83+ const MAP_EXCLUDED_DIRECTORY_NAMES = new Set (
84+ [ ...EXCLUDED_DIRECTORY_NAMES , '__fixtures__' , '__generated__' , 'fixtures' , 'generated' ] . map (
85+ ( segment ) => segment . toLowerCase ( )
86+ )
87+ ) ;
5988
6089// ---------------------------------------------------------------------------
6190// Builder
@@ -66,7 +95,11 @@ const ENTRYPOINT_EXCLUSION_RE =
6695 * Reads `intelligence.json`, `relationships.json`, and `index.json` from project paths.
6796 * Degrades gracefully when artifacts are missing.
6897 */
69- export async function buildCodebaseMap ( project : ProjectState ) : Promise < CodebaseMapSummary > {
98+ export async function buildCodebaseMap (
99+ project : ProjectState ,
100+ options : BuildCodebaseMapOptions = { }
101+ ) : Promise < CodebaseMapSummary > {
102+ const mode = options . mode ?? 'bounded' ;
70103 const projectName = path . basename ( project . rootPath ) ;
71104
72105 // Read intelligence.json
@@ -100,9 +133,10 @@ export async function buildCodebaseMap(project: ProjectState): Promise<CodebaseM
100133 }
101134
102135 const graph = relationships . graph ?? { } ;
103- const graphImports = graph . imports ?? { } ;
104- const graphImportedBy = graph . importedBy ?? { } ;
105- const graphExports = graph . exports ?? { } ;
136+ const graphImports = filterAdjacencyGraph ( graph . imports ?? { } , mode ) ;
137+ const graphImportedBy = filterAdjacencyGraph ( graph . importedBy ?? { } , mode ) ;
138+ const graphExports = filterExportGraph ( graph . exports ?? { } , mode ) ;
139+ const filteredChunks = chunks . filter ( ( chunk ) => isMapEligiblePath ( chunk . relativePath , mode ) ) ;
106140 // relationships.json has stats at top level OR inside graph
107141 const statsSource =
108142 relationships . stats ??
@@ -133,13 +167,13 @@ export async function buildCodebaseMap(project: ProjectState): Promise<CodebaseM
133167 const entrypoints : string [ ] = [ ] ;
134168 for ( const [ file , imports ] of Object . entries ( graphImports ) ) {
135169 if ( imports . length === 0 ) continue ; // no imports — not an entrypoint
136- if ( ENTRYPOINT_EXCLUSION_RE . test ( file ) ) continue ; // test/script file
137170 const importers = graphImportedBy [ file ] ;
138171 if ( ! importers || importers . length === 0 ) {
139172 entrypoints . push ( file ) ;
140173 }
141174 }
142175 entrypoints . sort ( ) ;
176+ const boundedEntrypoints = maybeLimit ( entrypoints , BOUNDED_SECTION_LIMITS . entrypoints , mode ) ;
143177
144178 // --- Hub files ---
145179 const importedByCounts : Array < { file : string ; count : number } > = Object . entries (
@@ -150,17 +184,17 @@ export async function buildCodebaseMap(project: ProjectState): Promise<CodebaseM
150184 ( x ) => x . count ,
151185 ( x ) => x . file
152186 )
153- . slice ( 0 , 5 )
187+ . slice ( 0 , mode === 'bounded' ? BOUNDED_SECTION_LIMITS . hubFiles : undefined )
154188 . map ( ( x ) => x . file ) ;
155189
156190 // --- Key interfaces ---
157- const keyInterfaces = deriveKeyInterfaces ( chunks , graphImportedBy ) ;
191+ const keyInterfaces = deriveKeyInterfaces ( filteredChunks , graphImportedBy , mode ) ;
158192
159193 // --- API surface ---
160- const apiSurface = deriveApiSurface ( entrypoints , graphExports ) ;
194+ const apiSurface = deriveApiSurface ( boundedEntrypoints , graphExports , mode ) ;
161195
162196 // --- Dependency hotspots ---
163- const hotspots = deriveHotspots ( graphImports , graphImportedBy ) ;
197+ const hotspots = deriveHotspots ( graphImports , graphImportedBy , mode ) ;
164198
165199 // --- Active patterns ---
166200 const patterns : PatternsData = intelligence . patterns ?? { } ;
@@ -187,8 +221,12 @@ export async function buildCodebaseMap(project: ProjectState): Promise<CodebaseM
187221 // --- Best examples ---
188222 const dominantPatternName =
189223 activePatterns . length > 0 ? activePatterns [ 0 ] . name : 'high-quality example' ;
190- const goldenFiles = intelligence . goldenFiles ?? [ ] ;
191- const bestExamples : CodebaseMapExample [ ] = goldenFiles . slice ( 0 , 3 ) . map ( ( gf ) => ( {
224+ const goldenFiles = ( intelligence . goldenFiles ?? [ ] ) . filter ( ( gf ) => isMapEligiblePath ( gf . file , mode ) ) ;
225+ const bestExamples : CodebaseMapExample [ ] = maybeLimit (
226+ goldenFiles ,
227+ BOUNDED_SECTION_LIMITS . bestExamples ,
228+ mode
229+ ) . map ( ( gf ) => ( {
192230 file : gf . file ,
193231 score : gf . score ,
194232 reason : dominantPatternName
@@ -210,7 +248,14 @@ export async function buildCodebaseMap(project: ProjectState): Promise<CodebaseM
210248
211249 return {
212250 project : projectName ,
213- architecture : { layers, entrypoints, hubFiles, keyInterfaces, apiSurface, hotspots } ,
251+ architecture : {
252+ layers,
253+ entrypoints : boundedEntrypoints ,
254+ hubFiles,
255+ keyInterfaces,
256+ apiSurface,
257+ hotspots
258+ } ,
214259 activePatterns,
215260 bestExamples,
216261 graphStats,
@@ -236,7 +281,8 @@ function buildSignatureHint(content: string): string {
236281
237282function deriveKeyInterfaces (
238283 chunks : CodeChunk [ ] ,
239- graphImportedBy : Record < string , string [ ] >
284+ graphImportedBy : Record < string , string [ ] > ,
285+ mode : CodebaseMapMode
240286) : CodebaseMapKeyInterface [ ] {
241287 const symbolChunks = chunks . filter (
242288 ( c ) => c . metadata ?. symbolAware === true && SYMBOL_KINDS . has ( c . metadata . symbolKind ?? '' )
@@ -251,18 +297,21 @@ function deriveKeyInterfaces(
251297 if ( lenDiff !== 0 ) return lenDiff ;
252298 return a . chunk . relativePath . localeCompare ( b . chunk . relativePath ) ;
253299 } ) ;
254- return scored . slice ( 0 , 10 ) . map ( ( { chunk, importerCount } ) => ( {
255- name : chunk . metadata . symbolName ?? path . basename ( chunk . relativePath ) ,
256- kind : chunk . metadata . symbolKind ?? 'unknown' ,
257- file : chunk . relativePath ,
258- importerCount,
259- signatureHint : buildSignatureHint ( chunk . content )
260- } ) ) ;
300+ return maybeLimit ( scored , BOUNDED_SECTION_LIMITS . keyInterfaces , mode ) . map (
301+ ( { chunk, importerCount } ) => ( {
302+ name : chunk . metadata . symbolName ?? path . basename ( chunk . relativePath ) ,
303+ kind : chunk . metadata . symbolKind ?? 'unknown' ,
304+ file : chunk . relativePath ,
305+ importerCount,
306+ signatureHint : buildSignatureHint ( chunk . content )
307+ } )
308+ ) ;
261309}
262310
263311function deriveApiSurface (
264312 entrypoints : string [ ] ,
265- graphExports : Record < string , Array < { name : string ; type : string } > >
313+ graphExports : Record < string , Array < { name : string ; type : string } > > ,
314+ mode : CodebaseMapMode
266315) : CodebaseMapApiSurface [ ] {
267316 const results : CodebaseMapApiSurface [ ] = [ ] ;
268317 for ( const ep of entrypoints ) {
@@ -271,16 +320,17 @@ function deriveApiSurface(
271320 const names = exps
272321 . map ( ( e ) => e . name )
273322 . filter ( ( n ) => n && n !== 'default' )
274- . slice ( 0 , 5 ) ;
323+ . slice ( 0 , mode === 'bounded' ? BOUNDED_SECTION_LIMITS . apiSurfaceExports : undefined ) ;
275324 if ( names . length === 0 ) continue ;
276325 results . push ( { file : ep , exports : names } ) ;
277326 }
278- return results ;
327+ return maybeLimit ( results , BOUNDED_SECTION_LIMITS . apiSurfaceFiles , mode ) ;
279328}
280329
281330function deriveHotspots (
282331 graphImports : Record < string , string [ ] > ,
283- graphImportedBy : Record < string , string [ ] >
332+ graphImportedBy : Record < string , string [ ] > ,
333+ mode : CodebaseMapMode
284334) : CodebaseMapHotspot [ ] {
285335 const allFiles = new Set ( [ ...Object . keys ( graphImports ) , ...Object . keys ( graphImportedBy ) ] ) ;
286336 const hotspots : CodebaseMapHotspot [ ] = [ ] ;
@@ -295,7 +345,7 @@ function deriveHotspots(
295345 if ( b . combined !== a . combined ) return b . combined - a . combined ;
296346 return a . file . localeCompare ( b . file ) ;
297347 } ) ;
298- return hotspots . slice ( 0 , 5 ) ;
348+ return maybeLimit ( hotspots , BOUNDED_SECTION_LIMITS . hotspots , mode ) ;
299349}
300350
301351function enrichLayers (
@@ -642,3 +692,61 @@ function sortByCountThenAlpha<T>(
642692 return getName ( a ) . localeCompare ( getName ( b ) ) ;
643693 } ) ;
644694}
695+
696+ function maybeLimit < T > ( items : T [ ] , limit : number , mode : CodebaseMapMode ) : T [ ] {
697+ return mode === 'bounded' ? items . slice ( 0 , limit ) : items ;
698+ }
699+
700+ function filterAdjacencyGraph (
701+ graph : Record < string , string [ ] > ,
702+ mode : CodebaseMapMode
703+ ) : Record < string , string [ ] > {
704+ if ( mode === 'full' ) {
705+ return graph ;
706+ }
707+
708+ return Object . fromEntries (
709+ Object . entries ( graph )
710+ . filter ( ( [ file ] ) => isMapEligiblePath ( file , mode ) )
711+ . map ( ( [ file , related ] ) => [ file , related . filter ( ( item ) => isMapEligiblePath ( item , mode ) ) ] )
712+ ) ;
713+ }
714+
715+ function filterExportGraph (
716+ graph : Record < string , Array < { name : string ; type : string } > > ,
717+ mode : CodebaseMapMode
718+ ) : Record < string , Array < { name : string ; type : string } > > {
719+ if ( mode === 'full' ) {
720+ return graph ;
721+ }
722+
723+ return Object . fromEntries (
724+ Object . entries ( graph ) . filter ( ( [ file ] ) => isMapEligiblePath ( file , mode ) )
725+ ) ;
726+ }
727+
728+ function isMapEligiblePath ( filePath : string , mode : CodebaseMapMode ) : boolean {
729+ if ( mode === 'full' ) {
730+ return true ;
731+ }
732+
733+ const normalizedPath = normalizeMapPath ( filePath ) ;
734+ if ( ! normalizedPath ) {
735+ return false ;
736+ }
737+
738+ const segments = normalizedPath
739+ . split ( '/' )
740+ . map ( ( segment ) => segment . toLowerCase ( ) )
741+ . filter ( Boolean ) ;
742+
743+ if ( segments . some ( ( segment ) => MAP_EXCLUDED_DIRECTORY_NAMES . has ( segment ) ) ) {
744+ return false ;
745+ }
746+
747+ return ! MAP_EXCLUDED_PATH_PATTERNS . some ( ( pattern ) => pattern . test ( normalizedPath ) ) ;
748+ }
749+
750+ function normalizeMapPath ( filePath : string ) : string {
751+ return filePath . replace ( / \\ / g, '/' ) . replace ( / ^ \. \/ / , '' ) . trim ( ) ;
752+ }
0 commit comments