mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
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:
@@ -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`**
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = (
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user