llm-tools.el

Opinionated tools for use by a (local) LLM
Log | Files | Refs | LICENSE

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:
Mllm-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)