Update contextFileName to support an optional list of strings (#1001)

This commit is contained in:
Billy Biggs
2025-06-13 09:19:08 -07:00
committed by GitHub
parent 34e0d9c0b6
commit 2a1ad1f5d9
10 changed files with 176 additions and 117 deletions

View File

@@ -74,7 +74,7 @@ export interface ConfigParameters {
geminiMdFileCount?: number;
approvalMode?: ApprovalMode;
showMemoryUsage?: boolean;
contextFileName?: string;
contextFileName?: string | string[];
geminiIgnorePatterns?: string[];
accessibility?: AccessibilitySettings;
telemetry?: boolean;

View File

@@ -9,6 +9,7 @@ import {
MemoryTool,
setGeminiMdFilename,
getCurrentGeminiMdFilename,
getAllGeminiMdFilenames,
DEFAULT_CONTEXT_FILENAME,
} from './memoryTool.js';
import * as fs from 'fs/promises';
@@ -74,6 +75,13 @@ describe('MemoryTool', () => {
setGeminiMdFilename('');
expect(getCurrentGeminiMdFilename()).toBe(initialName);
});
it('should handle an array of filenames', () => {
const newNames = ['CUSTOM_CONTEXT.md', 'ANOTHER_CONTEXT.md'];
setGeminiMdFilename(newNames);
expect(getCurrentGeminiMdFilename()).toBe('CUSTOM_CONTEXT.md');
expect(getAllGeminiMdFilenames()).toEqual(newNames);
});
});
describe('performAddMemoryEntry (static method)', () => {

View File

@@ -51,18 +51,32 @@ export const MEMORY_SECTION_HEADER = '## Gemini Added Memories';
// This variable will hold the currently configured filename for GEMINI.md context files.
// It defaults to DEFAULT_CONTEXT_FILENAME but can be overridden by setGeminiMdFilename.
let currentGeminiMdFilename = DEFAULT_CONTEXT_FILENAME;
let currentGeminiMdFilename: string | string[] = DEFAULT_CONTEXT_FILENAME;
export function setGeminiMdFilename(newFilename: string): void {
if (newFilename && newFilename.trim() !== '') {
export function setGeminiMdFilename(newFilename: string | string[]): void {
if (Array.isArray(newFilename)) {
if (newFilename.length > 0) {
currentGeminiMdFilename = newFilename.map((name) => name.trim());
}
} else if (newFilename && newFilename.trim() !== '') {
currentGeminiMdFilename = newFilename.trim();
}
}
export function getCurrentGeminiMdFilename(): string {
if (Array.isArray(currentGeminiMdFilename)) {
return currentGeminiMdFilename[0];
}
return currentGeminiMdFilename;
}
export function getAllGeminiMdFilenames(): string[] {
if (Array.isArray(currentGeminiMdFilename)) {
return currentGeminiMdFilename;
}
return [currentGeminiMdFilename];
}
interface SaveMemoryParams {
fact: string;
}

View File

@@ -11,7 +11,7 @@ import { homedir } from 'os';
import { bfsFileSearch } from './bfsFileSearch.js';
import {
GEMINI_CONFIG_DIR,
getCurrentGeminiMdFilename,
getAllGeminiMdFilenames,
} from '../tools/memoryTool.js';
// Simple console logger, similar to the one previously in CLI's config.ts
@@ -83,131 +83,135 @@ async function getGeminiMdFilePathsInternal(
debugMode: boolean,
extensionContextFilePaths: string[] = [],
): Promise<string[]> {
const resolvedCwd = path.resolve(currentWorkingDirectory);
const resolvedHome = path.resolve(userHomePath);
const globalMemoryPath = path.join(
resolvedHome,
GEMINI_CONFIG_DIR,
getCurrentGeminiMdFilename(),
);
const paths: string[] = [];
const allPaths = new Set<string>();
const geminiMdFilenames = getAllGeminiMdFilenames();
if (debugMode)
logger.debug(
`Searching for ${getCurrentGeminiMdFilename()} starting from CWD: ${resolvedCwd}`,
for (const geminiMdFilename of geminiMdFilenames) {
const resolvedCwd = path.resolve(currentWorkingDirectory);
const resolvedHome = path.resolve(userHomePath);
const globalMemoryPath = path.join(
resolvedHome,
GEMINI_CONFIG_DIR,
geminiMdFilename,
);
if (debugMode) logger.debug(`User home directory: ${resolvedHome}`);
try {
await fs.access(globalMemoryPath, fsSync.constants.R_OK);
paths.push(globalMemoryPath);
if (debugMode)
logger.debug(
`Found readable global ${getCurrentGeminiMdFilename()}: ${globalMemoryPath}`,
`Searching for ${geminiMdFilename} starting from CWD: ${resolvedCwd}`,
);
} catch {
if (debugMode)
logger.debug(
`Global ${getCurrentGeminiMdFilename()} not found or not readable: ${globalMemoryPath}`,
);
}
if (debugMode) logger.debug(`User home directory: ${resolvedHome}`);
const projectRoot = await findProjectRoot(resolvedCwd);
if (debugMode)
logger.debug(`Determined project root: ${projectRoot ?? 'None'}`);
const upwardPaths: string[] = [];
let currentDir = resolvedCwd;
// Determine the directory that signifies the top of the project or user-specific space.
const ultimateStopDir = projectRoot
? path.dirname(projectRoot)
: path.dirname(resolvedHome);
while (currentDir && currentDir !== path.dirname(currentDir)) {
// Loop until filesystem root or currentDir is empty
if (debugMode) {
logger.debug(
`Checking for ${getCurrentGeminiMdFilename()} in (upward scan): ${currentDir}`,
);
try {
await fs.access(globalMemoryPath, fsSync.constants.R_OK);
allPaths.add(globalMemoryPath);
if (debugMode)
logger.debug(
`Found readable global ${geminiMdFilename}: ${globalMemoryPath}`,
);
} catch {
if (debugMode)
logger.debug(
`Global ${geminiMdFilename} not found or not readable: ${globalMemoryPath}`,
);
}
// Skip the global .gemini directory itself during upward scan from CWD,
// as global is handled separately and explicitly first.
if (currentDir === path.join(resolvedHome, GEMINI_CONFIG_DIR)) {
const projectRoot = await findProjectRoot(resolvedCwd);
if (debugMode)
logger.debug(`Determined project root: ${projectRoot ?? 'None'}`);
const upwardPaths: string[] = [];
let currentDir = resolvedCwd;
// Determine the directory that signifies the top of the project or user-specific space.
const ultimateStopDir = projectRoot
? path.dirname(projectRoot)
: path.dirname(resolvedHome);
while (currentDir && currentDir !== path.dirname(currentDir)) {
// Loop until filesystem root or currentDir is empty
if (debugMode) {
logger.debug(
`Upward scan reached global config dir path, stopping upward search here: ${currentDir}`,
`Checking for ${geminiMdFilename} in (upward scan): ${currentDir}`,
);
}
break;
}
const potentialPath = path.join(currentDir, getCurrentGeminiMdFilename());
try {
await fs.access(potentialPath, fsSync.constants.R_OK);
// Add to upwardPaths only if it's not the already added globalMemoryPath
if (potentialPath !== globalMemoryPath) {
upwardPaths.unshift(potentialPath);
// Skip the global .gemini directory itself during upward scan from CWD,
// as global is handled separately and explicitly first.
if (currentDir === path.join(resolvedHome, GEMINI_CONFIG_DIR)) {
if (debugMode) {
logger.debug(
`Found readable upward ${getCurrentGeminiMdFilename()}: ${potentialPath}`,
`Upward scan reached global config dir path, stopping upward search here: ${currentDir}`,
);
}
break;
}
const potentialPath = path.join(currentDir, geminiMdFilename);
try {
await fs.access(potentialPath, fsSync.constants.R_OK);
// Add to upwardPaths only if it's not the already added globalMemoryPath
if (potentialPath !== globalMemoryPath) {
upwardPaths.unshift(potentialPath);
if (debugMode) {
logger.debug(
`Found readable upward ${geminiMdFilename}: ${potentialPath}`,
);
}
}
} catch {
if (debugMode) {
logger.debug(
`Upward ${geminiMdFilename} not found or not readable in: ${currentDir}`,
);
}
}
} catch {
if (debugMode) {
logger.debug(
`Upward ${getCurrentGeminiMdFilename()} not found or not readable in: ${currentDir}`,
);
// Stop condition: if currentDir is the ultimateStopDir, break after this iteration.
if (currentDir === ultimateStopDir) {
if (debugMode)
logger.debug(
`Reached ultimate stop directory for upward scan: ${currentDir}`,
);
break;
}
currentDir = path.dirname(currentDir);
}
upwardPaths.forEach((p) => allPaths.add(p));
// Stop condition: if currentDir is the ultimateStopDir, break after this iteration.
if (currentDir === ultimateStopDir) {
if (debugMode)
logger.debug(
`Reached ultimate stop directory for upward scan: ${currentDir}`,
);
break;
}
currentDir = path.dirname(currentDir);
}
paths.push(...upwardPaths);
const downwardPaths = await bfsFileSearch(resolvedCwd, {
fileName: getCurrentGeminiMdFilename(),
maxDirs: MAX_DIRECTORIES_TO_SCAN_FOR_MEMORY,
debug: debugMode,
respectGitIgnore: true,
projectRoot: projectRoot || resolvedCwd,
});
downwardPaths.sort(); // Sort for consistent ordering, though hierarchy might be more complex
if (debugMode && downwardPaths.length > 0)
logger.debug(
`Found downward ${getCurrentGeminiMdFilename()} files (sorted): ${JSON.stringify(
downwardPaths,
)}`,
);
// Add downward paths only if they haven't been included already (e.g. from upward scan)
for (const dPath of downwardPaths) {
if (!paths.includes(dPath)) {
paths.push(dPath);
const downwardPaths = await bfsFileSearch(resolvedCwd, {
fileName: geminiMdFilename,
maxDirs: MAX_DIRECTORIES_TO_SCAN_FOR_MEMORY,
debug: debugMode,
respectGitIgnore: true,
projectRoot: projectRoot || resolvedCwd,
});
downwardPaths.sort(); // Sort for consistent ordering, though hierarchy might be more complex
if (debugMode && downwardPaths.length > 0)
logger.debug(
`Found downward ${geminiMdFilename} files (sorted): ${JSON.stringify(
downwardPaths,
)}`,
);
// Add downward paths only if they haven't been included already (e.g. from upward scan)
for (const dPath of downwardPaths) {
allPaths.add(dPath);
}
}
// Add extension context file paths
for (const extensionPath of extensionContextFilePaths) {
if (!paths.includes(extensionPath)) {
paths.push(extensionPath);
}
allPaths.add(extensionPath);
}
const finalPaths = Array.from(allPaths);
if (debugMode)
logger.debug(
`Final ordered ${getCurrentGeminiMdFilename()} paths to read: ${JSON.stringify(paths)}`,
`Final ordered ${getAllGeminiMdFilenames()} paths to read: ${JSON.stringify(
finalPaths,
)}`,
);
return paths;
return finalPaths;
}
async function readGeminiMdFiles(
@@ -228,7 +232,7 @@ async function readGeminiMdFiles(
if (!isTestEnv) {
const message = error instanceof Error ? error.message : String(error);
logger.warn(
`Warning: Could not read ${getCurrentGeminiMdFilename()} file at ${filePath}. Error: ${message}`,
`Warning: Could not read ${getAllGeminiMdFilenames()} file at ${filePath}. Error: ${message}`,
);
}
results.push({ filePath, content: null }); // Still include it with null content