diff options
| author | Adam Malczewski <[email protected]> | 2026-03-31 23:10:45 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-03-31 23:10:45 +0900 |
| commit | 57c56daf5906442dacc15951c9b3405f89309839 (patch) | |
| tree | e76890119c0e47acb48f8585222b7a2f5e22df56 /lib/dispatch/tool/files/search_files.rb | |
| parent | 25488d32336e05b69a41391cc7b5153478d3cc8a (diff) | |
| download | dispatch-tool-files-dev.tar.gz dispatch-tool-files-dev.zip | |
impdev
Diffstat (limited to 'lib/dispatch/tool/files/search_files.rb')
| -rw-r--r-- | lib/dispatch/tool/files/search_files.rb | 89 |
1 files changed, 89 insertions, 0 deletions
diff --git a/lib/dispatch/tool/files/search_files.rb b/lib/dispatch/tool/files/search_files.rb new file mode 100644 index 0000000..b0ccc9e --- /dev/null +++ b/lib/dispatch/tool/files/search_files.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +module Dispatch + module Tool + module Files + SEARCH_FILES = Dispatch::Tools::Definition.new( + name: "search_files", + description: "Search for text in files. Returns matching lines with file paths and line numbers.", + parameters: { + type: "object", + properties: { + query: { + type: "string", + description: "The text or regex pattern to search for." + }, + path: { + type: "string", + description: "Directory to search within, relative to the worktree root. Defaults to root." + }, + pattern: { + type: "string", + description: "Glob pattern to filter which files to search (e.g. '**/*.rb')." + }, + is_regex: { + type: "boolean", + description: "Whether the query is a regular expression. Defaults to false." + } + }, + required: %w[query], + additionalProperties: false + } + ) do |params, context| + worktree_path = context[:worktree_path] + query = params[:query] + path = params.fetch(:path, ".") + file_pattern = params[:pattern] + is_regex = params.fetch(:is_regex, false) + + resolved = begin + Sandbox.resolve_path(path, worktree_path:) + rescue SandboxError => e + next Dispatch::Tools::Result.failure(error: e.message) + end + + regex = if is_regex + begin + Regexp.new(query) + rescue RegexpError => e + next Dispatch::Tools::Result.failure(error: "Invalid regex: #{e.message}") + end + else + Regexp.new(Regexp.escape(query)) + end + + # Build file list + glob = if file_pattern + File.join(resolved, file_pattern) + else + File.join(resolved, "**", "*") + end + + files = Dir.glob(glob).select { |f| File.file?(f) }.sort + + worktree_real = File.realpath(worktree_path) + max_results = 100 + matches = [] + + files.each do |file_path| + # Skip binary files + sample = File.binread(file_path, 8192) + next if sample&.include?("\x00") + + relative = file_path.delete_prefix("#{worktree_real}/") + + File.readlines(file_path).each_with_index do |line, idx| + if regex.match?(line) + matches << "#{relative}:#{idx + 1}: #{line.chomp}" + break if matches.length >= max_results + end + end + + break if matches.length >= max_results + end + + Dispatch::Tools::Result.success(output: matches.join("\n")) + end + end + end +end |
