diff --git a/packages/core/src/core/client.test.ts b/packages/core/src/core/client.test.ts index 1c69e381..032c9efc 100644 --- a/packages/core/src/core/client.test.ts +++ b/packages/core/src/core/client.test.ts @@ -19,6 +19,7 @@ import { Config } from '../config/config.js'; import { Turn } from './turn.js'; import { getCoreSystemPrompt } from './prompts.js'; import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js'; +import { FileDiscoveryService } from '../services/fileDiscoveryService.js'; // --- Mocks --- const mockChatCreateFn = vi.fn(); @@ -99,6 +100,7 @@ describe('Gemini Client (client.ts)', () => { getFunctionDeclarations: vi.fn().mockReturnValue([]), getTool: vi.fn().mockReturnValue(null), }; + const fileService = new FileDiscoveryService('/test/dir'); const MockedConfig = vi.mocked(Config, true); MockedConfig.mockImplementation(() => { const mock = { @@ -118,6 +120,7 @@ describe('Gemini Client (client.ts)', () => { getSessionId: vi.fn().mockReturnValue('test-session-id'), getProxy: vi.fn().mockReturnValue(undefined), getWorkingDir: vi.fn().mockReturnValue('/test/dir'), + getFileService: vi.fn().mockReturnValue(fileService), }; // eslint-disable-next-line @typescript-eslint/no-explicit-any return mock as any; diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index ac285aaf..7cfec9d6 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -85,7 +85,9 @@ export class GeminiClient { day: 'numeric', }); const platform = process.platform; - const folderStructure = await getFolderStructure(cwd); + const folderStructure = await getFolderStructure(cwd, { + fileService: await this.config.getFileService(), + }); const context = ` Okay, just setting up the context for our chat. Today is ${today}. diff --git a/packages/core/src/utils/getFolderStructure.test.ts b/packages/core/src/utils/getFolderStructure.test.ts index b3e5b723..843bf493 100644 --- a/packages/core/src/utils/getFolderStructure.test.ts +++ b/packages/core/src/utils/getFolderStructure.test.ts @@ -11,6 +11,7 @@ import { Dirent as FSDirent } from 'fs'; import * as nodePath from 'path'; import { getFolderStructure } from './getFolderStructure.js'; import * as gitUtils from './gitUtils.js'; +import { FileDiscoveryService } from '../services/fileDiscoveryService.js'; vi.mock('path', async (importOriginal) => { const original = (await importOriginal()) as typeof nodePath; @@ -319,8 +320,10 @@ describe('getFolderStructure gitignore', () => { }); it('should ignore files and folders specified in .gitignore', async () => { + const fileService = new FileDiscoveryService('/test/project'); + await fileService.initialize(); const structure = await getFolderStructure('/test/project', { - projectRoot: '/test/project', + fileService, }); expect(structure).not.toContain('ignored.txt'); expect(structure).toContain('node_modules/...'); @@ -328,9 +331,10 @@ describe('getFolderStructure gitignore', () => { }); it('should not ignore files if respectGitIgnore is false', async () => { + const fileService = new FileDiscoveryService('/test/project'); + await fileService.initialize({ respectGitIgnore: false }); const structure = await getFolderStructure('/test/project', { - projectRoot: '/test/project', - respectGitIgnore: false, + fileService, }); expect(structure).toContain('ignored.txt'); // node_modules is still ignored by default diff --git a/packages/core/src/utils/getFolderStructure.ts b/packages/core/src/utils/getFolderStructure.ts index 419a9769..7aee377b 100644 --- a/packages/core/src/utils/getFolderStructure.ts +++ b/packages/core/src/utils/getFolderStructure.ts @@ -8,8 +8,7 @@ import * as fs from 'fs/promises'; import { Dirent } from 'fs'; import * as path from 'path'; import { getErrorMessage, isNodeError } from './errors.js'; -import { GitIgnoreParser, GitIgnoreFilter } from './gitIgnoreParser.js'; -import { isGitRepository } from './gitUtils.js'; +import { FileDiscoveryService } from '../services/fileDiscoveryService.js'; const MAX_ITEMS = 200; const TRUNCATION_INDICATOR = '...'; @@ -25,18 +24,16 @@ interface FolderStructureOptions { ignoredFolders?: Set; /** Optional regex to filter included files by name. */ fileIncludePattern?: RegExp; - /** Whether to respect .gitignore patterns. Defaults to true. */ - respectGitIgnore?: boolean; - /** The root of the project, used for gitignore resolution. */ - projectRoot?: string; + /** For filtering files. */ + fileService?: FileDiscoveryService; } // Define a type for the merged options where fileIncludePattern remains optional type MergedFolderStructureOptions = Required< - Omit + Omit > & { fileIncludePattern?: RegExp; - projectRoot?: string; + fileService?: FileDiscoveryService; }; /** Represents the full, unfiltered information about a folder and its contents. */ @@ -59,7 +56,6 @@ interface FullFolderInfo { async function readFullStructure( rootPath: string, options: MergedFolderStructureOptions, - gitIgnoreFilter: GitIgnoreFilter | null, ): Promise { const rootName = path.basename(rootPath); const rootNode: FullFolderInfo = { @@ -128,8 +124,8 @@ async function readFullStructure( } const fileName = entry.name; const filePath = path.join(currentPath, fileName); - if (gitIgnoreFilter) { - if (gitIgnoreFilter.isIgnored(filePath)) { + if (options.fileService) { + if (options.fileService.shouldIgnoreFile(filePath)) { continue; } } @@ -163,8 +159,8 @@ async function readFullStructure( const subFolderPath = path.join(currentPath, subFolderName); let isIgnoredByGit = false; - if (gitIgnoreFilter) { - if (gitIgnoreFilter.isIgnored(subFolderPath)) { + if (options?.fileService) { + if (options.fileService.shouldIgnoreFile(subFolderPath)) { isIgnoredByGit = true; } } @@ -296,26 +292,12 @@ export async function getFolderStructure( maxItems: options?.maxItems ?? MAX_ITEMS, ignoredFolders: options?.ignoredFolders ?? DEFAULT_IGNORED_FOLDERS, fileIncludePattern: options?.fileIncludePattern, - respectGitIgnore: options?.respectGitIgnore ?? true, - projectRoot: options?.projectRoot ?? resolvedPath, + fileService: options?.fileService, }; - let gitIgnoreFilter: GitIgnoreFilter | null = null; - if (mergedOptions.respectGitIgnore && mergedOptions.projectRoot) { - if (isGitRepository(mergedOptions.projectRoot)) { - const parser = new GitIgnoreParser(mergedOptions.projectRoot); - await parser.initialize(); - gitIgnoreFilter = parser; - } - } - try { // 1. Read the structure using BFS, respecting maxItems - const structureRoot = await readFullStructure( - resolvedPath, - mergedOptions, - gitIgnoreFilter, - ); + const structureRoot = await readFullStructure(resolvedPath, mergedOptions); if (!structureRoot) { return `Error: Could not read directory "${resolvedPath}". Check path and permissions.`;