feat(memory): make directory search limit on memory discovery configurable with settings.json (#4460)

This commit is contained in:
Brandon Keiji
2025-07-23 14:48:35 -07:00
committed by GitHub
parent 9d3164621a
commit d7a304bcff
11 changed files with 104 additions and 83 deletions

View File

@@ -18,7 +18,6 @@ import {
} from '../core/contentGenerator.js';
import { GeminiClient } from '../core/client.js';
import { GitService } from '../services/gitService.js';
import { loadServerHierarchicalMemory } from '../utils/memoryDiscovery.js';
// Mock dependencies that might be called during Config construction or createServerConfig
vi.mock('../tools/tool-registry', () => {
@@ -313,39 +312,4 @@ describe('Server Config (config.ts)', () => {
expect(config.getTelemetryOtlpEndpoint()).toBe(DEFAULT_OTLP_ENDPOINT);
});
});
describe('refreshMemory', () => {
it('should update memory and file count on successful refresh', async () => {
const config = new Config(baseParams);
const mockMemoryData = {
memoryContent: 'new memory content',
fileCount: 5,
};
(loadServerHierarchicalMemory as Mock).mockResolvedValue(mockMemoryData);
const result = await config.refreshMemory();
expect(loadServerHierarchicalMemory).toHaveBeenCalledWith(
config.getWorkingDir(),
config.getDebugMode(),
config.getFileService(),
config.getExtensionContextFilePaths(),
config.getFileFilteringOptions(),
);
expect(config.getUserMemory()).toBe(mockMemoryData.memoryContent);
expect(config.getGeminiMdFileCount()).toBe(mockMemoryData.fileCount);
expect(result).toEqual(mockMemoryData);
});
it('should propagate errors from loadServerHierarchicalMemory', async () => {
const config = new Config(baseParams);
const testError = new Error('Failed to load memory');
(loadServerHierarchicalMemory as Mock).mockRejectedValue(testError);
await expect(config.refreshMemory()).rejects.toThrow(testError);
});
});
});

View File

@@ -30,7 +30,6 @@ import { WebSearchTool } from '../tools/web-search.js';
import { GeminiClient } from '../core/client.js';
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
import { GitService } from '../services/gitService.js';
import { loadServerHierarchicalMemory } from '../utils/memoryDiscovery.js';
import { getProjectTempDir } from '../utils/paths.js';
import {
initializeTelemetry,
@@ -577,21 +576,6 @@ export class Config {
return this.gitService;
}
async refreshMemory(): Promise<{ memoryContent: string; fileCount: number }> {
const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
this.getWorkingDir(),
this.getDebugMode(),
this.getFileService(),
this.getExtensionContextFilePaths(),
this.getFileFilteringOptions(),
);
this.setUserMemory(memoryContent);
this.setGeminiMdFileCount(fileCount);
return { memoryContent, fileCount };
}
async createToolRegistry(): Promise<ToolRegistry> {
const registry = new ToolRegistry(this);

View File

@@ -319,18 +319,35 @@ My code memory
});
});
it('should respect MAX_DIRECTORIES_TO_SCAN_FOR_MEMORY during downward scan', async () => {
// the max depth is 200 so it will give up before searching all these.
for (let i = 0; i < 250; i++) {
it('should respect the maxDirs parameter during downward scan', async () => {
const consoleDebugSpy = vi
.spyOn(console, 'debug')
.mockImplementation(() => {});
for (let i = 0; i < 100; i++) {
await createEmptyDir(path.join(cwd, `deep_dir_${i}`));
}
// "much_deeper" is alphabetically after "deep_dir_*" so it won't be loaded
await createTestFile(
path.join(cwd, 'much_deeper', DEFAULT_CONTEXT_FILENAME),
'Ignored memory',
// Pass the custom limit directly to the function
await loadServerHierarchicalMemory(
cwd,
true,
new FileDiscoveryService(projectRoot),
[],
{
respectGitIgnore: true,
respectGeminiIgnore: true,
},
50, // maxDirs
);
expect(consoleDebugSpy).toHaveBeenCalledWith(
expect.stringContaining('[DEBUG] [BfsFileSearch]'),
expect.stringContaining('Scanning [50/50]:'),
);
vi.mocked(console.debug).mockRestore();
const result = await loadServerHierarchicalMemory(
cwd,
false,

View File

@@ -33,8 +33,6 @@ const logger = {
console.error('[ERROR] [MemoryDiscovery]', ...args),
};
const MAX_DIRECTORIES_TO_SCAN_FOR_MEMORY = 200;
interface GeminiFileContent {
filePath: string;
content: string | null;
@@ -90,6 +88,7 @@ async function getGeminiMdFilePathsInternal(
fileService: FileDiscoveryService,
extensionContextFilePaths: string[] = [],
fileFilteringOptions: FileFilteringOptions,
maxDirs: number,
): Promise<string[]> {
const allPaths = new Set<string>();
const geminiMdFilenames = getAllGeminiMdFilenames();
@@ -194,7 +193,7 @@ async function getGeminiMdFilePathsInternal(
const downwardPaths = await bfsFileSearch(resolvedCwd, {
fileName: geminiMdFilename,
maxDirs: MAX_DIRECTORIES_TO_SCAN_FOR_MEMORY,
maxDirs,
debug: debugMode,
fileService,
fileFilteringOptions: mergedOptions, // Pass merged options as fileFilter
@@ -295,6 +294,7 @@ export async function loadServerHierarchicalMemory(
fileService: FileDiscoveryService,
extensionContextFilePaths: string[] = [],
fileFilteringOptions?: FileFilteringOptions,
maxDirs: number = 200,
): Promise<{ memoryContent: string; fileCount: number }> {
if (debugMode)
logger.debug(
@@ -311,6 +311,7 @@ export async function loadServerHierarchicalMemory(
fileService,
extensionContextFilePaths,
fileFilteringOptions || DEFAULT_MEMORY_FILE_FILTERING_OPTIONS,
maxDirs,
);
if (filePaths.length === 0) {
if (debugMode) logger.debug('No GEMINI.md files found in hierarchy.');