Skip to content

Commit 19690bb

Browse files
author
SqlRush
committed
Add Grep sort-files alias
1 parent f8db6a4 commit 19690bb

4 files changed

Lines changed: 41 additions & 5 deletions

File tree

docs/claude-code-go-rewrite-plan.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ test/parity/ # golden tests against TS/official behavior
232232
- 本轮补充:Grep 搜索现在支持 `no_ignore`/`noIgnore`/`no-ignore`/`--no-ignore`,可跳过 `.gitignore`/`.ignore` 规则,同时继续排除 VCS metadata 目录并保留 Read deny 额外 ignore 保护;`--no-ignore` 兼容 quoted boolean。
233233
- 本轮补充:Grep 的 `files_with_matches` 输出现在按官方行为使用文件修改时间倒序排序,mtime 相同再按路径排序;分页和 `head_limit` 会在排序后应用。
234234
- 本轮补充:Grep 结果排序现在支持 ripgrep 风格 `sort`/`--sort``sortr`/`--sortr` 参数,覆盖 `path`/`modified`/`none` 及常见别名;显式排序会作用于 files/content/count 输出,structured content 会回传实际 sort、reverse 和 explicit 状态。
235+
- 本轮补充:Grep 结果排序现在支持 ripgrep deprecated `sort_files`/`sortFiles`/`sort-files`/`--sort-files` aliases,统一映射到显式 `sort=path`,并兼容 quoted semantic boolean。
235236
- 本轮补充:Glob/Grep 搜索遍历现在会读取 permission context 中的 `Read(...)` deny 规则,并把对应 basename/path/directory pattern 作为额外 ignore rule,避免被禁止读取的文件出现在搜索结果中。
236237
- 本轮补充:Bash `grep`/`rg` read-only 分类现在会把 pattern-file 参数 `-f FILE``-fFILE``--file=FILE` 当作路径读取处理,缺值、绝对路径和 `..` 路径不再进入 read-only fast path。
237238
- 本轮补充:Bash/PowerShell read-only 分类会先校验 tokenizer 视角的语法完整性,未闭合 quote 或末尾 escape/line-continuation 不再进入只读 fast path。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ Covered behavior:
134134
- `Grep` content output now accepts `column`/`column_numbers`/`columnNumbers`/`column-number`/`--column`, emitting `path:line:column:text` for matching lines while preserving context-line formatting and structured column metadata.
135135
- `Grep` files-with-matches output now mirrors the official modified-time sort: newest files first, path tie-breaker, and pagination applied after sorting.
136136
- `Grep` now accepts ripgrep-style file listing through `files`/`--files` and `output_mode:"files"`, not requiring a pattern and listing paths after traversal, ignore, glob/iglob, type/type-not, hidden, and binary/text filters without reading file contents.
137-
- `Grep` now accepts ripgrep-style result ordering through `sort`/`--sort` and `sortr`/`--sortr`, covering path, modified-time, and none ordering for files/content/count modes with structured sort metadata.
137+
- `Grep` now accepts ripgrep-style result ordering through `sort`/`--sort`, `sortr`/`--sortr`, and deprecated `sort_files`/`--sort-files`, covering path, modified-time, and none ordering for files/content/count modes with structured sort metadata.
138138
- `Grep` now accepts ripgrep-style negative type filtering through `type_not`/`typeNot`/`type-not`/`--type-not`/`-T`, applying it after any positive `type` filter and preserving structured filter metadata.
139139
- `Grep` now accepts ripgrep-style case-insensitive glob filtering through `iglob`/`--iglob`, sharing positive and `!` negative glob rule semantics with `glob` while preserving structured filter metadata.
140140
- `Grep` glob filtering now accepts ripgrep-style negated `!pattern` rules, including combinations with positive patterns, comma/whitespace splitting, and brace-expanded globs.

internal/tools/file/search_tools.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ var allowedGrepInputKeys = map[string]struct{}{
4444
"no_context_separator": {}, "noContextSeparator": {}, "no-context-separator": {}, "--no-context-separator": {},
4545
"byte_offset": {}, "byteOffset": {}, "byte-offset": {}, "--byte-offset": {}, "-b": {},
4646
"hidden": {}, "--hidden": {}, "no_hidden": {}, "noHidden": {}, "no-hidden": {}, "--no-hidden": {},
47-
"sort": {}, "--sort": {}, "sortr": {}, "--sortr": {},
47+
"sort": {}, "--sort": {}, "sortr": {}, "--sortr": {}, "sort_files": {}, "sortFiles": {}, "sort-files": {}, "--sort-files": {},
4848
"context": {}, "-C": {}, "before_context": {}, "beforeContext": {}, "-B": {}, "after_context": {}, "afterContext": {}, "-A": {}, "line_numbers": {}, "lineNumbers": {}, "line-number": {}, "--line-number": {}, "-n": {},
4949
"no_line_number": {}, "noLineNumber": {}, "no_line_numbers": {}, "noLineNumbers": {}, "no-line-number": {}, "no-line-numbers": {}, "--no-line-number": {}, "-N": {},
5050
"column": {}, "column_numbers": {}, "columnNumbers": {}, "column-number": {}, "--column": {},
@@ -83,6 +83,7 @@ var grepSemanticBooleanKeys = map[string]struct{}{
8383
"no_context_separator": {}, "noContextSeparator": {}, "no-context-separator": {}, "--no-context-separator": {},
8484
"byte_offset": {}, "byteOffset": {}, "byte-offset": {}, "--byte-offset": {}, "-b": {},
8585
"hidden": {}, "--hidden": {}, "no_hidden": {}, "noHidden": {}, "no-hidden": {}, "--no-hidden": {},
86+
"sort_files": {}, "sortFiles": {}, "sort-files": {}, "--sort-files": {},
8687
"line_numbers": {}, "lineNumbers": {}, "line-number": {}, "--line-number": {}, "-n": {},
8788
"no_line_number": {}, "noLineNumber": {}, "no_line_numbers": {}, "noLineNumbers": {}, "no-line-number": {}, "no-line-numbers": {}, "--no-line-number": {}, "-N": {},
8889
"column": {}, "column_numbers": {}, "columnNumbers": {}, "column-number": {}, "--column": {},
@@ -210,6 +211,10 @@ type grepInput struct {
210211
LongSort string `json:"--sort,omitempty"`
211212
SortReverse string `json:"sortr,omitempty"`
212213
LongSortReverse string `json:"--sortr,omitempty"`
214+
SortFiles bool `json:"sort_files,omitempty"`
215+
SortFilesAlt bool `json:"sortFiles,omitempty"`
216+
SortFilesDash bool `json:"sort-files,omitempty"`
217+
LongSortFiles bool `json:"--sort-files,omitempty"`
213218
Context *int `json:"context,omitempty"`
214219
ShortContext *int `json:"-C,omitempty"`
215220
BeforeContext *int `json:"before_context,omitempty"`
@@ -539,8 +544,12 @@ func NewGrepTool() tool.Tool {
539544
"--sortr": map[string]any{
540545
"type": "string", "enum": []any{"path", "name", "file", "modified", "mtime", "modtime", "time", "none"},
541546
},
542-
"context": map[string]any{"type": "integer"},
543-
"-C": map[string]any{"type": "integer"},
547+
"sort_files": map[string]any{"type": "boolean"},
548+
"sortFiles": map[string]any{"type": "boolean"},
549+
"sort-files": map[string]any{"type": "boolean"},
550+
"--sort-files": map[string]any{"type": "boolean"},
551+
"context": map[string]any{"type": "integer"},
552+
"-C": map[string]any{"type": "integer"},
544553
"before_context": map[string]any{
545554
"type": "integer",
546555
},
@@ -697,7 +706,7 @@ func NewGrepTool() tool.Tool {
697706
},
698707
},
699708
PromptFunc: func(tool.PromptContext) (string, error) {
700-
return "Searches text files under path using a regular expression or fixed string. pattern is the canonical search expression; regex/regexp/--regexp/-e are accepted aliases. output_mode may be files, files_with_matches, files_without_matches, content, or count; glob/-g/--glob, iglob/--iglob, type/-t/--type, and type_not/-T/--type-not optionally filter file paths. glob and iglob accept whitespace/comma-separated patterns, negation, and brace alternation. content mode supports context, before_context, after_context, -C, -B, -A, -n/--line-number and -N/--no-line-number line-number control, --column column-number output, byte_offset/--byte-offset/-b byte offset output, -H/--with-filename and -I/--no-filename filename prefix control, heading/--heading grouped file headings, path_separator/--path-separator display path separator control, null/--null NUL path terminators/separators, field_match_separator/--field-match-separator and field_context_separator/--field-context-separator output field separators, context_separator/--context-separator and no_context_separator/--no-context-separator context group separator control, offset, head_limit pagination, max_count/-m per-file match limiting, max_columns/--max-columns long-line omission, --max-columns-preview long-line previews, replace/--replace/-r display-only replacement, only_matching/-o/--only-matching matched-text output, vimgrep/--vimgrep per-match line output, passthru/--passthru/--passthrough all-line output, trim/--trim leading-whitespace trimming, and hidden/--hidden or no_hidden/--no-hidden hidden file traversal control. Use files/--files to list files that would be searched without requiring pattern, files_with_matches or -l to list files with matches, files_without_match or -L to list files without matches, and count/--count/-c for count mode. Count mode supports count_matches/--count-matches for occurrence counts and include_zero/--include-zero to include zero-count files. Use sort/--sort or sortr/--sortr with path or modified to control result ordering. Use fixed_strings/-F/--fixed-strings for literal matching, text/-a/--text to search binary-extension files as text, word_regexp/-w/--word-regexp for whole-word matches, line_regexp/-x/--line-regexp for whole-line matches, ignore_case/-i/--ignore-case for case-insensitive search, case_sensitive/-s/--case-sensitive to force case-sensitive matching, smart_case/-S/--smart-case for lowercase-only patterns, and invert_match/-v/--invert-match to select non-matching lines. Set no_ignore/--no-ignore to skip .gitignore/.ignore files while still excluding VCS metadata and read-denied paths. Set multiline to allow patterns to span lines with dot matching newlines.", nil
709+
return "Searches text files under path using a regular expression or fixed string. pattern is the canonical search expression; regex/regexp/--regexp/-e are accepted aliases. output_mode may be files, files_with_matches, files_without_matches, content, or count; glob/-g/--glob, iglob/--iglob, type/-t/--type, and type_not/-T/--type-not optionally filter file paths. glob and iglob accept whitespace/comma-separated patterns, negation, and brace alternation. content mode supports context, before_context, after_context, -C, -B, -A, -n/--line-number and -N/--no-line-number line-number control, --column column-number output, byte_offset/--byte-offset/-b byte offset output, -H/--with-filename and -I/--no-filename filename prefix control, heading/--heading grouped file headings, path_separator/--path-separator display path separator control, null/--null NUL path terminators/separators, field_match_separator/--field-match-separator and field_context_separator/--field-context-separator output field separators, context_separator/--context-separator and no_context_separator/--no-context-separator context group separator control, offset, head_limit pagination, max_count/-m per-file match limiting, max_columns/--max-columns long-line omission, --max-columns-preview long-line previews, replace/--replace/-r display-only replacement, only_matching/-o/--only-matching matched-text output, vimgrep/--vimgrep per-match line output, passthru/--passthru/--passthrough all-line output, trim/--trim leading-whitespace trimming, and hidden/--hidden or no_hidden/--no-hidden hidden file traversal control. Use files/--files to list files that would be searched without requiring pattern, files_with_matches or -l to list files with matches, files_without_match or -L to list files without matches, and count/--count/-c for count mode. Count mode supports count_matches/--count-matches for occurrence counts and include_zero/--include-zero to include zero-count files. Use sort/--sort or sortr/--sortr with path or modified to control result ordering; --sort-files is accepted as a path-sort alias. Use fixed_strings/-F/--fixed-strings for literal matching, text/-a/--text to search binary-extension files as text, word_regexp/-w/--word-regexp for whole-word matches, line_regexp/-x/--line-regexp for whole-line matches, ignore_case/-i/--ignore-case for case-insensitive search, case_sensitive/-s/--case-sensitive to force case-sensitive matching, smart_case/-S/--smart-case for lowercase-only patterns, and invert_match/-v/--invert-match to select non-matching lines. Set no_ignore/--no-ignore to skip .gitignore/.ignore files while still excluding VCS metadata and read-denied paths. Set multiline to allow patterns to span lines with dot matching newlines.", nil
701710
},
702711
NormalizeFunc: normalizeGrepRawInput,
703712
ValidateFunc: validateGrep,
@@ -1000,6 +1009,7 @@ func callGrep(ctx tool.Context, raw json.RawMessage, _ tool.ProgressSink) (contr
10001009
"sort": grepStructuredSortMode(options),
10011010
"sort_reverse": grepStructuredSortReverse(options),
10021011
"sort_explicit": sortExplicit,
1012+
"sort_files": grepSortFiles(input),
10031013
"truncated": truncated,
10041014
},
10051015
}, nil
@@ -1851,6 +1861,9 @@ func grepSort(input grepInput) (string, bool, bool, error) {
18511861
mode, err := normalizeGrepSortMode(raw)
18521862
return mode, false, true, err
18531863
}
1864+
if grepSortFiles(input) {
1865+
return "path", false, true, nil
1866+
}
18541867
return "", false, false, nil
18551868
}
18561869

@@ -1879,6 +1892,13 @@ func firstNonEmpty(values ...string) string {
18791892
return ""
18801893
}
18811894

1895+
func grepSortFiles(input grepInput) bool {
1896+
return input.SortFiles ||
1897+
input.SortFilesAlt ||
1898+
input.SortFilesDash ||
1899+
input.LongSortFiles
1900+
}
1901+
18821902
func sortGrepMatches(matches []grepMatch, options grepOptions) {
18831903
if options.SortExplicit && options.SortMode == "none" {
18841904
return

internal/tools/file/tools_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1681,6 +1681,21 @@ func TestGrepToolSortAliases(t *testing.T) {
16811681
t.Fatalf("path sort result = %#v", pathResult)
16821682
}
16831683

1684+
sortFilesResult, err := executor.Execute(ctx, contracts.ToolUse{
1685+
ID: "toolu_grep_sort_files",
1686+
Name: "Grep",
1687+
Input: json.RawMessage(`{"pattern":"Needle","--sort-files":"true"}`),
1688+
}, nil)
1689+
if err != nil {
1690+
t.Fatal(err)
1691+
}
1692+
if sortFilesResult.Content != "Found 3 files\na.txt\nm.txt\nz.txt" ||
1693+
sortFilesResult.StructuredContent["sort"] != "path" ||
1694+
sortFilesResult.StructuredContent["sort_files"] != true ||
1695+
sortFilesResult.StructuredContent["sort_explicit"] != true {
1696+
t.Fatalf("sort-files result = %#v", sortFilesResult)
1697+
}
1698+
16841699
reversePathResult, err := executor.Execute(ctx, contracts.ToolUse{
16851700
ID: "toolu_grep_sort_reverse_path",
16861701
Name: "Grep",

0 commit comments

Comments
 (0)