@@ -35,6 +35,9 @@ describe("contextValidator", () => {
3535 vi . mocked ( fs . stat ) . mockResolvedValue ( {
3636 size : 1024 * 1024 , // 1MB
3737 } as any )
38+ vi . mocked ( fsPromises . stat ) . mockResolvedValue ( {
39+ size : 1024 * 1024 , // 1MB
40+ } as any )
3841
3942 // Mock Task instance
4043 mockTask = {
@@ -70,10 +73,10 @@ describe("contextValidator", () => {
7073 const mockStats = { size : 50000 }
7174 vi . mocked ( fs . stat ) . mockResolvedValue ( mockStats as any )
7275
73- // Mock readLines to return content in larger batches (500 lines)
76+ // Mock readLines to return content in batches (50 lines)
7477 vi . mocked ( readLines ) . mockImplementation ( async ( path , endLine , startLine ) => {
7578 const start = startLine ?? 0
76- const end = endLine ?? 499
79+ const end = endLine ?? 49
7780 const lines = [ ]
7881 for ( let i = start ; i <= end ; i ++ ) {
7982 // Each line is ~60 chars to simulate real code
@@ -119,10 +122,10 @@ describe("contextValidator", () => {
119122 const mockStats = { size : 50000 }
120123 vi . mocked ( fs . stat ) . mockResolvedValue ( mockStats as any )
121124
122- // Mock readLines with larger batches
125+ // Mock readLines with batches
123126 vi . mocked ( readLines ) . mockImplementation ( async ( path , endLine , startLine ) => {
124127 const start = startLine ?? 0
125- const end = endLine ?? 499
128+ const end = endLine ?? 49
126129 const lines = [ ]
127130 for ( let i = start ; i <= end && i < 2000 ; i ++ ) {
128131 // Dense content - 150 chars per line
@@ -172,7 +175,7 @@ describe("contextValidator", () => {
172175 // Mock readLines to return dense content
173176 vi . mocked ( readLines ) . mockImplementation ( async ( path , endLine , startLine ) => {
174177 const start = startLine ?? 0
175- const end = Math . min ( endLine ?? 499 , start + 499 )
178+ const end = Math . min ( endLine ?? 49 , start + 49 )
176179 const lines = [ ]
177180 for ( let i = start ; i <= end && i < 10000 ; i ++ ) {
178181 // Very dense content - 300 chars per line
@@ -212,10 +215,10 @@ describe("contextValidator", () => {
212215 const mockStats = { size : 60_000_000 } // 60MB file
213216 vi . mocked ( fs . stat ) . mockResolvedValue ( mockStats as any )
214217
215- // Mock readLines to return dense content in larger batches
218+ // Mock readLines to return dense content in batches
216219 vi . mocked ( readLines ) . mockImplementation ( async ( path , endLine , startLine ) => {
217220 const start = startLine ?? 0
218- const end = Math . min ( endLine ?? 499 , start + 499 )
221+ const end = Math . min ( endLine ?? 49 , start + 49 )
219222 const lines = [ ]
220223 for ( let i = start ; i <= end && i < 100000 ; i ++ ) {
221224 // Very dense content - 300 chars per line
@@ -308,9 +311,10 @@ describe("contextValidator", () => {
308311
309312 expect ( result . shouldLimit ) . toBe ( true )
310313 // With the new implementation, when content exceeds limit even after cutback,
311- // it returns a very small number (10) as specified in the safety check
312- expect ( result . safeMaxLines ) . toBe ( 10 )
313- expect ( result . reason ) . toContain ( "File too large for available context" )
314+ // it returns MIN_USEFUL_LINES (50) as the minimum
315+ expect ( result . safeMaxLines ) . toBe ( 50 )
316+ expect ( result . reason ) . toContain ( "File exceeds available context space" )
317+ expect ( result . reason ) . toContain ( "Safely read 50 lines" )
314318 } )
315319
316320 it ( "should handle negative available space gracefully" , async ( ) => {
@@ -347,9 +351,10 @@ describe("contextValidator", () => {
347351 )
348352
349353 expect ( result . shouldLimit ) . toBe ( true )
350- // When available space is negative, it returns minimal safe value
351- expect ( result . safeMaxLines ) . toBe ( 10 ) // Minimal safe value from safety check
352- expect ( result . reason ) . toContain ( "File too large for available context" )
354+ // When available space is negative, it returns MIN_USEFUL_LINES (50)
355+ expect ( result . safeMaxLines ) . toBe ( 50 ) // MIN_USEFUL_LINES from the refactored code
356+ expect ( result . reason ) . toContain ( "File exceeds available context space" )
357+ expect ( result . reason ) . toContain ( "Safely read 50 lines" )
353358 } )
354359
355360 it ( "should limit file when it is too large and would be truncated" , async ( ) => {
@@ -413,7 +418,7 @@ describe("contextValidator", () => {
413418 expect ( result . shouldLimit ) . toBe ( true )
414419 // With the new implementation, when space is very limited and content exceeds,
415420 // it returns the minimal safe value
416- expect ( result . reason ) . toContain ( "File too large for available context" )
421+ expect ( result . reason ) . toContain ( "File exceeds available context space " )
417422 } )
418423
419424 it ( "should not limit when file fits within context" , async ( ) => {
@@ -466,24 +471,27 @@ describe("contextValidator", () => {
466471 } )
467472
468473 describe ( "heuristic optimization" , ( ) => {
469- it ( "should skip validation for files with less than 100 lines " , async ( ) => {
474+ it ( "should skip validation for very small files by size " , async ( ) => {
470475 const filePath = "/test/small-file.ts"
471- const totalLines = 50 // Less than 100 lines
476+ const totalLines = 50
472477 const currentMaxReadFileLine = - 1
473478
474- // Mock file size to be small (3KB)
479+ // Mock file size to be very small (3KB - below 5KB threshold )
475480 vi . mocked ( fs . stat ) . mockResolvedValue ( {
476481 size : 3 * 1024 , // 3KB
477482 } as any )
483+ vi . mocked ( fsPromises . stat ) . mockResolvedValue ( {
484+ size : 3 * 1024 , // 3KB
485+ } as any )
478486
479487 const result = await validateFileSizeForContext ( filePath , totalLines , currentMaxReadFileLine , mockTask )
480488
481- // Should not limit small files
489+ // Should skip validation and return unlimited
482490 expect ( result . shouldLimit ) . toBe ( false )
483- expect ( result . safeMaxLines ) . toBe ( currentMaxReadFileLine )
484- // Should not call countTokens for small files
491+ expect ( result . safeMaxLines ) . toBe ( - 1 )
492+
493+ // Should not have made any API calls
485494 expect ( mockTask . api . countTokens ) . not . toHaveBeenCalled ( )
486- // Should not even attempt to read the file
487495 expect ( readLines ) . not . toHaveBeenCalled ( )
488496 } )
489497
@@ -618,4 +626,90 @@ describe("contextValidator", () => {
618626 expect ( result . safeMaxLines ) . toBeGreaterThan ( 0 )
619627 } )
620628 } )
629+
630+ describe ( "single-line file handling" , ( ) => {
631+ it ( "should handle single-line minified files that fit in context" , async ( ) => {
632+ const filePath = "/test/minified.js"
633+ const totalLines = 1
634+ const currentMaxReadFileLine = - 1
635+
636+ // Mock a large single-line file (500KB)
637+ vi . mocked ( fs . stat ) . mockResolvedValue ( {
638+ size : 500 * 1024 ,
639+ } as any )
640+
641+ // Mock reading the single line
642+ const minifiedContent = "const a=1;" . repeat ( 10000 ) // ~100KB of minified JS
643+ vi . mocked ( readLines ) . mockResolvedValue ( minifiedContent )
644+
645+ // Mock token count - fits within context
646+ mockTask . api . countTokens = vi . fn ( ) . mockResolvedValue ( 20000 ) // Well within available space
647+
648+ const result = await validateFileSizeForContext ( filePath , totalLines , currentMaxReadFileLine , mockTask )
649+
650+ // Should not limit since it fits
651+ expect ( result . shouldLimit ) . toBe ( false )
652+ expect ( result . safeMaxLines ) . toBe ( - 1 )
653+
654+ // Should have read the single line and counted tokens
655+ expect ( readLines ) . toHaveBeenCalledWith ( filePath , 0 , 0 )
656+ expect ( mockTask . api . countTokens ) . toHaveBeenCalledWith ( [ { type : "text" , text : minifiedContent } ] )
657+ } )
658+
659+ it ( "should limit single-line minified files that exceed context" , async ( ) => {
660+ const filePath = "/test/huge-minified.js"
661+ const totalLines = 1
662+ const currentMaxReadFileLine = - 1
663+
664+ // Mock a very large single-line file (5MB)
665+ vi . mocked ( fs . stat ) . mockResolvedValue ( {
666+ size : 5 * 1024 * 1024 ,
667+ } as any )
668+
669+ // Mock reading the single line
670+ const hugeMinifiedContent = "const a=1;" . repeat ( 100000 ) // ~1MB of minified JS
671+ vi . mocked ( readLines ) . mockResolvedValue ( hugeMinifiedContent )
672+
673+ // Mock token count - exceeds available space
674+ mockTask . api . countTokens = vi . fn ( ) . mockResolvedValue ( 80000 ) // Exceeds available ~63k tokens
675+
676+ const result = await validateFileSizeForContext ( filePath , totalLines , currentMaxReadFileLine , mockTask )
677+
678+ // Should limit the file
679+ expect ( result . shouldLimit ) . toBe ( true )
680+ expect ( result . safeMaxLines ) . toBe ( 0 )
681+ expect ( result . reason ) . toContain ( "Minified file exceeds available context space" )
682+ expect ( result . reason ) . toContain ( "80000 tokens" )
683+ expect ( result . reason ) . toContain ( "Consider using search_files" )
684+
685+ // Should have attempted to read and count tokens
686+ expect ( readLines ) . toHaveBeenCalledWith ( filePath , 0 , 0 )
687+ expect ( mockTask . api . countTokens ) . toHaveBeenCalledWith ( [ { type : "text" , text : hugeMinifiedContent } ] )
688+ } )
689+
690+ it ( "should fall back to regular validation if single-line processing fails" , async ( ) => {
691+ const filePath = "/test/problematic-minified.js"
692+ const totalLines = 1
693+ const currentMaxReadFileLine = - 1
694+
695+ // Mock file size
696+ vi . mocked ( fs . stat ) . mockResolvedValue ( {
697+ size : 100 * 1024 ,
698+ } as any )
699+
700+ // Mock readLines to fail on first call (single line read)
701+ vi . mocked ( readLines ) . mockRejectedValueOnce ( new Error ( "Read error" ) ) . mockResolvedValue ( "some content" ) // Subsequent reads succeed
702+
703+ // Mock token counting
704+ mockTask . api . countTokens = vi . fn ( ) . mockResolvedValue ( 1000 )
705+
706+ const result = await validateFileSizeForContext ( filePath , totalLines , currentMaxReadFileLine , mockTask )
707+
708+ // Should have attempted single-line read
709+ expect ( readLines ) . toHaveBeenCalledWith ( filePath , 0 , 0 )
710+
711+ // Should proceed with regular validation after failure
712+ expect ( result . shouldLimit ) . toBeDefined ( )
713+ } )
714+ } )
621715} )
0 commit comments