commit 905913b1c3d7c643d1fa3ccb98348740f270a16f
parent c37f38f829a9f12e5794db03ca31f1a827b51301
Author: Vineet Kumar <git@vineetk.net>
Date: Thu, 22 Oct 2026 16:14:04 +0200
improve grep tool so that model doesn't use grep via bash instead
Diffstat:
| M | llm-tools.el | | | 105 | +++++++++++++++++++++++++++++++++++++++++-------------------------------------- |
1 file changed, 55 insertions(+), 50 deletions(-)
diff --git a/llm-tools.el b/llm-tools.el
@@ -246,61 +246,66 @@ or editing them."
:confirm nil
:include t)
-;; grep (just grep (or git grep) :D)
-(defun llm-tools--grep (regexp &optional glob path)
+;; grep (just plain grep) :D)
+(defun llm-tools--grep (regexp &optional glob path ignore-case max-lines context exclude)
+ "Search REGEXP in PATH, optionally restricted by GLOB.
+IGNORE-CASE folds case. MAX-LINES caps output after EXCLUDE filtering.
+CONTEXT adds surrounding lines. EXCLUDE is a GNU extended regexp applied
+ to output lines before truncation. Search untracked files and treat binary
+files as text, making build logs searchable."
+ (dolist (value (list max-lines context))
+ (when (and value (not (and (integerp value) (>= value 0))))
+ (error "Limits must be non-negative integers")))
(with-temp-buffer
- (if (magit-gitdir)
- (let ((pathspec (cond
- ((and path glob)
- (concat (directory-file-name path) "/" glob))
- (glob glob)
- (path path)
- (t "."))))
- (call-process "git" nil t nil
- "grep" "-rnEH"
- regexp "--" pathspec))
- (apply #'call-process
- (append
- '("grep" nil t nil)
- (delq nil
- `("-rnEH"
- ,(if glob (concat "--include=" glob))
- ,regexp
- ,(or path "."))))))
+ (let ((status
+ (apply #'call-process "grep" nil t nil
+ (append '("-rnEH" "--binary-files=text" "--exclude-dir=.git")
+ (when ignore-case '("-i"))
+ (when glob (list (concat "--include=" glob)))
+ (when context (list "-C" (number-to-string context)))
+ (list "-e" regexp "--" (or path "."))))))
+ (unless (memq status '(0 1))
+ (error "grep failed: %s" (buffer-string))))
+ (when exclude
+ (let ((status (call-process-region
+ (point-min) (point-max) "grep" t t nil
+ "-aEv" (if ignore-case "-i" "--color=never")
+ "-e" exclude)))
+ (unless (memq status '(0 1))
+ (error "grep exclusion failed: %s" (buffer-string)))))
+ (when max-lines
+ (goto-char (point-min))
+ (forward-line max-lines)
+ (unless (eobp)
+ (delete-region (point) (point-max))
+ (insert "[Output truncated; increase max_lines or narrow the search.]\n")))
(buffer-string)))
(gptel-make-tool
:name "grep"
- :function (lambda (regexp &optional glob path)
- (llm-tools--grep regexp glob path))
- :description "\
-Recursively search for lines matching a regexp pattern in files under a
-given path. Uses `git grep` when inside a Git repository, otherwise falls
-back to GNU grep. Both are invoked with flags -rnEH (recursive, line
-numbers, extended regexp, always show filename). When using `git grep`,
-only tracked files are searched. Returns raw output with one match per
-line in the format FILE:LINE:CONTENT. Use this tool to find code
-patterns, locate function definitions, search for strings, or identify
-files containing specific text before reading them. For simple file name
-discovery, use `find_files` instead. For reading file contents, use
-`read_file`. The path argument is relative to the working directory;
-omit it to search from the current directory."
- :args '((:name "regexp"
- :type string
- :description "\
-Regular expression pattern to search for. Uses GNU grep extended syntax.")
- (:name "glob"
- :type string
- :description "\
-Optional restricts search to files matching the glob
-pattern. (e.g. \"*.el\", \"*.org\", \"*.c\")"
- :optional t)
- (:name "path"
- :type string
- :description "\
-Optional file or directory path to search within, relative to the
-working directory. Defaults to current directory if omitted."
- :optional t))
+ :function #'llm-tools--grep
+ :description "Search file contents, including untracked and ignored build logs.
+Use instead of shell grep, rg, or search pipelines. Supports GNU extended
+regexps, case-insensitive search, exclusions, context, and bounded output.
+Searches recursively, excluding .git directories; binary files are read as text.
+Returns FILE:LINE:CONTENT, with context lines when requested.
+Example: regexp=error|fatal|failed, path=build.log, ignore_case=true,
+exclude=warning, max_lines=40. For file names use find_files; for reading
+line ranges or the end of a log use read_file."
+ :args '((:name "regexp" :type string
+ :description "GNU extended regular expression (use | for alternatives).")
+ (:name "glob" :type string :optional t
+ :description "Restrict to file names matching this glob, e.g. *.el.")
+ (:name "path" :type string :optional t
+ :description "File or directory to search; defaults to current directory.")
+ (:name "ignore_case" :type boolean :optional t
+ :description "Case-insensitive matching (default false).")
+ (:name "max_lines" :type integer :optional t
+ :description "Maximum output lines after filtering, including context. Omit for all.")
+ (:name "context" :type integer :optional t
+ :description "Number of lines before and after matches (default 0).")
+ (:name "exclude" :type string :optional t
+ :description "GNU extended regexp: remove matching output lines before limiting. Uses ignore_case."))
:async nil
:confirm nil
:include t)