Skip to content

Commit 7d7df19

Browse files
committed
minified file partial fix
1 parent 32098cb commit 7d7df19

File tree

2 files changed

+367
-182
lines changed

2 files changed

+367
-182
lines changed

src/core/tools/__tests__/contextValidator.test.ts

Lines changed: 115 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)