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>
This commit is contained in:
sgnagnarella
2025-10-02 17:46:54 -04:00
committed by GitHub
parent 0713dd4d2d
commit 43bac6a038
8 changed files with 250 additions and 63 deletions
+2
View File
@@ -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. - **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`**: - **`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. - **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). - **Note:** For more details on how `GEMINI.md` files contribute to hierarchical memory, see the [CLI Configuration documentation](../get-started/configuration.md).
- **`/restore`** - **`/restore`**
+16 -14
View File
@@ -400,7 +400,7 @@ export async function loadHierarchicalGeminiMemory(
folderTrust: boolean, folderTrust: boolean,
memoryImportFormat: 'flat' | 'tree' = 'tree', memoryImportFormat: 'flat' | 'tree' = 'tree',
fileFilteringOptions?: FileFilteringOptions, 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. // FIX: Use real, canonical paths for a reliable comparison to handle symlinks.
const realCwd = fs.realpathSync(path.resolve(currentWorkingDirectory)); const realCwd = fs.realpathSync(path.resolve(currentWorkingDirectory));
const realHome = fs.realpathSync(path.resolve(homedir())); const realHome = fs.realpathSync(path.resolve(homedir()));
@@ -493,19 +493,20 @@ export async function loadCliConfig(
.concat((argv.includeDirectories || []).map(resolvePath)); .concat((argv.includeDirectories || []).map(resolvePath));
// 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, filePaths } =
cwd, await loadHierarchicalGeminiMemory(
settings.context?.loadMemoryFromIncludeDirectories cwd,
? includeDirectories settings.context?.loadMemoryFromIncludeDirectories
: [], ? includeDirectories
debugMode, : [],
fileService, debugMode,
settings, fileService,
extensionContextFilePaths, settings,
trustedFolder, extensionContextFilePaths,
memoryImportFormat, trustedFolder,
fileFiltering, memoryImportFormat,
); fileFiltering,
);
let mcpServers = mergeMcpServers(settings, activeExtensions); let mcpServers = mergeMcpServers(settings, activeExtensions);
const question = argv.promptInteractive || argv.prompt || ''; const question = argv.promptInteractive || argv.prompt || '';
@@ -657,6 +658,7 @@ export async function loadCliConfig(
mcpServers, mcpServers,
userMemory: memoryContent, userMemory: memoryContent,
geminiMdFileCount: fileCount, geminiMdFileCount: fileCount,
geminiMdFilePaths: filePaths,
approvalMode, approvalMode,
showMemoryUsage: showMemoryUsage:
argv.showMemoryUsage || settings.ui?.showMemoryUsage || false, argv.showMemoryUsage || settings.ui?.showMemoryUsage || false,
+16 -13
View File
@@ -510,22 +510,25 @@ Logging in with Google... Please restart Gemini CLI to continue.
Date.now(), Date.now(),
); );
try { try {
const { memoryContent, fileCount } = await loadHierarchicalGeminiMemory( const { memoryContent, fileCount, filePaths } =
process.cwd(), await loadHierarchicalGeminiMemory(
settings.merged.context?.loadMemoryFromIncludeDirectories process.cwd(),
? config.getWorkspaceContext().getDirectories() settings.merged.context?.loadMemoryFromIncludeDirectories
: [], ? config.getWorkspaceContext().getDirectories()
config.getDebugMode(), : [],
config.getFileService(), config.getDebugMode(),
settings.merged, config.getFileService(),
config.getExtensionContextFilePaths(), settings.merged,
config.isTrustedFolder(), config.getExtensionContextFilePaths(),
settings.merged.context?.importFormat || 'tree', // Use setting or default to 'tree' config.isTrustedFolder(),
config.getFileFilteringOptions(), settings.merged.context?.importFormat || 'tree', // Use setting or default to 'tree'
); config.getFileFilteringOptions(),
);
config.setUserMemory(memoryContent); config.setUserMemory(memoryContent);
config.setGeminiMdFileCount(fileCount); config.setGeminiMdFileCount(fileCount);
config.setGeminiMdFilePaths(filePaths);
setGeminiMdFileCount(fileCount); setGeminiMdFileCount(fileCount);
historyManager.addItem( historyManager.addItem(
@@ -7,7 +7,7 @@
import type { Mock } from 'vitest'; import type { Mock } from 'vitest';
import { vi, describe, it, expect, beforeEach } from 'vitest'; import { vi, describe, it, expect, beforeEach } from 'vitest';
import { memoryCommand } from './memoryCommand.js'; 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 { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
import { MessageType } from '../types.js'; import { MessageType } from '../types.js';
import type { LoadedSettings } from '../../config/settings.js'; import type { LoadedSettings } from '../../config/settings.js';
@@ -36,7 +36,9 @@ const mockLoadServerHierarchicalMemory = loadServerHierarchicalMemory as Mock;
describe('memoryCommand', () => { describe('memoryCommand', () => {
let mockContext: CommandContext; let mockContext: CommandContext;
const getSubCommand = (name: 'show' | 'add' | 'refresh'): SlashCommand => { const getSubCommand = (
name: 'show' | 'add' | 'refresh' | 'list',
): SlashCommand => {
const subCommand = memoryCommand.subCommands?.find( const subCommand = memoryCommand.subCommands?.find(
(cmd) => cmd.name === name, (cmd) => cmd.name === name,
); );
@@ -151,14 +153,18 @@ describe('memoryCommand', () => {
let refreshCommand: SlashCommand; let refreshCommand: SlashCommand;
let mockSetUserMemory: Mock; let mockSetUserMemory: Mock;
let mockSetGeminiMdFileCount: Mock; let mockSetGeminiMdFileCount: Mock;
let mockSetGeminiMdFilePaths: Mock;
beforeEach(() => { beforeEach(() => {
refreshCommand = getSubCommand('refresh'); refreshCommand = getSubCommand('refresh');
mockSetUserMemory = vi.fn(); mockSetUserMemory = vi.fn();
mockSetGeminiMdFileCount = vi.fn(); mockSetGeminiMdFileCount = vi.fn();
mockSetGeminiMdFilePaths = vi.fn();
const mockConfig = { const mockConfig = {
setUserMemory: mockSetUserMemory, setUserMemory: mockSetUserMemory,
setGeminiMdFileCount: mockSetGeminiMdFileCount, setGeminiMdFileCount: mockSetGeminiMdFileCount,
setGeminiMdFilePaths: mockSetGeminiMdFilePaths,
getWorkingDir: () => '/test/dir', getWorkingDir: () => '/test/dir',
getDebugMode: () => false, getDebugMode: () => false,
getFileService: () => ({}) as FileDiscoveryService, getFileService: () => ({}) as FileDiscoveryService,
@@ -176,7 +182,7 @@ describe('memoryCommand', () => {
mockContext = createMockCommandContext({ mockContext = createMockCommandContext({
services: { services: {
config: Promise.resolve(mockConfig), config: mockConfig,
settings: { settings: {
merged: { merged: {
memoryDiscoveryMaxDirs: 1000, memoryDiscoveryMaxDirs: 1000,
@@ -193,6 +199,7 @@ describe('memoryCommand', () => {
const refreshResult: LoadServerHierarchicalMemoryResponse = { const refreshResult: LoadServerHierarchicalMemoryResponse = {
memoryContent: 'new memory content', memoryContent: 'new memory content',
fileCount: 2, fileCount: 2,
filePaths: ['/path/one/GEMINI.md', '/path/two/GEMINI.md'],
}; };
mockLoadServerHierarchicalMemory.mockResolvedValue(refreshResult); mockLoadServerHierarchicalMemory.mockResolvedValue(refreshResult);
@@ -213,6 +220,9 @@ describe('memoryCommand', () => {
expect(mockSetGeminiMdFileCount).toHaveBeenCalledWith( expect(mockSetGeminiMdFileCount).toHaveBeenCalledWith(
refreshResult.fileCount, refreshResult.fileCount,
); );
expect(mockSetGeminiMdFilePaths).toHaveBeenCalledWith(
refreshResult.filePaths,
);
expect(mockContext.ui.addItem).toHaveBeenCalledWith( expect(mockContext.ui.addItem).toHaveBeenCalledWith(
{ {
@@ -226,7 +236,7 @@ describe('memoryCommand', () => {
it('should display success message when memory is refreshed with no content', async () => { it('should display success message when memory is refreshed with no content', async () => {
if (!refreshCommand.action) throw new Error('Command has no action'); if (!refreshCommand.action) throw new Error('Command has no action');
const refreshResult = { memoryContent: '', fileCount: 0 }; const refreshResult = { memoryContent: '', fileCount: 0, filePaths: [] };
mockLoadServerHierarchicalMemory.mockResolvedValue(refreshResult); mockLoadServerHierarchicalMemory.mockResolvedValue(refreshResult);
await refreshCommand.action(mockContext, ''); await refreshCommand.action(mockContext, '');
@@ -234,6 +244,7 @@ describe('memoryCommand', () => {
expect(loadServerHierarchicalMemory).toHaveBeenCalledOnce(); expect(loadServerHierarchicalMemory).toHaveBeenCalledOnce();
expect(mockSetUserMemory).toHaveBeenCalledWith(''); expect(mockSetUserMemory).toHaveBeenCalledWith('');
expect(mockSetGeminiMdFileCount).toHaveBeenCalledWith(0); expect(mockSetGeminiMdFileCount).toHaveBeenCalledWith(0);
expect(mockSetGeminiMdFilePaths).toHaveBeenCalledWith([]);
expect(mockContext.ui.addItem).toHaveBeenCalledWith( expect(mockContext.ui.addItem).toHaveBeenCalledWith(
{ {
@@ -255,6 +266,7 @@ describe('memoryCommand', () => {
expect(loadServerHierarchicalMemory).toHaveBeenCalledOnce(); expect(loadServerHierarchicalMemory).toHaveBeenCalledOnce();
expect(mockSetUserMemory).not.toHaveBeenCalled(); expect(mockSetUserMemory).not.toHaveBeenCalled();
expect(mockSetGeminiMdFileCount).not.toHaveBeenCalled(); expect(mockSetGeminiMdFileCount).not.toHaveBeenCalled();
expect(mockSetGeminiMdFilePaths).not.toHaveBeenCalled();
expect(mockContext.ui.addItem).toHaveBeenCalledWith( expect(mockContext.ui.addItem).toHaveBeenCalledWith(
{ {
@@ -289,4 +301,54 @@ describe('memoryCommand', () => {
expect(loadServerHierarchicalMemory).not.toHaveBeenCalled(); 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),
);
});
});
}); });
+24 -1
View File
@@ -83,7 +83,7 @@ export const memoryCommand: SlashCommand = {
try { try {
const config = await context.services.config; const config = await context.services.config;
if (config) { if (config) {
const { memoryContent, fileCount } = const { memoryContent, fileCount, filePaths } =
await loadServerHierarchicalMemory( await loadServerHierarchicalMemory(
config.getWorkingDir(), config.getWorkingDir(),
config.shouldLoadMemoryFromIncludeDirectories() config.shouldLoadMemoryFromIncludeDirectories()
@@ -100,6 +100,7 @@ export const memoryCommand: SlashCommand = {
); );
config.setUserMemory(memoryContent); config.setUserMemory(memoryContent);
config.setGeminiMdFileCount(fileCount); config.setGeminiMdFileCount(fileCount);
config.setGeminiMdFilePaths(filePaths);
const successMessage = const successMessage =
memoryContent.length > 0 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(),
);
},
},
], ],
}; };
+11
View File
@@ -208,6 +208,7 @@ export interface ConfigParameters {
mcpServers?: Record<string, MCPServerConfig>; mcpServers?: Record<string, MCPServerConfig>;
userMemory?: string; userMemory?: string;
geminiMdFileCount?: number; geminiMdFileCount?: number;
geminiMdFilePaths?: string[];
approvalMode?: ApprovalMode; approvalMode?: ApprovalMode;
showMemoryUsage?: boolean; showMemoryUsage?: boolean;
contextFileName?: string | string[]; contextFileName?: string | string[];
@@ -285,6 +286,7 @@ export class Config {
private readonly mcpServers: Record<string, MCPServerConfig> | undefined; private readonly mcpServers: Record<string, MCPServerConfig> | undefined;
private userMemory: string; private userMemory: string;
private geminiMdFileCount: number; private geminiMdFileCount: number;
private geminiMdFilePaths: string[];
private approvalMode: ApprovalMode; private approvalMode: ApprovalMode;
private readonly showMemoryUsage: boolean; private readonly showMemoryUsage: boolean;
private readonly accessibility: AccessibilitySettings; private readonly accessibility: AccessibilitySettings;
@@ -375,6 +377,7 @@ export class Config {
this.mcpServers = params.mcpServers; this.mcpServers = params.mcpServers;
this.userMemory = params.userMemory ?? ''; this.userMemory = params.userMemory ?? '';
this.geminiMdFileCount = params.geminiMdFileCount ?? 0; this.geminiMdFileCount = params.geminiMdFileCount ?? 0;
this.geminiMdFilePaths = params.geminiMdFilePaths ?? [];
this.approvalMode = params.approvalMode ?? ApprovalMode.DEFAULT; this.approvalMode = params.approvalMode ?? ApprovalMode.DEFAULT;
this.showMemoryUsage = params.showMemoryUsage ?? false; this.showMemoryUsage = params.showMemoryUsage ?? false;
this.accessibility = params.accessibility ?? {}; this.accessibility = params.accessibility ?? {};
@@ -698,6 +701,14 @@ export class Config {
this.geminiMdFileCount = count; this.geminiMdFileCount = count;
} }
getGeminiMdFilePaths(): string[] {
return this.geminiMdFilePaths;
}
setGeminiMdFilePaths(paths: string[]): void {
this.geminiMdFilePaths = paths;
}
getApprovalMode(): ApprovalMode { getApprovalMode(): ApprovalMode {
return this.approvalMode; return this.approvalMode;
} }
+112 -30
View File
@@ -82,7 +82,7 @@ describe('loadServerHierarchicalMemory', () => {
path.join(cwd, DEFAULT_CONTEXT_FILENAME), path.join(cwd, DEFAULT_CONTEXT_FILENAME),
'Src directory memory', 'Src directory memory',
); );
const { fileCount } = await loadServerHierarchicalMemory( const result = await loadServerHierarchicalMemory(
cwd, cwd,
[], [],
false, false,
@@ -91,32 +91,38 @@ describe('loadServerHierarchicalMemory', () => {
false, // untrusted false, // untrusted
); );
expect(fileCount).toEqual(0); expect(result).toEqual({
memoryContent: '',
fileCount: 0,
filePaths: [],
});
}); });
it('loads context from outside the untrusted workspace', async () => { it('loads context from outside the untrusted workspace', async () => {
await createTestFile( await createTestFile(
path.join(projectRoot, DEFAULT_CONTEXT_FILENAME), path.join(projectRoot, DEFAULT_CONTEXT_FILENAME),
'Project root memory', 'Project root memory', // Untrusted
); // Untrusted );
await createTestFile( await createTestFile(
path.join(cwd, DEFAULT_CONTEXT_FILENAME), path.join(cwd, DEFAULT_CONTEXT_FILENAME),
'Src directory memory', 'Src directory memory', // Untrusted
); // Untrusted );
const filepath = path.join(homedir, GEMINI_DIR, DEFAULT_CONTEXT_FILENAME); const filepath = path.join(homedir, GEMINI_DIR, DEFAULT_CONTEXT_FILENAME);
await createTestFile(filepath, 'default context content'); // In user home dir (outside untrusted space). await createTestFile(filepath, 'default context content'); // In user home dir (outside untrusted space).
const { fileCount, memoryContent } = await loadServerHierarchicalMemory( const { fileCount, memoryContent, filePaths } =
cwd, await loadServerHierarchicalMemory(
[], cwd,
false, [],
new FileDiscoveryService(projectRoot), false,
[], new FileDiscoveryService(projectRoot),
false, // untrusted [],
); false, // untrusted
);
expect(fileCount).toEqual(1); expect(fileCount).toEqual(1);
expect(memoryContent).toContain(path.relative(cwd, filepath).toString()); expect(memoryContent).toContain(path.relative(cwd, filepath).toString());
expect(filePaths).toEqual([filepath]);
}); });
}); });
@@ -133,6 +139,7 @@ describe('loadServerHierarchicalMemory', () => {
expect(result).toEqual({ expect(result).toEqual({
memoryContent: '', memoryContent: '',
fileCount: 0, fileCount: 0,
filePaths: [],
}); });
}); });
@@ -152,8 +159,11 @@ describe('loadServerHierarchicalMemory', () => {
); );
expect(result).toEqual({ 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, fileCount: 1,
filePaths: [defaultContextFile],
}); });
}); });
@@ -176,8 +186,11 @@ describe('loadServerHierarchicalMemory', () => {
); );
expect(result).toEqual({ 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, fileCount: 1,
filePaths: [customContextFile],
}); });
}); });
@@ -204,8 +217,15 @@ describe('loadServerHierarchicalMemory', () => {
); );
expect(result).toEqual({ 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, fileCount: 2,
filePaths: [projectContextFile, cwdContextFile],
}); });
}); });
@@ -213,11 +233,14 @@ describe('loadServerHierarchicalMemory', () => {
const customFilename = 'LOCAL_CONTEXT.md'; const customFilename = 'LOCAL_CONTEXT.md';
setGeminiMdFilename(customFilename); setGeminiMdFilename(customFilename);
await createTestFile( const subdirCustomFile = await createTestFile(
path.join(cwd, 'subdir', customFilename), path.join(cwd, 'subdir', customFilename),
'Subdir custom memory', '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( const result = await loadServerHierarchicalMemory(
cwd, cwd,
@@ -229,8 +252,15 @@ describe('loadServerHierarchicalMemory', () => {
); );
expect(result).toEqual({ 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, fileCount: 2,
filePaths: [cwdCustomFile, subdirCustomFile],
}); });
}); });
@@ -254,17 +284,24 @@ describe('loadServerHierarchicalMemory', () => {
); );
expect(result).toEqual({ 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, fileCount: 2,
filePaths: [projectRootGeminiFile, srcGeminiFile],
}); });
}); });
it('should load ORIGINAL_GEMINI_MD_FILENAME files by downward traversal from CWD', async () => { 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), path.join(cwd, 'subdir', DEFAULT_CONTEXT_FILENAME),
'Subdir memory', 'Subdir memory',
); );
await createTestFile( const cwdGeminiFile = await createTestFile(
path.join(cwd, DEFAULT_CONTEXT_FILENAME), path.join(cwd, DEFAULT_CONTEXT_FILENAME),
'CWD memory', 'CWD memory',
); );
@@ -279,8 +316,15 @@ describe('loadServerHierarchicalMemory', () => {
); );
expect(result).toEqual({ 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, fileCount: 2,
filePaths: [cwdGeminiFile, subDirGeminiFile],
}); });
}); });
@@ -316,8 +360,33 @@ describe('loadServerHierarchicalMemory', () => {
); );
expect(result).toEqual({ 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, fileCount: 5,
filePaths: [
defaultContextFile,
rootGeminiFile,
projectRootGeminiFile,
cwdGeminiFile,
subDirGeminiFile,
],
}); });
}); });
@@ -350,8 +419,11 @@ describe('loadServerHierarchicalMemory', () => {
); );
expect(result).toEqual({ 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, fileCount: 1,
filePaths: [regularSubDirGeminiFile],
}); });
}); });
@@ -401,6 +473,7 @@ describe('loadServerHierarchicalMemory', () => {
expect(result).toEqual({ expect(result).toEqual({
memoryContent: '', memoryContent: '',
fileCount: 0, fileCount: 0,
filePaths: [],
}); });
}); });
@@ -420,8 +493,11 @@ describe('loadServerHierarchicalMemory', () => {
); );
expect(result).toEqual({ 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, fileCount: 1,
filePaths: [extensionFilePath],
}); });
}); });
@@ -444,8 +520,11 @@ describe('loadServerHierarchicalMemory', () => {
); );
expect(result).toEqual({ 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, fileCount: 1,
filePaths: [includedFile],
}); });
}); });
@@ -477,6 +556,8 @@ describe('loadServerHierarchicalMemory', () => {
// Should have loaded all files // Should have loaded all files
expect(result.fileCount).toBe(numDirs); expect(result.fileCount).toBe(numDirs);
expect(result.filePaths.length).toBe(numDirs);
expect(result.filePaths.sort()).toEqual(createdFiles.sort());
// Content should include all project contents // Content should include all project contents
for (let i = 0; i < numDirs; i++) { for (let i = 0; i < numDirs; i++) {
@@ -489,11 +570,11 @@ describe('loadServerHierarchicalMemory', () => {
const parentDir = await createEmptyDir(path.join(testRootDir, 'parent')); const parentDir = await createEmptyDir(path.join(testRootDir, 'parent'));
const childDir = await createEmptyDir(path.join(parentDir, 'child')); const childDir = await createEmptyDir(path.join(parentDir, 'child'));
await createTestFile( const parentFile = await createTestFile(
path.join(parentDir, DEFAULT_CONTEXT_FILENAME), path.join(parentDir, DEFAULT_CONTEXT_FILENAME),
'Parent content', 'Parent content',
); );
await createTestFile( const childFile = await createTestFile(
path.join(childDir, DEFAULT_CONTEXT_FILENAME), path.join(childDir, DEFAULT_CONTEXT_FILENAME),
'Child content', 'Child content',
); );
@@ -512,6 +593,7 @@ describe('loadServerHierarchicalMemory', () => {
expect(result.fileCount).toBe(2); expect(result.fileCount).toBe(2);
expect(result.memoryContent).toContain('Parent content'); expect(result.memoryContent).toContain('Parent content');
expect(result.memoryContent).toContain('Child content'); expect(result.memoryContent).toContain('Child content');
expect(result.filePaths.sort()).toEqual([parentFile, childFile].sort());
// Check that files are not duplicated // Check that files are not duplicated
const parentOccurrences = ( const parentOccurrences = (
+3 -1
View File
@@ -331,6 +331,7 @@ function concatenateInstructions(
export interface LoadServerHierarchicalMemoryResponse { export interface LoadServerHierarchicalMemoryResponse {
memoryContent: string; memoryContent: string;
fileCount: number; fileCount: number;
filePaths: string[];
} }
/** /**
@@ -370,7 +371,7 @@ export async function loadServerHierarchicalMemory(
if (filePaths.length === 0) { if (filePaths.length === 0) {
if (debugMode) if (debugMode)
logger.debug('No GEMINI.md files found in hierarchy of the workspace.'); 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( const contentsWithPaths = await readGeminiMdFiles(
filePaths, filePaths,
@@ -393,5 +394,6 @@ export async function loadServerHierarchicalMemory(
return { return {
memoryContent: combinedInstructions, memoryContent: combinedInstructions,
fileCount: contentsWithPaths.length, fileCount: contentsWithPaths.length,
filePaths,
}; };
} }