summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/cli/src/ui/hooks/atCommandProcessor.test.ts49
-rw-r--r--packages/cli/src/ui/hooks/atCommandProcessor.ts12
-rw-r--r--packages/cli/src/ui/hooks/useCompletion.integration.test.ts35
-rw-r--r--packages/cli/src/ui/hooks/useCompletion.ts28
-rw-r--r--packages/core/src/services/fileDiscoveryService.ts18
5 files changed, 109 insertions, 33 deletions
diff --git a/packages/cli/src/ui/hooks/atCommandProcessor.test.ts b/packages/cli/src/ui/hooks/atCommandProcessor.test.ts
index 08631634..efe15c64 100644
--- a/packages/cli/src/ui/hooks/atCommandProcessor.test.ts
+++ b/packages/cli/src/ui/hooks/atCommandProcessor.test.ts
@@ -90,7 +90,7 @@ describe('handleAtCommand', () => {
// Mock FileDiscoveryService
mockFileDiscoveryService = {
initialize: vi.fn(),
- shouldGitIgnoreFile: vi.fn(() => false),
+ shouldIgnoreFile: vi.fn(() => false),
filterFiles: vi.fn((files) => files),
getIgnoreInfo: vi.fn(() => ({ gitIgnored: [] })),
isGitRepository: vi.fn(() => true),
@@ -171,7 +171,7 @@ describe('handleAtCommand', () => {
125,
);
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [filePath], respectGitIgnore: true },
+ { paths: [filePath], respect_git_ignore: true },
abortController.signal,
);
expect(mockAddItem).toHaveBeenCalledWith(
@@ -217,7 +217,7 @@ describe('handleAtCommand', () => {
126,
);
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [resolvedGlob], respectGitIgnore: true },
+ { paths: [resolvedGlob], respect_git_ignore: true },
abortController.signal,
);
expect(mockOnDebugMessage).toHaveBeenCalledWith(
@@ -318,7 +318,7 @@ describe('handleAtCommand', () => {
signal: abortController.signal,
});
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [unescapedPath], respectGitIgnore: true },
+ { paths: [unescapedPath], respect_git_ignore: true },
abortController.signal,
);
});
@@ -347,7 +347,7 @@ describe('handleAtCommand', () => {
signal: abortController.signal,
});
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [file1, file2], respectGitIgnore: true },
+ { paths: [file1, file2], respect_git_ignore: true },
abortController.signal,
);
expect(result.processedQuery).toEqual([
@@ -389,7 +389,7 @@ describe('handleAtCommand', () => {
signal: abortController.signal,
});
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [file1, file2], respectGitIgnore: true },
+ { paths: [file1, file2], respect_git_ignore: true },
abortController.signal,
);
expect(result.processedQuery).toEqual([
@@ -454,7 +454,7 @@ describe('handleAtCommand', () => {
});
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [file1, resolvedFile2], respectGitIgnore: true },
+ { paths: [file1, resolvedFile2], respect_git_ignore: true },
abortController.signal,
);
expect(result.processedQuery).toEqual([
@@ -556,7 +556,7 @@ describe('handleAtCommand', () => {
// If the mock is simpler, it might use queryPath if stat(queryPath) succeeds.
// The most important part is that *some* version of the path that leads to the content is used.
// Let's assume it uses the path from the query if stat confirms it exists (even if different case on disk)
- { paths: [queryPath], respectGitIgnore: true },
+ { paths: [queryPath], respect_git_ignore: true },
abortController.signal,
);
expect(mockAddItem).toHaveBeenCalledWith(
@@ -582,8 +582,9 @@ describe('handleAtCommand', () => {
const query = `@${gitIgnoredFile}`;
// Mock the file discovery service to report this file as git-ignored
- mockFileDiscoveryService.shouldGitIgnoreFile.mockImplementation(
- (path: string) => path === gitIgnoredFile,
+ mockFileDiscoveryService.shouldIgnoreFile.mockImplementation(
+ (path: string, options?: { respectGitIgnore?: boolean }) =>
+ path === gitIgnoredFile && options?.respectGitIgnore !== false,
);
const result = await handleAtCommand({
@@ -595,8 +596,9 @@ describe('handleAtCommand', () => {
signal: abortController.signal,
});
- expect(mockFileDiscoveryService.shouldGitIgnoreFile).toHaveBeenCalledWith(
+ expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith(
gitIgnoredFile,
+ { respectGitIgnore: true },
);
expect(mockOnDebugMessage).toHaveBeenCalledWith(
`Path ${gitIgnoredFile} is git-ignored and will be skipped.`,
@@ -614,7 +616,7 @@ describe('handleAtCommand', () => {
const query = `@${validFile}`;
const fileContent = 'console.log("Hello world");';
- mockFileDiscoveryService.shouldGitIgnoreFile.mockReturnValue(false);
+ mockFileDiscoveryService.shouldIgnoreFile.mockReturnValue(false);
mockReadManyFilesExecute.mockResolvedValue({
llmContent: [`--- ${validFile} ---\n\n${fileContent}\n\n`],
returnDisplay: 'Read 1 file.',
@@ -629,11 +631,12 @@ describe('handleAtCommand', () => {
signal: abortController.signal,
});
- expect(mockFileDiscoveryService.shouldGitIgnoreFile).toHaveBeenCalledWith(
+ expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith(
validFile,
+ { respectGitIgnore: true },
);
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [validFile], respectGitIgnore: true },
+ { paths: [validFile], respect_git_ignore: true },
abortController.signal,
);
expect(result.processedQuery).toEqual([
@@ -652,8 +655,9 @@ describe('handleAtCommand', () => {
const query = `@${validFile} @${gitIgnoredFile}`;
const fileContent = '# Project README';
- mockFileDiscoveryService.shouldGitIgnoreFile.mockImplementation(
- (path: string) => path === gitIgnoredFile,
+ mockFileDiscoveryService.shouldIgnoreFile.mockImplementation(
+ (path: string, options?: { respectGitIgnore?: boolean }) =>
+ path === gitIgnoredFile && options?.respectGitIgnore !== false,
);
mockReadManyFilesExecute.mockResolvedValue({
llmContent: [`--- ${validFile} ---\n\n${fileContent}\n\n`],
@@ -669,11 +673,13 @@ describe('handleAtCommand', () => {
signal: abortController.signal,
});
- expect(mockFileDiscoveryService.shouldGitIgnoreFile).toHaveBeenCalledWith(
+ expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith(
validFile,
+ { respectGitIgnore: true },
);
- expect(mockFileDiscoveryService.shouldGitIgnoreFile).toHaveBeenCalledWith(
+ expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith(
gitIgnoredFile,
+ { respectGitIgnore: true },
);
expect(mockOnDebugMessage).toHaveBeenCalledWith(
`Path ${gitIgnoredFile} is git-ignored and will be skipped.`,
@@ -682,7 +688,7 @@ describe('handleAtCommand', () => {
'Ignored 1 git-ignored files: .env',
);
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [validFile], respectGitIgnore: true },
+ { paths: [validFile], respect_git_ignore: true },
abortController.signal,
);
expect(result.processedQuery).toEqual([
@@ -699,7 +705,7 @@ describe('handleAtCommand', () => {
const gitFile = '.git/config';
const query = `@${gitFile}`;
- mockFileDiscoveryService.shouldGitIgnoreFile.mockReturnValue(true);
+ mockFileDiscoveryService.shouldIgnoreFile.mockReturnValue(true);
const result = await handleAtCommand({
query,
@@ -710,8 +716,9 @@ describe('handleAtCommand', () => {
signal: abortController.signal,
});
- expect(mockFileDiscoveryService.shouldGitIgnoreFile).toHaveBeenCalledWith(
+ expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith(
gitFile,
+ { respectGitIgnore: true },
);
expect(mockOnDebugMessage).toHaveBeenCalledWith(
`Path ${gitFile} is git-ignored and will be skipped.`,
diff --git a/packages/cli/src/ui/hooks/atCommandProcessor.ts b/packages/cli/src/ui/hooks/atCommandProcessor.ts
index 80393ef2..7fe68f10 100644
--- a/packages/cli/src/ui/hooks/atCommandProcessor.ts
+++ b/packages/cli/src/ui/hooks/atCommandProcessor.ts
@@ -181,12 +181,10 @@ export async function handleAtCommand({
return { processedQuery: null, shouldProceed: false };
}
- // Check if path should be ignored by git
- if (fileDiscovery.shouldGitIgnoreFile(pathName)) {
- const reason = respectGitIgnore
- ? 'git-ignored and will be skipped'
- : 'ignored by custom patterns';
- onDebugMessage(`Path ${pathName} is ${reason}.`);
+ // Check if path should be ignored based on filtering options
+ if (fileDiscovery.shouldIgnoreFile(pathName, { respectGitIgnore })) {
+ const reason = respectGitIgnore ? 'git-ignored' : 'custom-ignored';
+ onDebugMessage(`Path ${pathName} is ${reason} and will be skipped.`);
ignoredPaths.push(pathName);
continue;
}
@@ -349,7 +347,7 @@ export async function handleAtCommand({
const toolArgs = {
paths: pathSpecsToRead,
- respectGitIgnore, // Use configuration setting
+ respect_git_ignore: respectGitIgnore, // Use configuration setting
};
let toolCallDisplay: IndividualToolCallDisplay;
diff --git a/packages/cli/src/ui/hooks/useCompletion.integration.test.ts b/packages/cli/src/ui/hooks/useCompletion.integration.test.ts
index 6a178fb1..f5864a58 100644
--- a/packages/cli/src/ui/hooks/useCompletion.integration.test.ts
+++ b/packages/cli/src/ui/hooks/useCompletion.integration.test.ts
@@ -41,7 +41,10 @@ describe('useCompletion git-aware filtering integration', () => {
beforeEach(() => {
mockFileDiscoveryService = {
shouldGitIgnoreFile: vi.fn(),
+ shouldGeminiIgnoreFile: vi.fn(),
+ shouldIgnoreFile: vi.fn(),
filterFiles: vi.fn(),
+ getGeminiIgnorePatterns: vi.fn(() => []),
};
mockConfig = {
@@ -68,6 +71,14 @@ describe('useCompletion git-aware filtering integration', () => {
mockFileDiscoveryService.shouldGitIgnoreFile.mockImplementation(
(path: string) => path.includes('dist'),
);
+ mockFileDiscoveryService.shouldIgnoreFile.mockImplementation(
+ (path: string, options) => {
+ if (options?.respectGitIgnore !== false) {
+ return mockFileDiscoveryService.shouldGitIgnoreFile(path);
+ }
+ return false;
+ },
+ );
const { result } = renderHook(() =>
useCompletion('@d', testCwd, true, slashCommands, mockConfig),
@@ -102,6 +113,14 @@ describe('useCompletion git-aware filtering integration', () => {
path.includes('dist') ||
path.includes('.env'),
);
+ mockFileDiscoveryService.shouldIgnoreFile.mockImplementation(
+ (path: string, options) => {
+ if (options?.respectGitIgnore !== false) {
+ return mockFileDiscoveryService.shouldGitIgnoreFile(path);
+ }
+ return false;
+ },
+ );
const { result } = renderHook(() =>
useCompletion('@', testCwd, true, slashCommands, mockConfig),
@@ -153,6 +172,14 @@ describe('useCompletion git-aware filtering integration', () => {
mockFileDiscoveryService.shouldGitIgnoreFile.mockImplementation(
(path: string) => path.includes('node_modules') || path.includes('temp'),
);
+ mockFileDiscoveryService.shouldIgnoreFile.mockImplementation(
+ (path: string, options) => {
+ if (options?.respectGitIgnore !== false) {
+ return mockFileDiscoveryService.shouldGitIgnoreFile(path);
+ }
+ return false;
+ },
+ );
const { result } = renderHook(() =>
useCompletion('@t', testCwd, true, slashCommands, mockConfig),
@@ -261,6 +288,14 @@ describe('useCompletion git-aware filtering integration', () => {
mockFileDiscoveryService.shouldGitIgnoreFile.mockImplementation(
(path: string) => path.includes('.log'),
);
+ mockFileDiscoveryService.shouldIgnoreFile.mockImplementation(
+ (path: string, options) => {
+ if (options?.respectGitIgnore !== false) {
+ return mockFileDiscoveryService.shouldGitIgnoreFile(path);
+ }
+ return false;
+ },
+ );
const { result } = renderHook(() =>
useCompletion('@src/comp', testCwd, true, slashCommands, mockConfig),
diff --git a/packages/cli/src/ui/hooks/useCompletion.ts b/packages/cli/src/ui/hooks/useCompletion.ts
index 27a1c708..fd826c92 100644
--- a/packages/cli/src/ui/hooks/useCompletion.ts
+++ b/packages/cli/src/ui/hooks/useCompletion.ts
@@ -217,7 +217,11 @@ export function useCompletion(
const findFilesRecursively = async (
startDir: string,
searchPrefix: string,
- fileDiscovery: { shouldGitIgnoreFile: (path: string) => boolean } | null,
+ fileDiscovery: FileDiscoveryService | null,
+ filterOptions: {
+ respectGitIgnore?: boolean;
+ respectGeminiIgnore?: boolean;
+ },
currentRelativePath = '',
depth = 0,
maxDepth = 10, // Limit recursion depth
@@ -245,10 +249,10 @@ export function useCompletion(
continue;
}
- // Check if this entry should be ignored by git-aware filtering
+ // Check if this entry should be ignored by filtering options
if (
fileDiscovery &&
- fileDiscovery.shouldGitIgnoreFile(entryPathFromRoot)
+ fileDiscovery.shouldIgnoreFile(entryPathFromRoot, filterOptions)
) {
continue;
}
@@ -272,6 +276,7 @@ export function useCompletion(
path.join(startDir, entry.name),
searchPrefix, // Pass original searchPrefix for recursive calls
fileDiscovery,
+ filterOptions,
entryPathRelative,
depth + 1,
maxDepth,
@@ -290,6 +295,10 @@ export function useCompletion(
const findFilesWithGlob = async (
searchPrefix: string,
fileDiscoveryService: FileDiscoveryService,
+ filterOptions: {
+ respectGitIgnore?: boolean;
+ respectGeminiIgnore?: boolean;
+ },
maxResults = 50,
): Promise<Suggestion[]> => {
const globPattern = `**/${searchPrefix}*`;
@@ -309,7 +318,10 @@ export function useCompletion(
})
.filter((s) => {
if (fileDiscoveryService) {
- return !fileDiscoveryService.shouldGitIgnoreFile(s.label); // relative path
+ return !fileDiscoveryService.shouldIgnoreFile(
+ s.label,
+ filterOptions,
+ ); // relative path
}
return true;
})
@@ -325,6 +337,10 @@ export function useCompletion(
const fileDiscoveryService = config ? config.getFileService() : null;
const enableRecursiveSearch =
config?.getEnableRecursiveFileSearch() ?? true;
+ const filterOptions = {
+ respectGitIgnore: config?.getFileFilteringRespectGitIgnore() ?? true,
+ respectGeminiIgnore: true,
+ };
try {
// If there's no slash, or it's the root, do a recursive search from cwd
@@ -337,12 +353,14 @@ export function useCompletion(
fetchedSuggestions = await findFilesWithGlob(
prefix,
fileDiscoveryService,
+ filterOptions,
);
} else {
fetchedSuggestions = await findFilesRecursively(
cwd,
prefix,
fileDiscoveryService,
+ filterOptions,
);
}
} else {
@@ -367,7 +385,7 @@ export function useCompletion(
);
if (
fileDiscoveryService &&
- fileDiscoveryService.shouldGitIgnoreFile(relativePath)
+ fileDiscoveryService.shouldIgnoreFile(relativePath, filterOptions)
) {
continue;
}
diff --git a/packages/core/src/services/fileDiscoveryService.ts b/packages/core/src/services/fileDiscoveryService.ts
index 984f3f53..22092813 100644
--- a/packages/core/src/services/fileDiscoveryService.ts
+++ b/packages/core/src/services/fileDiscoveryService.ts
@@ -85,6 +85,24 @@ export class FileDiscoveryService {
}
/**
+ * Unified method to check if a file should be ignored based on filtering options
+ */
+ shouldIgnoreFile(
+ filePath: string,
+ options: FilterFilesOptions = {},
+ ): boolean {
+ const { respectGitIgnore = true, respectGeminiIgnore = true } = options;
+
+ if (respectGitIgnore && this.shouldGitIgnoreFile(filePath)) {
+ return true;
+ }
+ if (respectGeminiIgnore && this.shouldGeminiIgnoreFile(filePath)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Returns loaded patterns from .geminiignore
*/
getGeminiIgnorePatterns(): string[] {