diff --git a/integration-tests/ripgrep-max-matches.test.ts b/integration-tests/ripgrep-max-matches.test.ts new file mode 100644 index 0000000000..780a3d93c6 --- /dev/null +++ b/integration-tests/ripgrep-max-matches.test.ts @@ -0,0 +1,100 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import * as path from 'node:path'; +import * as fs from 'node:fs/promises'; +import * as os from 'node:os'; +import { RipGrepTool } from '../packages/core/src/tools/ripGrep.js'; +import { Config } from '../packages/core/src/config/config.js'; +import { WorkspaceContext } from '../packages/core/src/utils/workspaceContext.js'; + +// Mock Config to provide necessary context +class MockConfig { + constructor(private targetDir: string) {} + + getTargetDir() { + return this.targetDir; + } + + getWorkspaceContext() { + return new WorkspaceContext(this.targetDir, [this.targetDir]); + } + + getDebugMode() { + return true; + } + + getFileFilteringRespectGeminiIgnore() { + return true; + } + + getFileFilteringOptions() { + return { + respectGitIgnore: true, + respectGeminiIgnore: true, + customIgnoreFilePaths: [], + }; + } + + validatePathAccess() { + return null; + } +} + +describe('ripgrep-max-matches', () => { + let tempDir: string; + let tool: RipGrepTool; + + beforeAll(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'ripgrep-max-test-')); + + // Create a test file with multiple matches + const content = ` + match 1 + filler + match 2 + filler + match 3 + filler + match 4 + `; + await fs.writeFile(path.join(tempDir, 'many_matches.txt'), content); + + const config = new MockConfig(tempDir) as unknown as Config; + tool = new RipGrepTool(config); + }); + + afterAll(async () => { + await fs.rm(tempDir, { recursive: true, force: true }); + }); + + it('should limit matches per file when max_matches_per_file is set', async () => { + const invocation = tool.build({ + pattern: 'match', + max_matches_per_file: 2, + }); + const result = await invocation.execute(new AbortController().signal); + + expect(result.llmContent).toContain('Found 2 matches'); + expect(result.llmContent).toContain('many_matches.txt'); + expect(result.llmContent).toContain('match 1'); + expect(result.llmContent).toContain('match 2'); + expect(result.llmContent).not.toContain('match 3'); + expect(result.llmContent).not.toContain('match 4'); + }); + + it('should return all matches when max_matches_per_file is not set', async () => { + const invocation = tool.build({ pattern: 'match' }); + const result = await invocation.execute(new AbortController().signal); + + expect(result.llmContent).toContain('Found 4 matches'); + expect(result.llmContent).toContain('match 1'); + expect(result.llmContent).toContain('match 2'); + expect(result.llmContent).toContain('match 3'); + expect(result.llmContent).toContain('match 4'); + }); +}); diff --git a/packages/core/src/tools/grep.ts b/packages/core/src/tools/grep.ts index 52cb7475fb..56f96bf892 100644 --- a/packages/core/src/tools/grep.ts +++ b/packages/core/src/tools/grep.ts @@ -774,7 +774,7 @@ export class GrepTool extends BaseDeclarativeTool { super( GrepTool.Name, 'SearchText', - 'Searches for a regular expression pattern within file contents. Max 100 matches.', + 'Searches for a regular expression pattern within file contents.', Kind.Search, { properties: { diff --git a/packages/core/src/tools/ripGrep.ts b/packages/core/src/tools/ripGrep.ts index cbe1657b1a..6d999157b3 100644 --- a/packages/core/src/tools/ripGrep.ts +++ b/packages/core/src/tools/ripGrep.ts @@ -607,7 +607,7 @@ export class RipGrepTool extends BaseDeclarativeTool< super( RipGrepTool.Name, 'SearchText', - 'Searches for a regular expression pattern within file contents. Max 100 matches.', + 'Searches for a regular expression pattern within file contents.', Kind.Search, { properties: { @@ -663,7 +663,7 @@ export class RipGrepTool extends BaseDeclarativeTool< }, max_matches_per_file: { description: - 'Optional: Maximum number of matches to return per file. Use this to prevent being overwhelmed by repetitive matches in large files.', + 'Optional: Maximum number of matches to return per file. Use this to prevent being overwhelmed by repetitive matches in large files. Defaults to 100 if omitted.', type: 'integer', minimum: 1, },