1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
use clap::{Parser as ClapParser, Subcommand};
use std::path::PathBuf;
#[derive(ClapParser, Debug)]
#[command(
author,
version,
about = "AI-friendly, fully local, semantic code search tool for large codebases",
long_about = "Probe is a powerful code search tool designed for developers and AI assistants. \
It provides semantic code search with intelligent ranking, code block extraction, \
and language-aware parsing. Run without arguments to see this help message."
)]
pub struct Args {
/// Search pattern (used when no subcommand is provided)
#[arg(value_name = "PATTERN")]
pub pattern: Option<String>,
/// Files or directories to search (used when no subcommand is provided)
#[arg(value_name = "PATH")]
pub paths: Vec<PathBuf>,
/// Skip AST parsing and just output unique files
#[arg(short, long = "files-only")]
pub files_only: bool,
/// Custom patterns to ignore (in addition to .gitignore and common patterns)
#[arg(short, long)]
pub ignore: Vec<String>,
/// Exclude files whose names match query words (filename matching is enabled by default)
#[arg(short = 'n', long = "exclude-filenames")]
pub exclude_filenames: bool,
/// BM25 ranking for search results
#[arg(short = 'r', long = "reranker", default_value = "bm25", value_parser = ["bm25"])]
pub reranker: String,
/// Use frequency-based search with stemming and stopword removal (enabled by default)
#[arg(short = 's', long = "frequency", default_value = "true")]
pub frequency_search: bool,
/// Perform exact search without tokenization (case-insensitive)
#[arg(short = 'e', long = "exact")]
pub exact: bool,
/// Maximum number of results to return
#[arg(long = "max-results")]
pub max_results: Option<usize>,
/// Maximum total bytes of code content to return
#[arg(long = "max-bytes")]
pub max_bytes: Option<usize>,
/// Maximum total tokens in code content to return (for AI usage)
#[arg(long = "max-tokens")]
pub max_tokens: Option<usize>,
/// Allow test files and test code blocks in search results
#[arg(long = "allow-tests")]
pub allow_tests: bool,
/// Disable merging of adjacent code blocks after ranking (merging enabled by default)
#[arg(long = "no-merge", default_value = "false")]
pub no_merge: bool,
/// Maximum number of lines between code blocks to consider them adjacent for merging (default: 5)
#[arg(long = "merge-threshold")]
pub merge_threshold: Option<usize>,
/// Output only file names and line numbers without full content
#[arg(long = "dry-run")]
pub dry_run: bool,
/// Output format (default: color)
/// Use 'json' or 'xml' for machine-readable output
#[arg(short = 'o', long = "format", default_value = "color", value_parser = ["terminal", "markdown", "plain", "json", "xml", "color"])]
pub format: String,
/// Session ID for caching search results
#[arg(long = "session")]
pub session: Option<String>,
/// Timeout in seconds for search operation (default: 30)
#[arg(long = "timeout", default_value = "30")]
pub timeout: u64,
#[command(subcommand)]
pub command: Option<Commands>,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Search code using patterns with intelligent ranking
///
/// This command searches your codebase using regex patterns with semantic understanding.
/// It uses frequency-based search with stemming and stopword removal by default,
/// and ranks results using the BM25 algorithm.
/// Results are presented as code blocks with relevant context.
Search {
/// Search pattern (regex supported)
#[arg(value_name = "PATTERN")]
pattern: String,
/// Files or directories to search (defaults to current directory)
#[arg(value_name = "PATH", default_value = ".")]
paths: Vec<PathBuf>,
/// Skip AST parsing and just output unique files
#[arg(short, long = "files-only")]
files_only: bool,
/// Custom patterns to ignore (in addition to .gitignore and common patterns)
#[arg(short, long)]
ignore: Vec<String>,
/// Exclude files whose names match query words (filename matching is enabled by default)
#[arg(short = 'n', long = "exclude-filenames")]
exclude_filenames: bool,
/// BM25 ranking for search results
#[arg(short = 'r', long = "reranker", default_value = "bm25", value_parser = ["bm25"])]
reranker: String,
/// Use frequency-based search with stemming and stopword removal (enabled by default)
#[arg(short = 's', long = "frequency", default_value = "true")]
frequency_search: bool,
/// Perform exact search without tokenization (case-insensitive)
#[arg(short = 'e', long = "exact")]
exact: bool,
/// Programming language to limit search to specific file extensions
#[arg(short = 'l', long = "language", value_parser = [
"rust", "rs",
"javascript", "js", "jsx",
"typescript", "ts", "tsx",
"python", "py",
"go",
"c", "h",
"cpp", "cc", "cxx", "hpp", "hxx",
"java",
"ruby", "rb",
"php",
"swift",
"csharp", "cs"
])]
language: Option<String>,
/// Maximum number of results to return
#[arg(long = "max-results")]
max_results: Option<usize>,
/// Maximum total bytes of code content to return
#[arg(long = "max-bytes")]
max_bytes: Option<usize>,
/// Maximum total tokens in code content to return (for AI usage)
#[arg(long = "max-tokens")]
max_tokens: Option<usize>,
/// Allow test files and test code blocks in search results
#[arg(long = "allow-tests")]
allow_tests: bool,
/// Disable merging of adjacent code blocks after ranking (merging enabled by default)
#[arg(long = "no-merge", default_value = "false")]
no_merge: bool,
/// Maximum number of lines between code blocks to consider them adjacent for merging (default: 5)
#[arg(long = "merge-threshold")]
merge_threshold: Option<usize>,
/// Output only file names and line numbers without full content
#[arg(long = "dry-run")]
dry_run: bool,
/// Output format (default: color)
/// Use 'json' or 'xml' for machine-readable output with structured data
#[arg(short = 'o', long = "format", default_value = "color", value_parser = ["terminal", "markdown", "plain", "json", "xml", "color"])]
format: String,
/// Session ID for caching search results
#[arg(long = "session")]
session: Option<String>,
/// Timeout in seconds for search operation (default: 30)
#[arg(long = "timeout", default_value = "30")]
timeout: u64,
},
/// Extract code blocks from files
///
/// This command extracts code blocks from files based on file paths and optional line numbers.
/// When a line number is specified (e.g., file.rs:10), the command uses tree-sitter to find
/// the closest suitable parent node (function, struct, class, etc.) for that line.
/// You can also specify a symbol name using the hash syntax (e.g., file.rs#function_name) to
/// extract the code block for that specific symbol.
Extract {
/// Files to extract from (can include line numbers with colon, e.g., file.rs:10, or symbol names with hash, e.g., file.rs#function_name)
#[arg(value_name = "FILES")]
files: Vec<String>,
/// Custom patterns to ignore (in addition to .gitignore and common patterns)
#[arg(short, long)]
ignore: Vec<String>,
/// Number of context lines to include before and after the extracted block
#[arg(short = 'c', long = "context", default_value = "0")]
context_lines: usize,
/// Output format (default: color)
/// Use 'json' or 'xml' for machine-readable output with structured data
#[arg(short = 'o', long = "format", default_value = "color", value_parser = ["markdown", "plain", "json", "xml", "color"])]
format: String,
/// Read input from clipboard instead of files
#[arg(short = 'f', long = "from-clipboard")]
from_clipboard: bool,
/// Read input from a file (treats file content like stdin or clipboard)
#[arg(short = 'F', long = "input-file")]
input_file: Option<String>,
/// Write output to clipboard
#[arg(short = 't', long = "to-clipboard")]
to_clipboard: bool,
/// Output only file names and line numbers without full content
#[arg(long = "dry-run")]
dry_run: bool,
/// Parse input as git diff format
#[arg(long = "diff")]
diff: bool,
/// Allow test files and test code blocks in extraction results (only applies when reading from stdin or clipboard)
#[arg(long = "allow-tests")]
allow_tests: bool,
/// Keep and display the original, unstructured input content
#[arg(short = 'k', long = "keep-input")]
keep_input: bool,
/// System prompt template for LLM models (engineer, architect, or path to file)
#[arg(long = "prompt")]
prompt: Option<String>,
/// User instructions for LLM models
#[arg(long = "instructions")]
instructions: Option<String>,
},
/// Search code using AST patterns for precise structural matching
///
/// This command uses ast-grep to search for structural patterns in code.
/// It allows for more precise code searching based on the Abstract Syntax Tree,
/// which is particularly useful for finding specific code structures regardless
/// of variable names or formatting. This is more powerful than regex for
/// certain types of code searches.
Query {
/// AST pattern to search for (e.g., "fn $NAME() { $$$BODY }")
#[arg(value_name = "PATTERN")]
pattern: String,
/// Files or directories to search (defaults to current directory)
#[arg(value_name = "PATH", default_value = ".")]
path: PathBuf,
/// Programming language to use for parsing (auto-detected if not specified)
#[arg(short = 'l', long = "language", value_parser = [
"rust", "rs",
"javascript", "js", "jsx",
"typescript", "ts", "tsx",
"python", "py",
"go",
"c", "h",
"cpp", "cc", "cxx", "hpp", "hxx",
"java",
"ruby", "rb",
"php",
"swift",
"csharp", "cs"
])]
language: Option<String>,
/// Custom patterns to ignore (in addition to .gitignore and common patterns)
#[arg(short, long)]
ignore: Vec<String>,
/// Allow test files in search results
#[arg(long = "allow-tests")]
allow_tests: bool,
/// Maximum number of results to return
#[arg(long = "max-results")]
max_results: Option<usize>,
/// Output format (default: color)
/// Use 'json' or 'xml' for machine-readable output with structured data
#[arg(short = 'o', long = "format", default_value = "color", value_parser = ["markdown", "plain", "json", "xml", "color"])]
format: String,
},
/// Run performance benchmarks
///
/// This command runs comprehensive performance benchmarks using the Criterion framework.
/// It tests various aspects of the search engine including search patterns, result limits,
/// different options, timing infrastructure, and language parsing performance.
/// Results are saved to the target/criterion directory.
Benchmark {
/// Specific benchmark to run (default: all)
#[arg(long = "bench", value_parser = ["all", "search", "timing", "parsing"])]
bench: Option<String>,
/// Number of iterations for each benchmark (default: auto)
#[arg(long = "sample-size")]
sample_size: Option<usize>,
/// Benchmark output format
#[arg(long = "format", default_value = "pretty", value_parser = ["pretty", "json", "csv"])]
format: String,
/// Save benchmark results to file
#[arg(long = "output")]
output: Option<String>,
/// Compare with previous benchmark results
#[arg(long = "compare")]
compare: bool,
/// Baseline to compare against
#[arg(long = "baseline")]
baseline: Option<String>,
/// Run only fast benchmarks (shorter duration)
#[arg(long = "fast")]
fast: bool,
},
}