feat: Multi-Directory Workspace Support (part1: add --include-directories option) (#4605)

Co-authored-by: Allen Hutchison <adh@google.com>
This commit is contained in:
Yuki Okita
2025-07-31 05:38:20 +09:00
committed by GitHub
parent 21965f986c
commit c1fe688956
44 changed files with 1913 additions and 253 deletions

View File

@@ -19,6 +19,18 @@ import {
import { GeminiClient } from '../core/client.js';
import { GitService } from '../services/gitService.js';
vi.mock('fs', async (importOriginal) => {
const actual = await importOriginal<typeof import('fs')>();
return {
...actual,
existsSync: vi.fn().mockReturnValue(true),
statSync: vi.fn().mockReturnValue({
isDirectory: vi.fn().mockReturnValue(true),
}),
realpathSync: vi.fn((path) => path),
};
});
// Mock dependencies that might be called during Config construction or createServerConfig
vi.mock('../tools/tool-registry', () => {
const ToolRegistryMock = vi.fn();
@@ -219,6 +231,23 @@ describe('Server Config (config.ts)', () => {
expect(config.getFileFilteringRespectGitIgnore()).toBe(false);
});
it('should initialize WorkspaceContext with includeDirectories', () => {
const includeDirectories = ['/path/to/dir1', '/path/to/dir2'];
const paramsWithIncludeDirs: ConfigParameters = {
...baseParams,
includeDirectories,
};
const config = new Config(paramsWithIncludeDirs);
const workspaceContext = config.getWorkspaceContext();
const directories = workspaceContext.getDirectories();
// Should include the target directory plus the included directories
expect(directories).toHaveLength(3);
expect(directories).toContain(path.resolve(baseParams.targetDir));
expect(directories).toContain('/path/to/dir1');
expect(directories).toContain('/path/to/dir2');
});
it('Config constructor should set telemetry to true when provided as true', () => {
const paramsWithTelemetry: ConfigParameters = {
...baseParams,

View File

@@ -50,6 +50,7 @@ import { IdeClient } from '../ide/ide-client.js';
// Re-export OAuth config type
export type { MCPOAuthConfig };
import { WorkspaceContext } from '../utils/workspaceContext.js';
export enum ApprovalMode {
DEFAULT = 'default',
@@ -172,6 +173,7 @@ export interface ConfigParameters {
proxy?: string;
cwd: string;
fileDiscoveryService?: FileDiscoveryService;
includeDirectories?: string[];
bugCommand?: BugCommandSettings;
model: string;
extensionContextFilePaths?: string[];
@@ -194,6 +196,7 @@ export class Config {
private readonly embeddingModel: string;
private readonly sandbox: SandboxConfig | undefined;
private readonly targetDir: string;
private readonly workspaceContext: WorkspaceContext;
private readonly debugMode: boolean;
private readonly question: string | undefined;
private readonly fullContext: boolean;
@@ -248,6 +251,10 @@ export class Config {
params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL;
this.sandbox = params.sandbox;
this.targetDir = path.resolve(params.targetDir);
this.workspaceContext = new WorkspaceContext(
this.targetDir,
params.includeDirectories ?? [],
);
this.debugMode = params.debugMode;
this.question = params.question;
this.fullContext = params.fullContext ?? false;
@@ -392,6 +399,10 @@ export class Config {
return this.targetDir;
}
getWorkspaceContext(): WorkspaceContext {
return this.workspaceContext;
}
getToolRegistry(): Promise<ToolRegistry> {
return Promise.resolve(this.toolRegistry);
}

View File

@@ -4,14 +4,21 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { Config } from './config.js';
import { DEFAULT_GEMINI_MODEL, DEFAULT_GEMINI_FLASH_MODEL } from './models.js';
import fs from 'node:fs';
vi.mock('node:fs');
describe('Flash Model Fallback Configuration', () => {
let config: Config;
beforeEach(() => {
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.statSync).mockReturnValue({
isDirectory: () => true,
} as fs.Stats);
config = new Config({
sessionId: 'test-session',
targetDir: '/test',