summaryrefslogtreecommitdiff
path: root/packages/core/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/core/src')
-rw-r--r--packages/core/src/config/config.ts8
-rw-r--r--packages/core/src/utils/memoryDiscovery.test.ts102
-rw-r--r--packages/core/src/utils/memoryDiscovery.ts39
-rw-r--r--packages/core/src/utils/workspaceContext.ts43
4 files changed, 130 insertions, 62 deletions
diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts
index 3f5c11a0..22996f3e 100644
--- a/packages/core/src/config/config.ts
+++ b/packages/core/src/config/config.ts
@@ -188,6 +188,7 @@ export interface ConfigParameters {
ideModeFeature?: boolean;
ideMode?: boolean;
ideClient: IdeClient;
+ loadMemoryFromIncludeDirectories?: boolean;
}
export class Config {
@@ -247,6 +248,7 @@ export class Config {
| Record<string, SummarizeToolOutputSettings>
| undefined;
private readonly experimentalAcp: boolean = false;
+ private readonly loadMemoryFromIncludeDirectories: boolean = false;
constructor(params: ConfigParameters) {
this.sessionId = params.sessionId;
@@ -304,6 +306,8 @@ export class Config {
this.ideModeFeature = params.ideModeFeature ?? false;
this.ideMode = params.ideMode ?? false;
this.ideClient = params.ideClient;
+ this.loadMemoryFromIncludeDirectories =
+ params.loadMemoryFromIncludeDirectories ?? false;
if (params.contextFileName) {
setGeminiMdFilename(params.contextFileName);
@@ -366,6 +370,10 @@ export class Config {
return this.sessionId;
}
+ shouldLoadMemoryFromIncludeDirectories(): boolean {
+ return this.loadMemoryFromIncludeDirectories;
+ }
+
getContentGeneratorConfig(): ContentGeneratorConfig {
return this.contentGeneratorConfig;
}
diff --git a/packages/core/src/utils/memoryDiscovery.test.ts b/packages/core/src/utils/memoryDiscovery.test.ts
index 8c7a294d..6c229dbb 100644
--- a/packages/core/src/utils/memoryDiscovery.test.ts
+++ b/packages/core/src/utils/memoryDiscovery.test.ts
@@ -67,6 +67,7 @@ describe('loadServerHierarchicalMemory', () => {
it('should return empty memory and count if no context files are found', async () => {
const result = await loadServerHierarchicalMemory(
cwd,
+ [],
false,
new FileDiscoveryService(projectRoot),
);
@@ -85,14 +86,13 @@ describe('loadServerHierarchicalMemory', () => {
const result = await loadServerHierarchicalMemory(
cwd,
+ [],
false,
new FileDiscoveryService(projectRoot),
);
expect(result).toEqual({
- memoryContent: `--- Context from: ${path.relative(cwd, defaultContextFile)} ---
-default context content
---- End of Context from: ${path.relative(cwd, defaultContextFile)} ---`,
+ memoryContent: `--- Context from: ${path.relative(cwd, defaultContextFile)} ---\ndefault context content\n--- End of Context from: ${path.relative(cwd, defaultContextFile)} ---`,
fileCount: 1,
});
});
@@ -108,14 +108,13 @@ default context content
const result = await loadServerHierarchicalMemory(
cwd,
+ [],
false,
new FileDiscoveryService(projectRoot),
);
expect(result).toEqual({
- memoryContent: `--- Context from: ${path.relative(cwd, customContextFile)} ---
-custom context content
---- End of Context from: ${path.relative(cwd, customContextFile)} ---`,
+ memoryContent: `--- Context from: ${path.relative(cwd, customContextFile)} ---\ncustom context content\n--- End of Context from: ${path.relative(cwd, customContextFile)} ---`,
fileCount: 1,
});
});
@@ -135,18 +134,13 @@ custom context content
const result = await loadServerHierarchicalMemory(
cwd,
+ [],
false,
new FileDiscoveryService(projectRoot),
);
expect(result).toEqual({
- 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)} ---`,
+ 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)} ---`,
fileCount: 2,
});
});
@@ -163,18 +157,13 @@ cwd context content
const result = await loadServerHierarchicalMemory(
cwd,
+ [],
false,
new FileDiscoveryService(projectRoot),
);
expect(result).toEqual({
- 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)} ---`,
+ 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)} ---`,
fileCount: 2,
});
});
@@ -191,18 +180,13 @@ Subdir custom memory
const result = await loadServerHierarchicalMemory(
cwd,
+ [],
false,
new FileDiscoveryService(projectRoot),
);
expect(result).toEqual({
- 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)} ---`,
+ 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)} ---`,
fileCount: 2,
});
});
@@ -219,18 +203,13 @@ Src directory memory
const result = await loadServerHierarchicalMemory(
cwd,
+ [],
false,
new FileDiscoveryService(projectRoot),
);
expect(result).toEqual({
- 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)} ---`,
+ 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)} ---`,
fileCount: 2,
});
});
@@ -259,30 +238,13 @@ Subdir memory
const result = await loadServerHierarchicalMemory(
cwd,
+ [],
false,
new FileDiscoveryService(projectRoot),
);
expect(result).toEqual({
- 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)} ---`,
+ 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)} ---`,
fileCount: 5,
});
});
@@ -302,6 +264,7 @@ Subdir memory
const result = await loadServerHierarchicalMemory(
cwd,
+ [],
false,
new FileDiscoveryService(projectRoot),
[],
@@ -314,9 +277,7 @@ Subdir memory
);
expect(result).toEqual({
- memoryContent: `--- Context from: ${path.relative(cwd, regularSubDirGeminiFile)} ---
-My code memory
---- End of Context from: ${path.relative(cwd, regularSubDirGeminiFile)} ---`,
+ memoryContent: `--- Context from: ${path.relative(cwd, regularSubDirGeminiFile)} ---\nMy code memory\n--- End of Context from: ${path.relative(cwd, regularSubDirGeminiFile)} ---`,
fileCount: 1,
});
});
@@ -333,6 +294,7 @@ My code memory
// Pass the custom limit directly to the function
await loadServerHierarchicalMemory(
cwd,
+ [],
true,
new FileDiscoveryService(projectRoot),
[],
@@ -353,6 +315,7 @@ My code memory
const result = await loadServerHierarchicalMemory(
cwd,
+ [],
false,
new FileDiscoveryService(projectRoot),
);
@@ -371,15 +334,36 @@ My code memory
const result = await loadServerHierarchicalMemory(
cwd,
+ [],
false,
new FileDiscoveryService(projectRoot),
[extensionFilePath],
);
expect(result).toEqual({
- memoryContent: `--- Context from: ${path.relative(cwd, extensionFilePath)} ---
-Extension memory content
---- End of Context from: ${path.relative(cwd, extensionFilePath)} ---`,
+ memoryContent: `--- Context from: ${path.relative(cwd, extensionFilePath)} ---\nExtension memory content\n--- End of Context from: ${path.relative(cwd, extensionFilePath)} ---`,
+ fileCount: 1,
+ });
+ });
+
+ it('should load memory from included directories', async () => {
+ const includedDir = await createEmptyDir(
+ path.join(testRootDir, 'included'),
+ );
+ const includedFile = await createTestFile(
+ path.join(includedDir, DEFAULT_CONTEXT_FILENAME),
+ 'included directory memory',
+ );
+
+ const result = await loadServerHierarchicalMemory(
+ cwd,
+ [includedDir],
+ false,
+ new FileDiscoveryService(projectRoot),
+ );
+
+ expect(result).toEqual({
+ memoryContent: `--- Context from: ${path.relative(cwd, includedFile)} ---\nincluded directory memory\n--- End of Context from: ${path.relative(cwd, includedFile)} ---`,
fileCount: 1,
});
});
diff --git a/packages/core/src/utils/memoryDiscovery.ts b/packages/core/src/utils/memoryDiscovery.ts
index 323b13c5..f53d27a9 100644
--- a/packages/core/src/utils/memoryDiscovery.ts
+++ b/packages/core/src/utils/memoryDiscovery.ts
@@ -83,6 +83,36 @@ async function findProjectRoot(startDir: string): Promise<string | null> {
async function getGeminiMdFilePathsInternal(
currentWorkingDirectory: string,
+ includeDirectoriesToReadGemini: readonly string[],
+ userHomePath: string,
+ debugMode: boolean,
+ fileService: FileDiscoveryService,
+ extensionContextFilePaths: string[] = [],
+ fileFilteringOptions: FileFilteringOptions,
+ maxDirs: number,
+): Promise<string[]> {
+ const dirs = new Set<string>([
+ ...includeDirectoriesToReadGemini,
+ currentWorkingDirectory,
+ ]);
+ const paths = [];
+ for (const dir of dirs) {
+ const pathsByDir = await getGeminiMdFilePathsInternalForEachDir(
+ dir,
+ userHomePath,
+ debugMode,
+ fileService,
+ extensionContextFilePaths,
+ fileFilteringOptions,
+ maxDirs,
+ );
+ paths.push(...pathsByDir);
+ }
+ return Array.from(new Set<string>(paths));
+}
+
+async function getGeminiMdFilePathsInternalForEachDir(
+ dir: string,
userHomePath: string,
debugMode: boolean,
fileService: FileDiscoveryService,
@@ -115,8 +145,8 @@ async function getGeminiMdFilePathsInternal(
// FIX: Only perform the workspace search (upward and downward scans)
// if a valid currentWorkingDirectory is provided.
- if (currentWorkingDirectory) {
- const resolvedCwd = path.resolve(currentWorkingDirectory);
+ if (dir) {
+ const resolvedCwd = path.resolve(dir);
if (debugMode)
logger.debug(
`Searching for ${geminiMdFilename} starting from CWD: ${resolvedCwd}`,
@@ -257,6 +287,7 @@ function concatenateInstructions(
*/
export async function loadServerHierarchicalMemory(
currentWorkingDirectory: string,
+ includeDirectoriesToReadGemini: readonly string[],
debugMode: boolean,
fileService: FileDiscoveryService,
extensionContextFilePaths: string[] = [],
@@ -274,6 +305,7 @@ export async function loadServerHierarchicalMemory(
const userHomePath = homedir();
const filePaths = await getGeminiMdFilePathsInternal(
currentWorkingDirectory,
+ includeDirectoriesToReadGemini,
userHomePath,
debugMode,
fileService,
@@ -282,7 +314,8 @@ export async function loadServerHierarchicalMemory(
maxDirs,
);
if (filePaths.length === 0) {
- if (debugMode) logger.debug('No GEMINI.md files found in hierarchy.');
+ if (debugMode)
+ logger.debug('No GEMINI.md files found in hierarchy of the workspace.');
return { memoryContent: '', fileCount: 0 };
}
const contentsWithPaths = await readGeminiMdFiles(
diff --git a/packages/core/src/utils/workspaceContext.ts b/packages/core/src/utils/workspaceContext.ts
index 16d1b4c9..efbc8a4c 100644
--- a/packages/core/src/utils/workspaceContext.ts
+++ b/packages/core/src/utils/workspaceContext.ts
@@ -15,6 +15,8 @@ import * as path from 'path';
export class WorkspaceContext {
private directories: Set<string>;
+ private initialDirectories: Set<string>;
+
/**
* Creates a new WorkspaceContext with the given initial directory and optional additional directories.
* @param initialDirectory The initial working directory (usually cwd)
@@ -22,11 +24,14 @@ export class WorkspaceContext {
*/
constructor(initialDirectory: string, additionalDirectories: string[] = []) {
this.directories = new Set<string>();
+ this.initialDirectories = new Set<string>();
this.addDirectoryInternal(initialDirectory);
+ this.addInitialDirectoryInternal(initialDirectory);
for (const dir of additionalDirectories) {
this.addDirectoryInternal(dir);
+ this.addInitialDirectoryInternal(dir);
}
}
@@ -69,6 +74,33 @@ export class WorkspaceContext {
this.directories.add(realPath);
}
+ private addInitialDirectoryInternal(
+ directory: string,
+ basePath: string = process.cwd(),
+ ): void {
+ const absolutePath = path.isAbsolute(directory)
+ ? directory
+ : path.resolve(basePath, directory);
+
+ if (!fs.existsSync(absolutePath)) {
+ throw new Error(`Directory does not exist: ${absolutePath}`);
+ }
+
+ const stats = fs.statSync(absolutePath);
+ if (!stats.isDirectory()) {
+ throw new Error(`Path is not a directory: ${absolutePath}`);
+ }
+
+ let realPath: string;
+ try {
+ realPath = fs.realpathSync(absolutePath);
+ } catch (_error) {
+ throw new Error(`Failed to resolve path: ${absolutePath}`);
+ }
+
+ this.initialDirectories.add(realPath);
+ }
+
/**
* Gets a copy of all workspace directories.
* @returns Array of absolute directory paths
@@ -77,6 +109,17 @@ export class WorkspaceContext {
return Array.from(this.directories);
}
+ getInitialDirectories(): readonly string[] {
+ return Array.from(this.initialDirectories);
+ }
+
+ setDirectories(directories: readonly string[]): void {
+ this.directories.clear();
+ for (const dir of directories) {
+ this.addDirectoryInternal(dir);
+ }
+ }
+
/**
* Checks if a given path is within any of the workspace directories.
* @param pathToCheck The path to validate