initialize `FileDiscoveryService` once (#1029)

This commit is contained in:
Anas H. Sulaiman 2025-06-13 20:25:59 -04:00 committed by GitHub
parent 209381f06f
commit 8eb505fbba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 42 additions and 12 deletions

View File

@ -33,7 +33,8 @@ vi.mock('@gemini-cli/core', async () => {
return { return {
...actualServer, ...actualServer,
loadEnvironment: vi.fn(), loadEnvironment: vi.fn(),
loadServerHierarchicalMemory: vi.fn((cwd, debug, extensionPaths) => loadServerHierarchicalMemory: vi.fn(
(cwd, debug, fileService, extensionPaths) =>
Promise.resolve({ Promise.resolve({
memoryContent: extensionPaths?.join(',') || '', memoryContent: extensionPaths?.join(',') || '',
fileCount: extensionPaths?.length || 0, fileCount: extensionPaths?.length || 0,
@ -239,6 +240,7 @@ describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => {
expect(ServerConfig.loadServerHierarchicalMemory).toHaveBeenCalledWith( expect(ServerConfig.loadServerHierarchicalMemory).toHaveBeenCalledWith(
expect.any(String), expect.any(String),
false, false,
expect.any(Object),
[ [
'/path/to/ext1/GEMINI.md', '/path/to/ext1/GEMINI.md',
'/path/to/ext3/context1.md', '/path/to/ext3/context1.md',

View File

@ -17,6 +17,7 @@ import {
GEMINI_CONFIG_DIR as GEMINI_DIR, GEMINI_CONFIG_DIR as GEMINI_DIR,
DEFAULT_GEMINI_MODEL, DEFAULT_GEMINI_MODEL,
DEFAULT_GEMINI_EMBEDDING_MODEL, DEFAULT_GEMINI_EMBEDDING_MODEL,
FileDiscoveryService,
} from '@gemini-cli/core'; } from '@gemini-cli/core';
import { Settings } from './settings.js'; import { Settings } from './settings.js';
import { getEffectiveModel } from '../utils/modelCheck.js'; import { getEffectiveModel } from '../utils/modelCheck.js';
@ -114,6 +115,7 @@ async function parseArguments(): Promise<CliArgs> {
export async function loadHierarchicalGeminiMemory( export async function loadHierarchicalGeminiMemory(
currentWorkingDirectory: string, currentWorkingDirectory: string,
debugMode: boolean, debugMode: boolean,
fileService: FileDiscoveryService,
extensionContextFilePaths: string[] = [], extensionContextFilePaths: string[] = [],
): Promise<{ memoryContent: string; fileCount: number }> { ): Promise<{ memoryContent: string; fileCount: number }> {
if (debugMode) { if (debugMode) {
@ -126,6 +128,7 @@ export async function loadHierarchicalGeminiMemory(
return loadServerHierarchicalMemory( return loadServerHierarchicalMemory(
currentWorkingDirectory, currentWorkingDirectory,
debugMode, debugMode,
fileService,
extensionContextFilePaths, extensionContextFilePaths,
); );
} }
@ -154,10 +157,15 @@ export async function loadCliConfig(
const extensionContextFilePaths = extensions.flatMap((e) => e.contextFiles); const extensionContextFilePaths = extensions.flatMap((e) => e.contextFiles);
const fileService = new FileDiscoveryService(process.cwd());
await fileService.initialize({
respectGitIgnore: settings.fileFiltering?.respectGitIgnore,
});
// Call the (now wrapper) loadHierarchicalGeminiMemory which calls the server's version // Call the (now wrapper) loadHierarchicalGeminiMemory which calls the server's version
const { memoryContent, fileCount } = await loadHierarchicalGeminiMemory( const { memoryContent, fileCount } = await loadHierarchicalGeminiMemory(
process.cwd(), process.cwd(),
debugMode, debugMode,
fileService,
extensionContextFilePaths, extensionContextFilePaths,
); );
@ -201,6 +209,7 @@ export async function loadCliConfig(
process.env.http_proxy, process.env.http_proxy,
cwd: process.cwd(), cwd: process.cwd(),
telemetryOtlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, telemetryOtlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
fileDiscoveryService: fileService,
}); });
} }

View File

@ -139,6 +139,7 @@ const App = ({ config, settings, startupWarnings = [] }: AppProps) => {
const { memoryContent, fileCount } = await loadHierarchicalGeminiMemory( const { memoryContent, fileCount } = await loadHierarchicalGeminiMemory(
process.cwd(), process.cwd(),
config.getDebugMode(), config.getDebugMode(),
await config.getFileService(),
); );
config.setUserMemory(memoryContent); config.setUserMemory(memoryContent);
config.setGeminiMdFileCount(fileCount); config.setGeminiMdFileCount(fileCount);

View File

@ -86,6 +86,7 @@ export interface ConfigParameters {
checkpoint?: boolean; checkpoint?: boolean;
proxy?: string; proxy?: string;
cwd: string; cwd: string;
fileDiscoveryService?: FileDiscoveryService;
} }
export class Config { export class Config {
@ -152,6 +153,7 @@ export class Config {
this.checkpoint = params.checkpoint ?? false; this.checkpoint = params.checkpoint ?? false;
this.proxy = params.proxy; this.proxy = params.proxy;
this.cwd = params.cwd ?? process.cwd(); this.cwd = params.cwd ?? process.cwd();
this.fileDiscoveryService = params.fileDiscoveryService ?? null;
if (params.contextFileName) { if (params.contextFileName) {
setGeminiMdFilename(params.contextFileName); setGeminiMdFilename(params.contextFileName);

View File

@ -47,14 +47,17 @@ export class GitIgnoreParser implements GitIgnoreFilter {
} }
async loadPatterns(patternsFileName: string): Promise<void> { async loadPatterns(patternsFileName: string): Promise<void> {
const content = await fs.readFile( const patternsFilePath = path.join(this.projectRoot, patternsFileName);
path.join(this.projectRoot, patternsFileName), const content = await fs.readFile(patternsFilePath, 'utf-8');
'utf-8',
);
const patterns = content const patterns = content
.split('\n') .split('\n')
.map((p) => p.trim()) .map((p) => p.trim())
.filter((p) => p !== '' && !p.startsWith('#')); .filter((p) => p !== '' && !p.startsWith('#'));
if (patterns.length > 0) {
console.log(
`Loaded ${patterns.length} patterns from ${patternsFilePath}`,
);
}
this.addPatterns(patterns); this.addPatterns(patterns);
} }

View File

@ -17,6 +17,7 @@ import {
getCurrentGeminiMdFilename, getCurrentGeminiMdFilename,
DEFAULT_CONTEXT_FILENAME, DEFAULT_CONTEXT_FILENAME,
} from '../tools/memoryTool.js'; } from '../tools/memoryTool.js';
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
const ORIGINAL_GEMINI_MD_FILENAME_CONST_FOR_TEST = DEFAULT_CONTEXT_FILENAME; const ORIGINAL_GEMINI_MD_FILENAME_CONST_FOR_TEST = DEFAULT_CONTEXT_FILENAME;
@ -43,6 +44,7 @@ describe('loadServerHierarchicalMemory', () => {
let GLOBAL_GEMINI_DIR: string; let GLOBAL_GEMINI_DIR: string;
let GLOBAL_GEMINI_FILE: string; // Defined in beforeEach let GLOBAL_GEMINI_FILE: string; // Defined in beforeEach
const fileService = new FileDiscoveryService(PROJECT_ROOT);
beforeEach(() => { beforeEach(() => {
vi.resetAllMocks(); vi.resetAllMocks();
// Set environment variables to indicate test environment // Set environment variables to indicate test environment
@ -69,6 +71,7 @@ describe('loadServerHierarchicalMemory', () => {
const { memoryContent, fileCount } = await loadServerHierarchicalMemory( const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
CWD, CWD,
false, false,
fileService,
); );
expect(memoryContent).toBe(''); expect(memoryContent).toBe('');
expect(fileCount).toBe(0); expect(fileCount).toBe(0);
@ -95,6 +98,7 @@ describe('loadServerHierarchicalMemory', () => {
const { memoryContent, fileCount } = await loadServerHierarchicalMemory( const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
CWD, CWD,
false, false,
fileService,
); );
expect(memoryContent).toBe( expect(memoryContent).toBe(
@ -125,6 +129,7 @@ describe('loadServerHierarchicalMemory', () => {
const { memoryContent, fileCount } = await loadServerHierarchicalMemory( const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
CWD, CWD,
false, false,
fileService,
); );
expect(memoryContent).toBe( expect(memoryContent).toBe(
@ -167,6 +172,7 @@ describe('loadServerHierarchicalMemory', () => {
const { memoryContent, fileCount } = await loadServerHierarchicalMemory( const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
CWD, CWD,
false, false,
fileService,
); );
const expectedContent = const expectedContent =
`--- Context from: ${path.relative(CWD, projectRootCustomFile)} ---\nProject root custom memory\n--- End of Context from: ${path.relative(CWD, projectRootCustomFile)} ---\n\n` + `--- Context from: ${path.relative(CWD, projectRootCustomFile)} ---\nProject root custom memory\n--- End of Context from: ${path.relative(CWD, projectRootCustomFile)} ---\n\n` +
@ -231,6 +237,7 @@ describe('loadServerHierarchicalMemory', () => {
const { memoryContent, fileCount } = await loadServerHierarchicalMemory( const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
CWD, CWD,
false, false,
fileService,
); );
const expectedContent = const expectedContent =
`--- Context from: ${customFilename} ---\nCWD custom memory\n--- End of Context from: ${customFilename} ---\n\n` + `--- Context from: ${customFilename} ---\nCWD custom memory\n--- End of Context from: ${customFilename} ---\n\n` +
@ -277,6 +284,7 @@ describe('loadServerHierarchicalMemory', () => {
const { memoryContent, fileCount } = await loadServerHierarchicalMemory( const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
CWD, CWD,
false, false,
fileService,
); );
const expectedContent = const expectedContent =
`--- Context from: ${path.relative(CWD, projectRootGeminiFile)} ---\nProject root memory\n--- End of Context from: ${path.relative(CWD, projectRootGeminiFile)} ---\n\n` + `--- Context from: ${path.relative(CWD, projectRootGeminiFile)} ---\nProject root memory\n--- End of Context from: ${path.relative(CWD, projectRootGeminiFile)} ---\n\n` +
@ -345,6 +353,7 @@ describe('loadServerHierarchicalMemory', () => {
const { memoryContent, fileCount } = await loadServerHierarchicalMemory( const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
CWD, CWD,
false, false,
fileService,
); );
const expectedContent = const expectedContent =
`--- Context from: ${ORIGINAL_GEMINI_MD_FILENAME_CONST_FOR_TEST} ---\nCWD memory\n--- End of Context from: ${ORIGINAL_GEMINI_MD_FILENAME_CONST_FOR_TEST} ---\n\n` + `--- Context from: ${ORIGINAL_GEMINI_MD_FILENAME_CONST_FOR_TEST} ---\nCWD memory\n--- End of Context from: ${ORIGINAL_GEMINI_MD_FILENAME_CONST_FOR_TEST} ---\n\n` +
@ -438,6 +447,7 @@ describe('loadServerHierarchicalMemory', () => {
const { memoryContent, fileCount } = await loadServerHierarchicalMemory( const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
CWD, CWD,
false, false,
fileService,
); );
const relPathGlobal = path.relative(CWD, GLOBAL_GEMINI_FILE); const relPathGlobal = path.relative(CWD, GLOBAL_GEMINI_FILE);
@ -520,6 +530,7 @@ describe('loadServerHierarchicalMemory', () => {
const { memoryContent, fileCount } = await loadServerHierarchicalMemory( const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
CWD, CWD,
false, false,
fileService,
); );
const expectedContent = `--- Context from: ${path.join('my_code', ORIGINAL_GEMINI_MD_FILENAME_CONST_FOR_TEST)} ---\nMy code memory\n--- End of Context from: ${path.join('my_code', ORIGINAL_GEMINI_MD_FILENAME_CONST_FOR_TEST)} ---`; const expectedContent = `--- Context from: ${path.join('my_code', ORIGINAL_GEMINI_MD_FILENAME_CONST_FOR_TEST)} ---\nMy code memory\n--- End of Context from: ${path.join('my_code', ORIGINAL_GEMINI_MD_FILENAME_CONST_FOR_TEST)} ---`;
@ -556,7 +567,7 @@ describe('loadServerHierarchicalMemory', () => {
}) as unknown as typeof fsPromises.readdir); }) as unknown as typeof fsPromises.readdir);
mockFs.access.mockRejectedValue(new Error('not found')); mockFs.access.mockRejectedValue(new Error('not found'));
await loadServerHierarchicalMemory(CWD, true); await loadServerHierarchicalMemory(CWD, true, fileService);
expect(consoleDebugSpy).toHaveBeenCalledWith( expect(consoleDebugSpy).toHaveBeenCalledWith(
expect.stringContaining('[DEBUG] [BfsFileSearch]'), expect.stringContaining('[DEBUG] [BfsFileSearch]'),
@ -583,6 +594,7 @@ describe('loadServerHierarchicalMemory', () => {
const { memoryContent, fileCount } = await loadServerHierarchicalMemory( const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
CWD, CWD,
false, false,
fileService,
[extensionFilePath], [extensionFilePath],
); );

View File

@ -82,6 +82,7 @@ async function getGeminiMdFilePathsInternal(
currentWorkingDirectory: string, currentWorkingDirectory: string,
userHomePath: string, userHomePath: string,
debugMode: boolean, debugMode: boolean,
fileService: FileDiscoveryService,
extensionContextFilePaths: string[] = [], extensionContextFilePaths: string[] = [],
): Promise<string[]> { ): Promise<string[]> {
const allPaths = new Set<string>(); const allPaths = new Set<string>();
@ -179,8 +180,6 @@ async function getGeminiMdFilePathsInternal(
} }
upwardPaths.forEach((p) => allPaths.add(p)); upwardPaths.forEach((p) => allPaths.add(p));
const fileService = new FileDiscoveryService(projectRoot || resolvedCwd);
await fileService.initialize();
const downwardPaths = await bfsFileSearch(resolvedCwd, { const downwardPaths = await bfsFileSearch(resolvedCwd, {
fileName: geminiMdFilename, fileName: geminiMdFilename,
maxDirs: MAX_DIRECTORIES_TO_SCAN_FOR_MEMORY, maxDirs: MAX_DIRECTORIES_TO_SCAN_FOR_MEMORY,
@ -272,6 +271,7 @@ function concatenateInstructions(
export async function loadServerHierarchicalMemory( export async function loadServerHierarchicalMemory(
currentWorkingDirectory: string, currentWorkingDirectory: string,
debugMode: boolean, debugMode: boolean,
fileService: FileDiscoveryService,
extensionContextFilePaths: string[] = [], extensionContextFilePaths: string[] = [],
): Promise<{ memoryContent: string; fileCount: number }> { ): Promise<{ memoryContent: string; fileCount: number }> {
if (debugMode) if (debugMode)
@ -285,6 +285,7 @@ export async function loadServerHierarchicalMemory(
currentWorkingDirectory, currentWorkingDirectory,
userHomePath, userHomePath,
debugMode, debugMode,
fileService,
extensionContextFilePaths, extensionContextFilePaths,
); );
if (filePaths.length === 0) { if (filePaths.length === 0) {