From 43bac6a038611303f36ad4511d0acec7483de97a Mon Sep 17 00:00:00 2001 From: sgnagnarella Date: Thu, 2 Oct 2025 17:46:54 -0400 Subject: [PATCH] Adding list sub command to memoryCommand to list the path of GEMINI.md files (#10108) Co-authored-by: Abhi <43648792+abhipatel12@users.noreply.github.com> --- docs/cli/commands.md | 2 + packages/cli/src/config/config.ts | 30 ++-- packages/cli/src/ui/AppContainer.tsx | 29 ++-- .../cli/src/ui/commands/memoryCommand.test.ts | 70 ++++++++- packages/cli/src/ui/commands/memoryCommand.ts | 25 ++- packages/core/src/config/config.ts | 11 ++ .../core/src/utils/memoryDiscovery.test.ts | 142 ++++++++++++++---- packages/core/src/utils/memoryDiscovery.ts | 4 +- 8 files changed, 250 insertions(+), 63 deletions(-) diff --git a/docs/cli/commands.md b/docs/cli/commands.md index 7caeebf94c..3508f6e1c3 100644 --- a/docs/cli/commands.md +++ b/docs/cli/commands.md @@ -87,6 +87,8 @@ Slash commands provide meta-level control over the CLI itself. - **Description:** Display the full, concatenated content of the current hierarchical memory that has been loaded from all `GEMINI.md` files. This lets you inspect the instructional context being provided to the Gemini model. - **`refresh`**: - **Description:** Reload the hierarchical instructional memory from all `GEMINI.md` files found in the configured locations (global, project/ancestors, and sub-directories). This command updates the model with the latest `GEMINI.md` content. + - **`list`**: + - **Description:** Lists the paths of the GEMINI.md files in use for hierarchical memory. - **Note:** For more details on how `GEMINI.md` files contribute to hierarchical memory, see the [CLI Configuration documentation](../get-started/configuration.md). - **`/restore`** diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index cc4012cedd..805f2f4e1a 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -400,7 +400,7 @@ export async function loadHierarchicalGeminiMemory( folderTrust: boolean, memoryImportFormat: 'flat' | 'tree' = 'tree', fileFilteringOptions?: FileFilteringOptions, -): Promise<{ memoryContent: string; fileCount: number }> { +): Promise<{ memoryContent: string; fileCount: number; filePaths: string[] }> { // FIX: Use real, canonical paths for a reliable comparison to handle symlinks. const realCwd = fs.realpathSync(path.resolve(currentWorkingDirectory)); const realHome = fs.realpathSync(path.resolve(homedir())); @@ -493,19 +493,20 @@ export async function loadCliConfig( .concat((argv.includeDirectories || []).map(resolvePath)); // Call the (now wrapper) loadHierarchicalGeminiMemory which calls the server's version - const { memoryContent, fileCount } = await loadHierarchicalGeminiMemory( - cwd, - settings.context?.loadMemoryFromIncludeDirectories - ? includeDirectories - : [], - debugMode, - fileService, - settings, - extensionContextFilePaths, - trustedFolder, - memoryImportFormat, - fileFiltering, - ); + const { memoryContent, fileCount, filePaths } = + await loadHierarchicalGeminiMemory( + cwd, + settings.context?.loadMemoryFromIncludeDirectories + ? includeDirectories + : [], + debugMode, + fileService, + settings, + extensionContextFilePaths, + trustedFolder, + memoryImportFormat, + fileFiltering, + ); let mcpServers = mergeMcpServers(settings, activeExtensions); const question = argv.promptInteractive || argv.prompt || ''; @@ -657,6 +658,7 @@ export async function loadCliConfig( mcpServers, userMemory: memoryContent, geminiMdFileCount: fileCount, + geminiMdFilePaths: filePaths, approvalMode, showMemoryUsage: argv.showMemoryUsage || settings.ui?.showMemoryUsage || false, diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index d071115495..795040767f 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -510,22 +510,25 @@ Logging in with Google... Please restart Gemini CLI to continue. Date.now(), ); try { - const { memoryContent, fileCount } = await loadHierarchicalGeminiMemory( - process.cwd(), - settings.merged.context?.loadMemoryFromIncludeDirectories - ? config.getWorkspaceContext().getDirectories() - : [], - config.getDebugMode(), - config.getFileService(), - settings.merged, - config.getExtensionContextFilePaths(), - config.isTrustedFolder(), - settings.merged.context?.importFormat || 'tree', // Use setting or default to 'tree' - config.getFileFilteringOptions(), - ); + const { memoryContent, fileCount, filePaths } = + await loadHierarchicalGeminiMemory( + process.cwd(), + settings.merged.context?.loadMemoryFromIncludeDirectories + ? config.getWorkspaceContext().getDirectories() + : [], + config.getDebugMode(), + config.getFileService(), + settings.merged, + config.getExtensionContextFilePaths(), + config.isTrustedFolder(), + settings.merged.context?.importFormat || 'tree', // Use setting or default to 'tree' + config.getFileFilteringOptions(), + ); config.setUserMemory(memoryContent); config.setGeminiMdFileCount(fileCount); + config.setGeminiMdFilePaths(filePaths); + setGeminiMdFileCount(fileCount); historyManager.addItem( diff --git a/packages/cli/src/ui/commands/memoryCommand.test.ts b/packages/cli/src/ui/commands/memoryCommand.test.ts index 418320c231..2c285368a4 100644 --- a/packages/cli/src/ui/commands/memoryCommand.test.ts +++ b/packages/cli/src/ui/commands/memoryCommand.test.ts @@ -7,7 +7,7 @@ import type { Mock } from 'vitest'; import { vi, describe, it, expect, beforeEach } from 'vitest'; import { memoryCommand } from './memoryCommand.js'; -import type { SlashCommand, type CommandContext } from './types.js'; +import type { SlashCommand, CommandContext } from './types.js'; import { createMockCommandContext } from '../../test-utils/mockCommandContext.js'; import { MessageType } from '../types.js'; import type { LoadedSettings } from '../../config/settings.js'; @@ -36,7 +36,9 @@ const mockLoadServerHierarchicalMemory = loadServerHierarchicalMemory as Mock; describe('memoryCommand', () => { let mockContext: CommandContext; - const getSubCommand = (name: 'show' | 'add' | 'refresh'): SlashCommand => { + const getSubCommand = ( + name: 'show' | 'add' | 'refresh' | 'list', + ): SlashCommand => { const subCommand = memoryCommand.subCommands?.find( (cmd) => cmd.name === name, ); @@ -151,14 +153,18 @@ describe('memoryCommand', () => { let refreshCommand: SlashCommand; let mockSetUserMemory: Mock; let mockSetGeminiMdFileCount: Mock; + let mockSetGeminiMdFilePaths: Mock; beforeEach(() => { refreshCommand = getSubCommand('refresh'); mockSetUserMemory = vi.fn(); mockSetGeminiMdFileCount = vi.fn(); + mockSetGeminiMdFilePaths = vi.fn(); + const mockConfig = { setUserMemory: mockSetUserMemory, setGeminiMdFileCount: mockSetGeminiMdFileCount, + setGeminiMdFilePaths: mockSetGeminiMdFilePaths, getWorkingDir: () => '/test/dir', getDebugMode: () => false, getFileService: () => ({}) as FileDiscoveryService, @@ -176,7 +182,7 @@ describe('memoryCommand', () => { mockContext = createMockCommandContext({ services: { - config: Promise.resolve(mockConfig), + config: mockConfig, settings: { merged: { memoryDiscoveryMaxDirs: 1000, @@ -193,6 +199,7 @@ describe('memoryCommand', () => { const refreshResult: LoadServerHierarchicalMemoryResponse = { memoryContent: 'new memory content', fileCount: 2, + filePaths: ['/path/one/GEMINI.md', '/path/two/GEMINI.md'], }; mockLoadServerHierarchicalMemory.mockResolvedValue(refreshResult); @@ -213,6 +220,9 @@ describe('memoryCommand', () => { expect(mockSetGeminiMdFileCount).toHaveBeenCalledWith( refreshResult.fileCount, ); + expect(mockSetGeminiMdFilePaths).toHaveBeenCalledWith( + refreshResult.filePaths, + ); expect(mockContext.ui.addItem).toHaveBeenCalledWith( { @@ -226,7 +236,7 @@ describe('memoryCommand', () => { it('should display success message when memory is refreshed with no content', async () => { if (!refreshCommand.action) throw new Error('Command has no action'); - const refreshResult = { memoryContent: '', fileCount: 0 }; + const refreshResult = { memoryContent: '', fileCount: 0, filePaths: [] }; mockLoadServerHierarchicalMemory.mockResolvedValue(refreshResult); await refreshCommand.action(mockContext, ''); @@ -234,6 +244,7 @@ describe('memoryCommand', () => { expect(loadServerHierarchicalMemory).toHaveBeenCalledOnce(); expect(mockSetUserMemory).toHaveBeenCalledWith(''); expect(mockSetGeminiMdFileCount).toHaveBeenCalledWith(0); + expect(mockSetGeminiMdFilePaths).toHaveBeenCalledWith([]); expect(mockContext.ui.addItem).toHaveBeenCalledWith( { @@ -255,6 +266,7 @@ describe('memoryCommand', () => { expect(loadServerHierarchicalMemory).toHaveBeenCalledOnce(); expect(mockSetUserMemory).not.toHaveBeenCalled(); expect(mockSetGeminiMdFileCount).not.toHaveBeenCalled(); + expect(mockSetGeminiMdFilePaths).not.toHaveBeenCalled(); expect(mockContext.ui.addItem).toHaveBeenCalledWith( { @@ -289,4 +301,54 @@ describe('memoryCommand', () => { expect(loadServerHierarchicalMemory).not.toHaveBeenCalled(); }); }); + + describe('/memory list', () => { + let listCommand: SlashCommand; + let mockGetGeminiMdfilePaths: Mock; + + beforeEach(() => { + listCommand = getSubCommand('list'); + mockGetGeminiMdfilePaths = vi.fn(); + mockContext = createMockCommandContext({ + services: { + config: { + getGeminiMdFilePaths: mockGetGeminiMdfilePaths, + }, + }, + }); + }); + + it('should display a message if no GEMINI.md files are found', async () => { + if (!listCommand.action) throw new Error('Command has no action'); + + mockGetGeminiMdfilePaths.mockReturnValue([]); + + await listCommand.action(mockContext, ''); + + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + { + type: MessageType.INFO, + text: 'No GEMINI.md files in use.', + }, + expect.any(Number), + ); + }); + + it('should display the file count and paths if they exist', async () => { + if (!listCommand.action) throw new Error('Command has no action'); + + const filePaths = ['/path/one/GEMINI.md', '/path/two/GEMINI.md']; + mockGetGeminiMdfilePaths.mockReturnValue(filePaths); + + await listCommand.action(mockContext, ''); + + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + { + type: MessageType.INFO, + text: `There are 2 GEMINI.md file(s) in use:\n\n${filePaths.join('\n')}`, + }, + expect.any(Number), + ); + }); + }); }); diff --git a/packages/cli/src/ui/commands/memoryCommand.ts b/packages/cli/src/ui/commands/memoryCommand.ts index 6c418cdb80..b8fb4c03f7 100644 --- a/packages/cli/src/ui/commands/memoryCommand.ts +++ b/packages/cli/src/ui/commands/memoryCommand.ts @@ -83,7 +83,7 @@ export const memoryCommand: SlashCommand = { try { const config = await context.services.config; if (config) { - const { memoryContent, fileCount } = + const { memoryContent, fileCount, filePaths } = await loadServerHierarchicalMemory( config.getWorkingDir(), config.shouldLoadMemoryFromIncludeDirectories() @@ -100,6 +100,7 @@ export const memoryCommand: SlashCommand = { ); config.setUserMemory(memoryContent); config.setGeminiMdFileCount(fileCount); + config.setGeminiMdFilePaths(filePaths); const successMessage = memoryContent.length > 0 @@ -126,5 +127,27 @@ export const memoryCommand: SlashCommand = { } }, }, + { + name: 'list', + description: 'Lists the paths of the GEMINI.md files in use.', + kind: CommandKind.BUILT_IN, + action: async (context) => { + const filePaths = context.services.config?.getGeminiMdFilePaths() || []; + const fileCount = filePaths.length; + + const messageContent = + fileCount > 0 + ? `There are ${fileCount} GEMINI.md file(s) in use:\n\n${filePaths.join('\n')}` + : 'No GEMINI.md files in use.'; + + context.ui.addItem( + { + type: MessageType.INFO, + text: messageContent, + }, + Date.now(), + ); + }, + }, ], }; diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 33424e455e..1765e329a5 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -208,6 +208,7 @@ export interface ConfigParameters { mcpServers?: Record; userMemory?: string; geminiMdFileCount?: number; + geminiMdFilePaths?: string[]; approvalMode?: ApprovalMode; showMemoryUsage?: boolean; contextFileName?: string | string[]; @@ -285,6 +286,7 @@ export class Config { private readonly mcpServers: Record | undefined; private userMemory: string; private geminiMdFileCount: number; + private geminiMdFilePaths: string[]; private approvalMode: ApprovalMode; private readonly showMemoryUsage: boolean; private readonly accessibility: AccessibilitySettings; @@ -375,6 +377,7 @@ export class Config { this.mcpServers = params.mcpServers; this.userMemory = params.userMemory ?? ''; this.geminiMdFileCount = params.geminiMdFileCount ?? 0; + this.geminiMdFilePaths = params.geminiMdFilePaths ?? []; this.approvalMode = params.approvalMode ?? ApprovalMode.DEFAULT; this.showMemoryUsage = params.showMemoryUsage ?? false; this.accessibility = params.accessibility ?? {}; @@ -698,6 +701,14 @@ export class Config { this.geminiMdFileCount = count; } + getGeminiMdFilePaths(): string[] { + return this.geminiMdFilePaths; + } + + setGeminiMdFilePaths(paths: string[]): void { + this.geminiMdFilePaths = paths; + } + getApprovalMode(): ApprovalMode { return this.approvalMode; } diff --git a/packages/core/src/utils/memoryDiscovery.test.ts b/packages/core/src/utils/memoryDiscovery.test.ts index 9cf471b1be..0d928b76aa 100644 --- a/packages/core/src/utils/memoryDiscovery.test.ts +++ b/packages/core/src/utils/memoryDiscovery.test.ts @@ -82,7 +82,7 @@ describe('loadServerHierarchicalMemory', () => { path.join(cwd, DEFAULT_CONTEXT_FILENAME), 'Src directory memory', ); - const { fileCount } = await loadServerHierarchicalMemory( + const result = await loadServerHierarchicalMemory( cwd, [], false, @@ -91,32 +91,38 @@ describe('loadServerHierarchicalMemory', () => { false, // untrusted ); - expect(fileCount).toEqual(0); + expect(result).toEqual({ + memoryContent: '', + fileCount: 0, + filePaths: [], + }); }); it('loads context from outside the untrusted workspace', async () => { await createTestFile( path.join(projectRoot, DEFAULT_CONTEXT_FILENAME), - 'Project root memory', - ); // Untrusted + 'Project root memory', // Untrusted + ); await createTestFile( path.join(cwd, DEFAULT_CONTEXT_FILENAME), - 'Src directory memory', - ); // Untrusted + 'Src directory memory', // Untrusted + ); const filepath = path.join(homedir, GEMINI_DIR, DEFAULT_CONTEXT_FILENAME); await createTestFile(filepath, 'default context content'); // In user home dir (outside untrusted space). - const { fileCount, memoryContent } = await loadServerHierarchicalMemory( - cwd, - [], - false, - new FileDiscoveryService(projectRoot), - [], - false, // untrusted - ); + const { fileCount, memoryContent, filePaths } = + await loadServerHierarchicalMemory( + cwd, + [], + false, + new FileDiscoveryService(projectRoot), + [], + false, // untrusted + ); expect(fileCount).toEqual(1); expect(memoryContent).toContain(path.relative(cwd, filepath).toString()); + expect(filePaths).toEqual([filepath]); }); }); @@ -133,6 +139,7 @@ describe('loadServerHierarchicalMemory', () => { expect(result).toEqual({ memoryContent: '', fileCount: 0, + filePaths: [], }); }); @@ -152,8 +159,11 @@ describe('loadServerHierarchicalMemory', () => { ); expect(result).toEqual({ - memoryContent: `--- Context from: ${path.relative(cwd, defaultContextFile)} ---\ndefault context content\n--- End of Context from: ${path.relative(cwd, defaultContextFile)} ---`, + memoryContent: `--- Context from: ${path.relative(cwd, defaultContextFile)} --- +default context content +--- End of Context from: ${path.relative(cwd, defaultContextFile)} ---`, fileCount: 1, + filePaths: [defaultContextFile], }); }); @@ -176,8 +186,11 @@ describe('loadServerHierarchicalMemory', () => { ); expect(result).toEqual({ - memoryContent: `--- Context from: ${path.relative(cwd, customContextFile)} ---\ncustom context content\n--- End of Context from: ${path.relative(cwd, customContextFile)} ---`, + memoryContent: `--- Context from: ${path.relative(cwd, customContextFile)} --- +custom context content +--- End of Context from: ${path.relative(cwd, customContextFile)} ---`, fileCount: 1, + filePaths: [customContextFile], }); }); @@ -204,8 +217,15 @@ describe('loadServerHierarchicalMemory', () => { ); expect(result).toEqual({ - memoryContent: `--- Context from: ${path.relative(cwd, projectContextFile)} ---\nproject context content\n--- End of Context from: ${path.relative(cwd, projectContextFile)} ---\n\n--- Context from: ${path.relative(cwd, cwdContextFile)} ---\ncwd context content\n--- End of Context from: ${path.relative(cwd, cwdContextFile)} ---`, + memoryContent: `--- Context from: ${path.relative(cwd, projectContextFile)} --- +project context content +--- End of Context from: ${path.relative(cwd, projectContextFile)} --- + +--- Context from: ${path.relative(cwd, cwdContextFile)} --- +cwd context content +--- End of Context from: ${path.relative(cwd, cwdContextFile)} ---`, fileCount: 2, + filePaths: [projectContextFile, cwdContextFile], }); }); @@ -213,11 +233,14 @@ describe('loadServerHierarchicalMemory', () => { const customFilename = 'LOCAL_CONTEXT.md'; setGeminiMdFilename(customFilename); - await createTestFile( + const subdirCustomFile = await createTestFile( path.join(cwd, 'subdir', customFilename), 'Subdir custom memory', ); - await createTestFile(path.join(cwd, customFilename), 'CWD custom memory'); + const cwdCustomFile = await createTestFile( + path.join(cwd, customFilename), + 'CWD custom memory', + ); const result = await loadServerHierarchicalMemory( cwd, @@ -229,8 +252,15 @@ describe('loadServerHierarchicalMemory', () => { ); expect(result).toEqual({ - memoryContent: `--- Context from: ${customFilename} ---\nCWD custom memory\n--- End of Context from: ${customFilename} ---\n\n--- Context from: ${path.join('subdir', customFilename)} ---\nSubdir custom memory\n--- End of Context from: ${path.join('subdir', customFilename)} ---`, + memoryContent: `--- Context from: ${customFilename} --- +CWD custom memory +--- End of Context from: ${customFilename} --- + +--- Context from: ${path.join('subdir', customFilename)} --- +Subdir custom memory +--- End of Context from: ${path.join('subdir', customFilename)} ---`, fileCount: 2, + filePaths: [cwdCustomFile, subdirCustomFile], }); }); @@ -254,17 +284,24 @@ describe('loadServerHierarchicalMemory', () => { ); expect(result).toEqual({ - memoryContent: `--- 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, srcGeminiFile)} ---\nSrc directory memory\n--- End of Context from: ${path.relative(cwd, srcGeminiFile)} ---`, + memoryContent: `--- Context from: ${path.relative(cwd, projectRootGeminiFile)} --- +Project root memory +--- End of Context from: ${path.relative(cwd, projectRootGeminiFile)} --- + +--- Context from: ${path.relative(cwd, srcGeminiFile)} --- +Src directory memory +--- End of Context from: ${path.relative(cwd, srcGeminiFile)} ---`, fileCount: 2, + filePaths: [projectRootGeminiFile, srcGeminiFile], }); }); it('should load ORIGINAL_GEMINI_MD_FILENAME files by downward traversal from CWD', async () => { - await createTestFile( + const subDirGeminiFile = await createTestFile( path.join(cwd, 'subdir', DEFAULT_CONTEXT_FILENAME), 'Subdir memory', ); - await createTestFile( + const cwdGeminiFile = await createTestFile( path.join(cwd, DEFAULT_CONTEXT_FILENAME), 'CWD memory', ); @@ -279,8 +316,15 @@ describe('loadServerHierarchicalMemory', () => { ); expect(result).toEqual({ - memoryContent: `--- Context from: ${DEFAULT_CONTEXT_FILENAME} ---\nCWD memory\n--- End of Context from: ${DEFAULT_CONTEXT_FILENAME} ---\n\n--- Context from: ${path.join('subdir', DEFAULT_CONTEXT_FILENAME)} ---\nSubdir memory\n--- End of Context from: ${path.join('subdir', DEFAULT_CONTEXT_FILENAME)} ---`, + memoryContent: `--- Context from: ${DEFAULT_CONTEXT_FILENAME} --- +CWD memory +--- End of Context from: ${DEFAULT_CONTEXT_FILENAME} --- + +--- Context from: ${path.join('subdir', DEFAULT_CONTEXT_FILENAME)} --- +Subdir memory +--- End of Context from: ${path.join('subdir', DEFAULT_CONTEXT_FILENAME)} ---`, fileCount: 2, + filePaths: [cwdGeminiFile, subDirGeminiFile], }); }); @@ -316,8 +360,33 @@ describe('loadServerHierarchicalMemory', () => { ); expect(result).toEqual({ - memoryContent: `--- Context from: ${path.relative(cwd, defaultContextFile)} ---\ndefault context content\n--- End of Context from: ${path.relative(cwd, defaultContextFile)} ---\n\n--- Context from: ${path.relative(cwd, rootGeminiFile)} ---\nProject parent memory\n--- End of Context from: ${path.relative(cwd, rootGeminiFile)} ---\n\n--- 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, cwdGeminiFile)} ---\nCWD memory\n--- End of Context from: ${path.relative(cwd, cwdGeminiFile)} ---\n\n--- Context from: ${path.relative(cwd, subDirGeminiFile)} ---\nSubdir memory\n--- End of Context from: ${path.relative(cwd, subDirGeminiFile)} ---`, + memoryContent: `--- Context from: ${path.relative(cwd, defaultContextFile)} --- +default context content +--- End of Context from: ${path.relative(cwd, defaultContextFile)} --- + +--- Context from: ${path.relative(cwd, rootGeminiFile)} --- +Project parent memory +--- End of Context from: ${path.relative(cwd, rootGeminiFile)} --- + +--- Context from: ${path.relative(cwd, projectRootGeminiFile)} --- +Project root memory +--- End of Context from: ${path.relative(cwd, projectRootGeminiFile)} --- + +--- Context from: ${path.relative(cwd, cwdGeminiFile)} --- +CWD memory +--- End of Context from: ${path.relative(cwd, cwdGeminiFile)} --- + +--- Context from: ${path.relative(cwd, subDirGeminiFile)} --- +Subdir memory +--- End of Context from: ${path.relative(cwd, subDirGeminiFile)} ---`, fileCount: 5, + filePaths: [ + defaultContextFile, + rootGeminiFile, + projectRootGeminiFile, + cwdGeminiFile, + subDirGeminiFile, + ], }); }); @@ -350,8 +419,11 @@ describe('loadServerHierarchicalMemory', () => { ); expect(result).toEqual({ - memoryContent: `--- Context from: ${path.relative(cwd, regularSubDirGeminiFile)} ---\nMy code memory\n--- End of Context from: ${path.relative(cwd, regularSubDirGeminiFile)} ---`, + memoryContent: `--- Context from: ${path.relative(cwd, regularSubDirGeminiFile)} --- +My code memory +--- End of Context from: ${path.relative(cwd, regularSubDirGeminiFile)} ---`, fileCount: 1, + filePaths: [regularSubDirGeminiFile], }); }); @@ -401,6 +473,7 @@ describe('loadServerHierarchicalMemory', () => { expect(result).toEqual({ memoryContent: '', fileCount: 0, + filePaths: [], }); }); @@ -420,8 +493,11 @@ describe('loadServerHierarchicalMemory', () => { ); expect(result).toEqual({ - memoryContent: `--- Context from: ${path.relative(cwd, extensionFilePath)} ---\nExtension memory content\n--- End of Context from: ${path.relative(cwd, extensionFilePath)} ---`, + memoryContent: `--- Context from: ${path.relative(cwd, extensionFilePath)} --- +Extension memory content +--- End of Context from: ${path.relative(cwd, extensionFilePath)} ---`, fileCount: 1, + filePaths: [extensionFilePath], }); }); @@ -444,8 +520,11 @@ describe('loadServerHierarchicalMemory', () => { ); expect(result).toEqual({ - memoryContent: `--- Context from: ${path.relative(cwd, includedFile)} ---\nincluded directory memory\n--- End of Context from: ${path.relative(cwd, includedFile)} ---`, + memoryContent: `--- Context from: ${path.relative(cwd, includedFile)} --- +included directory memory +--- End of Context from: ${path.relative(cwd, includedFile)} ---`, fileCount: 1, + filePaths: [includedFile], }); }); @@ -477,6 +556,8 @@ describe('loadServerHierarchicalMemory', () => { // Should have loaded all files expect(result.fileCount).toBe(numDirs); + expect(result.filePaths.length).toBe(numDirs); + expect(result.filePaths.sort()).toEqual(createdFiles.sort()); // Content should include all project contents for (let i = 0; i < numDirs; i++) { @@ -489,11 +570,11 @@ describe('loadServerHierarchicalMemory', () => { const parentDir = await createEmptyDir(path.join(testRootDir, 'parent')); const childDir = await createEmptyDir(path.join(parentDir, 'child')); - await createTestFile( + const parentFile = await createTestFile( path.join(parentDir, DEFAULT_CONTEXT_FILENAME), 'Parent content', ); - await createTestFile( + const childFile = await createTestFile( path.join(childDir, DEFAULT_CONTEXT_FILENAME), 'Child content', ); @@ -512,6 +593,7 @@ describe('loadServerHierarchicalMemory', () => { expect(result.fileCount).toBe(2); expect(result.memoryContent).toContain('Parent content'); expect(result.memoryContent).toContain('Child content'); + expect(result.filePaths.sort()).toEqual([parentFile, childFile].sort()); // Check that files are not duplicated const parentOccurrences = ( diff --git a/packages/core/src/utils/memoryDiscovery.ts b/packages/core/src/utils/memoryDiscovery.ts index a2d9ed1e3e..a25309cefd 100644 --- a/packages/core/src/utils/memoryDiscovery.ts +++ b/packages/core/src/utils/memoryDiscovery.ts @@ -331,6 +331,7 @@ function concatenateInstructions( export interface LoadServerHierarchicalMemoryResponse { memoryContent: string; fileCount: number; + filePaths: string[]; } /** @@ -370,7 +371,7 @@ export async function loadServerHierarchicalMemory( if (filePaths.length === 0) { if (debugMode) logger.debug('No GEMINI.md files found in hierarchy of the workspace.'); - return { memoryContent: '', fileCount: 0 }; + return { memoryContent: '', fileCount: 0, filePaths: [] }; } const contentsWithPaths = await readGeminiMdFiles( filePaths, @@ -393,5 +394,6 @@ export async function loadServerHierarchicalMemory( return { memoryContent: combinedInstructions, fileCount: contentsWithPaths.length, + filePaths, }; }