2025-05-18 00:04:32 -07:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
2026-03-04 05:42:59 +05:30
import {
GlobTool ,
sortFileEntries ,
type GlobToolParams ,
type GlobPath ,
} from './glob.js' ;
2025-05-20 13:02:41 -07:00
import { partListUnionToString } from '../core/geminiRequest.js' ;
2025-08-25 22:11:27 +02:00
import path from 'node:path' ;
2026-01-27 13:17:40 -08:00
import { isSubpath } from '../utils/paths.js' ;
2025-08-25 22:11:27 +02:00
import fs from 'node:fs/promises' ;
import os from 'node:os' ;
2025-08-21 14:40:18 -07:00
import { describe , it , expect , beforeEach , afterEach , vi } from 'vitest' ;
Ignore folders files (#651)
# Add .gitignore-Aware File Filtering to gemini-cli
This pull request introduces .gitignore-based file filtering to the gemini-cli, ensuring that git-ignored files are automatically excluded from file-related operations and suggestions throughout the CLI. The update enhances usability, reduces noise from build artifacts and dependencies, and provides new configuration options for fine-tuning file discovery.
Key Improvements
.gitignore File Filtering
All @ (at) commands, file completions, and core discovery tools now honor .gitignore patterns by default.
Git-ignored files (such as node_modules/, dist/, .env, and .git) are excluded from results unless explicitly overridden.
The behavior can be customized via a new fileFiltering section in settings.json, including options for:
Turning .gitignore respect on/off.
Adding custom ignore patterns.
Allowing or excluding build artifacts.
Configuration & Documentation Updates
settings.json schema extended with fileFiltering options.
Documentation updated to explain new filtering controls and usage patterns.
Testing
New and updated integration/unit tests for file filtering logic, configuration merging, and edge cases.
Test coverage ensures .gitignore filtering works as intended across different workflows.
Internal Refactoring
Core file discovery logic refactored for maintainability and extensibility.
Underlying tools (ls, glob, read-many-files) now support git-aware filtering out of the box.
Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
2025-06-03 21:40:46 -07:00
import { FileDiscoveryService } from '../services/fileDiscoveryService.js' ;
2025-08-26 00:04:53 +02:00
import type { Config } from '../config/config.js' ;
2025-07-31 05:38:20 +09:00
import { createMockWorkspaceContext } from '../test-utils/mockWorkspaceContext.js' ;
2025-08-21 14:40:18 -07:00
import { ToolErrorType } from './tool-error.js' ;
import * as glob from 'glob' ;
2026-01-04 14:59:35 -05:00
import { createMockMessageBus } from '../test-utils/mock-message-bus.js' ;
2026-01-27 17:19:13 -08:00
import {
DEFAULT_FILE_FILTERING_OPTIONS ,
GEMINI_IGNORE_FILE_NAME ,
} from '../config/constants.js' ;
2025-08-21 14:40:18 -07:00
vi . mock ( 'glob' , { spy : true } ) ;
2025-05-18 00:04:32 -07:00
describe ( 'GlobTool' , ( ) = > {
let tempRootDir : string ; // This will be the rootDirectory for the GlobTool instance
let globTool : GlobTool ;
const abortSignal = new AbortController ( ) . signal ;
2026-01-27 13:17:40 -08:00
let mockConfig : Config ;
Ignore folders files (#651)
# Add .gitignore-Aware File Filtering to gemini-cli
This pull request introduces .gitignore-based file filtering to the gemini-cli, ensuring that git-ignored files are automatically excluded from file-related operations and suggestions throughout the CLI. The update enhances usability, reduces noise from build artifacts and dependencies, and provides new configuration options for fine-tuning file discovery.
Key Improvements
.gitignore File Filtering
All @ (at) commands, file completions, and core discovery tools now honor .gitignore patterns by default.
Git-ignored files (such as node_modules/, dist/, .env, and .git) are excluded from results unless explicitly overridden.
The behavior can be customized via a new fileFiltering section in settings.json, including options for:
Turning .gitignore respect on/off.
Adding custom ignore patterns.
Allowing or excluding build artifacts.
Configuration & Documentation Updates
settings.json schema extended with fileFiltering options.
Documentation updated to explain new filtering controls and usage patterns.
Testing
New and updated integration/unit tests for file filtering logic, configuration merging, and edge cases.
Test coverage ensures .gitignore filtering works as intended across different workflows.
Internal Refactoring
Core file discovery logic refactored for maintainability and extensibility.
Underlying tools (ls, glob, read-many-files) now support git-aware filtering out of the box.
Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
2025-06-03 21:40:46 -07:00
2025-05-18 00:04:32 -07:00
beforeEach ( async ( ) = > {
// Create a unique root directory for each test run
tempRootDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'glob-tool-root-' ) ) ;
2025-09-10 09:54:50 -07:00
await fs . writeFile ( path . join ( tempRootDir , '.git' ) , '' ) ; // Fake git repo
2026-01-27 13:17:40 -08:00
const rootDir = tempRootDir ;
const workspaceContext = createMockWorkspaceContext ( rootDir ) ;
const fileDiscovery = new FileDiscoveryService ( rootDir ) ;
const mockStorage = {
getProjectTempDir : vi.fn ( ) . mockReturnValue ( '/tmp/project' ) ,
} ;
mockConfig = {
getTargetDir : ( ) = > rootDir ,
getWorkspaceContext : ( ) = > workspaceContext ,
getFileService : ( ) = > fileDiscovery ,
getFileFilteringOptions : ( ) = > DEFAULT_FILE_FILTERING_OPTIONS ,
getFileExclusions : ( ) = > ( { getGlobExcludes : ( ) = > [ ] } ) ,
storage : mockStorage ,
isPathAllowed ( this : Config , absolutePath : string ) : boolean {
const workspaceContext = this . getWorkspaceContext ( ) ;
if ( workspaceContext . isPathWithinWorkspace ( absolutePath ) ) {
return true ;
}
const projectTempDir = this . storage . getProjectTempDir ( ) ;
return isSubpath ( path . resolve ( projectTempDir ) , absolutePath ) ;
} ,
validatePathAccess ( this : Config , absolutePath : string ) : string | null {
if ( this . isPathAllowed ( absolutePath ) ) {
return null ;
}
const workspaceDirs = this . getWorkspaceContext ( ) . getDirectories ( ) ;
const projectTempDir = this . storage . getProjectTempDir ( ) ;
return ` Path not in workspace: Attempted path " ${ absolutePath } " resolves outside the allowed workspace directories: ${ workspaceDirs . join ( ', ' ) } or the project temp directory: ${ projectTempDir } ` ;
} ,
} as unknown as Config ;
2026-01-04 14:59:35 -05:00
globTool = new GlobTool ( mockConfig , createMockMessageBus ( ) ) ;
2025-05-18 00:04:32 -07:00
// Create some test files and directories within this root
// Top-level files
await fs . writeFile ( path . join ( tempRootDir , 'fileA.txt' ) , 'contentA' ) ;
await fs . writeFile ( path . join ( tempRootDir , 'FileB.TXT' ) , 'contentB' ) ; // Different case for testing
// Subdirectory and files within it
await fs . mkdir ( path . join ( tempRootDir , 'sub' ) ) ;
await fs . writeFile ( path . join ( tempRootDir , 'sub' , 'fileC.md' ) , 'contentC' ) ;
await fs . writeFile ( path . join ( tempRootDir , 'sub' , 'FileD.MD' ) , 'contentD' ) ; // Different case
// Deeper subdirectory
await fs . mkdir ( path . join ( tempRootDir , 'sub' , 'deep' ) ) ;
await fs . writeFile (
path . join ( tempRootDir , 'sub' , 'deep' , 'fileE.log' ) ,
'contentE' ,
) ;
// Files for mtime sorting test
await fs . writeFile ( path . join ( tempRootDir , 'older.sortme' ) , 'older_content' ) ;
// Ensure a noticeable difference in modification time
await new Promise ( ( resolve ) = > setTimeout ( resolve , 50 ) ) ;
await fs . writeFile ( path . join ( tempRootDir , 'newer.sortme' ) , 'newer_content' ) ;
} ) ;
afterEach ( async ( ) = > {
// Clean up the temporary root directory
await fs . rm ( tempRootDir , { recursive : true , force : true } ) ;
2026-01-27 13:17:40 -08:00
vi . resetAllMocks ( ) ;
2025-05-18 00:04:32 -07:00
} ) ;
describe ( 'execute' , ( ) = > {
it ( 'should find files matching a simple pattern in the root' , async ( ) = > {
const params : GlobToolParams = { pattern : '*.txt' } ;
2025-08-07 10:05:37 -07:00
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
2025-05-18 00:04:32 -07:00
expect ( result . llmContent ) . toContain ( 'Found 2 file(s)' ) ;
expect ( result . llmContent ) . toContain ( path . join ( tempRootDir , 'fileA.txt' ) ) ;
expect ( result . llmContent ) . toContain ( path . join ( tempRootDir , 'FileB.TXT' ) ) ;
expect ( result . returnDisplay ) . toBe ( 'Found 2 matching file(s)' ) ;
2026-01-19 20:07:28 -08:00
} , 30000 ) ;
2025-05-18 00:04:32 -07:00
it ( 'should find files case-sensitively when case_sensitive is true' , async ( ) = > {
const params : GlobToolParams = { pattern : '*.txt' , case_sensitive : true } ;
2025-08-07 10:05:37 -07:00
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
2025-05-18 00:04:32 -07:00
expect ( result . llmContent ) . toContain ( 'Found 1 file(s)' ) ;
expect ( result . llmContent ) . toContain ( path . join ( tempRootDir , 'fileA.txt' ) ) ;
expect ( result . llmContent ) . not . toContain (
path . join ( tempRootDir , 'FileB.TXT' ) ,
) ;
2026-01-19 20:07:28 -08:00
} , 30000 ) ;
2025-05-18 00:04:32 -07:00
it ( 'should find files case-insensitively by default (pattern: *.TXT)' , async ( ) = > {
const params : GlobToolParams = { pattern : '*.TXT' } ;
2025-08-07 10:05:37 -07:00
const invocation = globTool . build ( params ) ;
2026-01-19 20:07:28 -08:00
2025-08-07 10:05:37 -07:00
const result = await invocation . execute ( abortSignal ) ;
2026-01-19 20:07:28 -08:00
expect ( result . llmContent ) . toContain ( 'fileA.txt' ) ;
expect ( result . llmContent ) . toContain ( 'FileB.TXT' ) ;
} , 30000 ) ;
2025-05-18 00:04:32 -07:00
it ( 'should find files case-insensitively when case_sensitive is false (pattern: *.TXT)' , async ( ) = > {
const params : GlobToolParams = {
pattern : '*.TXT' ,
case_sensitive : false ,
} ;
2025-08-07 10:05:37 -07:00
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
2025-05-18 00:04:32 -07:00
expect ( result . llmContent ) . toContain ( 'Found 2 file(s)' ) ;
expect ( result . llmContent ) . toContain ( path . join ( tempRootDir , 'fileA.txt' ) ) ;
expect ( result . llmContent ) . toContain ( path . join ( tempRootDir , 'FileB.TXT' ) ) ;
2026-01-19 20:07:28 -08:00
} , 30000 ) ;
2025-05-18 00:04:32 -07:00
it ( 'should find files using a pattern that includes a subdirectory' , async ( ) = > {
const params : GlobToolParams = { pattern : 'sub/*.md' } ;
2025-08-07 10:05:37 -07:00
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
2025-05-18 00:04:32 -07:00
expect ( result . llmContent ) . toContain ( 'Found 2 file(s)' ) ;
expect ( result . llmContent ) . toContain (
path . join ( tempRootDir , 'sub' , 'fileC.md' ) ,
) ;
expect ( result . llmContent ) . toContain (
path . join ( tempRootDir , 'sub' , 'FileD.MD' ) ,
) ;
2026-01-19 20:07:28 -08:00
} , 30000 ) ;
2025-05-18 00:04:32 -07:00
it ( 'should find files in a specified relative path (relative to rootDir)' , async ( ) = > {
2025-11-06 15:03:52 -08:00
const params : GlobToolParams = { pattern : '*.md' , dir_path : 'sub' } ;
2025-08-07 10:05:37 -07:00
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
2025-05-18 00:04:32 -07:00
expect ( result . llmContent ) . toContain ( 'Found 2 file(s)' ) ;
expect ( result . llmContent ) . toContain (
path . join ( tempRootDir , 'sub' , 'fileC.md' ) ,
) ;
expect ( result . llmContent ) . toContain (
path . join ( tempRootDir , 'sub' , 'FileD.MD' ) ,
) ;
2026-01-19 20:07:28 -08:00
} , 30000 ) ;
2025-05-18 00:04:32 -07:00
it ( 'should find files using a deep globstar pattern (e.g., **/*.log)' , async ( ) = > {
const params : GlobToolParams = { pattern : '**/*.log' } ;
2025-08-07 10:05:37 -07:00
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
2025-05-18 00:04:32 -07:00
expect ( result . llmContent ) . toContain ( 'Found 1 file(s)' ) ;
expect ( result . llmContent ) . toContain (
path . join ( tempRootDir , 'sub' , 'deep' , 'fileE.log' ) ,
) ;
2026-01-19 20:07:28 -08:00
} , 30000 ) ;
2025-05-18 00:04:32 -07:00
it ( 'should return "No files found" message when pattern matches nothing' , async ( ) = > {
const params : GlobToolParams = { pattern : '*.nonexistent' } ;
2025-08-07 10:05:37 -07:00
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
2025-05-18 00:04:32 -07:00
expect ( result . llmContent ) . toContain (
'No files found matching pattern "*.nonexistent"' ,
) ;
expect ( result . returnDisplay ) . toBe ( 'No files found' ) ;
2026-01-19 20:07:28 -08:00
} , 30000 ) ;
2025-05-18 00:04:32 -07:00
2025-08-18 16:39:05 -07:00
it ( 'should find files with special characters in the name' , async ( ) = > {
await fs . writeFile ( path . join ( tempRootDir , 'file[1].txt' ) , 'content' ) ;
const params : GlobToolParams = { pattern : 'file[1].txt' } ;
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
expect ( result . llmContent ) . toContain ( 'Found 1 file(s)' ) ;
expect ( result . llmContent ) . toContain (
path . join ( tempRootDir , 'file[1].txt' ) ,
) ;
2026-01-19 20:07:28 -08:00
} , 30000 ) ;
2025-08-18 16:39:05 -07:00
it ( 'should find files with special characters like [] and () in the path' , async ( ) = > {
const filePath = path . join (
tempRootDir ,
'src/app/[test]/(dashboard)/testing/components/code.tsx' ,
) ;
await fs . mkdir ( path . dirname ( filePath ) , { recursive : true } ) ;
await fs . writeFile ( filePath , 'content' ) ;
const params : GlobToolParams = {
pattern : 'src/app/[test]/(dashboard)/testing/components/code.tsx' ,
} ;
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
expect ( result . llmContent ) . toContain ( 'Found 1 file(s)' ) ;
expect ( result . llmContent ) . toContain ( filePath ) ;
2026-01-19 20:07:28 -08:00
} , 30000 ) ;
2025-08-18 16:39:05 -07:00
2025-05-18 00:04:32 -07:00
it ( 'should correctly sort files by modification time (newest first)' , async ( ) = > {
const params : GlobToolParams = { pattern : '*.sortme' } ;
2025-08-07 10:05:37 -07:00
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
2025-05-20 13:02:41 -07:00
const llmContent = partListUnionToString ( result . llmContent ) ;
2026-01-27 13:17:40 -08:00
const newerIndex = llmContent . indexOf ( 'newer.sortme' ) ;
const olderIndex = llmContent . indexOf ( 'older.sortme' ) ;
expect ( newerIndex ) . toBeLessThan ( olderIndex ) ;
2026-01-19 20:07:28 -08:00
} , 30000 ) ;
2025-08-21 14:40:18 -07:00
it ( 'should return a PATH_NOT_IN_WORKSPACE error if path is outside workspace' , async ( ) = > {
2026-01-27 13:17:40 -08:00
const params : GlobToolParams = { pattern : '*' , dir_path : '/etc' } ;
expect ( ( ) = > globTool . build ( params ) ) . toThrow ( /Path not in workspace/ ) ;
} ) ;
2025-08-21 14:40:18 -07:00
it ( 'should return a GLOB_EXECUTION_ERROR on glob failure' , async ( ) = > {
vi . mocked ( glob . glob ) . mockRejectedValue ( new Error ( 'Glob failed' ) ) ;
2026-01-27 13:17:40 -08:00
const params : GlobToolParams = { pattern : '*' } ;
2025-08-21 14:40:18 -07:00
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
expect ( result . error ? . type ) . toBe ( ToolErrorType . GLOB_EXECUTION_ERROR ) ;
2026-01-19 20:07:28 -08:00
} , 30000 ) ;
2025-05-18 00:04:32 -07:00
} ) ;
describe ( 'validateToolParams' , ( ) = > {
2026-01-27 13:17:40 -08:00
it ( 'should return null for valid parameters' , ( ) = > {
const params : GlobToolParams = { pattern : '*.txt' } ;
expect ( globTool . validateToolParams ( params ) ) . toBeNull ( ) ;
2025-05-18 00:04:32 -07:00
} ) ;
2025-07-31 05:38:20 +09:00
2026-01-27 13:17:40 -08:00
it ( 'should return null for valid parameters with dir_path' , ( ) = > {
const params : GlobToolParams = { pattern : '*.txt' , dir_path : 'sub' } ;
expect ( globTool . validateToolParams ( params ) ) . toBeNull ( ) ;
} ) ;
it ( 'should return null for valid parameters with absolute dir_path within workspace' , async ( ) = > {
const params : GlobToolParams = {
pattern : '*.txt' ,
dir_path : tempRootDir ,
} ;
expect ( globTool . validateToolParams ( params ) ) . toBeNull ( ) ;
} ) ;
it ( 'should return error if pattern is missing' , ( ) = > {
const params = { } as unknown as GlobToolParams ;
expect ( globTool . validateToolParams ( params ) ) . toContain (
"params must have required property 'pattern'" ,
) ;
} ) ;
it ( 'should return error if pattern is an empty string' , ( ) = > {
const params : GlobToolParams = { pattern : '' } ;
expect ( globTool . validateToolParams ( params ) ) . toContain (
"The 'pattern' parameter cannot be empty" ,
) ;
} ) ;
it ( 'should return error if pattern is only whitespace' , ( ) = > {
const params : GlobToolParams = { pattern : ' ' } ;
expect ( globTool . validateToolParams ( params ) ) . toContain (
"The 'pattern' parameter cannot be empty" ,
) ;
} ) ;
it ( 'should return error if dir_path is not a string' , ( ) = > {
const params = {
pattern : '*' ,
dir_path : 123 ,
} as unknown as GlobToolParams ;
expect ( globTool . validateToolParams ( params ) ) . toContain (
'params/dir_path must be string' ,
) ;
} ) ;
it ( 'should return error if case_sensitive is not a boolean' , ( ) = > {
const params = {
pattern : '*' ,
case_sensitive : 'true' ,
} as unknown as GlobToolParams ;
expect ( globTool . validateToolParams ( params ) ) . toContain (
'params/case_sensitive must be boolean' ,
) ;
} ) ;
2025-07-31 05:38:20 +09:00
2026-01-27 13:17:40 -08:00
it ( 'should return error if search path resolves outside workspace' , ( ) = > {
const params : GlobToolParams = { pattern : '*' , dir_path : '../' } ;
expect ( globTool . validateToolParams ( params ) ) . toContain (
2025-07-31 05:38:20 +09:00
'resolves outside the allowed workspace directories' ,
) ;
} ) ;
2026-01-27 13:17:40 -08:00
it ( 'should return error if specified search path does not exist' , ( ) = > {
const params : GlobToolParams = {
pattern : '*' ,
dir_path : 'non-existent' ,
} ;
expect ( globTool . validateToolParams ( params ) ) . toContain (
'Search path does not exist' ,
) ;
} ) ;
it ( 'should return error if specified search path is not a directory' , async ( ) = > {
await fs . writeFile ( path . join ( tempRootDir , 'not-a-dir' ) , 'content' ) ;
const params : GlobToolParams = { pattern : '*' , dir_path : 'not-a-dir' } ;
expect ( globTool . validateToolParams ( params ) ) . toContain (
'Search path is not a directory' ,
) ;
} ) ;
} ) ;
describe ( 'workspace boundary validation' , ( ) = > {
it ( 'should validate search paths are within workspace boundaries' , ( ) = > {
expect ( globTool . validateToolParams ( { pattern : '*' } ) ) . toBeNull ( ) ;
expect (
globTool . validateToolParams ( { pattern : '*' , dir_path : '.' } ) ,
) . toBeNull ( ) ;
expect (
globTool . validateToolParams ( { pattern : '*' , dir_path : tempRootDir } ) ,
) . toBeNull ( ) ;
expect (
globTool . validateToolParams ( { pattern : '*' , dir_path : '..' } ) ,
) . toContain ( 'resolves outside the allowed workspace directories' ) ;
expect (
globTool . validateToolParams ( { pattern : '*' , dir_path : '/' } ) ,
) . toContain ( 'resolves outside the allowed workspace directories' ) ;
} ) ;
2025-07-31 05:38:20 +09:00
2026-01-27 13:17:40 -08:00
it ( 'should provide clear error messages when path is outside workspace' , ( ) = > {
const result = globTool . validateToolParams ( {
pattern : '*' ,
dir_path : '/tmp/outside' ,
} ) ;
expect ( result ) . toContain (
2025-07-31 05:38:20 +09:00
'resolves outside the allowed workspace directories' ,
) ;
} ) ;
it ( 'should work with paths in workspace subdirectories' , async ( ) = > {
2026-01-27 13:17:40 -08:00
const subDir = path . join ( tempRootDir , 'allowed-sub' ) ;
await fs . mkdir ( subDir ) ;
expect (
globTool . validateToolParams ( { pattern : '*' , dir_path : 'allowed-sub' } ) ,
) . toBeNull ( ) ;
2025-07-31 05:38:20 +09:00
} ) ;
} ) ;
2025-09-10 09:54:50 -07:00
describe ( 'ignore file handling' , ( ) = > {
2026-01-27 13:17:40 -08:00
it ( 'should respect .gitignore files by default' , async ( ) = > {
await fs . writeFile (
path . join ( tempRootDir , '.gitignore' ) ,
'ignored_test.txt' ,
) ;
await fs . writeFile ( path . join ( tempRootDir , 'ignored_test.txt' ) , 'content' ) ;
await fs . writeFile ( path . join ( tempRootDir , 'visible_test.txt' ) , 'content' ) ;
2025-10-28 20:02:55 +05:30
2026-01-27 13:17:40 -08:00
const params : GlobToolParams = { pattern : '*_test.txt' } ;
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
2025-10-28 20:02:55 +05:30
2026-01-27 13:17:40 -08:00
expect ( result . llmContent ) . toContain ( 'Found 1 file(s)' ) ;
expect ( result . llmContent ) . toContain ( 'visible_test.txt' ) ;
expect ( result . llmContent ) . not . toContain ( 'ignored_test.txt' ) ;
} , 30000 ) ;
2025-10-28 20:02:55 +05:30
2026-01-27 13:17:40 -08:00
it ( 'should respect .geminiignore files by default' , async ( ) = > {
await fs . writeFile (
2026-01-27 17:19:13 -08:00
path . join ( tempRootDir , GEMINI_IGNORE_FILE_NAME ) ,
2026-01-27 13:17:40 -08:00
'gemini-ignored_test.txt' ,
) ;
await fs . writeFile (
path . join ( tempRootDir , 'gemini-ignored_test.txt' ) ,
'content' ,
) ;
await fs . writeFile ( path . join ( tempRootDir , 'visible_test.txt' ) , 'content' ) ;
const params : GlobToolParams = { pattern : 'visible_test.txt' } ;
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
expect ( result . llmContent ) . toContain ( 'Found 1 file(s)' ) ;
expect ( result . llmContent ) . toContain ( 'visible_test.txt' ) ;
expect ( result . llmContent ) . not . toContain ( 'gemini-ignored_test.txt' ) ;
} , 30000 ) ;
it ( 'should not respect .gitignore when respect_git_ignore is false' , async ( ) = > {
await fs . writeFile (
path . join ( tempRootDir , '.gitignore' ) ,
'ignored_test.txt' ,
) ;
await fs . writeFile ( path . join ( tempRootDir , 'ignored_test.txt' ) , 'content' ) ;
const params : GlobToolParams = {
pattern : 'ignored_test.txt' ,
respect_git_ignore : false ,
} ;
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
expect ( result . llmContent ) . toContain ( 'Found 1 file(s)' ) ;
expect ( result . llmContent ) . toContain ( 'ignored_test.txt' ) ;
} , 30000 ) ;
it ( 'should not respect .geminiignore when respect_gemini_ignore is false' , async ( ) = > {
await fs . writeFile (
2026-01-27 17:19:13 -08:00
path . join ( tempRootDir , GEMINI_IGNORE_FILE_NAME ) ,
2026-01-27 13:17:40 -08:00
'gemini-ignored_test.txt' ,
) ;
await fs . writeFile (
path . join ( tempRootDir , 'gemini-ignored_test.txt' ) ,
'content' ,
) ;
const params : GlobToolParams = {
pattern : 'gemini-ignored_test.txt' ,
respect_gemini_ignore : false ,
} ;
const invocation = globTool . build ( params ) ;
const result = await invocation . execute ( abortSignal ) ;
expect ( result . llmContent ) . toContain ( 'Found 1 file(s)' ) ;
expect ( result . llmContent ) . toContain ( 'gemini-ignored_test.txt' ) ;
} , 30000 ) ;
2025-09-10 09:54:50 -07:00
} ) ;
2025-05-18 00:04:32 -07:00
} ) ;
2025-06-09 08:07:24 -07:00
describe ( 'sortFileEntries' , ( ) = > {
2026-01-27 13:17:40 -08:00
const now = 1000000 ;
const threshold = 10000 ;
it ( 'should sort a mix of recent and older files correctly' , ( ) = > {
const entries : GlobPath [ ] = [
{ fullpath : ( ) = > 'older-b.txt' , mtimeMs : now - 20000 } ,
{ fullpath : ( ) = > 'recent-b.txt' , mtimeMs : now - 1000 } ,
{ fullpath : ( ) = > 'recent-a.txt' , mtimeMs : now - 500 } ,
{ fullpath : ( ) = > 'older-a.txt' , mtimeMs : now - 30000 } ,
] ;
const sorted = sortFileEntries ( entries , now , threshold ) ;
expect ( sorted . map ( ( e ) = > e . fullpath ( ) ) ) . toEqual ( [
'recent-a.txt' , // Recent, newest first
'recent-b.txt' ,
'older-a.txt' , // Older, alphabetical
'older-b.txt' ,
] ) ;
} ) ;
2025-06-09 08:07:24 -07:00
2026-01-27 13:17:40 -08:00
it ( 'should sort only recent files by mtime descending' , ( ) = > {
const entries : GlobPath [ ] = [
{ fullpath : ( ) = > 'a.txt' , mtimeMs : now - 2000 } ,
{ fullpath : ( ) = > 'b.txt' , mtimeMs : now - 1000 } ,
] ;
const sorted = sortFileEntries ( entries , now , threshold ) ;
expect ( sorted . map ( ( e ) = > e . fullpath ( ) ) ) . toEqual ( [ 'b.txt' , 'a.txt' ] ) ;
2025-06-09 08:07:24 -07:00
} ) ;
2026-01-27 13:17:40 -08:00
it ( 'should sort only older files alphabetically' , ( ) = > {
const entries : GlobPath [ ] = [
{ fullpath : ( ) = > 'b.txt' , mtimeMs : now - 20000 } ,
{ fullpath : ( ) = > 'a.txt' , mtimeMs : now - 30000 } ,
] ;
const sorted = sortFileEntries ( entries , now , threshold ) ;
expect ( sorted . map ( ( e ) = > e . fullpath ( ) ) ) . toEqual ( [ 'a.txt' , 'b.txt' ] ) ;
} ) ;
it ( 'should handle an empty array' , ( ) = > {
expect ( sortFileEntries ( [ ] , now , threshold ) ) . toEqual ( [ ] ) ;
} ) ;
it ( 'should correctly sort files when mtimeMs is missing' , ( ) = > {
const entries : GlobPath [ ] = [
{ fullpath : ( ) = > 'b.txt' } ,
{ fullpath : ( ) = > 'a.txt' } ,
] ;
const sorted = sortFileEntries ( entries , now , threshold ) ;
expect ( sorted . map ( ( e ) = > e . fullpath ( ) ) ) . toEqual ( [ 'a.txt' , 'b.txt' ] ) ;
} ) ;
it ( 'should use recencyThresholdMs parameter' , ( ) = > {
const customThreshold = 5000 ;
const entries : GlobPath [ ] = [
{ fullpath : ( ) = > 'old.txt' , mtimeMs : now - 8000 } ,
{ fullpath : ( ) = > 'new.txt' , mtimeMs : now - 3000 } ,
] ;
const sorted = sortFileEntries ( entries , now , customThreshold ) ;
expect ( sorted . map ( ( e ) = > e . fullpath ( ) ) ) . toEqual ( [ 'new.txt' , 'old.txt' ] ) ;
} ) ;
2025-06-09 08:07:24 -07:00
} ) ;