feat(core): share file list patterns between glob and grep tools (#6359)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Arya Gummadi <aryagummadi@google.com>
This commit is contained in:
sangwook
2025-08-23 13:35:00 +09:00
committed by GitHub
parent f55b294570
commit 494a996ff8
13 changed files with 727 additions and 97 deletions

View File

@@ -29,6 +29,9 @@ describe('GlobTool', () => {
getFileFilteringRespectGitIgnore: () => true,
getTargetDir: () => tempRootDir,
getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir),
getFileExclusions: () => ({
getGlobExcludes: () => [],
}),
} as unknown as Config;
beforeEach(async () => {

View File

@@ -155,7 +155,7 @@ class GlobToolInvocation extends BaseToolInvocation<
stat: true,
nocase: !this.params.case_sensitive,
dot: true,
ignore: ['**/node_modules/**', '**/.git/**'],
ignore: this.config.getFileExclusions().getGlobExcludes(),
follow: false,
signal,
})) as GlobPath[];

View File

@@ -39,6 +39,9 @@ describe('GrepTool', () => {
const mockConfig = {
getTargetDir: () => tempRootDir,
getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir),
getFileExclusions: () => ({
getGlobExcludes: () => [],
}),
} as unknown as Config;
beforeEach(async () => {
@@ -258,6 +261,9 @@ describe('GrepTool', () => {
getTargetDir: () => tempRootDir,
getWorkspaceContext: () =>
createMockWorkspaceContext(tempRootDir, [secondDir]),
getFileExclusions: () => ({
getGlobExcludes: () => [],
}),
} as unknown as Config;
const multiDirGrepTool = new GrepTool(multiDirConfig);
@@ -308,6 +314,9 @@ describe('GrepTool', () => {
getTargetDir: () => tempRootDir,
getWorkspaceContext: () =>
createMockWorkspaceContext(tempRootDir, [secondDir]),
getFileExclusions: () => ({
getGlobExcludes: () => [],
}),
} as unknown as Config;
const multiDirGrepTool = new GrepTool(multiDirConfig);
@@ -367,6 +376,9 @@ describe('GrepTool', () => {
getTargetDir: () => tempRootDir,
getWorkspaceContext: () =>
createMockWorkspaceContext(tempRootDir, ['/another/dir']),
getFileExclusions: () => ({
getGlobExcludes: () => [],
}),
} as unknown as Config;
const multiDirGrepTool = new GrepTool(multiDirConfig);

View File

@@ -21,6 +21,7 @@ import { makeRelative, shortenPath } from '../utils/paths.js';
import { getErrorMessage, isNodeError } from '../utils/errors.js';
import { isGitRepository } from '../utils/gitUtils.js';
import { Config } from '../config/config.js';
import { FileExclusions } from '../utils/ignorePatterns.js';
import { ToolErrorType } from './tool-error.js';
// --- Interfaces ---
@@ -58,11 +59,14 @@ class GrepToolInvocation extends BaseToolInvocation<
GrepToolParams,
ToolResult
> {
private readonly fileExclusions: FileExclusions;
constructor(
private readonly config: Config,
params: GrepToolParams,
) {
super(params);
this.fileExclusions = config.getFileExclusions();
}
/**
@@ -281,7 +285,6 @@ class GrepToolInvocation extends BaseToolInvocation<
/**
* Gets a description of the grep operation
* @param params Parameters for the grep operation
* @returns A string describing the grep
*/
getDescription(): string {
@@ -391,7 +394,27 @@ class GrepToolInvocation extends BaseToolInvocation<
if (grepAvailable) {
strategyUsed = 'system grep';
const grepArgs = ['-r', '-n', '-H', '-E'];
const commonExcludes = ['.git', 'node_modules', 'bower_components'];
// Extract directory names from exclusion patterns for grep --exclude-dir
const globExcludes = this.fileExclusions.getGlobExcludes();
const commonExcludes = globExcludes
.map((pattern) => {
let dir = pattern;
if (dir.startsWith('**/')) {
dir = dir.substring(3);
}
if (dir.endsWith('/**')) {
dir = dir.slice(0, -3);
} else if (dir.endsWith('/')) {
dir = dir.slice(0, -1);
}
// Only consider patterns that are likely directories. This filters out file patterns.
if (dir && !dir.includes('/') && !dir.includes('*')) {
return dir;
}
return null;
})
.filter((dir): dir is string => !!dir);
commonExcludes.forEach((dir) => grepArgs.push(`--exclude-dir=${dir}`));
if (include) {
grepArgs.push(`--include=${include}`);
@@ -474,13 +497,7 @@ class GrepToolInvocation extends BaseToolInvocation<
);
strategyUsed = 'javascript fallback';
const globPattern = include ? include : '**/*';
const ignorePatterns = [
'.git/**',
'node_modules/**',
'bower_components/**',
'.svn/**',
'.hg/**',
]; // Use glob patterns for ignores here
const ignorePatterns = this.fileExclusions.getGlobExcludes();
const filesStream = globStream(globPattern, {
cwd: absolutePath,

View File

@@ -16,6 +16,10 @@ import { Config } from '../config/config.js';
import { WorkspaceContext } from '../utils/workspaceContext.js';
import { StandardFileSystemService } from '../services/fileSystemService.js';
import { ToolErrorType } from './tool-error.js';
import {
COMMON_IGNORE_PATTERNS,
DEFAULT_FILE_EXCLUDES,
} from '../utils/ignorePatterns.js';
import * as glob from 'glob';
vi.mock('glob', { spy: true });
@@ -77,6 +81,13 @@ describe('ReadManyFilesTool', () => {
getTargetDir: () => tempRootDir,
getWorkspaceDirs: () => [tempRootDir],
getWorkspaceContext: () => new WorkspaceContext(tempRootDir),
getFileExclusions: () => ({
getCoreIgnorePatterns: () => COMMON_IGNORE_PATTERNS,
getDefaultExcludePatterns: () => DEFAULT_FILE_EXCLUDES,
getGlobExcludes: () => COMMON_IGNORE_PATTERNS,
buildExcludePatterns: () => DEFAULT_FILE_EXCLUDES,
getReadManyFilesExcludes: () => DEFAULT_FILE_EXCLUDES,
}),
} as Partial<Config> as Config;
tool = new ReadManyFilesTool(mockConfig);
@@ -484,6 +495,13 @@ describe('ReadManyFilesTool', () => {
}),
getWorkspaceContext: () => new WorkspaceContext(tempDir1, [tempDir2]),
getTargetDir: () => tempDir1,
getFileExclusions: () => ({
getCoreIgnorePatterns: () => COMMON_IGNORE_PATTERNS,
getDefaultExcludePatterns: () => [],
getGlobExcludes: () => COMMON_IGNORE_PATTERNS,
buildExcludePatterns: () => [],
getReadManyFilesExcludes: () => [],
}),
} as Partial<Config> as Config;
tool = new ReadManyFilesTool(mockConfig);

View File

@@ -15,7 +15,6 @@ import { getErrorMessage } from '../utils/errors.js';
import * as fs from 'fs';
import * as path from 'path';
import { glob, escape } from 'glob';
import { getCurrentGeminiMdFilename } from './memoryTool.js';
import {
detectFileType,
processSingleFileContent,
@@ -98,49 +97,13 @@ type FileProcessingResult =
};
/**
* Default exclusion patterns for commonly ignored directories and binary file types.
* These are compatible with glob ignore patterns.
* Creates the default exclusion patterns including dynamic patterns.
* This combines the shared patterns with dynamic patterns like GEMINI.md.
* TODO(adh): Consider making this configurable or extendable through a command line argument.
* TODO(adh): Look into sharing this list with the glob tool.
*/
const DEFAULT_EXCLUDES: string[] = [
'**/node_modules/**',
'**/.git/**',
'**/.vscode/**',
'**/.idea/**',
'**/dist/**',
'**/build/**',
'**/coverage/**',
'**/__pycache__/**',
'**/*.pyc',
'**/*.pyo',
'**/*.bin',
'**/*.exe',
'**/*.dll',
'**/*.so',
'**/*.dylib',
'**/*.class',
'**/*.jar',
'**/*.war',
'**/*.zip',
'**/*.tar',
'**/*.gz',
'**/*.bz2',
'**/*.rar',
'**/*.7z',
'**/*.doc',
'**/*.docx',
'**/*.xls',
'**/*.xlsx',
'**/*.ppt',
'**/*.pptx',
'**/*.odt',
'**/*.ods',
'**/*.odp',
'**/*.DS_Store',
'**/.env',
`**/${getCurrentGeminiMdFilename()}`,
];
function getDefaultExcludes(config?: Config): string[] {
return config?.getFileExclusions().getReadManyFilesExcludes() ?? [];
}
const DEFAULT_OUTPUT_SEPARATOR_FORMAT = '--- {filePath} ---';
const DEFAULT_OUTPUT_TERMINATOR = '\n--- End of content ---';
@@ -172,7 +135,11 @@ ${this.config.getTargetDir()}
.getGeminiIgnorePatterns();
const finalExclusionPatternsForDescription: string[] =
paramUseDefaultExcludes
? [...DEFAULT_EXCLUDES, ...paramExcludes, ...geminiIgnorePatterns]
? [
...getDefaultExcludes(this.config),
...paramExcludes,
...geminiIgnorePatterns,
]
: [...paramExcludes, ...geminiIgnorePatterns];
let excludeDesc = `Excluding: ${
@@ -230,7 +197,7 @@ ${finalExclusionPatternsForDescription
const contentParts: PartListUnion = [];
const effectiveExcludes = useDefaultExcludes
? [...DEFAULT_EXCLUDES, ...exclude]
? [...getDefaultExcludes(this.config), ...exclude]
: [...exclude];
const searchPatterns = [...inputPatterns, ...include];