@@ -359,7 +359,9 @@ describe('search_codebase compact/full mode', () => {
359359 [ key : string ] : unknown ;
360360 } ;
361361
362- expect ( payload . searchQuality . tokenEstimate ) . toBe ( Math . ceil ( response . content [ 0 ] . text . length / 4 ) ) ;
362+ expect ( payload . searchQuality . tokenEstimate ) . toBe (
363+ Math . ceil ( response . content [ 0 ] . text . length / 4 )
364+ ) ;
363365 expect ( payload . searchQuality . warning ) . toBeUndefined ( ) ;
364366 } ) ;
365367
@@ -396,7 +398,9 @@ describe('search_codebase compact/full mode', () => {
396398 } ;
397399 } ;
398400
399- expect ( payload . searchQuality . tokenEstimate ) . toBe ( Math . ceil ( response . content [ 0 ] . text . length / 4 ) ) ;
401+ expect ( payload . searchQuality . tokenEstimate ) . toBe (
402+ Math . ceil ( response . content [ 0 ] . text . length / 4 )
403+ ) ;
400404 expect ( payload . searchQuality . tokenEstimate ) . toBeGreaterThan ( 4000 ) ;
401405 expect ( payload . searchQuality . warning ) . toBe (
402406 `Large search payload: estimated ${ payload . searchQuality . tokenEstimate } tokens. Try tighter filters (e.g. layer=, language=) to reduce payload size.`
@@ -439,6 +443,123 @@ describe('search_codebase compact/full mode', () => {
439443 expect ( Array . isArray ( hints . callers ) ) . toBe ( true ) ;
440444 } ) ;
441445
446+ it ( 'full mode serializes chunk-level imports and exports' , async ( ) => {
447+ searchMocks . search . mockResolvedValueOnce ( [
448+ makeResult ( {
449+ imports : [
450+ 'src/auth/token-store.ts' ,
451+ 'src/auth/session.ts' ,
452+ 'src/shared/logger.ts' ,
453+ 'src/config/env.ts' ,
454+ 'src/http/client.ts' ,
455+ 'src/extra/ignored.ts'
456+ ] ,
457+ exports : [ 'AuthService' , 'createAuthService' , 'AUTH_TOKEN' , 'defaultIgnored' ]
458+ } )
459+ ] ) ;
460+
461+ const { server } = await import ( '../src/index.js' ) ;
462+ const handler = (
463+ server as {
464+ _requestHandlers ?: Map <
465+ string ,
466+ ( r : unknown ) => Promise < { content : Array < { type : string ; text : string } > } >
467+ > ;
468+ }
469+ ) . _requestHandlers ?. get ( 'tools/call' ) ;
470+ if ( ! handler ) throw new Error ( 'Expected tools/call handler' ) ;
471+
472+ const response = await handler ( {
473+ jsonrpc : '2.0' ,
474+ id : 1 ,
475+ method : 'tools/call' ,
476+ params : {
477+ name : 'search_codebase' ,
478+ arguments : { query : 'auth service' , mode : 'full' }
479+ }
480+ } ) ;
481+
482+ const payload = JSON . parse ( response . content [ 0 ] . text ) as {
483+ budget : { mode : string } ;
484+ results : Array < { imports ?: string [ ] ; exports ?: string [ ] } > ;
485+ } ;
486+
487+ expect ( payload . budget . mode ) . toBe ( 'full' ) ;
488+ expect ( payload . results [ 0 ] . imports ) . toEqual ( [
489+ 'src/auth/token-store.ts' ,
490+ 'src/auth/session.ts' ,
491+ 'src/shared/logger.ts' ,
492+ 'src/config/env.ts' ,
493+ 'src/http/client.ts'
494+ ] ) ;
495+ expect ( payload . results [ 0 ] . exports ) . toEqual ( [
496+ 'AuthService' ,
497+ 'createAuthService' ,
498+ 'AUTH_TOKEN' ,
499+ 'defaultIgnored'
500+ ] ) ;
501+ } ) ;
502+
503+ it ( 'real CodebaseSearcher preserves chunk imports and exports' , async ( ) => {
504+ if ( ! tempRoot ) throw new Error ( 'tempRoot not initialized' ) ;
505+
506+ const ctxDir = path . join ( tempRoot , CODEBASE_CONTEXT_DIRNAME ) ;
507+ const actualChunk = {
508+ id : 'auth-chunk' ,
509+ content :
510+ 'import { tokenStore } from "./token-store";\nexport class AuthService {\n getToken() { return tokenStore.read(); }\n}\nexport const AUTH_TOKEN = "auth";' ,
511+ filePath : path . join ( tempRoot , 'src' , 'auth' , 'auth.service.ts' ) ,
512+ relativePath : 'src/auth/auth.service.ts' ,
513+ startLine : 1 ,
514+ endLine : 5 ,
515+ language : 'ts' ,
516+ dependencies : [ ] ,
517+ imports : [
518+ 'src/auth/token-store.ts' ,
519+ 'src/auth/session.ts' ,
520+ 'src/shared/logger.ts' ,
521+ 'src/config/env.ts' ,
522+ 'src/http/client.ts'
523+ ] ,
524+ exports : [ 'AuthService' , 'AUTH_TOKEN' ] ,
525+ tags : [ 'service' ] ,
526+ metadata : {
527+ className : 'AuthService' ,
528+ symbolAware : true ,
529+ symbolName : 'AuthService' ,
530+ symbolKind : 'class'
531+ }
532+ } ;
533+
534+ await fs . writeFile (
535+ path . join ( ctxDir , KEYWORD_INDEX_FILENAME ) ,
536+ JSON . stringify (
537+ {
538+ header : { buildId : 'test-build-compact' , formatVersion : INDEX_FORMAT_VERSION } ,
539+ chunks : [ actualChunk ]
540+ } ,
541+ null ,
542+ 2
543+ ) ,
544+ 'utf-8'
545+ ) ;
546+
547+ const actualSearchModule = await vi . importActual < typeof import ( '../src/core/search.js' ) > (
548+ '../src/core/search.js'
549+ ) ;
550+ const searcher = new actualSearchModule . CodebaseSearcher ( tempRoot ) ;
551+ const results = await searcher . search ( 'AuthService token' , 5 , undefined , {
552+ useSemanticSearch : false ,
553+ useKeywordSearch : true ,
554+ enableReranker : false
555+ } ) ;
556+
557+ expect ( results ) . toHaveLength ( 1 ) ;
558+ expect ( results [ 0 ] . filePath ) . toBe ( actualChunk . filePath ) ;
559+ expect ( results [ 0 ] . imports ) . toEqual ( actualChunk . imports ) ;
560+ expect ( results [ 0 ] . exports ) . toEqual ( actualChunk . exports ) ;
561+ } ) ;
562+
442563 it ( 'adds a warning only when the final full payload exceeds the compact budget threshold' , async ( ) => {
443564 const oversizedSummary = 'Token-heavy summary ' . repeat ( 1200 ) ;
444565 const oversizedSnippet = 'const token = authService.getToken();\n' . repeat ( 600 ) ;
@@ -482,7 +603,9 @@ describe('search_codebase compact/full mode', () => {
482603 [ key : string ] : unknown ;
483604 } ;
484605
485- expect ( payload . searchQuality . tokenEstimate ) . toBe ( Math . ceil ( response . content [ 0 ] . text . length / 4 ) ) ;
606+ expect ( payload . searchQuality . tokenEstimate ) . toBe (
607+ Math . ceil ( response . content [ 0 ] . text . length / 4 )
608+ ) ;
486609 expect ( payload . searchQuality . tokenEstimate ) . toBeGreaterThan ( 4000 ) ;
487610 expect ( payload . searchQuality . warning ) . toBe (
488611 `Large search payload: estimated ${ payload . searchQuality . tokenEstimate } tokens. Prefer compact mode or tighter filters before pasting into an agent.`
0 commit comments