reuse filtering service in getFolderStructure (#1016)
This commit is contained in:
parent
1f63f3331f
commit
084b58a50e
|
@ -19,6 +19,7 @@ import { Config } from '../config/config.js';
|
||||||
import { Turn } from './turn.js';
|
import { Turn } from './turn.js';
|
||||||
import { getCoreSystemPrompt } from './prompts.js';
|
import { getCoreSystemPrompt } from './prompts.js';
|
||||||
import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
|
import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
|
||||||
|
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
||||||
|
|
||||||
// --- Mocks ---
|
// --- Mocks ---
|
||||||
const mockChatCreateFn = vi.fn();
|
const mockChatCreateFn = vi.fn();
|
||||||
|
@ -99,6 +100,7 @@ describe('Gemini Client (client.ts)', () => {
|
||||||
getFunctionDeclarations: vi.fn().mockReturnValue([]),
|
getFunctionDeclarations: vi.fn().mockReturnValue([]),
|
||||||
getTool: vi.fn().mockReturnValue(null),
|
getTool: vi.fn().mockReturnValue(null),
|
||||||
};
|
};
|
||||||
|
const fileService = new FileDiscoveryService('/test/dir');
|
||||||
const MockedConfig = vi.mocked(Config, true);
|
const MockedConfig = vi.mocked(Config, true);
|
||||||
MockedConfig.mockImplementation(() => {
|
MockedConfig.mockImplementation(() => {
|
||||||
const mock = {
|
const mock = {
|
||||||
|
@ -118,6 +120,7 @@ describe('Gemini Client (client.ts)', () => {
|
||||||
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||||
getProxy: vi.fn().mockReturnValue(undefined),
|
getProxy: vi.fn().mockReturnValue(undefined),
|
||||||
getWorkingDir: vi.fn().mockReturnValue('/test/dir'),
|
getWorkingDir: vi.fn().mockReturnValue('/test/dir'),
|
||||||
|
getFileService: vi.fn().mockReturnValue(fileService),
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return mock as any;
|
return mock as any;
|
||||||
|
|
|
@ -85,7 +85,9 @@ export class GeminiClient {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
});
|
});
|
||||||
const platform = process.platform;
|
const platform = process.platform;
|
||||||
const folderStructure = await getFolderStructure(cwd);
|
const folderStructure = await getFolderStructure(cwd, {
|
||||||
|
fileService: await this.config.getFileService(),
|
||||||
|
});
|
||||||
const context = `
|
const context = `
|
||||||
Okay, just setting up the context for our chat.
|
Okay, just setting up the context for our chat.
|
||||||
Today is ${today}.
|
Today is ${today}.
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { Dirent as FSDirent } from 'fs';
|
||||||
import * as nodePath from 'path';
|
import * as nodePath from 'path';
|
||||||
import { getFolderStructure } from './getFolderStructure.js';
|
import { getFolderStructure } from './getFolderStructure.js';
|
||||||
import * as gitUtils from './gitUtils.js';
|
import * as gitUtils from './gitUtils.js';
|
||||||
|
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
||||||
|
|
||||||
vi.mock('path', async (importOriginal) => {
|
vi.mock('path', async (importOriginal) => {
|
||||||
const original = (await importOriginal()) as typeof nodePath;
|
const original = (await importOriginal()) as typeof nodePath;
|
||||||
|
@ -319,8 +320,10 @@ describe('getFolderStructure gitignore', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore files and folders specified in .gitignore', async () => {
|
it('should ignore files and folders specified in .gitignore', async () => {
|
||||||
|
const fileService = new FileDiscoveryService('/test/project');
|
||||||
|
await fileService.initialize();
|
||||||
const structure = await getFolderStructure('/test/project', {
|
const structure = await getFolderStructure('/test/project', {
|
||||||
projectRoot: '/test/project',
|
fileService,
|
||||||
});
|
});
|
||||||
expect(structure).not.toContain('ignored.txt');
|
expect(structure).not.toContain('ignored.txt');
|
||||||
expect(structure).toContain('node_modules/...');
|
expect(structure).toContain('node_modules/...');
|
||||||
|
@ -328,9 +331,10 @@ describe('getFolderStructure gitignore', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not ignore files if respectGitIgnore is false', async () => {
|
it('should not ignore files if respectGitIgnore is false', async () => {
|
||||||
|
const fileService = new FileDiscoveryService('/test/project');
|
||||||
|
await fileService.initialize({ respectGitIgnore: false });
|
||||||
const structure = await getFolderStructure('/test/project', {
|
const structure = await getFolderStructure('/test/project', {
|
||||||
projectRoot: '/test/project',
|
fileService,
|
||||||
respectGitIgnore: false,
|
|
||||||
});
|
});
|
||||||
expect(structure).toContain('ignored.txt');
|
expect(structure).toContain('ignored.txt');
|
||||||
// node_modules is still ignored by default
|
// node_modules is still ignored by default
|
||||||
|
|
|
@ -8,8 +8,7 @@ import * as fs from 'fs/promises';
|
||||||
import { Dirent } from 'fs';
|
import { Dirent } from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { getErrorMessage, isNodeError } from './errors.js';
|
import { getErrorMessage, isNodeError } from './errors.js';
|
||||||
import { GitIgnoreParser, GitIgnoreFilter } from './gitIgnoreParser.js';
|
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
||||||
import { isGitRepository } from './gitUtils.js';
|
|
||||||
|
|
||||||
const MAX_ITEMS = 200;
|
const MAX_ITEMS = 200;
|
||||||
const TRUNCATION_INDICATOR = '...';
|
const TRUNCATION_INDICATOR = '...';
|
||||||
|
@ -25,18 +24,16 @@ interface FolderStructureOptions {
|
||||||
ignoredFolders?: Set<string>;
|
ignoredFolders?: Set<string>;
|
||||||
/** Optional regex to filter included files by name. */
|
/** Optional regex to filter included files by name. */
|
||||||
fileIncludePattern?: RegExp;
|
fileIncludePattern?: RegExp;
|
||||||
/** Whether to respect .gitignore patterns. Defaults to true. */
|
/** For filtering files. */
|
||||||
respectGitIgnore?: boolean;
|
fileService?: FileDiscoveryService;
|
||||||
/** The root of the project, used for gitignore resolution. */
|
|
||||||
projectRoot?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define a type for the merged options where fileIncludePattern remains optional
|
// Define a type for the merged options where fileIncludePattern remains optional
|
||||||
type MergedFolderStructureOptions = Required<
|
type MergedFolderStructureOptions = Required<
|
||||||
Omit<FolderStructureOptions, 'fileIncludePattern' | 'projectRoot'>
|
Omit<FolderStructureOptions, 'fileIncludePattern' | 'fileService'>
|
||||||
> & {
|
> & {
|
||||||
fileIncludePattern?: RegExp;
|
fileIncludePattern?: RegExp;
|
||||||
projectRoot?: string;
|
fileService?: FileDiscoveryService;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Represents the full, unfiltered information about a folder and its contents. */
|
/** Represents the full, unfiltered information about a folder and its contents. */
|
||||||
|
@ -59,7 +56,6 @@ interface FullFolderInfo {
|
||||||
async function readFullStructure(
|
async function readFullStructure(
|
||||||
rootPath: string,
|
rootPath: string,
|
||||||
options: MergedFolderStructureOptions,
|
options: MergedFolderStructureOptions,
|
||||||
gitIgnoreFilter: GitIgnoreFilter | null,
|
|
||||||
): Promise<FullFolderInfo | null> {
|
): Promise<FullFolderInfo | null> {
|
||||||
const rootName = path.basename(rootPath);
|
const rootName = path.basename(rootPath);
|
||||||
const rootNode: FullFolderInfo = {
|
const rootNode: FullFolderInfo = {
|
||||||
|
@ -128,8 +124,8 @@ async function readFullStructure(
|
||||||
}
|
}
|
||||||
const fileName = entry.name;
|
const fileName = entry.name;
|
||||||
const filePath = path.join(currentPath, fileName);
|
const filePath = path.join(currentPath, fileName);
|
||||||
if (gitIgnoreFilter) {
|
if (options.fileService) {
|
||||||
if (gitIgnoreFilter.isIgnored(filePath)) {
|
if (options.fileService.shouldIgnoreFile(filePath)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,8 +159,8 @@ async function readFullStructure(
|
||||||
const subFolderPath = path.join(currentPath, subFolderName);
|
const subFolderPath = path.join(currentPath, subFolderName);
|
||||||
|
|
||||||
let isIgnoredByGit = false;
|
let isIgnoredByGit = false;
|
||||||
if (gitIgnoreFilter) {
|
if (options?.fileService) {
|
||||||
if (gitIgnoreFilter.isIgnored(subFolderPath)) {
|
if (options.fileService.shouldIgnoreFile(subFolderPath)) {
|
||||||
isIgnoredByGit = true;
|
isIgnoredByGit = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -296,26 +292,12 @@ export async function getFolderStructure(
|
||||||
maxItems: options?.maxItems ?? MAX_ITEMS,
|
maxItems: options?.maxItems ?? MAX_ITEMS,
|
||||||
ignoredFolders: options?.ignoredFolders ?? DEFAULT_IGNORED_FOLDERS,
|
ignoredFolders: options?.ignoredFolders ?? DEFAULT_IGNORED_FOLDERS,
|
||||||
fileIncludePattern: options?.fileIncludePattern,
|
fileIncludePattern: options?.fileIncludePattern,
|
||||||
respectGitIgnore: options?.respectGitIgnore ?? true,
|
fileService: options?.fileService,
|
||||||
projectRoot: options?.projectRoot ?? resolvedPath,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let gitIgnoreFilter: GitIgnoreFilter | null = null;
|
|
||||||
if (mergedOptions.respectGitIgnore && mergedOptions.projectRoot) {
|
|
||||||
if (isGitRepository(mergedOptions.projectRoot)) {
|
|
||||||
const parser = new GitIgnoreParser(mergedOptions.projectRoot);
|
|
||||||
await parser.initialize();
|
|
||||||
gitIgnoreFilter = parser;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Read the structure using BFS, respecting maxItems
|
// 1. Read the structure using BFS, respecting maxItems
|
||||||
const structureRoot = await readFullStructure(
|
const structureRoot = await readFullStructure(resolvedPath, mergedOptions);
|
||||||
resolvedPath,
|
|
||||||
mergedOptions,
|
|
||||||
gitIgnoreFilter,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!structureRoot) {
|
if (!structureRoot) {
|
||||||
return `Error: Could not read directory "${resolvedPath}". Check path and permissions.`;
|
return `Error: Could not read directory "${resolvedPath}". Check path and permissions.`;
|
||||||
|
|
Loading…
Reference in New Issue