sqry_cli/args/mod.rs
1//! Command-line argument parsing for sqry
2
3pub mod headings;
4mod sort;
5
6use crate::output;
7use clap::{ArgGroup, Args, Parser, Subcommand, ValueEnum};
8pub use sort::SortField;
9use sqry_lsp::LspOptions;
10use std::path::PathBuf;
11
12/// sqry - Semantic Query for Code
13///
14/// Search code by what it means, not just what it says.
15/// Uses AST analysis to find functions, classes, and symbols with precision.
16#[derive(Parser, Debug)]
17#[command(
18 name = "sqry",
19 version,
20 about = "Semantic code search tool",
21 long_about = "sqry is a semantic code search tool that understands code structure through AST analysis.\n\
22 Find functions, classes, and symbols with precision using AST-aware queries.\n\n\
23 Examples:\n \
24 sqry main # Search for 'main' in current directory\n \
25 sqry test src/ # Search for 'test' in src/\n \
26 sqry --kind function . # Find all functions\n \
27 sqry --json main # Output as JSON\n \
28 sqry --csv --headers main # Output as CSV with headers\n \
29 sqry --preview main # Show code context around matches",
30 group = ArgGroup::new("output_format").args(["json", "csv", "tsv"]),
31 verbatim_doc_comment
32)]
33// CLI flags are intentionally modeled as independent booleans for clarity.
34#[allow(clippy::struct_excessive_bools)]
35pub struct Cli {
36 /// Subcommand (optional - defaults to search if pattern provided)
37 #[command(subcommand)]
38 pub command: Option<Box<Command>>,
39
40 /// Search pattern (shorthand for 'search' command)
41 ///
42 /// Supports regex patterns by default. Use --exact for literal matching.
43 #[arg(required = false)]
44 pub pattern: Option<String>,
45
46 /// Search path (defaults to current directory)
47 #[arg(required = false)]
48 pub path: Option<String>,
49
50 /// Output format as JSON
51 #[arg(long, short = 'j', global = true, group = "output_format", help_heading = headings::COMMON_OPTIONS, display_order = 10)]
52 pub json: bool,
53
54 /// Output format as CSV (comma-separated values)
55 ///
56 /// RFC 4180 compliant CSV output. Use with --headers to include column names.
57 /// By default, formula-triggering characters are prefixed with single quote
58 /// for Excel/LibreOffice safety. Use --raw-csv to disable this protection.
59 #[arg(long, global = true, group = "output_format", help_heading = headings::COMMON_OPTIONS, display_order = 12)]
60 pub csv: bool,
61
62 /// Output format as TSV (tab-separated values)
63 ///
64 /// Tab-delimited output for easy Unix pipeline processing.
65 /// Newlines and tabs in field values are replaced with spaces.
66 #[arg(long, global = true, group = "output_format", help_heading = headings::COMMON_OPTIONS, display_order = 13)]
67 pub tsv: bool,
68
69 /// Include header row in CSV/TSV output
70 ///
71 /// Requires --csv or --tsv to be specified.
72 #[arg(long, global = true, help_heading = headings::OUTPUT_CONTROL, display_order = 11)]
73 pub headers: bool,
74
75 /// Columns to include in CSV/TSV output (comma-separated)
76 ///
77 /// Available columns: `name`, `qualified_name`, `kind`, `file`, `line`, `column`,
78 /// `end_line`, `end_column`, `language`, `preview`
79 ///
80 /// Example: --columns name,file,line
81 ///
82 /// Requires --csv or --tsv to be specified.
83 #[arg(long, global = true, value_name = "COLUMNS", help_heading = headings::OUTPUT_CONTROL, display_order = 12)]
84 pub columns: Option<String>,
85
86 /// Output raw CSV without formula injection protection
87 ///
88 /// By default, values starting with =, +, -, @, tab, or carriage return
89 /// are prefixed with single quote to prevent Excel/LibreOffice formula
90 /// injection attacks. Use this flag to disable protection for programmatic
91 /// processing where raw values are needed.
92 ///
93 /// Requires --csv or --tsv to be specified.
94 #[arg(long, global = true, help_heading = headings::OUTPUT_CONTROL, display_order = 13)]
95 pub raw_csv: bool,
96
97 /// Show code context around matches (number of lines before/after)
98 #[arg(
99 long, short = 'p', global = true, value_name = "LINES",
100 default_missing_value = "3", num_args = 0..=1,
101 help_heading = headings::OUTPUT_CONTROL, display_order = 14,
102 long_help = "Show code context around matches (number of lines before/after)\n\n\
103 Displays source code context around each match. Use -p or --preview\n\
104 for default 3 lines, or specify a number like --preview 5.\n\
105 Use --preview 0 to show only the matched line without context.\n\n\
106 Examples:\n \
107 sqry --preview main # 3 lines context (default)\n \
108 sqry -p main # Same as above\n \
109 sqry --preview 5 main # 5 lines context\n \
110 sqry --preview 0 main # No context, just matched line"
111 )]
112 pub preview: Option<usize>,
113
114 /// Disable colored output
115 #[arg(long, global = true, help_heading = headings::COMMON_OPTIONS, display_order = 14)]
116 pub no_color: bool,
117
118 /// Select output color theme (default, dark, light, none)
119 #[arg(
120 long,
121 value_enum,
122 default_value = "default",
123 global = true,
124 help_heading = headings::COMMON_OPTIONS,
125 display_order = 15
126 )]
127 pub theme: crate::output::ThemeName,
128
129 /// Sort results (opt-in)
130 #[arg(
131 long,
132 value_enum,
133 global = true,
134 help_heading = headings::OUTPUT_CONTROL,
135 display_order = 16
136 )]
137 pub sort: Option<SortField>,
138
139 // ===== Pager Flags (P2-29) =====
140 /// Enable pager for output (auto-detected by default)
141 ///
142 /// Forces output to be piped through a pager (like `less`).
143 /// In auto mode (default), paging is enabled when:
144 /// - Output exceeds terminal height
145 /// - stdout is connected to an interactive terminal
146 #[arg(
147 long,
148 global = true,
149 conflicts_with = "no_pager",
150 help_heading = headings::OUTPUT_CONTROL,
151 display_order = 17
152 )]
153 pub pager: bool,
154
155 /// Disable pager (write directly to stdout)
156 ///
157 /// Disables auto-paging, writing all output directly to stdout.
158 /// Useful for scripting or piping to other commands.
159 #[arg(
160 long,
161 global = true,
162 conflicts_with = "pager",
163 help_heading = headings::OUTPUT_CONTROL,
164 display_order = 18
165 )]
166 pub no_pager: bool,
167
168 /// Custom pager command (overrides `$SQRY_PAGER` and `$PAGER`)
169 ///
170 /// Specify a custom pager command. Supports quoted arguments.
171 /// Examples:
172 /// --pager-cmd "less -R"
173 /// --pager-cmd "bat --style=plain"
174 /// --pager-cmd "more"
175 #[arg(
176 long,
177 value_name = "COMMAND",
178 global = true,
179 help_heading = headings::OUTPUT_CONTROL,
180 display_order = 19
181 )]
182 pub pager_cmd: Option<String>,
183
184 /// Filter by symbol type (function, class, struct, etc.)
185 ///
186 /// Applies to search mode (top-level shorthand and `sqry search`).
187 /// For structured queries, use `sqry query "kind:function AND ..."` instead.
188 #[arg(long, short = 'k', value_enum, help_heading = headings::MATCH_BEHAVIOUR, display_order = 20)]
189 pub kind: Option<SymbolKind>,
190
191 /// Filter by programming language
192 ///
193 /// Applies to search mode (top-level shorthand and `sqry search`).
194 /// For structured queries, use `sqry query "lang:rust AND ..."` instead.
195 #[arg(long, short = 'l', help_heading = headings::MATCH_BEHAVIOUR, display_order = 21)]
196 pub lang: Option<String>,
197
198 /// Case-insensitive search
199 #[arg(long, short = 'i', help_heading = headings::MATCH_BEHAVIOUR, display_order = 11)]
200 pub ignore_case: bool,
201
202 /// Exact match (disable regex)
203 ///
204 /// Applies to search mode (top-level shorthand and `sqry search`).
205 #[arg(long, short = 'x', help_heading = headings::MATCH_BEHAVIOUR, display_order = 10)]
206 pub exact: bool,
207
208 /// Show count only (number of matches)
209 #[arg(long, short = 'c', help_heading = headings::OUTPUT_CONTROL, display_order = 10)]
210 pub count: bool,
211
212 /// Maximum directory depth to search
213 #[arg(long, default_value = "32", help_heading = headings::FILE_FILTERING, display_order = 20)]
214 pub max_depth: usize,
215
216 /// Include hidden files and directories
217 #[arg(long, help_heading = headings::FILE_FILTERING, display_order = 10)]
218 pub hidden: bool,
219
220 /// Follow symlinks
221 #[arg(long, help_heading = headings::FILE_FILTERING, display_order = 11)]
222 pub follow: bool,
223
224 /// Enable fuzzy search (requires index)
225 ///
226 /// Applies to search mode (top-level shorthand and `sqry search`).
227 #[arg(long, help_heading = headings::SEARCH_MODES_FUZZY, display_order = 20)]
228 pub fuzzy: bool,
229
230 /// Fuzzy matching algorithm (jaro-winkler or levenshtein)
231 #[arg(long, default_value = "jaro-winkler", value_name = "ALGORITHM", help_heading = headings::SEARCH_MODES_FUZZY, display_order = 30)]
232 pub fuzzy_algorithm: String,
233
234 /// Minimum similarity score for fuzzy matches (0.0-1.0)
235 #[arg(long, default_value = "0.6", value_name = "SCORE", help_heading = headings::SEARCH_MODES_FUZZY, display_order = 31)]
236 pub fuzzy_threshold: f64,
237
238 /// Maximum number of fuzzy candidates to consider
239 #[arg(long, default_value = "1000", value_name = "COUNT", help_heading = headings::SEARCH_MODES_FUZZY, display_order = 40)]
240 pub fuzzy_max_candidates: usize,
241
242 /// Enable JSON streaming mode for fuzzy search
243 ///
244 /// Emits results as JSON-lines (newline-delimited JSON).
245 /// Each line is a `StreamEvent` with either a partial result or final summary.
246 /// Requires --fuzzy (fuzzy search) and is inherently JSON output.
247 #[arg(long, requires = "fuzzy", help_heading = headings::SEARCH_MODES_FUZZY, display_order = 51)]
248 pub json_stream: bool,
249
250 /// Allow fuzzy matching for query field names (opt-in).
251 /// Applies typo correction to field names (e.g., "knd" → "kind").
252 /// Ambiguous corrections are rejected with an error.
253 #[arg(long, global = true, help_heading = headings::SEARCH_MODES_FUZZY, display_order = 52)]
254 pub fuzzy_fields: bool,
255
256 /// Maximum edit distance for fuzzy field correction
257 #[arg(
258 long,
259 default_value_t = 2,
260 global = true,
261 help_heading = headings::SEARCH_MODES_FUZZY,
262 display_order = 53
263 )]
264 pub fuzzy_field_distance: usize,
265
266 /// Maximum number of results to return
267 ///
268 /// Limits the output to a manageable size for downstream consumers.
269 /// Defaults: search=100, query=1000, fuzzy=50
270 #[arg(long, global = true, help_heading = headings::OUTPUT_CONTROL, display_order = 20)]
271 pub limit: Option<usize>,
272
273 /// List enabled languages and exit
274 #[arg(long, global = true, help_heading = headings::COMMON_OPTIONS, display_order = 30)]
275 pub list_languages: bool,
276
277 /// Print cache telemetry to stderr after the command completes
278 #[arg(long, global = true, help_heading = headings::COMMON_OPTIONS, display_order = 40)]
279 pub debug_cache: bool,
280
281 /// Display fully qualified symbol names in CLI output.
282 ///
283 /// Helpful for disambiguating relation queries (callers/callees) where
284 /// multiple namespaces define the same method name.
285 #[arg(long, global = true, help_heading = headings::OUTPUT_CONTROL, display_order = 30)]
286 pub qualified_names: bool,
287
288 // ===== Index Validation Flags (P1-14) =====
289 /// Index validation strictness level (off, warn, fail)
290 ///
291 /// Controls how to handle index corruption during load:
292 /// - off: Skip validation entirely (fastest)
293 /// - warn: Log warnings but continue (default)
294 /// - fail: Abort on validation errors
295 #[arg(long, value_enum, default_value = "warn", global = true, help_heading = headings::INDEX_CONFIGURATION, display_order = 40)]
296 pub validate: ValidationMode,
297
298 /// Automatically rebuild index if validation fails
299 ///
300 /// When set, if index validation fails in strict mode, sqry will
301 /// automatically rebuild the index once and retry. Useful for
302 /// recovering from transient corruption without manual intervention.
303 #[arg(long, requires = "validate", global = true, help_heading = headings::INDEX_CONFIGURATION, display_order = 41)]
304 pub auto_rebuild: bool,
305
306 /// Maximum ratio of dangling references before rebuild (0.0-1.0)
307 ///
308 /// Sets the threshold for dangling reference errors during validation.
309 /// Default: 0.05 (5%). If more than this ratio of symbols have dangling
310 /// references, validation will fail in strict mode.
311 #[arg(long, value_name = "RATIO", global = true, help_heading = headings::INDEX_CONFIGURATION, display_order = 42)]
312 pub threshold_dangling_refs: Option<f64>,
313
314 /// Maximum ratio of orphaned files before rebuild (0.0-1.0)
315 ///
316 /// Sets the threshold for orphaned file errors during validation.
317 /// Default: 0.20 (20%). If more than this ratio of indexed files are
318 /// orphaned (no longer exist on disk), validation will fail.
319 #[arg(long, value_name = "RATIO", global = true, help_heading = headings::INDEX_CONFIGURATION, display_order = 43)]
320 pub threshold_orphaned_files: Option<f64>,
321
322 /// Maximum ratio of ID gaps before warning (0.0-1.0)
323 ///
324 /// Sets the threshold for ID gap warnings during validation.
325 /// Default: 0.10 (10%). If more than this ratio of symbol IDs have gaps,
326 /// validation will warn or fail depending on strictness.
327 #[arg(long, value_name = "RATIO", global = true, help_heading = headings::INDEX_CONFIGURATION, display_order = 44)]
328 pub threshold_id_gaps: Option<f64>,
329
330 // ===== Hybrid Search Flags =====
331 /// Force text search mode (skip semantic, use ripgrep)
332 #[arg(long, short = 't', conflicts_with = "semantic", help_heading = headings::SEARCH_MODES, display_order = 10)]
333 pub text: bool,
334
335 /// Force semantic search mode (skip text fallback)
336 #[arg(long, short = 's', conflicts_with = "text", help_heading = headings::SEARCH_MODES, display_order = 11)]
337 pub semantic: bool,
338
339 /// Disable automatic fallback to text search
340 #[arg(long, conflicts_with_all = ["text", "semantic"], help_heading = headings::SEARCH_MODES, display_order = 20)]
341 pub no_fallback: bool,
342
343 /// Number of context lines for text search results
344 #[arg(long, default_value = "2", help_heading = headings::SEARCH_MODES, display_order = 30)]
345 pub context: usize,
346
347 /// Maximum text search results
348 #[arg(long, default_value = "1000", help_heading = headings::SEARCH_MODES, display_order = 31)]
349 pub max_text_results: usize,
350}
351
352/// Plugin-selection controls shared by indexing and selected read paths.
353#[derive(Args, Debug, Clone, Default)]
354pub struct PluginSelectionArgs {
355 /// Enable all high-cost plugins.
356 ///
357 /// High-cost plugins are those classified as `high_wall_clock` in the
358 /// shared plugin registry.
359 #[arg(long, conflicts_with = "exclude_high_cost", help_heading = headings::PLUGIN_SELECTION, display_order = 10)]
360 pub include_high_cost: bool,
361
362 /// Exclude all high-cost plugins.
363 ///
364 /// This is mainly useful to override `SQRY_INCLUDE_HIGH_COST=1`.
365 #[arg(long, conflicts_with = "include_high_cost", help_heading = headings::PLUGIN_SELECTION, display_order = 20)]
366 pub exclude_high_cost: bool,
367
368 /// Force-enable a plugin by id.
369 ///
370 /// Repeat this flag to enable multiple plugins. Explicit enable beats the
371 /// global high-cost mode unless the same plugin is also explicitly disabled.
372 #[arg(long = "enable-plugin", alias = "enable-language", value_name = "ID", help_heading = headings::PLUGIN_SELECTION, display_order = 30)]
373 pub enable_plugins: Vec<String>,
374
375 /// Force-disable a plugin by id.
376 ///
377 /// Repeat this flag to disable multiple plugins. Explicit disable wins over
378 /// explicit enable and global high-cost mode.
379 #[arg(long = "disable-plugin", alias = "disable-language", value_name = "ID", help_heading = headings::PLUGIN_SELECTION, display_order = 40)]
380 pub disable_plugins: Vec<String>,
381}
382
383/// Batch command arguments with taxonomy headings and workflow ordering
384#[derive(Args, Debug, Clone)]
385pub struct BatchCommand {
386 /// Directory containing the indexed codebase (`.sqry/graph/snapshot.sqry`).
387 #[arg(value_name = "PATH", help_heading = headings::BATCH_INPUTS, display_order = 10)]
388 pub path: Option<String>,
389
390 /// File containing queries (one per line).
391 #[arg(long, value_name = "FILE", help_heading = headings::BATCH_INPUTS, display_order = 20)]
392 pub queries: PathBuf,
393
394 /// Set output format for results.
395 #[arg(long, value_name = "FORMAT", default_value = "text", value_enum, help_heading = headings::BATCH_OUTPUT_TARGETS, display_order = 10)]
396 pub output: BatchFormat,
397
398 /// Write results to specified file instead of stdout.
399 #[arg(long, value_name = "FILE", help_heading = headings::BATCH_OUTPUT_TARGETS, display_order = 20)]
400 pub output_file: Option<PathBuf>,
401
402 /// Continue processing if a query fails.
403 #[arg(long, help_heading = headings::BATCH_SESSION_CONTROL, display_order = 10)]
404 pub continue_on_error: bool,
405
406 /// Print aggregate statistics after completion.
407 #[arg(long, help_heading = headings::BATCH_SESSION_CONTROL, display_order = 20)]
408 pub stats: bool,
409
410 /// Use sequential execution instead of parallel (for debugging).
411 ///
412 /// By default, batch queries execute in parallel for better performance.
413 /// Use this flag to force sequential execution for debugging or profiling.
414 #[arg(long, help_heading = headings::BATCH_SESSION_CONTROL, display_order = 30)]
415 pub sequential: bool,
416}
417
418/// Completions command arguments with taxonomy headings and workflow ordering
419#[derive(Args, Debug, Clone)]
420pub struct CompletionsCommand {
421 /// Shell to generate completions for.
422 #[arg(value_enum, help_heading = headings::COMPLETIONS_SHELL_TARGETS, display_order = 10)]
423 pub shell: Shell,
424}
425
426/// Available subcommands
427#[derive(Subcommand, Debug, Clone)]
428#[command(verbatim_doc_comment)]
429pub enum Command {
430 /// Visualize code relationships as diagrams
431 #[command(display_order = 30)]
432 Visualize(VisualizeCommand),
433
434 /// Search for symbols by pattern (simple pattern matching)
435 ///
436 /// Fast pattern-based search using regex or literal matching.
437 /// Use this for quick searches with simple text patterns.
438 ///
439 /// For complex queries with boolean logic and AST predicates, use 'query' instead.
440 ///
441 /// Examples:
442 /// sqry search "test.*" # Find symbols matching regex
443 /// sqry search "test" --save-as find-tests # Save as alias
444 /// sqry search "test" --validate fail # Strict index validation
445 ///
446 /// For kind/language/fuzzy filtering, use the top-level shorthand:
447 /// sqry --kind function "test" # Filter by kind
448 /// sqry --exact "main" # Exact match
449 /// sqry --fuzzy "config" # Fuzzy search
450 ///
451 /// See also: 'sqry query' for structured AST-aware queries
452 #[command(display_order = 1, verbatim_doc_comment)]
453 Search {
454 /// Search pattern (regex or literal with --exact).
455 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
456 pattern: String,
457
458 /// Search path. For fuzzy search, walks up directory tree to find nearest .sqry-index if needed.
459 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 20)]
460 path: Option<String>,
461
462 /// Save this search as a named alias for later reuse.
463 ///
464 /// The alias can be invoked with @name syntax:
465 /// sqry search "test" --save-as find-tests
466 /// sqry @find-tests src/
467 #[arg(long, value_name = "NAME", help_heading = headings::PERSISTENCE_OPTIONS, display_order = 10)]
468 save_as: Option<String>,
469
470 /// Save alias to global storage (~/.config/sqry/) instead of local.
471 ///
472 /// Global aliases are available across all projects.
473 /// Local aliases (default) are project-specific.
474 #[arg(long, requires = "save_as", help_heading = headings::PERSISTENCE_OPTIONS, display_order = 20)]
475 global: bool,
476
477 /// Optional description for the saved alias.
478 #[arg(long, requires = "save_as", help_heading = headings::PERSISTENCE_OPTIONS, display_order = 30)]
479 description: Option<String>,
480
481 /// Index validation mode before search execution.
482 ///
483 /// Controls how sqry handles stale indices (files removed since indexing):
484 /// - `warn`: Log warning but continue (default)
485 /// - `fail`: Exit with code 2 if >20% of indexed files are missing
486 /// - `off`: Skip validation entirely
487 ///
488 /// Examples:
489 /// sqry search "test" --validate fail # Strict mode
490 /// sqry search "test" --validate off # Fast mode
491 #[arg(long, value_enum, default_value = "warn", help_heading = headings::SECURITY_LIMITS, display_order = 30)]
492 validate: ValidationMode,
493
494 /// Only show symbols active under given cfg predicate.
495 ///
496 /// Filters search results to symbols matching the specified cfg condition.
497 /// Example: --cfg-filter test only shows symbols gated by #[cfg(test)].
498 #[arg(long, value_name = "PREDICATE", help_heading = headings::SEARCH_INPUT, display_order = 30)]
499 cfg_filter: Option<String>,
500
501 /// Include macro-generated symbols in results (default: excluded).
502 ///
503 /// By default, symbols generated by macro expansion (e.g., derive impls)
504 /// are excluded from search results. Use this flag to include them.
505 #[arg(long, help_heading = headings::SEARCH_INPUT, display_order = 31)]
506 include_generated: bool,
507
508 /// Show macro boundary metadata in output.
509 ///
510 /// When enabled, search results include macro boundary information
511 /// such as cfg conditions, macro source, and generated-symbol markers.
512 #[arg(long, help_heading = headings::OUTPUT_CONTROL, display_order = 40)]
513 macro_boundaries: bool,
514 },
515
516 /// Execute AST-aware query (structured queries with boolean logic)
517 ///
518 /// Powerful structured queries using predicates and boolean operators.
519 /// Use this for complex searches that combine multiple criteria.
520 ///
521 /// For simple pattern matching, use 'search' instead.
522 ///
523 /// Predicate examples:
524 /// - kind:function # Find functions
525 /// - name:test # Name contains 'test'
526 /// - lang:rust # Rust files only
527 /// - visibility:public # Public symbols
528 /// - async:true # Async functions
529 ///
530 /// Boolean logic:
531 /// - kind:function AND name:test # Functions with 'test' in name
532 /// - kind:class OR kind:struct # All classes or structs
533 /// - lang:rust AND visibility:public # Public Rust symbols
534 ///
535 /// Relation queries (28 languages with full support):
536 /// - callers:authenticate # Who calls authenticate?
537 /// - callees:processData # What does processData call?
538 /// - exports:UserService # What does `UserService` export?
539 /// - imports:database # What imports database?
540 ///
541 /// Supported for: C, C++, C#, CSS, Dart, Elixir, Go, Groovy, Haskell, HTML,
542 /// Java, JavaScript, Kotlin, Lua, Perl, PHP, Python, R, Ruby, Rust, Scala,
543 /// Shell, SQL, Svelte, Swift, TypeScript, Vue, Zig
544 ///
545 /// Saving as alias:
546 /// sqry query "kind:function AND name:test" --save-as test-funcs
547 /// sqry @test-funcs src/
548 ///
549 /// See also: 'sqry search' for simple pattern-based searches
550 #[command(display_order = 2, verbatim_doc_comment)]
551 Query {
552 /// Query expression with predicates.
553 #[arg(help_heading = headings::QUERY_INPUT, display_order = 10)]
554 query: String,
555
556 /// Search path. If no index exists here, walks up directory tree to find nearest .sqry-index.
557 #[arg(help_heading = headings::QUERY_INPUT, display_order = 20)]
558 path: Option<String>,
559
560 /// Use persistent session (keeps .sqry-index hot for repeated queries).
561 #[arg(long, help_heading = headings::PERFORMANCE_DEBUGGING, display_order = 10)]
562 session: bool,
563
564 /// Explain query execution (debug mode).
565 #[arg(long, help_heading = headings::PERFORMANCE_DEBUGGING, display_order = 20)]
566 explain: bool,
567
568 /// Disable parallel query execution (for A/B performance testing).
569 ///
570 /// By default, OR branches (3+) and symbol filtering (100+) use parallel execution.
571 /// Use this flag to force sequential execution for performance comparison.
572 #[arg(long, help_heading = headings::PERFORMANCE_DEBUGGING, display_order = 30)]
573 no_parallel: bool,
574
575 /// Show verbose output including cache statistics.
576 #[arg(long, short = 'v', help_heading = headings::OUTPUT_CONTROL, display_order = 10)]
577 verbose: bool,
578
579 /// Maximum query execution time in seconds (default: 30s, max: 30s).
580 ///
581 /// Queries exceeding this limit will be terminated with partial results.
582 /// The 30-second ceiling is a NON-NEGOTIABLE security requirement.
583 /// Specify lower values for faster feedback on interactive queries.
584 ///
585 /// Examples:
586 /// sqry query --timeout 10 "impl:Debug" # 10 second timeout
587 /// sqry query --timeout 5 "kind:function" # 5 second timeout
588 #[arg(long, value_name = "SECS", help_heading = headings::SECURITY_LIMITS, display_order = 10)]
589 timeout: Option<u64>,
590
591 /// Maximum number of results to return (default: 10000).
592 ///
593 /// Queries returning more results will be truncated.
594 /// Use this to limit memory usage for large result sets.
595 ///
596 /// Examples:
597 /// sqry query --limit 100 "kind:function" # First 100 functions
598 /// sqry query --limit 1000 "impl:Debug" # First 1000 Debug impls
599 #[arg(long, value_name = "N", help_heading = headings::SECURITY_LIMITS, display_order = 20)]
600 limit: Option<usize>,
601
602 /// Save this query as a named alias for later reuse.
603 ///
604 /// The alias can be invoked with @name syntax:
605 /// sqry query "kind:function" --save-as all-funcs
606 /// sqry @all-funcs src/
607 #[arg(long, value_name = "NAME", help_heading = headings::PERSISTENCE_OPTIONS, display_order = 10)]
608 save_as: Option<String>,
609
610 /// Save alias to global storage (~/.config/sqry/) instead of local.
611 ///
612 /// Global aliases are available across all projects.
613 /// Local aliases (default) are project-specific.
614 #[arg(long, requires = "save_as", help_heading = headings::PERSISTENCE_OPTIONS, display_order = 20)]
615 global: bool,
616
617 /// Optional description for the saved alias.
618 #[arg(long, requires = "save_as", help_heading = headings::PERSISTENCE_OPTIONS, display_order = 30)]
619 description: Option<String>,
620
621 /// Index validation mode before query execution.
622 ///
623 /// Controls how sqry handles stale indices (files removed since indexing):
624 /// - `warn`: Log warning but continue (default)
625 /// - `fail`: Exit with code 2 if >20% of indexed files are missing
626 /// - `off`: Skip validation entirely
627 ///
628 /// Examples:
629 /// sqry query "kind:function" --validate fail # Strict mode
630 /// sqry query "kind:function" --validate off # Fast mode
631 #[arg(long, value_enum, default_value = "warn", help_heading = headings::SECURITY_LIMITS, display_order = 30)]
632 validate: ValidationMode,
633
634 /// Substitute variables in the query expression.
635 ///
636 /// Variables are referenced as $name in queries and resolved before execution.
637 /// Specify as KEY=VALUE pairs; can be repeated.
638 ///
639 /// Examples:
640 /// sqry query "kind:\$type" --var type=function
641 /// sqry query "kind:\$k AND lang:\$l" --var k=function --var l=rust
642 #[arg(long = "var", value_name = "KEY=VALUE", help_heading = headings::QUERY_INPUT, display_order = 30)]
643 var: Vec<String>,
644
645 #[command(flatten)]
646 plugin_selection: PluginSelectionArgs,
647 },
648
649 /// Graph-based queries and analysis
650 ///
651 /// Advanced graph operations using the unified graph architecture.
652 /// All subcommands are noun-based and represent different analysis types.
653 ///
654 /// Available analyses:
655 /// - `trace-path <from> <to>` # Find shortest path between symbols
656 /// - `call-chain-depth <symbol>` # Calculate maximum call depth
657 /// - `dependency-tree <module>` # Show transitive dependencies
658 /// - nodes # List unified graph nodes
659 /// - edges # List unified graph edges
660 /// - cross-language # List cross-language relationships
661 /// - stats # Show graph statistics
662 /// - cycles # Detect circular dependencies
663 /// - complexity # Calculate code complexity
664 ///
665 /// All commands support --format json for programmatic use.
666 #[command(display_order = 20)]
667 Graph {
668 #[command(subcommand)]
669 operation: GraphOperation,
670
671 /// Search path (defaults to current directory).
672 #[arg(long, help_heading = headings::GRAPH_CONFIGURATION, display_order = 10)]
673 path: Option<String>,
674
675 /// Output format (json, text, dot, mermaid, d2).
676 #[arg(long, short = 'f', default_value = "text", help_heading = headings::GRAPH_CONFIGURATION, display_order = 20)]
677 format: String,
678
679 /// Show verbose output with detailed metadata.
680 #[arg(long, short = 'v', help_heading = headings::GRAPH_CONFIGURATION, display_order = 30)]
681 verbose: bool,
682 },
683
684 /// Start an interactive shell that keeps the session cache warm
685 #[command(display_order = 60)]
686 Shell {
687 /// Directory containing the `.sqry-index` file.
688 #[arg(value_name = "PATH", help_heading = headings::SHELL_CONFIGURATION, display_order = 10)]
689 path: Option<String>,
690 },
691
692 /// Execute multiple queries from a batch file using a warm session
693 #[command(display_order = 61)]
694 Batch(BatchCommand),
695
696 /// Build symbol index and graph analyses for fast queries
697 ///
698 /// Creates a persistent index of all symbols in the specified directory.
699 /// The index is saved to .sqry/ and includes precomputed graph analyses
700 /// for cycle detection, reachability, and path queries.
701 /// Uses parallel processing by default for faster indexing.
702 #[command(display_order = 10)]
703 Index {
704 /// Directory to index (defaults to current directory).
705 #[arg(help_heading = headings::INDEX_INPUT, display_order = 10)]
706 path: Option<String>,
707
708 /// Force rebuild even if index exists.
709 #[arg(long, short = 'f', alias = "rebuild", help_heading = headings::INDEX_CONFIGURATION, display_order = 10)]
710 force: bool,
711
712 /// Show index status without building.
713 ///
714 /// Returns metadata about the existing index (age, symbol count, languages).
715 /// Useful for programmatic consumers to check if indexing is needed.
716 #[arg(long, short = 's', help_heading = headings::INDEX_CONFIGURATION, display_order = 20)]
717 status: bool,
718
719 /// Automatically add .sqry-index/ to .gitignore if not already present.
720 #[arg(long, help_heading = headings::INDEX_CONFIGURATION, display_order = 30)]
721 add_to_gitignore: bool,
722
723 /// Number of threads for parallel indexing (default: auto-detect).
724 ///
725 /// Set to 1 for single-threaded (useful for debugging).
726 /// Defaults to number of CPU cores.
727 #[arg(long, short = 't', help_heading = headings::PERFORMANCE_TUNING, display_order = 10)]
728 threads: Option<usize>,
729
730 /// Disable incremental indexing (hash-based change detection).
731 ///
732 /// When set, indexing will skip the persistent hash index and avoid
733 /// hash-based change detection entirely. Useful for debugging or
734 /// forcing metadata-only evaluation.
735 #[arg(long = "no-incremental", help_heading = headings::PERFORMANCE_TUNING, display_order = 20)]
736 no_incremental: bool,
737
738 /// Override cache directory for incremental indexing (default: .sqry-cache).
739 ///
740 /// Points sqry at an alternate cache location for the hash index.
741 /// Handy for ephemeral or sandboxed environments.
742 #[arg(long = "cache-dir", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 10)]
743 cache_dir: Option<String>,
744
745 /// Disable index compression (P1-12: Index Compression).
746 ///
747 /// By default, indexes are compressed with zstd for faster load times
748 /// and reduced disk space. Use this flag to store uncompressed indexes
749 /// (useful for debugging or compatibility testing).
750 #[arg(long, help_heading = headings::ADVANCED_CONFIGURATION, display_order = 20)]
751 no_compress: bool,
752
753 /// Metrics export format for validation status (json or prometheus).
754 ///
755 /// Used with --status --json to export validation metrics in different
756 /// formats. Prometheus format outputs OpenMetrics-compatible text for
757 /// monitoring systems. JSON format (default) provides structured data.
758 #[arg(long, short = 'M', value_enum, default_value = "json", requires = "status", help_heading = headings::OUTPUT_CONTROL, display_order = 30)]
759 metrics_format: MetricsFormat,
760
761 /// Enable live macro expansion during indexing (executes cargo expand — security opt-in).
762 ///
763 /// When enabled, sqry runs `cargo expand` to capture macro-generated symbols.
764 /// This executes build scripts and proc macros, so only use on trusted codebases.
765 #[arg(long, help_heading = headings::ADVANCED_CONFIGURATION, display_order = 30)]
766 enable_macro_expansion: bool,
767
768 /// Set active cfg flags for conditional compilation analysis.
769 ///
770 /// Can be specified multiple times (e.g., --cfg test --cfg unix).
771 /// Symbols gated by `#[cfg()]` will be marked active/inactive based on these flags.
772 #[arg(long = "cfg", value_name = "PREDICATE", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 31)]
773 cfg_flags: Vec<String>,
774
775 /// Use pre-generated expand cache instead of live expansion.
776 ///
777 /// Points to a directory containing cached macro expansion output
778 /// (generated by `sqry cache expand`). Avoids executing cargo expand
779 /// during indexing.
780 #[arg(long, value_name = "DIR", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 32)]
781 expand_cache: Option<PathBuf>,
782
783 /// Enable JVM classpath analysis.
784 ///
785 /// Detects the project's build system (Gradle, Maven, Bazel, sbt),
786 /// resolves dependency JARs, parses bytecode into class stubs, and
787 /// emits synthetic graph nodes for classpath types. Enables cross-
788 /// reference resolution from workspace source to library classes.
789 ///
790 /// Requires the `jvm-classpath` feature at compile time.
791 #[arg(long, help_heading = headings::ADVANCED_CONFIGURATION, display_order = 40)]
792 classpath: bool,
793
794 /// Disable classpath analysis (overrides config defaults).
795 #[arg(long, conflicts_with = "classpath", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 41)]
796 no_classpath: bool,
797
798 /// Classpath analysis depth.
799 ///
800 /// `full` (default): include all transitive dependencies.
801 /// `shallow`: only direct (compile-scope) dependencies.
802 #[arg(long, value_enum, default_value = "full", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 42)]
803 classpath_depth: ClasspathDepthArg,
804
805 /// Manual classpath file (one JAR path per line).
806 ///
807 /// When provided, skips build system detection and resolution entirely.
808 /// Lines starting with `#` are treated as comments.
809 #[arg(long, value_name = "FILE", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 43)]
810 classpath_file: Option<PathBuf>,
811
812 /// Override build system detection for classpath analysis.
813 ///
814 /// Valid values: gradle, maven, bazel, sbt (case-insensitive).
815 #[arg(long, value_name = "SYSTEM", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 44)]
816 build_system: Option<String>,
817
818 /// Force classpath re-resolution (ignore cached classpath).
819 #[arg(long, help_heading = headings::ADVANCED_CONFIGURATION, display_order = 45)]
820 force_classpath: bool,
821
822 #[command(flatten)]
823 plugin_selection: PluginSelectionArgs,
824 },
825
826 /// Build precomputed graph analyses for fast query performance
827 ///
828 /// Computes CSR adjacency, SCC (Strongly Connected Components), condensation DAGs,
829 /// and 2-hop interval labels to eliminate O(V+E) query-time costs. Analysis files
830 /// are persisted to .sqry/analysis/ and enable fast cycle detection, reachability
831 /// queries, and path finding.
832 ///
833 /// Note: `sqry index` already builds a ready graph with analysis artifacts.
834 /// Run `sqry analyze` when you want to rebuild analyses with explicit
835 /// tuning controls or after changing analysis configuration.
836 ///
837 /// Examples:
838 /// sqry analyze # Rebuild analyses for current index
839 /// sqry analyze --force # Force analysis rebuild
840 #[command(display_order = 13, verbatim_doc_comment)]
841 Analyze {
842 /// Search path (defaults to current directory).
843 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
844 path: Option<String>,
845
846 /// Force rebuild even if analysis files exist.
847 #[arg(long, short = 'f', help_heading = headings::INDEX_CONFIGURATION, display_order = 10)]
848 force: bool,
849
850 /// Number of threads for parallel analysis (default: auto-detect).
851 ///
852 /// Controls the rayon thread pool size for SCC/condensation DAG
853 /// computation. Set to 1 for single-threaded (useful for debugging).
854 /// Defaults to number of CPU cores.
855 #[arg(long, short = 't', help_heading = headings::PERFORMANCE_TUNING, display_order = 10)]
856 threads: Option<usize>,
857
858 /// Override maximum 2-hop label intervals per edge kind.
859 ///
860 /// Controls the maximum number of reachability intervals computed
861 /// per edge kind. Larger budgets enable O(1) reachability queries
862 /// but use more memory. Default: from config or 15,000,000.
863 #[arg(long = "label-budget", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 30)]
864 label_budget: Option<u64>,
865
866 /// Override density gate threshold.
867 ///
868 /// Skip 2-hop label computation when `condensation_edges > threshold * scc_count`.
869 /// Prevents multi-minute hangs on dense import/reference graphs.
870 /// 0 = disabled. Default: from config or 64.
871 #[arg(long = "density-threshold", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 31)]
872 density_threshold: Option<u64>,
873
874 /// Override budget-exceeded policy: `"degrade"` (BFS fallback) or `"fail"`.
875 ///
876 /// When the label budget is exceeded for an edge kind:
877 /// - `"degrade"`: Fall back to BFS on the condensation DAG (default)
878 /// - "fail": Return an error and abort analysis
879 #[arg(long = "budget-exceeded-policy", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 32, value_parser = clap::builder::PossibleValuesParser::new(["degrade", "fail"]))]
880 budget_exceeded_policy: Option<String>,
881
882 /// Skip 2-hop interval label computation entirely.
883 ///
884 /// When set, the analysis builds CSR + SCC + Condensation DAG but skips
885 /// the expensive 2-hop label phase. Reachability queries fall back to BFS
886 /// on the condensation DAG (~10-50ms per query instead of O(1)).
887 /// Useful for very large codebases where label computation is too slow.
888 #[arg(long = "no-labels", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 33)]
889 no_labels: bool,
890 },
891
892 /// Start the sqry Language Server Protocol endpoint
893 #[command(display_order = 50)]
894 Lsp {
895 #[command(flatten)]
896 options: LspOptions,
897 },
898
899 /// Update existing symbol index
900 ///
901 /// Incrementally updates the index by re-indexing only changed files.
902 /// Much faster than a full rebuild for large codebases.
903 #[command(display_order = 11)]
904 Update {
905 /// Directory with existing index (defaults to current directory).
906 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
907 path: Option<String>,
908
909 /// Number of threads for parallel indexing (default: auto-detect).
910 ///
911 /// Set to 1 for single-threaded (useful for debugging).
912 /// Defaults to number of CPU cores.
913 #[arg(long, short = 't', help_heading = headings::PERFORMANCE_TUNING, display_order = 10)]
914 threads: Option<usize>,
915
916 /// Disable incremental indexing (force metadata-only or full updates).
917 ///
918 /// When set, the update process will not use the hash index and will
919 /// rely on metadata-only checks for staleness.
920 #[arg(long = "no-incremental", help_heading = headings::UPDATE_CONFIGURATION, display_order = 10)]
921 no_incremental: bool,
922
923 /// Override cache directory for incremental indexing (default: .sqry-cache).
924 ///
925 /// Points sqry at an alternate cache location for the hash index.
926 #[arg(long = "cache-dir", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 10)]
927 cache_dir: Option<String>,
928
929 /// Show statistics about the update.
930 #[arg(long, help_heading = headings::OUTPUT_CONTROL, display_order = 10)]
931 stats: bool,
932
933 /// Enable JVM classpath analysis.
934 #[arg(long, help_heading = headings::ADVANCED_CONFIGURATION, display_order = 40)]
935 classpath: bool,
936
937 /// Disable classpath analysis (overrides config defaults).
938 #[arg(long, conflicts_with = "classpath", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 41)]
939 no_classpath: bool,
940
941 /// Classpath analysis depth.
942 #[arg(long, value_enum, default_value = "full", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 42)]
943 classpath_depth: ClasspathDepthArg,
944
945 /// Manual classpath file (one JAR path per line).
946 #[arg(long, value_name = "FILE", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 43)]
947 classpath_file: Option<PathBuf>,
948
949 /// Override build system detection for classpath analysis.
950 #[arg(long, value_name = "SYSTEM", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 44)]
951 build_system: Option<String>,
952
953 /// Force classpath re-resolution (ignore cached classpath).
954 #[arg(long, help_heading = headings::ADVANCED_CONFIGURATION, display_order = 45)]
955 force_classpath: bool,
956
957 #[command(flatten)]
958 plugin_selection: PluginSelectionArgs,
959 },
960
961 /// Watch directory and auto-update index on file changes
962 ///
963 /// Monitors the directory for file system changes and automatically updates
964 /// the index in real-time. Uses OS-level file monitoring (inotify/FSEvents/Windows)
965 /// for <1ms change detection latency.
966 ///
967 /// Press Ctrl+C to stop watching.
968 #[command(display_order = 12)]
969 Watch {
970 /// Directory to watch (defaults to current directory).
971 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
972 path: Option<String>,
973
974 /// Number of threads for parallel indexing (default: auto-detect).
975 ///
976 /// Set to 1 for single-threaded (useful for debugging).
977 /// Defaults to number of CPU cores.
978 #[arg(long, short = 't', help_heading = headings::PERFORMANCE_TUNING, display_order = 10)]
979 threads: Option<usize>,
980
981 /// Build initial index if it doesn't exist.
982 #[arg(long, help_heading = headings::WATCH_CONFIGURATION, display_order = 10)]
983 build: bool,
984
985 /// Debounce duration in milliseconds.
986 ///
987 /// Wait time after detecting a change before processing to collect
988 /// rapid-fire changes (e.g., from editor saves).
989 ///
990 /// Default is platform-aware: 400ms on macOS, 100ms on Linux/Windows.
991 /// Can also be set via `SQRY_LIMITS__WATCH__DEBOUNCE_MS` env var.
992 #[arg(long, short = 'd', help_heading = headings::WATCH_CONFIGURATION, display_order = 20)]
993 debounce: Option<u64>,
994
995 /// Show detailed statistics for each update.
996 #[arg(long, short = 's', help_heading = headings::OUTPUT_CONTROL, display_order = 10)]
997 stats: bool,
998
999 /// Enable JVM classpath analysis.
1000 #[arg(long, help_heading = headings::ADVANCED_CONFIGURATION, display_order = 40)]
1001 classpath: bool,
1002
1003 /// Disable classpath analysis (overrides config defaults).
1004 #[arg(long, conflicts_with = "classpath", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 41)]
1005 no_classpath: bool,
1006
1007 /// Classpath analysis depth.
1008 #[arg(long, value_enum, default_value = "full", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 42)]
1009 classpath_depth: ClasspathDepthArg,
1010
1011 /// Manual classpath file (one JAR path per line).
1012 #[arg(long, value_name = "FILE", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 43)]
1013 classpath_file: Option<PathBuf>,
1014
1015 /// Override build system detection for classpath analysis.
1016 #[arg(long, value_name = "SYSTEM", help_heading = headings::ADVANCED_CONFIGURATION, display_order = 44)]
1017 build_system: Option<String>,
1018
1019 /// Force classpath re-resolution (ignore cached classpath).
1020 #[arg(long, help_heading = headings::ADVANCED_CONFIGURATION, display_order = 45)]
1021 force_classpath: bool,
1022
1023 #[command(flatten)]
1024 plugin_selection: PluginSelectionArgs,
1025 },
1026
1027 /// Repair corrupted index by fixing common issues
1028 ///
1029 /// Automatically detects and fixes common index corruption issues:
1030 /// - Orphaned symbols (files no longer exist)
1031 /// - Dangling references (symbols reference non-existent dependencies)
1032 /// - Invalid checksums
1033 ///
1034 /// Use --dry-run to preview changes without modifying the index.
1035 #[command(display_order = 14)]
1036 Repair {
1037 /// Directory with existing index (defaults to current directory).
1038 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
1039 path: Option<String>,
1040
1041 /// Remove symbols for files that no longer exist on disk.
1042 #[arg(long, help_heading = headings::REPAIR_OPTIONS, display_order = 10)]
1043 fix_orphans: bool,
1044
1045 /// Remove dangling references to non-existent symbols.
1046 #[arg(long, help_heading = headings::REPAIR_OPTIONS, display_order = 20)]
1047 fix_dangling: bool,
1048
1049 /// Recompute index checksum after repairs.
1050 #[arg(long, help_heading = headings::REPAIR_OPTIONS, display_order = 30)]
1051 recompute_checksum: bool,
1052
1053 /// Fix all detected issues (combines all repair options).
1054 #[arg(long, conflicts_with_all = ["fix_orphans", "fix_dangling", "recompute_checksum"], help_heading = headings::REPAIR_OPTIONS, display_order = 5)]
1055 fix_all: bool,
1056
1057 /// Preview changes without modifying the index (dry run).
1058 #[arg(long, help_heading = headings::REPAIR_OPTIONS, display_order = 40)]
1059 dry_run: bool,
1060 },
1061
1062 /// Manage AST cache
1063 ///
1064 /// Control the disk-persisted AST cache that speeds up queries by avoiding
1065 /// expensive tree-sitter parsing. The cache is stored in .sqry-cache/ and
1066 /// is shared across all sqry processes.
1067 #[command(display_order = 41)]
1068 Cache {
1069 #[command(subcommand)]
1070 action: CacheAction,
1071 },
1072
1073 /// Manage graph config (.sqry/graph/config/config.json)
1074 ///
1075 /// Configure sqry behavior through the unified config partition.
1076 /// All settings are stored in `.sqry/graph/config/config.json`.
1077 ///
1078 /// Examples:
1079 /// sqry config init # Initialize config with defaults
1080 /// sqry config show # Display effective config
1081 /// sqry config set `limits.max_results` 10000 # Update a setting
1082 /// sqry config get `limits.max_results` # Get a single value
1083 /// sqry config validate # Validate config file
1084 /// sqry config alias set my-funcs "kind:function" # Create alias
1085 /// sqry config alias list # List all aliases
1086 #[command(display_order = 40, verbatim_doc_comment)]
1087 Config {
1088 #[command(subcommand)]
1089 action: ConfigAction,
1090 },
1091
1092 /// Generate shell completions
1093 ///
1094 /// Generate shell completion scripts for bash, zsh, fish, or `PowerShell`.
1095 /// Install by redirecting output to the appropriate location for your shell.
1096 ///
1097 /// Examples:
1098 /// sqry completions bash > /`etc/bash_completion.d/sqry`
1099 /// sqry completions zsh > ~/.zfunc/_sqry
1100 /// sqry completions fish > ~/.config/fish/completions/sqry.fish
1101 #[command(display_order = 45, verbatim_doc_comment)]
1102 Completions(CompletionsCommand),
1103
1104 /// Manage multi-repository workspaces
1105 #[command(display_order = 42)]
1106 Workspace {
1107 #[command(subcommand)]
1108 action: WorkspaceCommand,
1109 },
1110
1111 /// Manage saved query aliases
1112 ///
1113 /// Save frequently used queries as named aliases for easy reuse.
1114 /// Aliases can be stored globally (~/.config/sqry/) or locally (.sqry-index.user).
1115 ///
1116 /// Examples:
1117 /// sqry alias list # List all aliases
1118 /// sqry alias show my-funcs # Show alias details
1119 /// sqry alias delete my-funcs # Delete an alias
1120 /// sqry alias rename old-name new # Rename an alias
1121 ///
1122 /// To create an alias, use --save-as with search/query commands:
1123 /// sqry query "kind:function" --save-as my-funcs
1124 /// sqry search "test" --save-as find-tests --global
1125 ///
1126 /// To execute an alias, use @name syntax:
1127 /// sqry @my-funcs
1128 /// sqry @find-tests src/
1129 #[command(display_order = 43, verbatim_doc_comment)]
1130 Alias {
1131 #[command(subcommand)]
1132 action: AliasAction,
1133 },
1134
1135 /// Manage query history
1136 ///
1137 /// View and manage your query history. History is recorded automatically
1138 /// for search and query commands (unless disabled via `SQRY_NO_HISTORY=1`).
1139 ///
1140 /// Examples:
1141 /// sqry history list # List recent queries
1142 /// sqry history list --limit 50 # Show last 50 queries
1143 /// sqry history search "function" # Search history
1144 /// sqry history clear # Clear all history
1145 /// sqry history clear --older 30d # Clear entries older than 30 days
1146 /// sqry history stats # Show history statistics
1147 ///
1148 /// Sensitive data (API keys, tokens) is automatically redacted.
1149 #[command(display_order = 44, verbatim_doc_comment)]
1150 History {
1151 #[command(subcommand)]
1152 action: HistoryAction,
1153 },
1154
1155 /// Natural language interface for sqry queries
1156 ///
1157 /// Translate natural language descriptions into sqry commands.
1158 /// Uses a safety-focused translation pipeline that validates all
1159 /// generated commands before execution.
1160 ///
1161 /// Response tiers based on confidence:
1162 /// - Execute (≥85%): Run command automatically
1163 /// - Confirm (65-85%): Ask for user confirmation
1164 /// - Disambiguate (<65%): Present options to choose from
1165 /// - Reject: Cannot safely translate
1166 ///
1167 /// Examples:
1168 /// sqry ask "find all public functions in rust"
1169 /// sqry ask "who calls authenticate"
1170 /// sqry ask "trace path from main to database"
1171 /// sqry ask --auto-execute "find all classes"
1172 ///
1173 /// Safety: Commands are validated against a whitelist and checked
1174 /// for shell injection, path traversal, and other attacks.
1175 #[command(display_order = 3, verbatim_doc_comment)]
1176 Ask {
1177 /// Natural language query to translate.
1178 #[arg(help_heading = headings::NL_INPUT, display_order = 10)]
1179 query: String,
1180
1181 /// Search path (defaults to current directory).
1182 #[arg(help_heading = headings::NL_INPUT, display_order = 20)]
1183 path: Option<String>,
1184
1185 /// Auto-execute high-confidence commands without confirmation.
1186 ///
1187 /// When enabled, commands with ≥85% confidence will execute
1188 /// immediately. Otherwise, all commands require confirmation.
1189 #[arg(long, help_heading = headings::NL_CONFIGURATION, display_order = 10)]
1190 auto_execute: bool,
1191
1192 /// Show the translated command without executing.
1193 ///
1194 /// Useful for understanding what command would be generated
1195 /// from your natural language query.
1196 #[arg(long, help_heading = headings::NL_CONFIGURATION, display_order = 20)]
1197 dry_run: bool,
1198
1199 /// Minimum confidence threshold for auto-execution (0.0-1.0).
1200 ///
1201 /// Commands with confidence below this threshold will always
1202 /// require confirmation, even with --auto-execute.
1203 #[arg(long, default_value = "0.85", help_heading = headings::NL_CONFIGURATION, display_order = 30)]
1204 threshold: f32,
1205 },
1206
1207 /// View usage insights and manage local diagnostics
1208 ///
1209 /// sqry captures anonymous behavioral patterns locally to help you
1210 /// understand your usage and improve the tool. All data stays on
1211 /// your machine unless you explicitly choose to share.
1212 ///
1213 /// Examples:
1214 /// sqry insights show # Show current week's summary
1215 /// sqry insights show --week 2025-W50 # Show specific week
1216 /// sqry insights config # Show configuration
1217 /// sqry insights config --disable # Disable uses capture
1218 /// sqry insights status # Show storage status
1219 /// sqry insights prune --older 90d # Clean up old data
1220 ///
1221 /// Privacy: All data is stored locally. No network calls are made
1222 /// unless you explicitly use --share (which generates a file, not
1223 /// a network request).
1224 #[command(display_order = 62, verbatim_doc_comment)]
1225 Insights {
1226 #[command(subcommand)]
1227 action: InsightsAction,
1228 },
1229
1230 /// Generate a troubleshooting bundle for issue reporting
1231 ///
1232 /// Creates a structured bundle containing diagnostic information
1233 /// that can be shared with the sqry team. All data is sanitized -
1234 /// no code content, file paths, or secrets are included.
1235 ///
1236 /// The bundle includes:
1237 /// - System information (OS, architecture)
1238 /// - sqry version and build type
1239 /// - Sanitized configuration
1240 /// - Recent use events (last 24h)
1241 /// - Recent errors
1242 ///
1243 /// Examples:
1244 /// sqry troubleshoot # Generate to stdout
1245 /// sqry troubleshoot -o bundle.json # Save to file
1246 /// sqry troubleshoot --dry-run # Preview without generating
1247 /// sqry troubleshoot --include-trace # Include workflow trace
1248 ///
1249 /// Privacy: No paths, code content, or secrets are included.
1250 /// Review the output before sharing if you have concerns.
1251 #[command(display_order = 63, verbatim_doc_comment)]
1252 Troubleshoot {
1253 /// Output file path (default: stdout)
1254 #[arg(short = 'o', long, value_name = "FILE", help_heading = headings::INSIGHTS_OUTPUT, display_order = 10)]
1255 output: Option<String>,
1256
1257 /// Preview bundle contents without generating
1258 #[arg(long = "dry-run", help_heading = headings::INSIGHTS_CONFIGURATION, display_order = 10)]
1259 dry_run: bool,
1260
1261 /// Include workflow trace (opt-in, requires explicit consent)
1262 ///
1263 /// Adds a sequence of recent workflow steps to the bundle.
1264 /// The trace helps understand how operations were performed
1265 /// but reveals more behavioral patterns than the default bundle.
1266 #[arg(long, help_heading = headings::INSIGHTS_CONFIGURATION, display_order = 20)]
1267 include_trace: bool,
1268
1269 /// Time window for events to include (e.g., 24h, 7d)
1270 ///
1271 /// Defaults to 24 hours. Longer windows provide more context
1272 /// but may include older events.
1273 #[arg(long, default_value = "24h", value_name = "DURATION", help_heading = headings::INSIGHTS_CONFIGURATION, display_order = 30)]
1274 window: String,
1275 },
1276
1277 /// Find duplicate code in the codebase
1278 ///
1279 /// Detects similar or identical code patterns using structural analysis.
1280 /// Supports different duplicate types:
1281 /// - body: Functions with identical/similar bodies
1282 /// - signature: Functions with identical signatures
1283 /// - struct: Structs with similar field layouts
1284 ///
1285 /// Examples:
1286 /// sqry duplicates # Find body duplicates
1287 /// sqry duplicates --type signature # Find signature duplicates
1288 /// sqry duplicates --threshold 90 # 90% similarity threshold
1289 /// sqry duplicates --exact # Exact matches only
1290 #[command(display_order = 21, verbatim_doc_comment)]
1291 Duplicates {
1292 /// Search path (defaults to current directory).
1293 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
1294 path: Option<String>,
1295
1296 /// Type of duplicate detection.
1297 ///
1298 /// - body: Functions with identical/similar bodies (default)
1299 /// - signature: Functions with identical signatures
1300 /// - struct: Structs with similar field layouts
1301 #[arg(long, short = 't', default_value = "body", help_heading = headings::DUPLICATE_OPTIONS, display_order = 10)]
1302 r#type: String,
1303
1304 /// Similarity threshold (0-100, default: 80).
1305 ///
1306 /// Higher values require more similarity to be considered duplicates.
1307 /// 100 means exact matches only.
1308 #[arg(long, default_value = "80", help_heading = headings::DUPLICATE_OPTIONS, display_order = 20)]
1309 threshold: u32,
1310
1311 /// Maximum results to return.
1312 #[arg(long, default_value = "100", help_heading = headings::OUTPUT_CONTROL, display_order = 10)]
1313 max_results: usize,
1314
1315 /// Exact matches only (equivalent to --threshold 100).
1316 #[arg(long, help_heading = headings::DUPLICATE_OPTIONS, display_order = 30)]
1317 exact: bool,
1318 },
1319
1320 /// Find circular dependencies in the codebase
1321 ///
1322 /// Detects cycles in call graphs, import graphs, or module dependencies.
1323 /// Uses Tarjan's SCC algorithm for efficient O(V+E) detection.
1324 ///
1325 /// Examples:
1326 /// sqry cycles # Find call cycles
1327 /// sqry cycles --type imports # Find import cycles
1328 /// sqry cycles --min-depth 3 # Cycles with 3+ nodes
1329 /// sqry cycles --include-self # Include self-loops
1330 #[command(display_order = 22, verbatim_doc_comment)]
1331 Cycles {
1332 /// Search path (defaults to current directory).
1333 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
1334 path: Option<String>,
1335
1336 /// Type of cycle detection.
1337 ///
1338 /// - calls: Function/method call cycles (default)
1339 /// - imports: File import cycles
1340 /// - modules: Module-level cycles
1341 #[arg(long, short = 't', default_value = "calls", help_heading = headings::CYCLE_OPTIONS, display_order = 10)]
1342 r#type: String,
1343
1344 /// Minimum cycle depth (default: 2).
1345 #[arg(long, default_value = "2", help_heading = headings::CYCLE_OPTIONS, display_order = 20)]
1346 min_depth: usize,
1347
1348 /// Maximum cycle depth (default: unlimited).
1349 #[arg(long, help_heading = headings::CYCLE_OPTIONS, display_order = 30)]
1350 max_depth: Option<usize>,
1351
1352 /// Include self-loops (A → A).
1353 #[arg(long, help_heading = headings::CYCLE_OPTIONS, display_order = 40)]
1354 include_self: bool,
1355
1356 /// Maximum results to return.
1357 #[arg(long, default_value = "100", help_heading = headings::OUTPUT_CONTROL, display_order = 10)]
1358 max_results: usize,
1359 },
1360
1361 /// Find unused/dead code in the codebase
1362 ///
1363 /// Detects symbols that are never referenced using reachability analysis.
1364 /// Entry points (main, public lib exports, tests) are considered reachable.
1365 ///
1366 /// Examples:
1367 /// sqry unused # Find all unused symbols
1368 /// sqry unused --scope public # Only public unused symbols
1369 /// sqry unused --scope function # Only unused functions
1370 /// sqry unused --lang rust # Only in Rust files
1371 #[command(display_order = 23, verbatim_doc_comment)]
1372 Unused {
1373 /// Search path (defaults to current directory).
1374 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
1375 path: Option<String>,
1376
1377 /// Scope of unused detection.
1378 ///
1379 /// - all: All unused symbols (default)
1380 /// - public: Public symbols with no external references
1381 /// - private: Private symbols with no references
1382 /// - function: Unused functions only
1383 /// - struct: Unused structs/types only
1384 #[arg(long, short = 's', default_value = "all", help_heading = headings::UNUSED_OPTIONS, display_order = 10)]
1385 scope: String,
1386
1387 /// Filter by language.
1388 #[arg(long, help_heading = headings::UNUSED_OPTIONS, display_order = 20)]
1389 lang: Option<String>,
1390
1391 /// Filter by symbol kind.
1392 #[arg(long, help_heading = headings::UNUSED_OPTIONS, display_order = 30)]
1393 kind: Option<String>,
1394
1395 /// Maximum results to return.
1396 #[arg(long, default_value = "100", help_heading = headings::OUTPUT_CONTROL, display_order = 10)]
1397 max_results: usize,
1398 },
1399
1400 /// Export the code graph in various formats
1401 ///
1402 /// Exports the unified code graph to DOT, D2, Mermaid, or JSON formats
1403 /// for visualization or further analysis.
1404 ///
1405 /// Examples:
1406 /// sqry export # DOT format to stdout
1407 /// sqry export --format mermaid # Mermaid format
1408 /// sqry export --format d2 -o graph.d2 # D2 format to file
1409 /// sqry export --highlight-cross # Highlight cross-language edges
1410 /// sqry export --filter-lang rust,python # Filter languages
1411 #[command(display_order = 31, verbatim_doc_comment)]
1412 Export {
1413 /// Search path (defaults to current directory).
1414 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
1415 path: Option<String>,
1416
1417 /// Output format.
1418 ///
1419 /// - dot: Graphviz DOT format (default)
1420 /// - d2: D2 diagram format
1421 /// - mermaid: Mermaid markdown format
1422 /// - json: JSON format for programmatic use
1423 #[arg(long, short = 'f', default_value = "dot", help_heading = headings::EXPORT_OPTIONS, display_order = 10)]
1424 format: String,
1425
1426 /// Graph layout direction.
1427 ///
1428 /// - lr: Left to right (default)
1429 /// - tb: Top to bottom
1430 #[arg(long, short = 'd', default_value = "lr", help_heading = headings::EXPORT_OPTIONS, display_order = 20)]
1431 direction: String,
1432
1433 /// Filter by languages (comma-separated).
1434 #[arg(long, help_heading = headings::EXPORT_OPTIONS, display_order = 30)]
1435 filter_lang: Option<String>,
1436
1437 /// Filter by edge types (comma-separated: calls,imports,exports).
1438 #[arg(long, help_heading = headings::EXPORT_OPTIONS, display_order = 40)]
1439 filter_edge: Option<String>,
1440
1441 /// Highlight cross-language edges.
1442 #[arg(long, help_heading = headings::EXPORT_OPTIONS, display_order = 50)]
1443 highlight_cross: bool,
1444
1445 /// Show node details (signatures, docs).
1446 #[arg(long, help_heading = headings::EXPORT_OPTIONS, display_order = 60)]
1447 show_details: bool,
1448
1449 /// Show edge labels.
1450 #[arg(long, help_heading = headings::EXPORT_OPTIONS, display_order = 70)]
1451 show_labels: bool,
1452
1453 /// Output file (default: stdout).
1454 #[arg(long, short = 'o', help_heading = headings::OUTPUT_CONTROL, display_order = 10)]
1455 output: Option<String>,
1456 },
1457
1458 /// Explain a symbol with context and relations
1459 ///
1460 /// Get detailed information about a symbol including its code context,
1461 /// callers, callees, and other relationships.
1462 ///
1463 /// Examples:
1464 /// sqry explain src/main.rs main # Explain main function
1465 /// sqry explain src/lib.rs `MyStruct` # Explain a struct
1466 /// sqry explain --no-context file.rs func # Skip code context
1467 /// sqry explain --no-relations file.rs fn # Skip relations
1468 #[command(alias = "exp", display_order = 26, verbatim_doc_comment)]
1469 Explain {
1470 /// File containing the symbol.
1471 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
1472 file: String,
1473
1474 /// Symbol name to explain.
1475 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 20)]
1476 symbol: String,
1477
1478 /// Search path (defaults to current directory).
1479 #[arg(long, help_heading = headings::SEARCH_INPUT, display_order = 30)]
1480 path: Option<String>,
1481
1482 /// Skip code context in output.
1483 #[arg(long, help_heading = headings::OUTPUT_CONTROL, display_order = 10)]
1484 no_context: bool,
1485
1486 /// Skip relation information in output.
1487 #[arg(long, help_heading = headings::OUTPUT_CONTROL, display_order = 20)]
1488 no_relations: bool,
1489 },
1490
1491 /// Find symbols similar to a reference symbol
1492 ///
1493 /// Uses fuzzy name matching to find symbols that are similar
1494 /// to a given reference symbol.
1495 ///
1496 /// Examples:
1497 /// sqry similar src/lib.rs processData # Find similar to processData
1498 /// sqry similar --threshold 0.8 file.rs fn # 80% similarity threshold
1499 /// sqry similar --limit 20 file.rs func # Limit to 20 results
1500 #[command(alias = "sim", display_order = 27, verbatim_doc_comment)]
1501 Similar {
1502 /// File containing the reference symbol.
1503 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
1504 file: String,
1505
1506 /// Reference symbol name.
1507 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 20)]
1508 symbol: String,
1509
1510 /// Search path (defaults to current directory).
1511 #[arg(long, help_heading = headings::SEARCH_INPUT, display_order = 30)]
1512 path: Option<String>,
1513
1514 /// Minimum similarity threshold (0.0 to 1.0, default: 0.7).
1515 #[arg(long, short = 't', default_value = "0.7", help_heading = headings::GRAPH_FILTERING, display_order = 10)]
1516 threshold: f64,
1517
1518 /// Maximum results to return (default: 20).
1519 #[arg(long, short = 'l', default_value = "20", help_heading = headings::GRAPH_FILTERING, display_order = 20)]
1520 limit: usize,
1521 },
1522
1523 /// Extract a focused subgraph around seed symbols
1524 ///
1525 /// Collects nodes and edges within a specified depth from seed symbols,
1526 /// useful for understanding local code structure.
1527 ///
1528 /// Examples:
1529 /// sqry subgraph main # Subgraph around main
1530 /// sqry subgraph -d 3 func1 func2 # Depth 3, multiple seeds
1531 /// sqry subgraph --no-callers main # Only callees
1532 /// sqry subgraph --include-imports main # Include import edges
1533 #[command(alias = "sub", display_order = 28, verbatim_doc_comment)]
1534 Subgraph {
1535 /// Seed symbol names (at least one required).
1536 #[arg(required = true, help_heading = headings::SEARCH_INPUT, display_order = 10)]
1537 symbols: Vec<String>,
1538
1539 /// Search path (defaults to current directory).
1540 #[arg(long, help_heading = headings::SEARCH_INPUT, display_order = 20)]
1541 path: Option<String>,
1542
1543 /// Maximum traversal depth from seeds (default: 2).
1544 #[arg(long, short = 'd', default_value = "2", help_heading = headings::GRAPH_FILTERING, display_order = 10)]
1545 depth: usize,
1546
1547 /// Maximum nodes to include (default: 50).
1548 #[arg(long, short = 'n', default_value = "50", help_heading = headings::GRAPH_FILTERING, display_order = 20)]
1549 max_nodes: usize,
1550
1551 /// Exclude callers (incoming edges).
1552 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 30)]
1553 no_callers: bool,
1554
1555 /// Exclude callees (outgoing edges).
1556 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 40)]
1557 no_callees: bool,
1558
1559 /// Include import relationships.
1560 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 50)]
1561 include_imports: bool,
1562 },
1563
1564 /// Analyze what would break if a symbol changes
1565 ///
1566 /// Performs reverse dependency analysis to find all symbols
1567 /// that directly or indirectly depend on the target.
1568 ///
1569 /// Examples:
1570 /// sqry impact authenticate # Impact of changing authenticate
1571 /// sqry impact -d 5 `MyClass` # Deep analysis (5 levels)
1572 /// sqry impact --direct-only func # Only direct dependents
1573 /// sqry impact --show-files func # Show affected files
1574 #[command(alias = "imp", display_order = 24, verbatim_doc_comment)]
1575 Impact {
1576 /// Symbol to analyze.
1577 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
1578 symbol: String,
1579
1580 /// Search path (defaults to current directory).
1581 #[arg(long, help_heading = headings::SEARCH_INPUT, display_order = 20)]
1582 path: Option<String>,
1583
1584 /// Maximum analysis depth (default: 3).
1585 #[arg(long, short = 'd', default_value = "3", help_heading = headings::GRAPH_FILTERING, display_order = 10)]
1586 depth: usize,
1587
1588 /// Maximum results to return (default: 100).
1589 #[arg(long, short = 'l', default_value = "100", help_heading = headings::GRAPH_FILTERING, display_order = 20)]
1590 limit: usize,
1591
1592 /// Only show direct dependents (depth 1).
1593 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 30)]
1594 direct_only: bool,
1595
1596 /// Show list of affected files.
1597 #[arg(long, help_heading = headings::OUTPUT_CONTROL, display_order = 10)]
1598 show_files: bool,
1599 },
1600
1601 /// Compare semantic changes between git refs
1602 ///
1603 /// Analyzes AST differences between two git refs to detect added, removed,
1604 /// modified, and renamed symbols. Provides structured output showing what
1605 /// changed semantically, not just textually.
1606 ///
1607 /// Examples:
1608 /// sqry diff main HEAD # Compare branches
1609 /// sqry diff v1.0.0 v2.0.0 --json # Release comparison
1610 /// sqry diff HEAD~5 HEAD --kind function # Functions only
1611 /// sqry diff main feature --change-type added # New symbols only
1612 #[command(alias = "sdiff", display_order = 25, verbatim_doc_comment)]
1613 Diff {
1614 /// Base git ref (commit, branch, or tag).
1615 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
1616 base: String,
1617
1618 /// Target git ref (commit, branch, or tag).
1619 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 20)]
1620 target: String,
1621
1622 /// Path to git repository (defaults to current directory).
1623 ///
1624 /// Can be the repository root or any path within it - sqry will walk up
1625 /// the directory tree to find the .git directory.
1626 #[arg(long, help_heading = headings::SEARCH_INPUT, display_order = 30)]
1627 path: Option<String>,
1628
1629 /// Maximum total results to display (default: 100).
1630 #[arg(long, short = 'l', default_value = "100", help_heading = headings::GRAPH_FILTERING, display_order = 10)]
1631 limit: usize,
1632
1633 /// Filter by symbol kinds (comma-separated).
1634 #[arg(long, short = 'k', help_heading = headings::GRAPH_FILTERING, display_order = 20)]
1635 kind: Option<String>,
1636
1637 /// Filter by change types (comma-separated).
1638 ///
1639 /// Valid values: `added`, `removed`, `modified`, `renamed`, `signature_changed`
1640 ///
1641 /// Example: --change-type added,modified
1642 #[arg(long, short = 'c', help_heading = headings::GRAPH_FILTERING, display_order = 30)]
1643 change_type: Option<String>,
1644 },
1645
1646 /// Hierarchical semantic search (RAG-optimized)
1647 ///
1648 /// Performs semantic search with results grouped by file and container,
1649 /// optimized for retrieval-augmented generation (RAG) workflows.
1650 ///
1651 /// Examples:
1652 /// sqry hier "kind:function" # All functions, grouped
1653 /// sqry hier "auth" --max-files 10 # Limit file groups
1654 /// sqry hier --kind function "test" # Filter by kind
1655 /// sqry hier --context 5 "validate" # More context lines
1656 #[command(display_order = 4, verbatim_doc_comment)]
1657 Hier {
1658 /// Search query.
1659 #[arg(help_heading = headings::SEARCH_INPUT, display_order = 10)]
1660 query: String,
1661
1662 /// Search path (defaults to current directory).
1663 #[arg(long, help_heading = headings::SEARCH_INPUT, display_order = 20)]
1664 path: Option<String>,
1665
1666 /// Maximum symbols before grouping (default: 200).
1667 #[arg(long, short = 'l', default_value = "200", help_heading = headings::GRAPH_FILTERING, display_order = 10)]
1668 limit: usize,
1669
1670 /// Maximum files in output (default: 20).
1671 #[arg(long, default_value = "20", help_heading = headings::GRAPH_FILTERING, display_order = 20)]
1672 max_files: usize,
1673
1674 /// Context lines around matches (default: 3).
1675 #[arg(long, short = 'c', default_value = "3", help_heading = headings::OUTPUT_CONTROL, display_order = 10)]
1676 context: usize,
1677
1678 /// Filter by symbol kinds (comma-separated).
1679 #[arg(long, short = 'k', help_heading = headings::GRAPH_FILTERING, display_order = 30)]
1680 kind: Option<String>,
1681
1682 /// Filter by languages (comma-separated).
1683 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 40)]
1684 lang: Option<String>,
1685 },
1686
1687 /// Configure MCP server integration for AI coding tools
1688 ///
1689 /// Auto-detect and configure sqry MCP for Claude Code, Codex, and Gemini CLI.
1690 /// The setup command writes tool-specific configuration so AI coding assistants
1691 /// can use sqry's semantic code search capabilities.
1692 ///
1693 /// Examples:
1694 /// sqry mcp setup # Auto-configure all detected tools
1695 /// sqry mcp setup --tool claude # Configure Claude Code only
1696 /// sqry mcp setup --scope global --dry-run # Preview global config changes
1697 /// sqry mcp status # Show current MCP configuration
1698 /// sqry mcp status --json # Machine-readable status
1699 #[command(display_order = 51, verbatim_doc_comment)]
1700 Mcp {
1701 #[command(subcommand)]
1702 command: McpCommand,
1703 },
1704}
1705
1706/// MCP server integration subcommands
1707#[derive(Subcommand, Debug, Clone)]
1708pub enum McpCommand {
1709 /// Auto-configure sqry MCP for detected AI tools (Claude Code, Codex, Gemini)
1710 ///
1711 /// Detects installed AI coding tools and writes configuration entries
1712 /// pointing to the sqry-mcp binary. Uses tool-appropriate scoping:
1713 /// - Claude Code: per-project entries with pinned workspace root (default)
1714 /// - Codex/Gemini: global entries using CWD-based workspace discovery
1715 ///
1716 /// Note: Codex and Gemini only support global MCP configs.
1717 /// They rely on being launched from within a project directory
1718 /// for sqry-mcp's CWD discovery to resolve the correct workspace.
1719 Setup {
1720 /// Target tool(s) to configure.
1721 #[arg(long, value_enum, default_value = "all")]
1722 tool: ToolTarget,
1723
1724 /// Configuration scope.
1725 ///
1726 /// - auto: project scope for Claude (when inside a repo), global for Codex/Gemini
1727 /// - project: per-project Claude entry with pinned workspace root
1728 /// - global: global entries for all tools (CWD-dependent for workspace resolution)
1729 ///
1730 /// Note: For Codex and Gemini, --scope project and --scope global behave
1731 /// identically because these tools only support global MCP configs.
1732 #[arg(long, value_enum, default_value = "auto")]
1733 scope: SetupScope,
1734
1735 /// Explicit workspace root path (overrides auto-detection).
1736 ///
1737 /// Only applicable for Claude Code project scope. Rejected for
1738 /// Codex/Gemini because setting a workspace root in their global
1739 /// config would pin to one repo and break multi-repo workflows.
1740 #[arg(long)]
1741 workspace_root: Option<PathBuf>,
1742
1743 /// Overwrite existing sqry configuration.
1744 #[arg(long)]
1745 force: bool,
1746
1747 /// Preview changes without writing.
1748 #[arg(long)]
1749 dry_run: bool,
1750
1751 /// Skip creating .bak backup files.
1752 #[arg(long)]
1753 no_backup: bool,
1754 },
1755
1756 /// Show current MCP configuration status across all tools
1757 ///
1758 /// Reports the sqry-mcp binary location and configuration state
1759 /// for each supported AI tool, including scope, workspace root,
1760 /// and any detected issues (shim usage, drift, missing config).
1761 Status {
1762 /// Output as JSON for programmatic use.
1763 #[arg(long)]
1764 json: bool,
1765 },
1766}
1767
1768/// Target AI tool(s) for MCP configuration
1769#[derive(Debug, Clone, ValueEnum)]
1770pub enum ToolTarget {
1771 /// Configure Claude Code only
1772 Claude,
1773 /// Configure Codex only
1774 Codex,
1775 /// Configure Gemini CLI only
1776 Gemini,
1777 /// Configure all detected tools (default)
1778 All,
1779}
1780
1781/// Configuration scope for MCP setup
1782#[derive(Debug, Clone, ValueEnum)]
1783pub enum SetupScope {
1784 /// Per-project for Claude, global for Codex/Gemini (auto-detect)
1785 Auto,
1786 /// Per-project entries with pinned workspace root
1787 Project,
1788 /// Global entries (CWD-dependent workspace resolution)
1789 Global,
1790}
1791
1792/// Graph-based query operations
1793#[derive(Subcommand, Debug, Clone)]
1794pub enum GraphOperation {
1795 /// Find shortest path between two symbols
1796 ///
1797 /// Traces the shortest execution path from one symbol to another,
1798 /// following Call, `HTTPRequest`, and `FFICall` edges.
1799 ///
1800 /// Example: sqry graph trace-path main processData
1801 TracePath {
1802 /// Source symbol name (e.g., "main", "User.authenticate").
1803 #[arg(help_heading = headings::GRAPH_ANALYSIS_INPUT, display_order = 10)]
1804 from: String,
1805
1806 /// Target symbol name.
1807 #[arg(help_heading = headings::GRAPH_ANALYSIS_INPUT, display_order = 20)]
1808 to: String,
1809
1810 /// Filter by languages (comma-separated, e.g., "javascript,python").
1811 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 10)]
1812 languages: Option<String>,
1813
1814 /// Show full file paths in output.
1815 #[arg(long, help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 10)]
1816 full_paths: bool,
1817 },
1818
1819 /// Calculate maximum call chain depth from a symbol
1820 ///
1821 /// Computes the longest call chain starting from the given symbol,
1822 /// useful for complexity analysis and recursion detection.
1823 ///
1824 /// Example: sqry graph call-chain-depth main
1825 CallChainDepth {
1826 /// Symbol name to analyze.
1827 #[arg(help_heading = headings::GRAPH_ANALYSIS_INPUT, display_order = 10)]
1828 symbol: String,
1829
1830 /// Filter by languages (comma-separated).
1831 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 10)]
1832 languages: Option<String>,
1833
1834 /// Show the actual call chain, not just the depth.
1835 #[arg(long, help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 10)]
1836 show_chain: bool,
1837 },
1838
1839 /// Show transitive dependencies for a module
1840 ///
1841 /// Analyzes all imports transitively to build a complete dependency tree,
1842 /// including circular dependency detection.
1843 ///
1844 /// Example: sqry graph dependency-tree src/main.js
1845 #[command(alias = "deps")]
1846 DependencyTree {
1847 /// Module path or name.
1848 #[arg(help_heading = headings::GRAPH_ANALYSIS_INPUT, display_order = 10)]
1849 module: String,
1850
1851 /// Maximum depth to traverse (default: unlimited).
1852 #[arg(long, help_heading = headings::GRAPH_ANALYSIS_OPTIONS, display_order = 10)]
1853 max_depth: Option<usize>,
1854
1855 /// Show circular dependencies only.
1856 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 10)]
1857 cycles_only: bool,
1858 },
1859
1860 /// List all cross-language relationships
1861 ///
1862 /// Finds edges connecting symbols in different programming languages,
1863 /// such as TypeScript→JavaScript imports, Python→C FFI calls, SQL table
1864 /// access, Dart `MethodChannel` invocations, and Flutter widget hierarchies.
1865 ///
1866 /// Supported languages for --from-lang/--to-lang:
1867 /// js, ts, py, cpp, c, csharp (cs), java, go, ruby, php,
1868 /// swift, kotlin, scala, sql, dart, lua, perl, shell (bash),
1869 /// groovy, http
1870 ///
1871 /// Examples:
1872 /// sqry graph cross-language --from-lang dart --edge-type `channel_invoke`
1873 /// sqry graph cross-language --from-lang sql --edge-type `table_read`
1874 /// sqry graph cross-language --edge-type `widget_child`
1875 #[command(verbatim_doc_comment)]
1876 CrossLanguage {
1877 /// Filter by source language.
1878 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 10)]
1879 from_lang: Option<String>,
1880
1881 /// Filter by target language.
1882 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 20)]
1883 to_lang: Option<String>,
1884
1885 /// Edge type filter.
1886 ///
1887 /// Supported values:
1888 /// call, import, http, ffi,
1889 /// `table_read`, `table_write`, `triggered_by`,
1890 /// `channel_invoke`, `widget_child`
1891 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 30)]
1892 edge_type: Option<String>,
1893
1894 /// Minimum confidence threshold (0.0-1.0).
1895 #[arg(long, default_value = "0.0", help_heading = headings::GRAPH_FILTERING, display_order = 40)]
1896 min_confidence: f64,
1897 },
1898
1899 /// List unified graph nodes
1900 ///
1901 /// Enumerates nodes from the unified graph snapshot and applies filters.
1902 /// Useful for inspecting graph coverage and metadata details.
1903 Nodes {
1904 /// Filter by node kind(s) (comma-separated: function,method,macro).
1905 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 10)]
1906 kind: Option<String>,
1907
1908 /// Filter by language(s) (comma-separated: rust,python).
1909 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 20)]
1910 languages: Option<String>,
1911
1912 /// Filter by file path substring (case-insensitive).
1913 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 30)]
1914 file: Option<String>,
1915
1916 /// Filter by name substring (case-sensitive).
1917 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 40)]
1918 name: Option<String>,
1919
1920 /// Filter by qualified name substring (case-sensitive).
1921 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 50)]
1922 qualified_name: Option<String>,
1923
1924 /// Maximum results (default: 1000, max: 10000; use 0 for default).
1925 #[arg(long, default_value = "1000", help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 10)]
1926 limit: usize,
1927
1928 /// Skip N results.
1929 #[arg(long, default_value = "0", help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 20)]
1930 offset: usize,
1931
1932 /// Show full file paths in output.
1933 #[arg(long, help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 30)]
1934 full_paths: bool,
1935 },
1936
1937 /// List unified graph edges
1938 ///
1939 /// Enumerates edges from the unified graph snapshot and applies filters.
1940 /// Useful for inspecting relationships and cross-cutting metadata.
1941 Edges {
1942 /// Filter by edge kind tag(s) (comma-separated: calls,imports).
1943 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 10)]
1944 kind: Option<String>,
1945
1946 /// Filter by source label substring (case-sensitive).
1947 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 20)]
1948 from: Option<String>,
1949
1950 /// Filter by target label substring (case-sensitive).
1951 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 30)]
1952 to: Option<String>,
1953
1954 /// Filter by source language.
1955 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 40)]
1956 from_lang: Option<String>,
1957
1958 /// Filter by target language.
1959 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 50)]
1960 to_lang: Option<String>,
1961
1962 /// Filter by file path substring (case-insensitive, source file only).
1963 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 60)]
1964 file: Option<String>,
1965
1966 /// Maximum results (default: 1000, max: 10000; use 0 for default).
1967 #[arg(long, default_value = "1000", help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 10)]
1968 limit: usize,
1969
1970 /// Skip N results.
1971 #[arg(long, default_value = "0", help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 20)]
1972 offset: usize,
1973
1974 /// Show full file paths in output.
1975 #[arg(long, help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 30)]
1976 full_paths: bool,
1977 },
1978
1979 /// Show graph statistics and summary
1980 ///
1981 /// Displays overall graph metrics including node counts by language,
1982 /// edge counts by type, and cross-language relationship statistics.
1983 ///
1984 /// Example: sqry graph stats
1985 Stats {
1986 /// Show detailed breakdown by file.
1987 #[arg(long, help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 10)]
1988 by_file: bool,
1989
1990 /// Show detailed breakdown by language.
1991 #[arg(long, help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 20)]
1992 by_language: bool,
1993 },
1994
1995 /// Show unified graph snapshot status
1996 ///
1997 /// Reports on the state of the unified graph snapshot stored in
1998 /// `.sqry/graph/` directory. Displays build timestamp, node/edge counts,
1999 /// and snapshot age.
2000 ///
2001 /// Example: sqry graph status
2002 Status,
2003
2004 /// Show Phase 1 fact-layer provenance for a symbol
2005 ///
2006 /// Prints the snapshot's fact epoch, node provenance (first/last seen
2007 /// epoch, content hash), file provenance, and an edge-provenance summary
2008 /// for the matched symbol. This is the end-to-end proof that the V8
2009 /// save → load → accessor → CLI path is wired.
2010 ///
2011 /// Example: sqry graph provenance `my_function`
2012 #[command(alias = "prov")]
2013 Provenance {
2014 /// Symbol name to inspect (qualified or unqualified).
2015 #[arg(help_heading = headings::GRAPH_ANALYSIS_INPUT, display_order = 10)]
2016 symbol: String,
2017
2018 /// Output as JSON.
2019 #[arg(long, help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 10)]
2020 json: bool,
2021 },
2022
2023 /// Detect circular dependencies in the codebase
2024 ///
2025 /// Finds all cycles in the call and import graphs, which can indicate
2026 /// potential design issues or circular dependency problems.
2027 ///
2028 /// Example: sqry graph cycles
2029 #[command(alias = "cyc")]
2030 Cycles {
2031 /// Minimum cycle length to report (default: 2).
2032 #[arg(long, default_value = "2", help_heading = headings::GRAPH_ANALYSIS_OPTIONS, display_order = 10)]
2033 min_length: usize,
2034
2035 /// Maximum cycle length to report (default: unlimited).
2036 #[arg(long, help_heading = headings::GRAPH_ANALYSIS_OPTIONS, display_order = 20)]
2037 max_length: Option<usize>,
2038
2039 /// Only analyze import edges (ignore calls).
2040 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 10)]
2041 imports_only: bool,
2042
2043 /// Filter by languages (comma-separated).
2044 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 20)]
2045 languages: Option<String>,
2046 },
2047
2048 /// Calculate code complexity metrics
2049 ///
2050 /// Analyzes cyclomatic complexity, call graph depth, and other
2051 /// complexity metrics for functions and modules.
2052 ///
2053 /// Example: sqry graph complexity
2054 #[command(alias = "cx")]
2055 Complexity {
2056 /// Target symbol or module (default: analyze all).
2057 #[arg(help_heading = headings::GRAPH_ANALYSIS_INPUT, display_order = 10)]
2058 target: Option<String>,
2059
2060 /// Sort by complexity score.
2061 #[arg(long = "sort-complexity", help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 10)]
2062 sort_complexity: bool,
2063
2064 /// Show only items above this complexity threshold.
2065 #[arg(long, default_value = "0", help_heading = headings::GRAPH_FILTERING, display_order = 10)]
2066 min_complexity: usize,
2067
2068 /// Filter by languages (comma-separated).
2069 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 20)]
2070 languages: Option<String>,
2071 },
2072
2073 /// Find direct callers of a symbol
2074 ///
2075 /// Lists all symbols that directly call the specified function, method,
2076 /// or other callable. Useful for understanding symbol usage and impact analysis.
2077 ///
2078 /// Example: sqry graph direct-callers authenticate
2079 #[command(alias = "callers")]
2080 DirectCallers {
2081 /// Symbol name to find callers for.
2082 #[arg(help_heading = headings::GRAPH_ANALYSIS_INPUT, display_order = 10)]
2083 symbol: String,
2084
2085 /// Maximum results (default: 100).
2086 #[arg(long, short = 'l', default_value = "100", help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 10)]
2087 limit: usize,
2088
2089 /// Filter by languages (comma-separated).
2090 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 10)]
2091 languages: Option<String>,
2092
2093 /// Show full file paths in output.
2094 #[arg(long, help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 20)]
2095 full_paths: bool,
2096 },
2097
2098 /// Find direct callees of a symbol
2099 ///
2100 /// Lists all symbols that are directly called by the specified function
2101 /// or method. Useful for understanding dependencies and refactoring scope.
2102 ///
2103 /// Example: sqry graph direct-callees processData
2104 #[command(alias = "callees")]
2105 DirectCallees {
2106 /// Symbol name to find callees for.
2107 #[arg(help_heading = headings::GRAPH_ANALYSIS_INPUT, display_order = 10)]
2108 symbol: String,
2109
2110 /// Maximum results (default: 100).
2111 #[arg(long, short = 'l', default_value = "100", help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 10)]
2112 limit: usize,
2113
2114 /// Filter by languages (comma-separated).
2115 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 10)]
2116 languages: Option<String>,
2117
2118 /// Show full file paths in output.
2119 #[arg(long, help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 20)]
2120 full_paths: bool,
2121 },
2122
2123 /// Show call hierarchy for a symbol
2124 ///
2125 /// Displays incoming and/or outgoing call relationships in a tree format.
2126 /// Useful for understanding code flow and impact of changes.
2127 ///
2128 /// Example: sqry graph call-hierarchy main --depth 3
2129 #[command(alias = "ch")]
2130 CallHierarchy {
2131 /// Symbol name to show hierarchy for.
2132 #[arg(help_heading = headings::GRAPH_ANALYSIS_INPUT, display_order = 10)]
2133 symbol: String,
2134
2135 /// Maximum depth to traverse (default: 3).
2136 #[arg(long, short = 'd', default_value = "3", help_heading = headings::GRAPH_ANALYSIS_OPTIONS, display_order = 10)]
2137 depth: usize,
2138
2139 /// Direction: incoming, outgoing, or both (default: both).
2140 #[arg(long, default_value = "both", help_heading = headings::GRAPH_ANALYSIS_OPTIONS, display_order = 20)]
2141 direction: String,
2142
2143 /// Filter by languages (comma-separated).
2144 #[arg(long, help_heading = headings::GRAPH_FILTERING, display_order = 10)]
2145 languages: Option<String>,
2146
2147 /// Show full file paths in output.
2148 #[arg(long, help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 10)]
2149 full_paths: bool,
2150 },
2151
2152 /// Check if a symbol is in a cycle
2153 ///
2154 /// Determines whether a specific symbol participates in any circular
2155 /// dependency chains. Can optionally show the cycle path.
2156 ///
2157 /// Example: sqry graph is-in-cycle `UserService` --show-cycle
2158 #[command(alias = "incycle")]
2159 IsInCycle {
2160 /// Symbol name to check.
2161 #[arg(help_heading = headings::GRAPH_ANALYSIS_INPUT, display_order = 10)]
2162 symbol: String,
2163
2164 /// Cycle type to check: calls, imports, or all (default: calls).
2165 #[arg(long, default_value = "calls", help_heading = headings::GRAPH_ANALYSIS_OPTIONS, display_order = 10)]
2166 cycle_type: String,
2167
2168 /// Show the full cycle path if found.
2169 #[arg(long, help_heading = headings::GRAPH_OUTPUT_OPTIONS, display_order = 10)]
2170 show_cycle: bool,
2171 },
2172}
2173
2174/// Output format choices for `sqry batch`.
2175#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq)]
2176pub enum BatchFormat {
2177 /// Human-readable text output (default)
2178 Text,
2179 /// Aggregated JSON output containing all query results
2180 Json,
2181 /// Newline-delimited JSON objects (one per query)
2182 Jsonl,
2183 /// Comma-separated summary per query
2184 Csv,
2185}
2186
2187/// Cache management actions
2188#[derive(Subcommand, Debug, Clone)]
2189pub enum CacheAction {
2190 /// Show cache statistics
2191 ///
2192 /// Display hit rate, size, and entry count for the AST cache.
2193 Stats {
2194 /// Path to check cache for (defaults to current directory).
2195 #[arg(help_heading = headings::CACHE_INPUT, display_order = 10)]
2196 path: Option<String>,
2197 },
2198
2199 /// Clear the cache
2200 ///
2201 /// Remove all cached AST data. Next queries will re-parse files.
2202 Clear {
2203 /// Path to clear cache for (defaults to current directory).
2204 #[arg(help_heading = headings::CACHE_INPUT, display_order = 10)]
2205 path: Option<String>,
2206
2207 /// Confirm deletion (required for safety).
2208 #[arg(long, help_heading = headings::SAFETY_CONTROL, display_order = 10)]
2209 confirm: bool,
2210 },
2211
2212 /// Prune the cache
2213 ///
2214 /// Remove old or excessive cache entries to reclaim disk space.
2215 /// Supports time-based (--days) and size-based (--size) retention policies.
2216 Prune {
2217 /// Target cache directory (defaults to user cache dir).
2218 #[arg(long, help_heading = headings::CACHE_INPUT, display_order = 10)]
2219 path: Option<String>,
2220
2221 /// Remove entries older than N days.
2222 #[arg(long, help_heading = headings::CACHE_INPUT, display_order = 20)]
2223 days: Option<u64>,
2224
2225 /// Cap cache to maximum size (e.g., "1GB", "500MB").
2226 #[arg(long, help_heading = headings::CACHE_INPUT, display_order = 30)]
2227 size: Option<String>,
2228
2229 /// Preview deletions without removing files.
2230 #[arg(long, help_heading = headings::SAFETY_CONTROL, display_order = 10)]
2231 dry_run: bool,
2232 },
2233
2234 /// Generate or refresh the macro expansion cache
2235 ///
2236 /// Runs `cargo expand` to generate expanded macro output, then caches
2237 /// the results for use during indexing. Requires `cargo-expand` installed.
2238 ///
2239 /// # Security
2240 ///
2241 /// This executes build scripts and proc macros. Only use on trusted codebases.
2242 Expand {
2243 /// Force regeneration even if cache is fresh.
2244 #[arg(long, help_heading = headings::CACHE_INPUT, display_order = 40)]
2245 refresh: bool,
2246
2247 /// Only expand a specific crate (default: all workspace crates).
2248 #[arg(long, help_heading = headings::CACHE_INPUT, display_order = 50)]
2249 crate_name: Option<String>,
2250
2251 /// Show what would be expanded without actually running cargo expand.
2252 #[arg(long, help_heading = headings::SAFETY_CONTROL, display_order = 20)]
2253 dry_run: bool,
2254
2255 /// Cache output directory (default: .sqry/expand-cache/).
2256 #[arg(long, help_heading = headings::CACHE_INPUT, display_order = 60)]
2257 output: Option<PathBuf>,
2258 },
2259}
2260
2261/// Config action subcommands
2262#[derive(Subcommand, Debug, Clone)]
2263pub enum ConfigAction {
2264 /// Initialize config with defaults
2265 ///
2266 /// Creates `.sqry/graph/config/config.json` with default settings.
2267 /// Use --force to overwrite existing config.
2268 ///
2269 /// Examples:
2270 /// sqry config init
2271 /// sqry config init --force
2272 #[command(verbatim_doc_comment)]
2273 Init {
2274 /// Project root path (defaults to current directory).
2275 // Path defaults to current directory if not specified
2276 #[arg(long, help_heading = headings::CONFIG_INPUT, display_order = 5)]
2277 path: Option<String>,
2278
2279 /// Overwrite existing config.
2280 #[arg(long, help_heading = headings::CONFIG_INPUT, display_order = 20)]
2281 force: bool,
2282 },
2283
2284 /// Show effective config
2285 ///
2286 /// Displays the complete config with source annotations.
2287 /// Use --key to show a single value.
2288 ///
2289 /// Examples:
2290 /// sqry config show
2291 /// sqry config show --json
2292 /// sqry config show --key `limits.max_results`
2293 #[command(verbatim_doc_comment)]
2294 Show {
2295 /// Project root path (defaults to current directory).
2296 // Path defaults to current directory if not specified
2297 #[arg(long, help_heading = headings::CONFIG_INPUT, display_order = 5)]
2298 path: Option<String>,
2299
2300 /// Output as JSON.
2301 #[arg(long, help_heading = headings::OUTPUT_CONTROL, display_order = 10)]
2302 json: bool,
2303
2304 /// Show only this config key (e.g., `limits.max_results`).
2305 #[arg(long, help_heading = headings::CONFIG_INPUT, display_order = 20)]
2306 key: Option<String>,
2307 },
2308
2309 /// Set a config value
2310 ///
2311 /// Updates a config key and persists to disk.
2312 /// Shows a diff before applying (use --yes to skip).
2313 ///
2314 /// Examples:
2315 /// sqry config set `limits.max_results` 10000
2316 /// sqry config set `locking.stale_takeover_policy` warn
2317 /// sqry config set `output.page_size` 100 --yes
2318 #[command(verbatim_doc_comment)]
2319 Set {
2320 /// Project root path (defaults to current directory).
2321 // Path defaults to current directory if not specified
2322 #[arg(long, help_heading = headings::CONFIG_INPUT, display_order = 5)]
2323 path: Option<String>,
2324
2325 /// Config key (e.g., `limits.max_results`).
2326 #[arg(help_heading = headings::CONFIG_INPUT, display_order = 20)]
2327 key: String,
2328
2329 /// New value.
2330 #[arg(help_heading = headings::CONFIG_INPUT, display_order = 30)]
2331 value: String,
2332
2333 /// Skip confirmation prompt.
2334 #[arg(long, help_heading = headings::CONFIG_INPUT, display_order = 40)]
2335 yes: bool,
2336 },
2337
2338 /// Get a config value
2339 ///
2340 /// Retrieves a single config value.
2341 ///
2342 /// Examples:
2343 /// sqry config get `limits.max_results`
2344 /// sqry config get `locking.stale_takeover_policy`
2345 #[command(verbatim_doc_comment)]
2346 Get {
2347 /// Project root path (defaults to current directory).
2348 // Path defaults to current directory if not specified
2349 #[arg(long, help_heading = headings::CONFIG_INPUT, display_order = 5)]
2350 path: Option<String>,
2351
2352 /// Config key (e.g., `limits.max_results`).
2353 #[arg(help_heading = headings::CONFIG_INPUT, display_order = 20)]
2354 key: String,
2355 },
2356
2357 /// Validate config file
2358 ///
2359 /// Checks config syntax and schema validity.
2360 ///
2361 /// Examples:
2362 /// sqry config validate
2363 #[command(verbatim_doc_comment)]
2364 Validate {
2365 /// Project root path (defaults to current directory).
2366 // Path defaults to current directory if not specified
2367 #[arg(long, help_heading = headings::CONFIG_INPUT, display_order = 5)]
2368 path: Option<String>,
2369 },
2370
2371 /// Manage query aliases
2372 #[command(subcommand)]
2373 Alias(ConfigAliasAction),
2374}
2375
2376/// Config alias subcommands
2377#[derive(Subcommand, Debug, Clone)]
2378pub enum ConfigAliasAction {
2379 /// Create or update an alias
2380 ///
2381 /// Examples:
2382 /// sqry config alias set my-funcs "kind:function"
2383 /// sqry config alias set my-funcs "kind:function" --description "All functions"
2384 #[command(verbatim_doc_comment)]
2385 Set {
2386 /// Project root path (defaults to current directory).
2387 // Path defaults to current directory if not specified
2388 #[arg(long, help_heading = headings::CONFIG_INPUT, display_order = 5)]
2389 path: Option<String>,
2390
2391 /// Alias name.
2392 #[arg(help_heading = headings::CONFIG_INPUT, display_order = 20)]
2393 name: String,
2394
2395 /// Query expression.
2396 #[arg(help_heading = headings::CONFIG_INPUT, display_order = 30)]
2397 query: String,
2398
2399 /// Optional description.
2400 #[arg(long, help_heading = headings::CONFIG_INPUT, display_order = 40)]
2401 description: Option<String>,
2402 },
2403
2404 /// List all aliases
2405 ///
2406 /// Examples:
2407 /// sqry config alias list
2408 /// sqry config alias list --json
2409 #[command(verbatim_doc_comment)]
2410 List {
2411 /// Project root path (defaults to current directory).
2412 // Path defaults to current directory if not specified
2413 #[arg(long, help_heading = headings::CONFIG_INPUT, display_order = 5)]
2414 path: Option<String>,
2415
2416 /// Output as JSON.
2417 #[arg(long, help_heading = headings::OUTPUT_CONTROL, display_order = 10)]
2418 json: bool,
2419 },
2420
2421 /// Remove an alias
2422 ///
2423 /// Examples:
2424 /// sqry config alias remove my-funcs
2425 #[command(verbatim_doc_comment)]
2426 Remove {
2427 /// Project root path (defaults to current directory).
2428 // Path defaults to current directory if not specified
2429 #[arg(long, help_heading = headings::CONFIG_INPUT, display_order = 5)]
2430 path: Option<String>,
2431
2432 /// Alias name to remove.
2433 #[arg(help_heading = headings::CONFIG_INPUT, display_order = 20)]
2434 name: String,
2435 },
2436}
2437
2438/// Visualize code relationships from relation queries.
2439///
2440/// Examples:
2441/// sqry visualize "callers:main" --format mermaid
2442/// sqry visualize "imports:std" --format graphviz --output-file deps.dot
2443/// sqry visualize "callees:process" --depth 5 --max-nodes 200
2444#[derive(Debug, Args, Clone)]
2445#[command(
2446 about = "Visualize code relationships as diagrams",
2447 long_about = "Visualize code relationships as diagrams.\n\n\
2448Examples:\n sqry visualize \"callers:main\" --format mermaid\n \
2449sqry visualize \"imports:std\" --format graphviz --output-file deps.dot\n \
2450sqry visualize \"callees:process\" --depth 5 --max-nodes 200",
2451 after_help = "Examples:\n sqry visualize \"callers:main\" --format mermaid\n \
2452sqry visualize \"imports:std\" --format graphviz --output-file deps.dot\n \
2453sqry visualize \"callees:process\" --depth 5 --max-nodes 200"
2454)]
2455pub struct VisualizeCommand {
2456 /// Relation query (e.g., callers:main, callees:helper).
2457 #[arg(help_heading = headings::VISUALIZATION_INPUT, display_order = 10)]
2458 pub query: String,
2459
2460 /// Target path (defaults to CLI positional path).
2461 #[arg(long, help_heading = headings::VISUALIZATION_INPUT, display_order = 20)]
2462 pub path: Option<String>,
2463
2464 /// Diagram syntax format (mermaid, graphviz, d2).
2465 ///
2466 /// Specifies the diagram language/syntax to generate.
2467 /// Output will be plain text in the chosen format.
2468 #[arg(long, short = 'f', value_enum, default_value = "mermaid", help_heading = headings::DIAGRAM_CONFIGURATION, display_order = 10)]
2469 pub format: DiagramFormatArg,
2470
2471 /// Layout direction for the graph.
2472 #[arg(long, value_enum, default_value = "top-down", help_heading = headings::DIAGRAM_CONFIGURATION, display_order = 20)]
2473 pub direction: DirectionArg,
2474
2475 /// File path to save the output (stdout when omitted).
2476 #[arg(long, help_heading = headings::DIAGRAM_CONFIGURATION, display_order = 30)]
2477 pub output_file: Option<PathBuf>,
2478
2479 /// Maximum traversal depth for graph expansion.
2480 #[arg(long, short = 'd', default_value_t = 3, help_heading = headings::TRAVERSAL_CONTROL, display_order = 10)]
2481 pub depth: usize,
2482
2483 /// Maximum number of nodes to include in the diagram (1-500).
2484 #[arg(long, default_value_t = 100, help_heading = headings::TRAVERSAL_CONTROL, display_order = 20)]
2485 pub max_nodes: usize,
2486}
2487
2488/// Supported diagram text formats.
2489#[derive(Debug, Clone, Copy, ValueEnum)]
2490pub enum DiagramFormatArg {
2491 Mermaid,
2492 Graphviz,
2493 D2,
2494}
2495
2496/// Diagram layout direction.
2497#[derive(Debug, Clone, Copy, ValueEnum)]
2498#[value(rename_all = "kebab-case")]
2499pub enum DirectionArg {
2500 TopDown,
2501 BottomUp,
2502 LeftRight,
2503 RightLeft,
2504}
2505
2506/// Workspace management subcommands
2507#[derive(Subcommand, Debug, Clone)]
2508pub enum WorkspaceCommand {
2509 /// Initialise a new workspace registry
2510 Init {
2511 /// Directory that will contain the workspace registry.
2512 #[arg(value_name = "WORKSPACE", help_heading = headings::WORKSPACE_INPUT, display_order = 10)]
2513 workspace: String,
2514
2515 /// Preferred discovery mode for initial scans.
2516 #[arg(long, value_enum, default_value_t = WorkspaceDiscoveryMode::IndexFiles, help_heading = headings::WORKSPACE_CONFIGURATION, display_order = 10)]
2517 mode: WorkspaceDiscoveryMode,
2518
2519 /// Friendly workspace name stored in the registry metadata.
2520 #[arg(long, help_heading = headings::WORKSPACE_CONFIGURATION, display_order = 20)]
2521 name: Option<String>,
2522 },
2523
2524 /// Scan for repositories inside the workspace root
2525 Scan {
2526 /// Workspace root containing the .sqry-workspace file.
2527 #[arg(value_name = "WORKSPACE", help_heading = headings::WORKSPACE_INPUT, display_order = 10)]
2528 workspace: String,
2529
2530 /// Discovery mode to use when scanning for repositories.
2531 #[arg(long, value_enum, default_value_t = WorkspaceDiscoveryMode::IndexFiles, help_heading = headings::WORKSPACE_CONFIGURATION, display_order = 10)]
2532 mode: WorkspaceDiscoveryMode,
2533
2534 /// Remove entries whose indexes are no longer present.
2535 #[arg(long, help_heading = headings::WORKSPACE_CONFIGURATION, display_order = 20)]
2536 prune_stale: bool,
2537 },
2538
2539 /// Add a repository to the workspace manually
2540 Add {
2541 /// Workspace root containing the .sqry-workspace file.
2542 #[arg(value_name = "WORKSPACE", help_heading = headings::WORKSPACE_INPUT, display_order = 10)]
2543 workspace: String,
2544
2545 /// Path to the repository root (must contain .sqry-index).
2546 #[arg(value_name = "REPO", help_heading = headings::WORKSPACE_INPUT, display_order = 20)]
2547 repo: String,
2548
2549 /// Optional friendly name for the repository.
2550 #[arg(long, help_heading = headings::WORKSPACE_CONFIGURATION, display_order = 10)]
2551 name: Option<String>,
2552 },
2553
2554 /// Remove a repository from the workspace
2555 Remove {
2556 /// Workspace root containing the .sqry-workspace file.
2557 #[arg(value_name = "WORKSPACE", help_heading = headings::WORKSPACE_INPUT, display_order = 10)]
2558 workspace: String,
2559
2560 /// Repository identifier (workspace-relative path).
2561 #[arg(value_name = "REPO_ID", help_heading = headings::WORKSPACE_INPUT, display_order = 20)]
2562 repo_id: String,
2563 },
2564
2565 /// Run a workspace-level query across registered repositories
2566 Query {
2567 /// Workspace root containing the .sqry-workspace file.
2568 #[arg(value_name = "WORKSPACE", help_heading = headings::WORKSPACE_INPUT, display_order = 10)]
2569 workspace: String,
2570
2571 /// Query expression (supports repo: predicates).
2572 #[arg(value_name = "QUERY", help_heading = headings::WORKSPACE_INPUT, display_order = 20)]
2573 query: String,
2574
2575 /// Override parallel query threads.
2576 #[arg(long, help_heading = headings::PERFORMANCE_TUNING, display_order = 10)]
2577 threads: Option<usize>,
2578 },
2579
2580 /// Emit aggregate statistics for the workspace
2581 Stats {
2582 /// Workspace root containing the .sqry-workspace file.
2583 #[arg(value_name = "WORKSPACE", help_heading = headings::WORKSPACE_INPUT, display_order = 10)]
2584 workspace: String,
2585 },
2586}
2587
2588/// CLI discovery modes converted to workspace `DiscoveryMode` values
2589#[derive(Clone, Copy, Debug, ValueEnum)]
2590pub enum WorkspaceDiscoveryMode {
2591 #[value(name = "index-files", alias = "index")]
2592 IndexFiles,
2593 #[value(name = "git-roots", alias = "git")]
2594 GitRoots,
2595}
2596
2597/// Alias management subcommands
2598#[derive(Subcommand, Debug, Clone)]
2599pub enum AliasAction {
2600 /// List all saved aliases
2601 ///
2602 /// Shows aliases from both global (~/.config/sqry/) and local (.sqry-index.user)
2603 /// storage. Local aliases take precedence over global ones with the same name.
2604 ///
2605 /// Examples:
2606 /// sqry alias list # List all aliases
2607 /// sqry alias list --local # Only local aliases
2608 /// sqry alias list --global # Only global aliases
2609 /// sqry alias list --json # JSON output
2610 #[command(verbatim_doc_comment)]
2611 List {
2612 /// Show only local aliases (project-specific).
2613 #[arg(long, conflicts_with = "global", help_heading = headings::ALIAS_CONFIGURATION, display_order = 10)]
2614 local: bool,
2615
2616 /// Show only global aliases (cross-project).
2617 #[arg(long, conflicts_with = "local", help_heading = headings::ALIAS_CONFIGURATION, display_order = 20)]
2618 global: bool,
2619 },
2620
2621 /// Show details of a specific alias
2622 ///
2623 /// Displays the command, arguments, description, and storage location
2624 /// for the named alias.
2625 ///
2626 /// Example: sqry alias show my-funcs
2627 Show {
2628 /// Name of the alias to show.
2629 #[arg(value_name = "NAME", help_heading = headings::ALIAS_INPUT, display_order = 10)]
2630 name: String,
2631 },
2632
2633 /// Delete a saved alias
2634 ///
2635 /// Removes an alias from storage. If the alias exists in both local
2636 /// and global storage, specify --local or --global to delete from
2637 /// a specific location.
2638 ///
2639 /// Examples:
2640 /// sqry alias delete my-funcs # Delete (prefers local)
2641 /// sqry alias delete my-funcs --global # Delete from global only
2642 /// sqry alias delete my-funcs --force # Skip confirmation
2643 #[command(verbatim_doc_comment)]
2644 Delete {
2645 /// Name of the alias to delete.
2646 #[arg(value_name = "NAME", help_heading = headings::ALIAS_INPUT, display_order = 10)]
2647 name: String,
2648
2649 /// Delete from local storage only.
2650 #[arg(long, conflicts_with = "global", help_heading = headings::ALIAS_CONFIGURATION, display_order = 10)]
2651 local: bool,
2652
2653 /// Delete from global storage only.
2654 #[arg(long, conflicts_with = "local", help_heading = headings::ALIAS_CONFIGURATION, display_order = 20)]
2655 global: bool,
2656
2657 /// Skip confirmation prompt.
2658 #[arg(long, short = 'f', help_heading = headings::SAFETY_CONTROL, display_order = 10)]
2659 force: bool,
2660 },
2661
2662 /// Rename an existing alias
2663 ///
2664 /// Changes the name of an alias while preserving its command and arguments.
2665 /// The alias is renamed in the same storage location where it was found.
2666 ///
2667 /// Example: sqry alias rename old-name new-name
2668 Rename {
2669 /// Current name of the alias.
2670 #[arg(value_name = "OLD_NAME", help_heading = headings::ALIAS_INPUT, display_order = 10)]
2671 old_name: String,
2672
2673 /// New name for the alias.
2674 #[arg(value_name = "NEW_NAME", help_heading = headings::ALIAS_INPUT, display_order = 20)]
2675 new_name: String,
2676
2677 /// Rename in local storage only.
2678 #[arg(long, conflicts_with = "global", help_heading = headings::ALIAS_CONFIGURATION, display_order = 10)]
2679 local: bool,
2680
2681 /// Rename in global storage only.
2682 #[arg(long, conflicts_with = "local", help_heading = headings::ALIAS_CONFIGURATION, display_order = 20)]
2683 global: bool,
2684 },
2685
2686 /// Export aliases to a JSON file
2687 ///
2688 /// Exports aliases for backup or sharing. The export format is compatible
2689 /// with the import command for easy restoration.
2690 ///
2691 /// Examples:
2692 /// sqry alias export aliases.json # Export all
2693 /// sqry alias export aliases.json --local # Export local only
2694 #[command(verbatim_doc_comment)]
2695 Export {
2696 /// Output file path (use - for stdout).
2697 #[arg(value_name = "FILE", help_heading = headings::ALIAS_INPUT, display_order = 10)]
2698 file: String,
2699
2700 /// Export only local aliases.
2701 #[arg(long, conflicts_with = "global", help_heading = headings::ALIAS_CONFIGURATION, display_order = 10)]
2702 local: bool,
2703
2704 /// Export only global aliases.
2705 #[arg(long, conflicts_with = "local", help_heading = headings::ALIAS_CONFIGURATION, display_order = 20)]
2706 global: bool,
2707 },
2708
2709 /// Import aliases from a JSON file
2710 ///
2711 /// Imports aliases from an export file. Handles conflicts with existing
2712 /// aliases using the specified strategy.
2713 ///
2714 /// Examples:
2715 /// sqry alias import aliases.json # Import to local
2716 /// sqry alias import aliases.json --global # Import to global
2717 /// sqry alias import aliases.json --on-conflict skip
2718 #[command(verbatim_doc_comment)]
2719 Import {
2720 /// Input file path (use - for stdin).
2721 #[arg(value_name = "FILE", help_heading = headings::ALIAS_INPUT, display_order = 10)]
2722 file: String,
2723
2724 /// Import to local storage (default).
2725 #[arg(long, conflicts_with = "global", help_heading = headings::ALIAS_CONFIGURATION, display_order = 10)]
2726 local: bool,
2727
2728 /// Import to global storage.
2729 #[arg(long, conflicts_with = "local", help_heading = headings::ALIAS_CONFIGURATION, display_order = 20)]
2730 global: bool,
2731
2732 /// How to handle conflicts with existing aliases.
2733 #[arg(long, value_enum, default_value = "error", help_heading = headings::ALIAS_CONFIGURATION, display_order = 30)]
2734 on_conflict: ImportConflictArg,
2735
2736 /// Preview import without making changes.
2737 #[arg(long, help_heading = headings::SAFETY_CONTROL, display_order = 10)]
2738 dry_run: bool,
2739 },
2740}
2741
2742/// History management subcommands
2743#[derive(Subcommand, Debug, Clone)]
2744pub enum HistoryAction {
2745 /// List recent query history
2746 ///
2747 /// Shows recently executed queries with their timestamps, commands,
2748 /// and execution status.
2749 ///
2750 /// Examples:
2751 /// sqry history list # List recent (default 100)
2752 /// sqry history list --limit 50 # Last 50 entries
2753 /// sqry history list --json # JSON output
2754 #[command(verbatim_doc_comment)]
2755 List {
2756 /// Maximum number of entries to show.
2757 #[arg(long, short = 'n', default_value = "100", help_heading = headings::HISTORY_CONFIGURATION, display_order = 10)]
2758 limit: usize,
2759 },
2760
2761 /// Search query history
2762 ///
2763 /// Searches history entries by pattern. The pattern is matched
2764 /// against command names and arguments.
2765 ///
2766 /// Examples:
2767 /// sqry history search "function" # Find queries with "function"
2768 /// sqry history search "callers:" # Find caller queries
2769 #[command(verbatim_doc_comment)]
2770 Search {
2771 /// Search pattern (matched against command and args).
2772 #[arg(value_name = "PATTERN", help_heading = headings::HISTORY_INPUT, display_order = 10)]
2773 pattern: String,
2774
2775 /// Maximum number of results.
2776 #[arg(long, short = 'n', default_value = "100", help_heading = headings::HISTORY_CONFIGURATION, display_order = 10)]
2777 limit: usize,
2778 },
2779
2780 /// Clear query history
2781 ///
2782 /// Removes history entries. Can clear all entries or only those
2783 /// older than a specified duration.
2784 ///
2785 /// Examples:
2786 /// sqry history clear # Clear all (requires --confirm)
2787 /// sqry history clear --older 30d # Clear entries older than 30 days
2788 /// sqry history clear --older 1w # Clear entries older than 1 week
2789 #[command(verbatim_doc_comment)]
2790 Clear {
2791 /// Remove only entries older than this duration (e.g., 30d, 1w, 24h).
2792 #[arg(long, value_name = "DURATION", help_heading = headings::HISTORY_CONFIGURATION, display_order = 10)]
2793 older: Option<String>,
2794
2795 /// Confirm clearing history (required when clearing all).
2796 #[arg(long, help_heading = headings::SAFETY_CONTROL, display_order = 10)]
2797 confirm: bool,
2798 },
2799
2800 /// Show history statistics
2801 ///
2802 /// Displays aggregate statistics about query history including
2803 /// total entries, most used commands, and storage information.
2804 Stats,
2805}
2806
2807/// Insights management subcommands
2808#[derive(Subcommand, Debug, Clone)]
2809pub enum InsightsAction {
2810 /// Show usage summary for a time period
2811 ///
2812 /// Displays aggregated usage statistics including query counts,
2813 /// timing metrics, and workflow patterns.
2814 ///
2815 /// Examples:
2816 /// sqry insights show # Current week
2817 /// sqry insights show --week 2025-W50 # Specific week
2818 /// sqry insights show --json # JSON output
2819 #[command(verbatim_doc_comment)]
2820 Show {
2821 /// ISO week to display (e.g., 2025-W50). Defaults to current week.
2822 #[arg(long, short = 'w', value_name = "WEEK", help_heading = headings::INSIGHTS_CONFIGURATION, display_order = 10)]
2823 week: Option<String>,
2824 },
2825
2826 /// Show or modify uses configuration
2827 ///
2828 /// View the current configuration or change settings like
2829 /// enabling/disabling uses capture.
2830 ///
2831 /// Examples:
2832 /// sqry insights config # Show current config
2833 /// sqry insights config --enable # Enable uses capture
2834 /// sqry insights config --disable # Disable uses capture
2835 /// sqry insights config --retention 90 # Set retention to 90 days
2836 #[command(verbatim_doc_comment)]
2837 Config {
2838 /// Enable uses capture.
2839 #[arg(long, conflicts_with = "disable", help_heading = headings::INSIGHTS_CONFIGURATION, display_order = 10)]
2840 enable: bool,
2841
2842 /// Disable uses capture.
2843 #[arg(long, conflicts_with = "enable", help_heading = headings::INSIGHTS_CONFIGURATION, display_order = 20)]
2844 disable: bool,
2845
2846 /// Set retention period in days.
2847 #[arg(long, value_name = "DAYS", help_heading = headings::INSIGHTS_CONFIGURATION, display_order = 30)]
2848 retention: Option<u32>,
2849 },
2850
2851 /// Show storage status and statistics
2852 ///
2853 /// Displays information about the uses storage including
2854 /// total size, file count, and date range of stored events.
2855 ///
2856 /// Example:
2857 /// sqry insights status
2858 Status,
2859
2860 /// Clean up old event data
2861 ///
2862 /// Removes event logs older than the specified duration.
2863 /// Uses the configured retention period if --older is not specified.
2864 ///
2865 /// Examples:
2866 /// sqry insights prune # Use configured retention
2867 /// sqry insights prune --older 90d # Prune older than 90 days
2868 /// sqry insights prune --dry-run # Preview without deleting
2869 #[command(verbatim_doc_comment)]
2870 Prune {
2871 /// Remove entries older than this duration (e.g., 30d, 90d).
2872 /// Defaults to configured retention period.
2873 #[arg(long, value_name = "DURATION", help_heading = headings::INSIGHTS_CONFIGURATION, display_order = 10)]
2874 older: Option<String>,
2875
2876 /// Preview deletions without removing files.
2877 #[arg(long, help_heading = headings::SAFETY_CONTROL, display_order = 10)]
2878 dry_run: bool,
2879 },
2880
2881 /// Generate an anonymous usage snapshot for sharing
2882 ///
2883 /// Creates a privacy-safe snapshot of your usage patterns that you can
2884 /// share with the sqry community or attach to bug reports. All fields
2885 /// are strongly-typed enums and numerics — no code content, paths, or
2886 /// identifiers are ever included.
2887 ///
2888 /// Uses are disabled → exits 1. Empty weeks produce a valid snapshot
2889 /// with total_uses: 0 (not an error).
2890 ///
2891 /// JSON output is controlled by the global --json flag.
2892 ///
2893 /// Examples:
2894 /// sqry insights share # Current week, human-readable
2895 /// sqry --json insights share # JSON to stdout
2896 /// sqry insights share --output snap.json # Write JSON to file
2897 /// sqry insights share --week 2026-W09 # Specific week
2898 /// sqry insights share --from 2026-W07 --to 2026-W09 # Merge 3 weeks
2899 /// sqry insights share --dry-run # Preview without writing
2900 #[cfg(feature = "share")]
2901 #[command(verbatim_doc_comment)]
2902 Share {
2903 /// Specific ISO week to share (e.g., 2026-W09). Defaults to current week.
2904 /// Conflicts with --from / --to.
2905 #[arg(long, value_name = "WEEK", help_heading = headings::INSIGHTS_CONFIGURATION, display_order = 10,
2906 conflicts_with_all = ["from", "to"])]
2907 week: Option<String>,
2908
2909 /// Start of multi-week range (e.g., 2026-W07). Requires --to.
2910 #[arg(long, value_name = "WEEK", help_heading = headings::INSIGHTS_CONFIGURATION, display_order = 11,
2911 conflicts_with = "week", requires = "to")]
2912 from: Option<String>,
2913
2914 /// End of multi-week range (e.g., 2026-W09). Requires --from.
2915 #[arg(long, value_name = "WEEK", help_heading = headings::INSIGHTS_CONFIGURATION, display_order = 12,
2916 conflicts_with = "week", requires = "from")]
2917 to: Option<String>,
2918
2919 /// Write JSON snapshot to this file.
2920 #[arg(long, short = 'o', value_name = "FILE", help_heading = headings::INSIGHTS_OUTPUT, display_order = 20,
2921 conflicts_with = "dry_run")]
2922 output: Option<PathBuf>,
2923
2924 /// Preview what would be shared without writing a file.
2925 #[arg(long, help_heading = headings::SAFETY_CONTROL, display_order = 30,
2926 conflicts_with = "output")]
2927 dry_run: bool,
2928 },
2929}
2930
2931/// Import conflict resolution strategies
2932#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)]
2933#[value(rename_all = "lowercase")]
2934pub enum ImportConflictArg {
2935 /// Fail on any conflict (default)
2936 Error,
2937 /// Skip conflicting aliases
2938 Skip,
2939 /// Overwrite existing aliases
2940 Overwrite,
2941}
2942
2943/// Shell types for completions
2944#[derive(Debug, Clone, Copy, ValueEnum)]
2945#[allow(missing_docs)]
2946#[allow(clippy::enum_variant_names)]
2947pub enum Shell {
2948 Bash,
2949 Zsh,
2950 Fish,
2951 PowerShell,
2952 Elvish,
2953}
2954
2955/// Symbol types for filtering
2956#[derive(Debug, Clone, Copy, ValueEnum)]
2957#[allow(missing_docs)]
2958pub enum SymbolKind {
2959 Function,
2960 Class,
2961 Method,
2962 Struct,
2963 Enum,
2964 Interface,
2965 Trait,
2966 Variable,
2967 Constant,
2968 Type,
2969 Module,
2970 Namespace,
2971}
2972
2973impl std::fmt::Display for SymbolKind {
2974 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2975 match self {
2976 SymbolKind::Function => write!(f, "function"),
2977 SymbolKind::Class => write!(f, "class"),
2978 SymbolKind::Method => write!(f, "method"),
2979 SymbolKind::Struct => write!(f, "struct"),
2980 SymbolKind::Enum => write!(f, "enum"),
2981 SymbolKind::Interface => write!(f, "interface"),
2982 SymbolKind::Trait => write!(f, "trait"),
2983 SymbolKind::Variable => write!(f, "variable"),
2984 SymbolKind::Constant => write!(f, "constant"),
2985 SymbolKind::Type => write!(f, "type"),
2986 SymbolKind::Module => write!(f, "module"),
2987 SymbolKind::Namespace => write!(f, "namespace"),
2988 }
2989 }
2990}
2991
2992/// Index validation strictness modes
2993#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)]
2994#[value(rename_all = "lowercase")]
2995pub enum ValidationMode {
2996 /// Skip validation entirely (fastest)
2997 Off,
2998 /// Log warnings but continue (default)
2999 Warn,
3000 /// Abort on validation errors
3001 Fail,
3002}
3003
3004/// Metrics export format for validation status
3005#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
3006#[value(rename_all = "lower")]
3007pub enum MetricsFormat {
3008 /// JSON format (default, structured data)
3009 #[value(alias = "jsn")]
3010 Json,
3011 /// Prometheus `OpenMetrics` text format
3012 #[value(alias = "prom")]
3013 Prometheus,
3014}
3015
3016/// Classpath analysis depth for the `--classpath-depth` flag.
3017#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
3018#[value(rename_all = "lower")]
3019pub enum ClasspathDepthArg {
3020 /// Include all transitive dependencies.
3021 Full,
3022 /// Only direct (compile-scope) dependencies.
3023 Shallow,
3024}
3025
3026// Helper function to get the command with applied taxonomy
3027impl Cli {
3028 /// Get the command with taxonomy headings applied
3029 #[must_use]
3030 pub fn command_with_taxonomy() -> clap::Command {
3031 use clap::CommandFactory;
3032 let cmd = Self::command();
3033 headings::apply_root_layout(cmd)
3034 }
3035
3036 /// Validate CLI arguments that have dependencies not enforceable via clap
3037 ///
3038 /// Returns an error message if validation fails, None if valid.
3039 #[must_use]
3040 pub fn validate(&self) -> Option<&'static str> {
3041 let tabular_mode = self.csv || self.tsv;
3042
3043 // --headers, --columns, and --raw-csv require CSV or TSV mode
3044 if self.headers && !tabular_mode {
3045 return Some("--headers requires --csv or --tsv");
3046 }
3047 if self.columns.is_some() && !tabular_mode {
3048 return Some("--columns requires --csv or --tsv");
3049 }
3050 if self.raw_csv && !tabular_mode {
3051 return Some("--raw-csv requires --csv or --tsv");
3052 }
3053
3054 if tabular_mode && let Err(msg) = output::parse_columns(self.columns.as_ref()) {
3055 return Some(Box::leak(msg.into_boxed_str()));
3056 }
3057
3058 None
3059 }
3060
3061 /// Get the search path, defaulting to current directory if not specified
3062 #[must_use]
3063 pub fn search_path(&self) -> &str {
3064 self.path.as_deref().unwrap_or(".")
3065 }
3066
3067 /// Return the plugin-selection arguments for the active subcommand.
3068 #[must_use]
3069 pub fn plugin_selection_args(&self) -> PluginSelectionArgs {
3070 match self.command.as_deref() {
3071 Some(
3072 Command::Query {
3073 plugin_selection, ..
3074 }
3075 | Command::Index {
3076 plugin_selection, ..
3077 }
3078 | Command::Update {
3079 plugin_selection, ..
3080 }
3081 | Command::Watch {
3082 plugin_selection, ..
3083 },
3084 ) => plugin_selection.clone(),
3085 _ => PluginSelectionArgs::default(),
3086 }
3087 }
3088
3089 /// Check if tabular output mode is enabled
3090 #[allow(dead_code)]
3091 #[must_use]
3092 pub fn is_tabular_output(&self) -> bool {
3093 self.csv || self.tsv
3094 }
3095
3096 /// Create pager configuration from CLI flags
3097 ///
3098 /// Returns `PagerConfig` based on `--pager`, `--no-pager`, and `--pager-cmd` flags.
3099 ///
3100 /// # Structured Output Handling
3101 ///
3102 /// For machine-readable formats (JSON, CSV, TSV), paging is disabled by default
3103 /// to avoid breaking pipelines. Use `--pager` to explicitly enable paging for
3104 /// these formats.
3105 #[must_use]
3106 pub fn pager_config(&self) -> crate::output::PagerConfig {
3107 // Structured output bypasses pager unless --pager is explicit
3108 let is_structured_output = self.json || self.csv || self.tsv;
3109 let effective_no_pager = self.no_pager || (is_structured_output && !self.pager);
3110
3111 crate::output::PagerConfig::from_cli_flags(
3112 self.pager,
3113 effective_no_pager,
3114 self.pager_cmd.as_deref(),
3115 )
3116 }
3117}
3118
3119#[cfg(test)]
3120mod tests {
3121 use super::*;
3122 use crate::large_stack_test;
3123
3124 /// Guard: keep the `Command` enum from silently ballooning.
3125 /// If this fails, consider extracting the largest variant into a Box<T>.
3126 #[test]
3127 fn test_command_enum_size() {
3128 let size = std::mem::size_of::<Command>();
3129 assert!(
3130 size <= 256,
3131 "Command enum is {size} bytes, should be <= 256"
3132 );
3133 }
3134
3135 large_stack_test! {
3136 #[test]
3137 fn test_cli_parse_basic_search() {
3138 let cli = Cli::parse_from(["sqry", "main"]);
3139 assert!(cli.command.is_none());
3140 assert_eq!(cli.pattern, Some("main".to_string()));
3141 assert_eq!(cli.path, None); // Defaults to None, use cli.search_path() to get "."
3142 assert_eq!(cli.search_path(), ".");
3143 }
3144 }
3145
3146 large_stack_test! {
3147 #[test]
3148 fn test_cli_parse_with_path() {
3149 let cli = Cli::parse_from(["sqry", "test", "src/"]);
3150 assert_eq!(cli.pattern, Some("test".to_string()));
3151 assert_eq!(cli.path, Some("src/".to_string()));
3152 assert_eq!(cli.search_path(), "src/");
3153 }
3154 }
3155
3156 large_stack_test! {
3157 #[test]
3158 fn test_cli_parse_search_subcommand() {
3159 let cli = Cli::parse_from(["sqry", "search", "main"]);
3160 assert!(matches!(cli.command.as_deref(), Some(Command::Search { .. })));
3161 }
3162 }
3163
3164 large_stack_test! {
3165 #[test]
3166 fn test_cli_parse_query_subcommand() {
3167 let cli = Cli::parse_from(["sqry", "query", "kind:function"]);
3168 assert!(matches!(cli.command.as_deref(), Some(Command::Query { .. })));
3169 }
3170 }
3171
3172 large_stack_test! {
3173 #[test]
3174 fn test_cli_flags() {
3175 let cli = Cli::parse_from(["sqry", "main", "--json", "--no-color", "--ignore-case"]);
3176 assert!(cli.json);
3177 assert!(cli.no_color);
3178 assert!(cli.ignore_case);
3179 }
3180 }
3181
3182 large_stack_test! {
3183 #[test]
3184 fn test_validation_mode_default() {
3185 let cli = Cli::parse_from(["sqry", "index"]);
3186 assert_eq!(cli.validate, ValidationMode::Warn);
3187 assert!(!cli.auto_rebuild);
3188 }
3189 }
3190
3191 large_stack_test! {
3192 #[test]
3193 fn test_validation_mode_flags() {
3194 let cli = Cli::parse_from(["sqry", "index", "--validate", "fail", "--auto-rebuild"]);
3195 assert_eq!(cli.validate, ValidationMode::Fail);
3196 assert!(cli.auto_rebuild);
3197 }
3198 }
3199
3200 large_stack_test! {
3201 #[test]
3202 fn test_plugin_selection_flags_parse() {
3203 let cli = Cli::parse_from([
3204 "sqry",
3205 "index",
3206 "--include-high-cost",
3207 "--enable-plugin",
3208 "json",
3209 "--disable-plugin",
3210 "rust",
3211 ]);
3212 let plugin_selection = cli.plugin_selection_args();
3213 assert!(plugin_selection.include_high_cost);
3214 assert_eq!(plugin_selection.enable_plugins, vec!["json".to_string()]);
3215 assert_eq!(plugin_selection.disable_plugins, vec!["rust".to_string()]);
3216 }
3217 }
3218
3219 large_stack_test! {
3220 #[test]
3221 fn test_plugin_selection_language_aliases_parse() {
3222 let cli = Cli::parse_from([
3223 "sqry",
3224 "index",
3225 "--enable-language",
3226 "json",
3227 "--disable-language",
3228 "rust",
3229 ]);
3230 let plugin_selection = cli.plugin_selection_args();
3231 assert_eq!(plugin_selection.enable_plugins, vec!["json".to_string()]);
3232 assert_eq!(plugin_selection.disable_plugins, vec!["rust".to_string()]);
3233 }
3234 }
3235
3236 large_stack_test! {
3237 #[test]
3238 fn test_validate_rejects_invalid_columns() {
3239 let cli = Cli::parse_from([
3240 "sqry",
3241 "--csv",
3242 "--columns",
3243 "name,unknown",
3244 "query",
3245 "path",
3246 ]);
3247 let msg = cli.validate().expect("validation should fail");
3248 assert!(msg.contains("Unknown column"), "Unexpected message: {msg}");
3249 }
3250 }
3251
3252 large_stack_test! {
3253 #[test]
3254 fn test_index_rebuild_alias_sets_force() {
3255 // Verify --rebuild is an alias for --force
3256 let cli = Cli::parse_from(["sqry", "index", "--rebuild", "."]);
3257 if let Some(Command::Index { force, .. }) = cli.command.as_deref() {
3258 assert!(force, "--rebuild should set force=true");
3259 } else {
3260 panic!("Expected Index command");
3261 }
3262 }
3263 }
3264
3265 large_stack_test! {
3266 #[test]
3267 fn test_index_force_still_works() {
3268 // Ensure --force continues to work (backward compat)
3269 let cli = Cli::parse_from(["sqry", "index", "--force", "."]);
3270 if let Some(Command::Index { force, .. }) = cli.command.as_deref() {
3271 assert!(force, "--force should set force=true");
3272 } else {
3273 panic!("Expected Index command");
3274 }
3275 }
3276 }
3277
3278 large_stack_test! {
3279 #[test]
3280 fn test_graph_deps_alias() {
3281 // Verify "deps" is an alias for dependency-tree
3282 let cli = Cli::parse_from(["sqry", "graph", "deps", "main"]);
3283 assert!(matches!(
3284 cli.command.as_deref(),
3285 Some(Command::Graph {
3286 operation: GraphOperation::DependencyTree { .. },
3287 ..
3288 })
3289 ));
3290 }
3291 }
3292
3293 large_stack_test! {
3294 #[test]
3295 fn test_graph_cyc_alias() {
3296 let cli = Cli::parse_from(["sqry", "graph", "cyc"]);
3297 assert!(matches!(
3298 cli.command.as_deref(),
3299 Some(Command::Graph {
3300 operation: GraphOperation::Cycles { .. },
3301 ..
3302 })
3303 ));
3304 }
3305 }
3306
3307 large_stack_test! {
3308 #[test]
3309 fn test_graph_cx_alias() {
3310 let cli = Cli::parse_from(["sqry", "graph", "cx"]);
3311 assert!(matches!(
3312 cli.command.as_deref(),
3313 Some(Command::Graph {
3314 operation: GraphOperation::Complexity { .. },
3315 ..
3316 })
3317 ));
3318 }
3319 }
3320
3321 large_stack_test! {
3322 #[test]
3323 fn test_graph_nodes_args() {
3324 let cli = Cli::parse_from([
3325 "sqry",
3326 "graph",
3327 "nodes",
3328 "--kind",
3329 "function",
3330 "--languages",
3331 "rust",
3332 "--file",
3333 "src/",
3334 "--name",
3335 "main",
3336 "--qualified-name",
3337 "crate::main",
3338 "--limit",
3339 "5",
3340 "--offset",
3341 "2",
3342 "--full-paths",
3343 ]);
3344 if let Some(Command::Graph {
3345 operation:
3346 GraphOperation::Nodes {
3347 kind,
3348 languages,
3349 file,
3350 name,
3351 qualified_name,
3352 limit,
3353 offset,
3354 full_paths,
3355 },
3356 ..
3357 }) = cli.command.as_deref()
3358 {
3359 assert_eq!(kind, &Some("function".to_string()));
3360 assert_eq!(languages, &Some("rust".to_string()));
3361 assert_eq!(file, &Some("src/".to_string()));
3362 assert_eq!(name, &Some("main".to_string()));
3363 assert_eq!(qualified_name, &Some("crate::main".to_string()));
3364 assert_eq!(*limit, 5);
3365 assert_eq!(*offset, 2);
3366 assert!(full_paths);
3367 } else {
3368 panic!("Expected Graph Nodes command");
3369 }
3370 }
3371 }
3372
3373 large_stack_test! {
3374 #[test]
3375 fn test_graph_edges_args() {
3376 let cli = Cli::parse_from([
3377 "sqry",
3378 "graph",
3379 "edges",
3380 "--kind",
3381 "calls",
3382 "--from",
3383 "main",
3384 "--to",
3385 "worker",
3386 "--from-lang",
3387 "rust",
3388 "--to-lang",
3389 "python",
3390 "--file",
3391 "src/main.rs",
3392 "--limit",
3393 "10",
3394 "--offset",
3395 "1",
3396 "--full-paths",
3397 ]);
3398 if let Some(Command::Graph {
3399 operation:
3400 GraphOperation::Edges {
3401 kind,
3402 from,
3403 to,
3404 from_lang,
3405 to_lang,
3406 file,
3407 limit,
3408 offset,
3409 full_paths,
3410 },
3411 ..
3412 }) = cli.command.as_deref()
3413 {
3414 assert_eq!(kind, &Some("calls".to_string()));
3415 assert_eq!(from, &Some("main".to_string()));
3416 assert_eq!(to, &Some("worker".to_string()));
3417 assert_eq!(from_lang, &Some("rust".to_string()));
3418 assert_eq!(to_lang, &Some("python".to_string()));
3419 assert_eq!(file, &Some("src/main.rs".to_string()));
3420 assert_eq!(*limit, 10);
3421 assert_eq!(*offset, 1);
3422 assert!(full_paths);
3423 } else {
3424 panic!("Expected Graph Edges command");
3425 }
3426 }
3427 }
3428
3429 // ===== Pager Tests (P2-29) =====
3430
3431 large_stack_test! {
3432 #[test]
3433 fn test_pager_flag_default() {
3434 let cli = Cli::parse_from(["sqry", "query", "kind:function"]);
3435 assert!(!cli.pager);
3436 assert!(!cli.no_pager);
3437 assert!(cli.pager_cmd.is_none());
3438 }
3439 }
3440
3441 large_stack_test! {
3442 #[test]
3443 fn test_pager_flag() {
3444 let cli = Cli::parse_from(["sqry", "--pager", "query", "kind:function"]);
3445 assert!(cli.pager);
3446 assert!(!cli.no_pager);
3447 }
3448 }
3449
3450 large_stack_test! {
3451 #[test]
3452 fn test_no_pager_flag() {
3453 let cli = Cli::parse_from(["sqry", "--no-pager", "query", "kind:function"]);
3454 assert!(!cli.pager);
3455 assert!(cli.no_pager);
3456 }
3457 }
3458
3459 large_stack_test! {
3460 #[test]
3461 fn test_pager_cmd_flag() {
3462 let cli = Cli::parse_from([
3463 "sqry",
3464 "--pager-cmd",
3465 "bat --style=plain",
3466 "query",
3467 "kind:function",
3468 ]);
3469 assert_eq!(cli.pager_cmd, Some("bat --style=plain".to_string()));
3470 }
3471 }
3472
3473 large_stack_test! {
3474 #[test]
3475 fn test_pager_and_no_pager_conflict() {
3476 // These flags conflict and clap should reject
3477 let result =
3478 Cli::try_parse_from(["sqry", "--pager", "--no-pager", "query", "kind:function"]);
3479 assert!(result.is_err());
3480 }
3481 }
3482
3483 large_stack_test! {
3484 #[test]
3485 fn test_pager_flags_global() {
3486 // Pager flags work with any subcommand
3487 let cli = Cli::parse_from(["sqry", "--no-pager", "search", "test"]);
3488 assert!(cli.no_pager);
3489
3490 let cli = Cli::parse_from(["sqry", "--pager", "index"]);
3491 assert!(cli.pager);
3492 }
3493 }
3494
3495 large_stack_test! {
3496 #[test]
3497 fn test_pager_config_json_bypasses_pager() {
3498 use crate::output::pager::PagerMode;
3499
3500 // JSON output should bypass pager by default
3501 let cli = Cli::parse_from(["sqry", "--json", "search", "test"]);
3502 let config = cli.pager_config();
3503 assert_eq!(config.enabled, PagerMode::Never);
3504 }
3505 }
3506
3507 large_stack_test! {
3508 #[test]
3509 fn test_pager_config_csv_bypasses_pager() {
3510 use crate::output::pager::PagerMode;
3511
3512 // CSV output should bypass pager by default
3513 let cli = Cli::parse_from(["sqry", "--csv", "search", "test"]);
3514 let config = cli.pager_config();
3515 assert_eq!(config.enabled, PagerMode::Never);
3516 }
3517 }
3518
3519 large_stack_test! {
3520 #[test]
3521 fn test_pager_config_tsv_bypasses_pager() {
3522 use crate::output::pager::PagerMode;
3523
3524 // TSV output should bypass pager by default
3525 let cli = Cli::parse_from(["sqry", "--tsv", "search", "test"]);
3526 let config = cli.pager_config();
3527 assert_eq!(config.enabled, PagerMode::Never);
3528 }
3529 }
3530
3531 large_stack_test! {
3532 #[test]
3533 fn test_pager_config_json_with_explicit_pager() {
3534 use crate::output::pager::PagerMode;
3535
3536 // JSON with explicit --pager should enable pager
3537 let cli = Cli::parse_from(["sqry", "--json", "--pager", "search", "test"]);
3538 let config = cli.pager_config();
3539 assert_eq!(config.enabled, PagerMode::Always);
3540 }
3541 }
3542
3543 large_stack_test! {
3544 #[test]
3545 fn test_pager_config_text_output_auto() {
3546 use crate::output::pager::PagerMode;
3547
3548 // Text output (default) should use auto pager mode
3549 let cli = Cli::parse_from(["sqry", "search", "test"]);
3550 let config = cli.pager_config();
3551 assert_eq!(config.enabled, PagerMode::Auto);
3552 }
3553 }
3554
3555 // ===== Macro boundary CLI tests =====
3556
3557 large_stack_test! {
3558 #[test]
3559 fn test_cache_expand_args_parsing() {
3560 let cli = Cli::parse_from([
3561 "sqry", "cache", "expand",
3562 "--refresh",
3563 "--crate-name", "my_crate",
3564 "--dry-run",
3565 "--output", "/tmp/expand-out",
3566 ]);
3567 if let Some(Command::Cache { action }) = cli.command.as_deref() {
3568 match action {
3569 CacheAction::Expand {
3570 refresh,
3571 crate_name,
3572 dry_run,
3573 output,
3574 } => {
3575 assert!(refresh);
3576 assert_eq!(crate_name.as_deref(), Some("my_crate"));
3577 assert!(dry_run);
3578 assert_eq!(output.as_deref(), Some(std::path::Path::new("/tmp/expand-out")));
3579 }
3580 _ => panic!("Expected CacheAction::Expand"),
3581 }
3582 } else {
3583 panic!("Expected Cache command");
3584 }
3585 }
3586 }
3587
3588 large_stack_test! {
3589 #[test]
3590 fn test_cache_expand_defaults() {
3591 let cli = Cli::parse_from(["sqry", "cache", "expand"]);
3592 if let Some(Command::Cache { action }) = cli.command.as_deref() {
3593 match action {
3594 CacheAction::Expand {
3595 refresh,
3596 crate_name,
3597 dry_run,
3598 output,
3599 } => {
3600 assert!(!refresh);
3601 assert!(crate_name.is_none());
3602 assert!(!dry_run);
3603 assert!(output.is_none());
3604 }
3605 _ => panic!("Expected CacheAction::Expand"),
3606 }
3607 } else {
3608 panic!("Expected Cache command");
3609 }
3610 }
3611 }
3612
3613 large_stack_test! {
3614 #[test]
3615 fn test_index_macro_flags_parsing() {
3616 let cli = Cli::parse_from([
3617 "sqry", "index",
3618 "--enable-macro-expansion",
3619 "--cfg", "test",
3620 "--cfg", "unix",
3621 "--expand-cache", "/tmp/expand",
3622 ]);
3623 if let Some(Command::Index {
3624 enable_macro_expansion,
3625 cfg_flags,
3626 expand_cache,
3627 ..
3628 }) = cli.command.as_deref()
3629 {
3630 assert!(enable_macro_expansion);
3631 assert_eq!(cfg_flags, &["test".to_string(), "unix".to_string()]);
3632 assert_eq!(expand_cache.as_deref(), Some(std::path::Path::new("/tmp/expand")));
3633 } else {
3634 panic!("Expected Index command");
3635 }
3636 }
3637 }
3638
3639 large_stack_test! {
3640 #[test]
3641 fn test_index_macro_flags_defaults() {
3642 let cli = Cli::parse_from(["sqry", "index"]);
3643 if let Some(Command::Index {
3644 enable_macro_expansion,
3645 cfg_flags,
3646 expand_cache,
3647 ..
3648 }) = cli.command.as_deref()
3649 {
3650 assert!(!enable_macro_expansion);
3651 assert!(cfg_flags.is_empty());
3652 assert!(expand_cache.is_none());
3653 } else {
3654 panic!("Expected Index command");
3655 }
3656 }
3657 }
3658
3659 large_stack_test! {
3660 #[test]
3661 fn test_search_macro_flags_parsing() {
3662 let cli = Cli::parse_from([
3663 "sqry", "search", "test_fn",
3664 "--cfg-filter", "test",
3665 "--include-generated",
3666 "--macro-boundaries",
3667 ]);
3668 if let Some(Command::Search {
3669 cfg_filter,
3670 include_generated,
3671 macro_boundaries,
3672 ..
3673 }) = cli.command.as_deref()
3674 {
3675 assert_eq!(cfg_filter.as_deref(), Some("test"));
3676 assert!(include_generated);
3677 assert!(macro_boundaries);
3678 } else {
3679 panic!("Expected Search command");
3680 }
3681 }
3682 }
3683
3684 large_stack_test! {
3685 #[test]
3686 fn test_search_macro_flags_defaults() {
3687 let cli = Cli::parse_from(["sqry", "search", "test_fn"]);
3688 if let Some(Command::Search {
3689 cfg_filter,
3690 include_generated,
3691 macro_boundaries,
3692 ..
3693 }) = cli.command.as_deref()
3694 {
3695 assert!(cfg_filter.is_none());
3696 assert!(!include_generated);
3697 assert!(!macro_boundaries);
3698 } else {
3699 panic!("Expected Search command");
3700 }
3701 }
3702 }
3703}