codetool-explore 0.5.0__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. codetool_explore-0.7.0/.gitignore +11 -0
  2. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/PKG-INFO +25 -20
  3. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/README.md +24 -19
  4. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/pyproject.toml +1 -1
  5. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/scripts/update_readme_benchmarks.py +17 -37
  6. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/api.py +7 -6
  7. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/cli.py +2 -2
  8. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/explorer.py +255 -62
  9. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/tests/test_api.py +156 -1
  10. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/tests/test_cli.py +57 -0
  11. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/uv.lock +1 -1
  12. codetool_explore-0.5.0/.gitignore +0 -19
  13. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/LICENSE +0 -0
  14. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/benchmarks/benchmark_output_lengths.py +0 -0
  15. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/benchmarks/benchmark_search.py +0 -0
  16. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/hatch_build.py +0 -0
  17. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/Cargo.lock +0 -0
  18. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/Cargo.toml +0 -0
  19. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/app.rs +0 -0
  20. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/case.rs +0 -0
  21. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/config.rs +0 -0
  22. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/constants.rs +0 -0
  23. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/file_search.rs +0 -0
  24. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/ignore_rules.rs +0 -0
  25. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/literal.rs +0 -0
  26. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/main.rs +0 -0
  27. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/matcher.rs +0 -0
  28. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/models.rs +0 -0
  29. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/output.rs +0 -0
  30. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/path_utils.rs +0 -0
  31. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/ranking.rs +0 -0
  32. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/regex_search.rs +0 -0
  33. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/search.rs +0 -0
  34. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/text.rs +0 -0
  35. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/rust/src/walker.rs +0 -0
  36. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/scripts/package_rust_binary.py +0 -0
  37. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/__init__.py +0 -0
  38. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/compression.py +0 -0
  39. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/cursor.py +0 -0
  40. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/errors.py +0 -0
  41. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/ignore.py +0 -0
  42. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/py.typed +0 -0
  43. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/__init__.py +0 -0
  44. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/case.py +0 -0
  45. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/config.py +0 -0
  46. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/constants.py +0 -0
  47. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/file_search.py +0 -0
  48. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/ignore_rules.py +0 -0
  49. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/literal.py +0 -0
  50. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/matcher.py +0 -0
  51. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/models.py +0 -0
  52. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/output.py +0 -0
  53. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/regex_search.py +0 -0
  54. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/search.py +0 -0
  55. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/text.py +0 -0
  56. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/python_backend/walker.py +0 -0
  57. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/ranking.py +0 -0
  58. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/roots.py +0 -0
  59. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/rust_backend.py +0 -0
  60. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/src/codetool_explore/text_output.py +0 -0
  61. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/tests/test___init__.py +0 -0
  62. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/tests/test_cursor.py +0 -0
  63. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/tests/test_hatch_build.py +0 -0
  64. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/tests/test_ignore.py +0 -0
  65. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/tests/test_packaged_binary.py +0 -0
  66. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/tests/test_python_backend.py +0 -0
  67. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/tests/test_ranking.py +0 -0
  68. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/tests/test_rust_backend.py +0 -0
  69. {codetool_explore-0.5.0 → codetool_explore-0.7.0}/tests/test_rust_cli.py +0 -0
@@ -0,0 +1,11 @@
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ *.py[cod]
5
+ rust/target/
6
+ benchmark-corpus/
7
+ benchmarks/corpus/
8
+ reports/
9
+ research/
10
+ .tmp*/
11
+ .ruff_cache/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codetool-explore
3
- Version: 0.5.0
3
+ Version: 0.7.0
4
4
  Summary: Fast, dependency-free workspace search, read, and list exploration for coding-agent tools with Rust backend
5
5
  Project-URL: Homepage, https://github.com/pbi-agent/codetool-explore
6
6
  Project-URL: Repository, https://github.com/pbi-agent/codetool-explore
@@ -109,16 +109,21 @@ path-only rows under `target="content_or_path"` are returned without
109
109
  line/snippet fields.
110
110
 
111
111
  `target="read"` treats `pattern` as one known file path, resolves relative paths
112
- under a single `root`, and returns plain text with no line-number prefixes.
113
- Use `start_line` and `limit` to cap the returned line range; if more lines
114
- remain, text output starts with `-- more: cursor=N`. CSV files are read as
115
- ordinary text. Binary-looking, missing, unreadable, or directory paths fail with
116
- controlled `ExploreError` subclasses.
112
+ under each root, and returns plain text with no line-number prefixes. When more
113
+ than one file is read, each file is prefixed by a compact `path:` header. Use
114
+ `start_line` and `limit` to cap the returned line range; if more lines remain,
115
+ text output starts with `-- more: cursor=N`. CSV files are read as ordinary
116
+ text. Binary-looking, missing, unreadable, or directory paths fail with
117
+ controlled `ExploreError` subclasses. If `root` is already a file,
118
+ `target="read"` reads that file directly, so accidental search-like `pattern`
119
+ values from tool calls still produce the requested line range.
117
120
 
118
121
  `target="list"` treats `pattern` as one file/directory path and returns one
119
- directory level. Text output uses the same compact tree display as raw search
120
- output when that saves tokens. Directories end with `/`; file paths are returned
121
- as one entry. It honors `glob`, `exclude`, ignore files, `limit`, and `cursor`.
122
+ directory level under each root. Text output uses the same compact tree display
123
+ as raw search output when that saves tokens, appending each root's listing in
124
+ root order. Directories end with `/`; file paths are returned as one entry. It
125
+ honors multi-root common-base paths, `glob`, `exclude`, ignore files, `limit`,
126
+ and `cursor`.
122
127
  Read/list use the pure-Python stdlib implementation even when `backend="auto"`
123
128
  or `"rust"` is requested.
124
129
 
@@ -139,9 +144,10 @@ treated as multiple roots when that exact path does not exist and every split
139
144
  token is an existing file or directory. Existing paths with spaces still take
140
145
  priority; quote individual spaced paths if combining them in one string.
141
146
 
142
- File roots search only that file and report paths relative to the file's parent
143
- directory. Multi-root searches report paths relative to the roots' common base,
144
- so sibling roots keep prefixes such as `src/...` and `tests/...`; this also lets
147
+ File roots search/read only that file and report paths relative to the file's
148
+ parent directory; listing a file path returns one file entry. Multi-root
149
+ searches/reads/listings report paths relative to the roots' common base, so
150
+ sibling roots keep prefixes such as `src/...` and `tests/...`; this also lets
145
151
  `exclude=["src/generated/**"]` target one root.
146
152
 
147
153
  Controlled failures raise `ExploreError` subclasses:
@@ -159,15 +165,16 @@ codetool-explore "service" . --target path --literal
159
165
  codetool-explore "User(Service|Repository)" --root src --mode snippets --raw
160
166
  codetool-explore "search_workspace" --root src --root webapp --root tests --literal
161
167
  codetool-explore --read README.md --start-line 20 --limit 40
162
- codetool-explore --list src --glob "*.py"
168
+ codetool-explore --read settings.py --root src --root tests
169
+ codetool-explore --list . --root src --root tests --glob "*.py"
163
170
  ```
164
171
 
165
172
  The CLI defaults to compact JSON for search, plain text for `--read`, and
166
173
  tree-compressed text for `--list`.
167
174
  Use `--format text` or `--raw` for raw search text; no search matches print
168
- `No Match`. Repeat `--root` for multiple search roots; read/list accept a single
169
- root only. A single quoted space-delimited `--root` is accepted as a compatibility
170
- fallback when it can be split into existing roots.
175
+ `No Match`. Repeat `--root` for multiple search/read/list roots. A single
176
+ quoted space-delimited `--root` is accepted as a compatibility fallback when it
177
+ can be split into existing roots.
171
178
 
172
179
  ## Install
173
180
 
@@ -217,13 +224,11 @@ Token counts use `tiktoken` when available. The table compares output across 7 R
217
224
 
218
225
  | Output | Tokens | Bytes | Chart |
219
226
  | --- | ---: | ---: | --- |
220
- | `explore(..., result_format="text")` | 11,008 | 34.3 KB | ██░░░░░░░░░░░░░░░░ |
227
+ | `codetool-explore` | 11,008 | 34.3 KB | ██░░░░░░░░░░░░░░░░ |
221
228
  | `rtk grep` stdout | 19,646 | 60.1 KB | ███░░░░░░░░░░░░░░░ |
222
- | default `explore(...)` | 38,393 | 125.3 KB | █████░░░░░░░░░░░░░ |
223
- | `explore(..., result_format="full")` | 39,027 | 134.7 KB | █████░░░░░░░░░░░░░ |
224
229
  | `rg` stdout | 129,775 | 402.4 KB | ██████████████████ |
225
230
 
226
- Default structured output is 7.03% smaller than the full structured shape. Raw text omits backend/totals metadata, includes only a cursor hint when truncated, and prints `No Match` for empty pages. Raw text is 0.56× the `rtk grep` token count in this run.
231
+ `codetool-explore` is raw text from `explore(..., result_format="text")`; it omits backend/totals metadata, includes only a cursor hint when truncated, and prints `No Match` for empty pages. It is 0.56× the `rtk grep` token count in this run.
227
232
 
228
233
  Source: `reports/rtk_vs_codetool_output_lengths.json`.
229
234
 
@@ -79,16 +79,21 @@ path-only rows under `target="content_or_path"` are returned without
79
79
  line/snippet fields.
80
80
 
81
81
  `target="read"` treats `pattern` as one known file path, resolves relative paths
82
- under a single `root`, and returns plain text with no line-number prefixes.
83
- Use `start_line` and `limit` to cap the returned line range; if more lines
84
- remain, text output starts with `-- more: cursor=N`. CSV files are read as
85
- ordinary text. Binary-looking, missing, unreadable, or directory paths fail with
86
- controlled `ExploreError` subclasses.
82
+ under each root, and returns plain text with no line-number prefixes. When more
83
+ than one file is read, each file is prefixed by a compact `path:` header. Use
84
+ `start_line` and `limit` to cap the returned line range; if more lines remain,
85
+ text output starts with `-- more: cursor=N`. CSV files are read as ordinary
86
+ text. Binary-looking, missing, unreadable, or directory paths fail with
87
+ controlled `ExploreError` subclasses. If `root` is already a file,
88
+ `target="read"` reads that file directly, so accidental search-like `pattern`
89
+ values from tool calls still produce the requested line range.
87
90
 
88
91
  `target="list"` treats `pattern` as one file/directory path and returns one
89
- directory level. Text output uses the same compact tree display as raw search
90
- output when that saves tokens. Directories end with `/`; file paths are returned
91
- as one entry. It honors `glob`, `exclude`, ignore files, `limit`, and `cursor`.
92
+ directory level under each root. Text output uses the same compact tree display
93
+ as raw search output when that saves tokens, appending each root's listing in
94
+ root order. Directories end with `/`; file paths are returned as one entry. It
95
+ honors multi-root common-base paths, `glob`, `exclude`, ignore files, `limit`,
96
+ and `cursor`.
92
97
  Read/list use the pure-Python stdlib implementation even when `backend="auto"`
93
98
  or `"rust"` is requested.
94
99
 
@@ -109,9 +114,10 @@ treated as multiple roots when that exact path does not exist and every split
109
114
  token is an existing file or directory. Existing paths with spaces still take
110
115
  priority; quote individual spaced paths if combining them in one string.
111
116
 
112
- File roots search only that file and report paths relative to the file's parent
113
- directory. Multi-root searches report paths relative to the roots' common base,
114
- so sibling roots keep prefixes such as `src/...` and `tests/...`; this also lets
117
+ File roots search/read only that file and report paths relative to the file's
118
+ parent directory; listing a file path returns one file entry. Multi-root
119
+ searches/reads/listings report paths relative to the roots' common base, so
120
+ sibling roots keep prefixes such as `src/...` and `tests/...`; this also lets
115
121
  `exclude=["src/generated/**"]` target one root.
116
122
 
117
123
  Controlled failures raise `ExploreError` subclasses:
@@ -129,15 +135,16 @@ codetool-explore "service" . --target path --literal
129
135
  codetool-explore "User(Service|Repository)" --root src --mode snippets --raw
130
136
  codetool-explore "search_workspace" --root src --root webapp --root tests --literal
131
137
  codetool-explore --read README.md --start-line 20 --limit 40
132
- codetool-explore --list src --glob "*.py"
138
+ codetool-explore --read settings.py --root src --root tests
139
+ codetool-explore --list . --root src --root tests --glob "*.py"
133
140
  ```
134
141
 
135
142
  The CLI defaults to compact JSON for search, plain text for `--read`, and
136
143
  tree-compressed text for `--list`.
137
144
  Use `--format text` or `--raw` for raw search text; no search matches print
138
- `No Match`. Repeat `--root` for multiple search roots; read/list accept a single
139
- root only. A single quoted space-delimited `--root` is accepted as a compatibility
140
- fallback when it can be split into existing roots.
145
+ `No Match`. Repeat `--root` for multiple search/read/list roots. A single
146
+ quoted space-delimited `--root` is accepted as a compatibility fallback when it
147
+ can be split into existing roots.
141
148
 
142
149
  ## Install
143
150
 
@@ -187,13 +194,11 @@ Token counts use `tiktoken` when available. The table compares output across 7 R
187
194
 
188
195
  | Output | Tokens | Bytes | Chart |
189
196
  | --- | ---: | ---: | --- |
190
- | `explore(..., result_format="text")` | 11,008 | 34.3 KB | ██░░░░░░░░░░░░░░░░ |
197
+ | `codetool-explore` | 11,008 | 34.3 KB | ██░░░░░░░░░░░░░░░░ |
191
198
  | `rtk grep` stdout | 19,646 | 60.1 KB | ███░░░░░░░░░░░░░░░ |
192
- | default `explore(...)` | 38,393 | 125.3 KB | █████░░░░░░░░░░░░░ |
193
- | `explore(..., result_format="full")` | 39,027 | 134.7 KB | █████░░░░░░░░░░░░░ |
194
199
  | `rg` stdout | 129,775 | 402.4 KB | ██████████████████ |
195
200
 
196
- Default structured output is 7.03% smaller than the full structured shape. Raw text omits backend/totals metadata, includes only a cursor hint when truncated, and prints `No Match` for empty pages. Raw text is 0.56× the `rtk grep` token count in this run.
201
+ `codetool-explore` is raw text from `explore(..., result_format="text")`; it omits backend/totals metadata, includes only a cursor hint when truncated, and prints `No Match` for empty pages. It is 0.56× the `rtk grep` token count in this run.
197
202
 
198
203
  Source: `reports/rtk_vs_codetool_output_lengths.json`.
199
204
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codetool-explore"
3
- version = "0.5.0"
3
+ version = "0.7.0"
4
4
  description = "Fast, dependency-free workspace search, read, and list exploration for coding-agent tools with Rust backend"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -175,37 +175,30 @@ def render_tokens(data: dict[str, Any], source: str) -> list[str]:
175
175
  if "summary" not in data:
176
176
  aggregate = compression_totals(data)
177
177
  totals = aggregate["totals"]
178
- full = int(totals["full_bytes"])
179
- compressed = int(totals["compressed_bytes"])
180
178
  raw = int(totals["raw_bytes"])
181
179
  rtk = int(totals["rtk_bytes"])
182
180
  rg = int(totals["rg_bytes"])
183
- grep = int(totals["grep_bytes"])
184
- full_tokens = int(totals["full_tokens"])
185
- compressed_tokens = int(totals["compressed_tokens"])
186
181
  raw_tokens = int(totals["raw_tokens"])
187
182
  rtk_tokens = int(totals["rtk_tokens"])
188
183
  rg_tokens = int(totals["rg_tokens"])
189
- grep_tokens = int(totals["grep_tokens"])
190
184
  savings = aggregate["savings"]
191
- if not full or not compressed:
185
+ if not raw:
192
186
  raise SystemExit(f"{source} does not contain token-compression data")
193
- rows = [
194
- ('`explore(..., result_format="full")`', full_tokens, full),
195
- ("default `explore(...)`", compressed_tokens, compressed),
196
- ]
187
+ rows = []
197
188
  if raw:
198
- rows.append(('`explore(..., result_format="text")`', raw_tokens, raw))
189
+ rows.append(("`codetool-explore`", raw_tokens, raw))
199
190
  if rtk:
200
191
  rows.append(("`rtk grep` stdout", rtk_tokens, rtk))
201
192
  if rg:
202
193
  rows.append(("`rg` stdout", rg_tokens, rg))
203
- if grep:
204
- rows.append(("GNU `grep` stdout", grep_tokens, grep))
205
194
  max_tokens = max(tokens for _, tokens, _ in rows)
206
- percent = round(((full - compressed) / full) * 100, 2)
207
- average_percent = round(mean(savings), 2) if savings else percent
208
195
  samples = aggregate["samples"] or len(savings)
196
+ raw_ratio = round(raw_tokens / rtk_tokens, 2) if rtk_tokens else None
197
+ raw_note = (
198
+ f" It is {raw_ratio}× the `rtk grep` token count in this run."
199
+ if raw_ratio is not None
200
+ else ""
201
+ )
209
202
  lines = [
210
203
  "### Token compression",
211
204
  "",
@@ -224,9 +217,9 @@ def render_tokens(data: dict[str, Any], source: str) -> list[str]:
224
217
  lines += [
225
218
  "",
226
219
  (
227
- f"Default structured output is {percent}% smaller in bytes total and "
228
- f"{average_percent}% smaller on average per sample than the full shape. "
229
- "Raw text omits metadata and is optimized for minimum tokens."
220
+ "`codetool-explore` is raw text from "
221
+ '`explore(..., result_format="text")`; it omits metadata and '
222
+ "is optimized for minimum tokens." + raw_note
230
223
  ),
231
224
  "",
232
225
  f"Source: `{source}`.",
@@ -235,33 +228,20 @@ def render_tokens(data: dict[str, Any], source: str) -> list[str]:
235
228
 
236
229
  summary = data["summary"]
237
230
  totals = summary["total_bytes"]
238
- compressed = int(totals["codetool_compressed_json"])
239
- full = int(totals["codetool_full_json"])
240
231
  raw = int(totals.get("codetool_raw_text", 0))
241
232
  rtk = int(totals.get("rtk_grep_stdout", 0))
242
233
  rg = int(totals.get("rg_stdout", 0))
243
- grep = int(totals.get("grep_stdout", 0))
244
234
  token_totals = summary.get("total_tokens", {})
245
- full_tokens = int(token_totals.get("codetool_full_json", round(full / 4)))
246
- compressed_tokens = int(
247
- token_totals.get("codetool_compressed_json", round(compressed / 4))
248
- )
249
235
  raw_tokens = int(token_totals.get("codetool_raw_text", round(raw / 4)))
250
236
  rtk_tokens = int(token_totals.get("rtk_grep_stdout", round(rtk / 4)))
251
237
  rg_tokens = int(token_totals.get("rg_stdout", round(rg / 4)))
252
- grep_tokens = int(token_totals.get("grep_stdout", round(grep / 4)))
253
- rows = [
254
- ('`explore(..., result_format="full")`', full_tokens, full),
255
- ("default `explore(...)`", compressed_tokens, compressed),
256
- ]
238
+ rows = []
257
239
  if raw:
258
- rows.append(('`explore(..., result_format="text")`', raw_tokens, raw))
240
+ rows.append(("`codetool-explore`", raw_tokens, raw))
259
241
  if rtk:
260
242
  rows.append(("`rtk grep` stdout", rtk_tokens, rtk))
261
243
  if rg:
262
244
  rows.append(("`rg` stdout", rg_tokens, rg))
263
- if grep:
264
- rows.append(("GNU `grep` stdout", grep_tokens, grep))
265
245
  max_tokens = max(tokens for _, tokens, _ in rows)
266
246
  comparison = summary["total_comparison"]
267
247
  lines = [
@@ -281,15 +261,15 @@ def render_tokens(data: dict[str, Any], source: str) -> list[str]:
281
261
  )
282
262
  raw_ratio = comparison.get("codetool_raw_over_rtk_tokens_ratio")
283
263
  raw_note = (
284
- f" Raw text is {raw_ratio}× the `rtk grep` token count in this run."
264
+ f" It is {raw_ratio}× the `rtk grep` token count in this run."
285
265
  if raw_ratio is not None
286
266
  else ""
287
267
  )
288
268
  lines += [
289
269
  "",
290
270
  (
291
- f"Default structured output is {comparison['codetool_compressed_vs_full_percent_smaller']}% "
292
- "smaller than the full structured shape. Raw text omits backend/totals "
271
+ "`codetool-explore` is raw text from "
272
+ '`explore(..., result_format="text")`; it omits backend/totals '
293
273
  "metadata, includes only a cursor hint when truncated, and prints "
294
274
  "`No Match` for empty pages." + raw_note
295
275
  ),
@@ -107,12 +107,13 @@ def explore(
107
107
  paths without opening files, and ``"content_or_path"`` returns files
108
108
  matching either.
109
109
  ``"read"`` treats ``pattern`` as one file path and returns a controlled
110
- line range. ``"list"`` treats ``pattern`` as one file/directory path and
111
- returns a one-level listing.
110
+ line range for each root. ``"list"`` treats ``pattern`` as one
111
+ file/directory path and returns a one-level listing for each root.
112
112
  Patterns are interpreted as regex by default; pass ``regex=False`` for exact
113
113
  literal search. ``root`` may be one directory/file path or a non-empty list
114
- of directory/file paths. Multi-root searches report paths relative to the
115
- roots' common base, so sibling roots keep prefixes such as ``src/...``.
114
+ of directory/file paths. Multi-root searches/reads/listings report paths
115
+ relative to the roots' common base, so sibling roots keep prefixes such as
116
+ ``src/...``.
116
117
  To tolerate common JSON/tool-call mistakes, a whitespace-separated root
117
118
  string is treated as multiple roots only when that exact path does not
118
119
  exist and every split token is an existing file or directory.
@@ -122,8 +123,8 @@ def explore(
122
123
  compatibility fallback.
123
124
 
124
125
  Search results are returned in a compact structured shape by default; read
125
- results default to plain text and list results default to tree-compressed
126
- text. Pass
126
+ results default to plain text (with path headers only when multiple files
127
+ are read) and list results default to tree-compressed text. Pass
127
128
  ``result_format="full"`` to receive the pre-compression backend result
128
129
  dictionary unchanged. Pass ``result_format="text"`` (or ``"raw"``) for an
129
130
  RTK-inspired plain-text rendering optimized for token compression.
@@ -26,12 +26,12 @@ def build_parser() -> argparse.ArgumentParser:
26
26
  action_group.add_argument(
27
27
  "--read",
28
28
  dest="read_path",
29
- help="read one known file path with controlled line output",
29
+ help="read one known file path under each root with controlled line output",
30
30
  )
31
31
  action_group.add_argument(
32
32
  "--list",
33
33
  dest="list_path",
34
- help="list one directory level or one file path",
34
+ help="list one directory level or one file path under each root",
35
35
  )
36
36
  parser.add_argument(
37
37
  "--root",