Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/core/prompts/tools/search-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,31 @@ import { ToolArgs } from "./types"

export function getSearchFilesDescription(args: ToolArgs): string {
return `## search_files
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context. By default, the tool respects .gitignore files (including nested ones) and excludes ignored paths from results.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we be more explicit here about the nested .gitignore behavior? Something like: "By default, the tool respects ALL .gitignore files in the directory tree (including nested ones), not just the root .gitignore." This would make the behavior crystal clear to users.

Parameters:
- path: (required) The path of the directory to search in (relative to the current workspace directory ${args.cwd}). This directory will be recursively searched.
- regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
- file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
- include_ignored: (optional) Set to "true" to include files that are ignored by .gitignore. Default is "false" (respects .gitignore).
Usage:
<search_files>
<path>Directory path here</path>
<regex>Your regex pattern here</regex>
<file_pattern>file pattern here (optional)</file_pattern>
<include_ignored>true or false (optional, default: false)</include_ignored>
</search_files>

Example: Requesting to search for all .ts files in the current directory
Example: Requesting to search for all .ts files in the current directory (respecting .gitignore)
<search_files>
<path>.</path>
<regex>.*</regex>
<file_pattern>*.ts</file_pattern>
</search_files>

Example: Requesting to search including ignored files
<search_files>
<path>.</path>
<regex>TODO</regex>
<include_ignored>true</include_ignored>
</search_files>`
}
6 changes: 6 additions & 0 deletions src/core/tools/searchFilesTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export async function searchFilesTool(
const relDirPath: string | undefined = block.params.path
const regex: string | undefined = block.params.regex
const filePattern: string | undefined = block.params.file_pattern
const includeIgnored: string | undefined = block.params.include_ignored

const absolutePath = relDirPath ? path.resolve(cline.cwd, relDirPath) : cline.cwd
const isOutsideWorkspace = isPathOutsideWorkspace(absolutePath)
Expand All @@ -27,6 +28,7 @@ export async function searchFilesTool(
path: getReadablePath(cline.cwd, removeClosingTag("path", relDirPath)),
regex: removeClosingTag("regex", regex),
filePattern: removeClosingTag("file_pattern", filePattern),
includeIgnored: removeClosingTag("include_ignored", includeIgnored),
isOutsideWorkspace,
}

Expand All @@ -52,12 +54,16 @@ export async function searchFilesTool(

cline.consecutiveMistakeCount = 0

// Parse includeIgnored as boolean (default to false)
const shouldIncludeIgnored = includeIgnored === "true"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional that we only check for the exact string "true"? What happens if someone passes "yes", "1", or "TRUE"? Should we consider adding validation or at least normalizing the input to handle common boolean representations?


const results = await regexSearchFiles(
cline.cwd,
absolutePath,
regex,
filePattern,
cline.rooIgnoreController,
shouldIncludeIgnored,
)

const completeMessage = JSON.stringify({ ...sharedMessageProps, content: results } satisfies ClineSayTool)
Expand Down
6 changes: 6 additions & 0 deletions src/services/ripgrep/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,9 @@ describe("Ripgrep line truncation", () => {
expect(truncated).toContain("[truncated...]")
})
})

// Note: Integration tests for gitignore handling would require actual file system setup
// The implementation has been updated to:
// 1. By default, ripgrep respects .gitignore files (no --no-ignore flag)
// 2. When includeIgnored is true, it adds --no-ignore flag to include ignored files
// This behavior is implemented in src/services/ripgrep/index.ts lines 154-159
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I appreciate my own comment about integration tests requiring file system setup, wouldn't it be better to actually write those tests? We could use a temporary directory structure to verify the gitignore behavior works correctly with nested .gitignore files.

10 changes: 9 additions & 1 deletion src/services/ripgrep/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export async function regexSearchFiles(
regex: string,
filePattern?: string,
rooIgnoreController?: RooIgnoreController,
includeIgnored: boolean = false,
): Promise<string> {
const vscodeAppRoot = vscode.env.appRoot
const rgPath = await getBinPath(vscodeAppRoot)
Expand All @@ -150,7 +151,14 @@ export async function regexSearchFiles(
throw new Error("Could not find ripgrep binary")
}

const args = ["--json", "-e", regex, "--glob", filePattern || "*", "--context", "1", "--no-messages", directoryPath]
const args = ["--json", "-e", regex, "--glob", filePattern || "*", "--context", "1", "--no-messages"]

// By default, ripgrep respects .gitignore files. Add --no-ignore to include ignored files
if (includeIgnored) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment here explaining the design decision would help future maintainers (including future me who will forget why I did this). Something like:

args.push("--no-ignore")
}

args.push(directoryPath)

let output: string
try {
Expand Down
1 change: 1 addition & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ export interface ClineSayTool {
content?: string
regex?: string
filePattern?: string
includeIgnored?: string
mode?: string
reason?: string
isOutsideWorkspace?: boolean
Expand Down
3 changes: 2 additions & 1 deletion src/shared/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const toolParamNames = [
"todos",
"prompt",
"image",
"include_ignored",
] as const

export type ToolParamName = (typeof toolParamNames)[number]
Expand Down Expand Up @@ -112,7 +113,7 @@ export interface CodebaseSearchToolUse extends ToolUse {

export interface SearchFilesToolUse extends ToolUse {
name: "search_files"
params: Partial<Pick<Record<ToolParamName, string>, "path" | "regex" | "file_pattern">>
params: Partial<Pick<Record<ToolParamName, string>, "path" | "regex" | "file_pattern" | "include_ignored">>
}

export interface ListFilesToolUse extends ToolUse {
Expand Down
Loading