diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index 57d1a150f8..a94d1f0a28 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -763,6 +763,48 @@ describe('loadCliConfig', () => { }); }); + it('should add IDE workspace folders from GEMINI_CLI_IDE_WORKSPACE_PATH to include directories', async () => { + vi.stubEnv( + 'GEMINI_CLI_IDE_WORKSPACE_PATH', + ['/project/folderA', '/project/folderB'].join(path.delimiter), + ); + process.argv = ['node', 'script.js']; + const argv = await parseArguments(createTestMergedSettings()); + const settings = createTestMergedSettings(); + const config = await loadCliConfig(settings, 'test-session', argv); + const dirs = config.getPendingIncludeDirectories(); + expect(dirs).toContain('/project/folderA'); + expect(dirs).toContain('/project/folderB'); + }); + + it('should skip inaccessible workspace folders from GEMINI_CLI_IDE_WORKSPACE_PATH', async () => { + const resolveToRealPathSpy = vi + .spyOn(ServerConfig, 'resolveToRealPath') + .mockImplementation((p) => { + if (p.toString().includes('restricted')) { + const err = new Error('EACCES: permission denied'); + (err as NodeJS.ErrnoException).code = 'EACCES'; + throw err; + } + return p.toString(); + }); + vi.stubEnv( + 'GEMINI_CLI_IDE_WORKSPACE_PATH', + ['/project/folderA', '/nonexistent/restricted/folder'].join( + path.delimiter, + ), + ); + process.argv = ['node', 'script.js']; + const argv = await parseArguments(createTestMergedSettings()); + const settings = createTestMergedSettings(); + const config = await loadCliConfig(settings, 'test-session', argv); + const dirs = config.getPendingIncludeDirectories(); + expect(dirs).toContain('/project/folderA'); + expect(dirs).not.toContain('/nonexistent/restricted/folder'); + + resolveToRealPathSpy.mockRestore(); + }); + it('should use default fileFilter options when unconfigured', async () => { process.argv = ['node', 'script.js']; const argv = await parseArguments(createTestMergedSettings()); @@ -798,6 +840,7 @@ describe('loadCliConfig', () => { describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => { beforeEach(() => { vi.resetAllMocks(); + vi.stubEnv('GEMINI_CLI_IDE_WORKSPACE_PATH', ''); // Restore ExtensionManager mocks that were reset ExtensionManager.prototype.getExtensions = vi.fn().mockReturnValue([]); ExtensionManager.prototype.loadExtensions = vi @@ -809,6 +852,7 @@ describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => { }); afterEach(() => { + vi.unstubAllEnvs(); vi.restoreAllMocks(); }); diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index b4c8c9ca2e..010e6d8d99 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -475,10 +475,32 @@ export async function loadCliConfig( ...settings.context?.fileFiltering, }; + //changes the includeDirectories to be absolute paths based on the cwd, and also include any additional directories specified via CLI args const includeDirectories = (settings.context?.includeDirectories || []) .map(resolvePath) .concat((argv.includeDirectories || []).map(resolvePath)); + // When running inside VSCode with multiple workspace folders, + // automatically add the other folders as include directories + // so Gemini has context of all open folders, not just the cwd. + const ideWorkspacePath = process.env['GEMINI_CLI_IDE_WORKSPACE_PATH']; + if (ideWorkspacePath) { + const realCwd = resolveToRealPath(cwd); + const ideFolders = ideWorkspacePath.split(path.delimiter).filter((p) => { + const trimmedPath = p.trim(); + if (!trimmedPath) return false; + try { + return resolveToRealPath(trimmedPath) !== realCwd; + } catch (e) { + debugLogger.debug( + `[IDE] Skipping inaccessible workspace folder: ${trimmedPath} (${e instanceof Error ? e.message : String(e)})`, + ); + return false; + } + }); + includeDirectories.push(...ideFolders); + } + const extensionManager = new ExtensionManager({ settings, requestConsent: requestConsentNonInteractive,