cargo-brief 0.7.1

Visibility-aware Rust API extractor — pseudo-Rust output for AI agent consumption
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
use clap::{Args, Parser, Subcommand};

/// Cargo subcommand wrapper.
#[derive(Parser, Debug)]
#[command(name = "cargo", bin_name = "cargo", version)]
pub struct Cargo {
    #[command(subcommand)]
    pub command: CargoCommand,
}

#[derive(Subcommand, Debug)]
pub enum CargoCommand {
    /// Extract and display Rust crate API as pseudo-Rust documentation.
    Brief(BriefDirect),
}

/// Direct invocation wrapper (`cargo-brief <subcommand> ...`).
#[derive(Parser, Debug)]
#[command(
    version,
    about = "Visibility-aware Rust API extractor for AI agents",
    after_help = "Run `cargo brief <subcommand> --help` for subcommand-specific options."
)]
pub struct BriefDirect {
    /// Interpret TARGET as a crates.io package spec (e.g., serde@1, tokio@1.0)
    #[arg(short = 'C', long, global = true)]
    pub crates: bool,

    /// Comma-separated features to enable (requires -C)
    #[arg(
        short = 'F',
        long,
        value_name = "FEATURES",
        global = true,
        requires = "crates"
    )]
    pub features: Option<String>,

    /// Disable default features (requires -C)
    #[arg(long, global = true, requires = "crates")]
    pub no_default_features: bool,

    /// Skip cache and use a temporary workspace (requires -C)
    #[arg(long, global = true, requires = "crates")]
    pub no_cache: bool,

    #[command(subcommand)]
    pub command: BriefCommand,
}

impl BriefDirect {
    /// Extract remote-mode options from the top-level flags.
    pub fn remote_opts(&self) -> RemoteOpts {
        RemoteOpts {
            crates: self.crates,
            features: self.features.clone(),
            no_default_features: self.no_default_features,
            no_cache: self.no_cache,
        }
    }
}

/// Remote crate mode options (extracted from BriefDirect global flags).
#[derive(Debug, Clone, Default)]
pub struct RemoteOpts {
    pub crates: bool,
    pub features: Option<String>,
    pub no_default_features: bool,
    pub no_cache: bool,
}

#[derive(Subcommand, Debug, Clone)]
pub enum BriefCommand {
    /// Extract and render crate API as pseudo-Rust documentation
    #[command(after_help = "\
EXAMPLES:
  # Browse the current crate's API (run inside a Cargo project)
  cargo brief api

  # Browse a specific module in the current crate
  cargo brief api self::net::tcp

  # Inspect a crates.io dependency (cached after first run)
  cargo brief -C api serde@1 --compact
  cargo brief -C -F rt,net,io-util api tokio@1

  # Browse a specific module of a remote crate
  cargo brief -C api tokio@1::net
  cargo brief -C -F net api tokio@1 net

  # Reduce output verbosity for large crates
  cargo brief -C api tokio@1 --compact
  cargo brief -C api tokio@1 --doc-lines 1

RESOLUTION RULES:
  The <TARGET> argument is resolved as follows:
    1. \"self\"           → current package (cwd-based detection)
    2. \"self::mod\"      → current package, specific module
    3. \"crate::mod\"     → named crate + module in one argument
    4. \"src/foo.rs\"     → file path auto-converted to module path
    5. \"crate_name\"     → workspace package (hyphen/underscore normalized)
    6. \"unknown_name\"   → treated as package name (use \"self::mod\" for modules)

  With -C, TARGET is the crate spec (e.g., serde@1, tokio@1.0).

  The [MODULE_PATH] argument also accepts file paths (e.g., src/foo.rs).")]
    Api(ApiArgs),

    /// Search for items by name across a crate
    #[command(after_help = "\
EXAMPLES:
  # Substring search (smart-case: all-lowercase = insensitive)
  cargo brief -C search axum@0.8 Router route
  cargo brief -C search bevy ShaderRef Material

  # OR-match with comma, methods-of
  cargo brief search self \"EventReader,EventWriter\"
  cargo brief -C search bytes@1 --methods-of Bytes

  # Glob, exact match, exclusion
  cargo brief search bevy \"Shader*Ref\"               # * = 0+ chars, ? = 1 char
  cargo brief search bevy \"=Router\"                   # final :: segment only
  cargo brief search bevy -- spawn -test              # -- needed for -prefix args
  cargo brief search bevy \"*Plugin*,*Resource* -test\"

PATTERN SYNTAX:
  Smart-case: all-lowercase → case-insensitive, any uppercase → case-sensitive.
  Space = AND, comma = OR. Multiple args are joined with spaces.

  Operators (per token):
    word     substring — path contains \"word\"
    w*ld     glob — * matches 0+ chars, ? matches 1 char (full-path anchored)
    =Name    exact — final path segment (after last ::) equals \"Name\"
    -term    exclude — remove matches (works with substring, glob, or -=exact)

  Exclusions are global across all OR groups.")]
    Search(SearchArgs),

    /// Grep example/test/bench source files from a crate
    #[command(after_help = "\
EXAMPLES:
  # List example files with their module docs
  cargo brief examples self
  cargo brief -C examples tokio@1

  # Grep for a pattern in example files
  cargo brief examples self spawn
  cargo brief -C examples hecs spawn_at --context 3

  # Multiple patterns are AND-matched (no quotes needed)
  cargo brief examples self spawn async

  # Include tests and benches directories
  cargo brief -C examples serde --tests --benches derive

MATCHING:
  Multiple pattern arguments are joined with spaces (AND-matched).
  Smart-case: all-lowercase pattern = case-insensitive, any uppercase = case-sensitive.
  Without a pattern, lists files with their //! doc comments.")]
    Examples(ExamplesArgs),

    /// Show a compact module-level summary with item counts
    #[command(after_help = "\
EXAMPLES:
  # Summarize the current crate
  cargo brief summary self

  # Summarize a remote crate
  cargo brief -C -F full summary tokio@1

  # Summarize a specific module
  cargo brief -C summary bevy bevy::ecs")]
    Summary(SummaryArgs),

    /// Run a tree-sitter structural query against crate source files
    #[command(after_help = "\
EXAMPLES:
  # Find all function definitions
  cargo brief ts self '(function_item)'

  # Capture function names
  cargo brief ts self '(function_item name: (identifier) @name)' --captures

  # Find impl blocks for a specific trait
  cargo brief ts self '(impl_item trait: (type_identifier) @t (#eq? @t \"MyTrait\"))'

  # Find struct definitions with their fields
  cargo brief ts self '(struct_item name: (type_identifier) @name body: (field_declaration_list) @fields)' --captures

  # Find functions returning Result
  cargo brief ts self '(function_item return_type: (generic_type type: (type_identifier) @r (#eq? @r \"Result\")))'

  # Find let bindings with type annotations
  cargo brief ts self '(let_declaration pattern: (_) @pat type: (_) @type)' --captures

  # Find all call expressions to a specific function
  cargo brief ts self '(call_expression function: (identifier) @fn (#eq? @fn \"spawn\"))'

  # Location-only output for large result sets
  cargo brief ts self '(function_item)' --quiet

  # Limit results
  cargo brief ts self '(function_item)' --limit 10

  # Only search src/ (skip tests/examples/benches)
  cargo brief ts self '(function_item)' --src-only

  # Query a remote crate's source
  cargo brief -C ts serde@1 '(struct_item)'

QUERY SYNTAX:
  Tree-sitter S-expression patterns match AST nodes by type and structure.
  Explore the AST interactively: https://tree-sitter.github.io/tree-sitter/playground

NODE TYPES (common Rust constructs):
  Items:       function_item, struct_item, enum_item, impl_item, trait_item,
               type_item, const_item, static_item, macro_definition,
               use_declaration, mod_item
  Expressions: call_expression, method_call_expression, field_expression,
               match_expression, if_expression, closure_expression,
               return_expression, await_expression
  Statements:  let_declaration, expression_statement, assignment_expression
  Types:       type_identifier, generic_type, reference_type, array_type,
               function_type, tuple_type
  Other:       attribute_item, line_comment, block_comment, string_literal,
               identifier, field_identifier

CAPTURES:
  @name binds a node. In default mode, the outermost matched node is shown
  (captures used only in predicates don't affect output). With --captures,
  each @name: text pair is shown separately. Capture-less queries work too —
  an internal @_match capture is auto-added.

PREDICATES:
  (#eq? @cap \"value\")     — exact string match on captured node text
  (#match? @cap \"regex\")  — regex match on captured node text
  (#not-eq? @cap \"value\") — negated exact match
  (#any-of? @cap \"a\" \"b\") — match any of the listed strings

TIPS:
  - Rust async functions are function_item with modifiers, not a separate type
  - Use (type_identifier) for type names, (identifier) for variable/fn names
  - Nested captures: (struct_item (field_declaration_list
      (field_declaration name: (field_identifier) @field)))
  - Wildcards: (_) matches any single node type
  - #[derive(...)] is: (attribute_item (attribute (identifier) @a (#eq? @a \"derive\")))")]
    Ts(TsArgs),

    /// Clear cached remote crate workspaces
    #[command(after_help = "\
EXAMPLES:
  # Clear all cached workspaces
  cargo brief clean

  # Clear caches for a specific crate
  cargo brief clean serde")]
    Clean(CleanArgs),
}

// === Shared Args Groups ===

/// Target resolution arguments (crate + module).
#[derive(Args, Debug, Clone)]
pub struct TargetArgs {
    /// Target to inspect: crate name, "self", crate::module, or file path
    #[arg(value_name = "TARGET", default_value = "self")]
    pub crate_name: String,

    /// Module path or file path within the crate (e.g., "my_mod::submod" or "src/foo.rs")
    pub module_path: Option<String>,

    /// Caller's package name (for visibility resolution)
    #[arg(long, help_heading = "Local Workspace")]
    pub at_package: Option<String>,

    /// Caller's module path (determines what is visible)
    #[arg(long, help_heading = "Local Workspace")]
    pub at_mod: Option<String>,

    /// Path to Cargo.toml
    #[arg(long, help_heading = "Local Workspace")]
    pub manifest_path: Option<String>,
}

/// Output filtering and density flags.
#[derive(Args, Debug, Clone)]
pub struct FilterArgs {
    /// Exclude structs
    #[arg(long, help_heading = "Filtering")]
    pub no_structs: bool,

    /// Exclude enums
    #[arg(long, help_heading = "Filtering")]
    pub no_enums: bool,

    /// Exclude traits
    #[arg(long, help_heading = "Filtering")]
    pub no_traits: bool,

    /// Exclude free functions
    #[arg(long, help_heading = "Filtering")]
    pub no_functions: bool,

    /// Exclude type aliases
    #[arg(long, help_heading = "Filtering")]
    pub no_aliases: bool,

    /// Exclude constants and statics
    #[arg(long, help_heading = "Filtering")]
    pub no_constants: bool,

    /// Exclude unions
    #[arg(long, help_heading = "Filtering")]
    pub no_unions: bool,

    /// Exclude macros
    #[arg(long, help_heading = "Filtering")]
    pub no_macros: bool,

    /// Suppress doc comments from output
    #[arg(long, help_heading = "Filtering")]
    pub no_docs: bool,

    /// Suppress crate-level //! documentation
    #[arg(long, help_heading = "Filtering")]
    pub no_crate_docs: bool,

    /// Limit doc comments to first N lines (0 = suppress all)
    #[arg(long, value_name = "N", help_heading = "Filtering")]
    pub doc_lines: Option<usize>,

    /// Compact output: suppress doc comments, collapse struct fields, enum variants, and trait items
    #[arg(long, help_heading = "Filtering")]
    pub compact: bool,

    /// Show all attributes (#[must_use], #[repr(...)], etc.)
    #[arg(long, help_heading = "Filtering")]
    pub verbose_metadata: bool,

    /// Show all item kinds including blanket/auto-trait impls
    #[arg(long)]
    pub all: bool,
}

/// Global options shared across all subcommands.
#[derive(Args, Debug, Clone)]
pub struct GlobalArgs {
    /// Nightly toolchain name
    #[arg(long, default_value = "nightly", help_heading = "Advanced")]
    pub toolchain: String,

    /// Show progress messages on stderr during pipeline execution
    #[arg(short, long)]
    pub verbose: bool,
}

// === Subcommand Args ===

/// Arguments for the `api` subcommand.
#[derive(Args, Debug, Clone)]
pub struct ApiArgs {
    #[command(flatten)]
    pub target: TargetArgs,

    #[command(flatten)]
    pub filter: FilterArgs,

    #[command(flatten)]
    pub global: GlobalArgs,

    /// How many submodule levels to recurse into
    #[arg(long, default_value = "1")]
    pub depth: u32,

    /// Recurse into all submodules (no depth limit)
    #[arg(long)]
    pub recursive: bool,

    /// Suppress glob re-export expansion (show pub use lines instead of inlined definitions)
    #[arg(long)]
    pub no_expand_glob: bool,
}

/// Arguments for the `search` subcommand.
#[derive(Args, Debug, Clone)]
pub struct SearchArgs {
    /// Target crate to search: crate name, "self", or crate::module
    #[arg(value_name = "TARGET", default_value = "self")]
    pub crate_name: String,

    /// Search patterns — multiple args are AND-matched (use -- for patterns starting with -)
    #[arg(value_name = "PATTERN", num_args = 0..)]
    pub patterns: Vec<String>,

    #[command(flatten)]
    pub filter: FilterArgs,

    #[command(flatten)]
    pub global: GlobalArgs,

    /// Caller's package name (for visibility resolution)
    #[arg(long, help_heading = "Local Workspace")]
    pub at_package: Option<String>,

    /// Caller's module path (determines what is visible)
    #[arg(long, help_heading = "Local Workspace")]
    pub at_mod: Option<String>,

    /// Path to Cargo.toml
    #[arg(long, help_heading = "Local Workspace")]
    pub manifest_path: Option<String>,

    /// Limit search results: N (first N) or OFFSET:N (skip OFFSET, show N)
    #[arg(long, value_name = "[OFFSET:]N")]
    pub limit: Option<String>,

    /// Show methods/fields of a type (shorthand for pattern + exclusion flags)
    #[arg(long, value_name = "TYPE")]
    pub methods_of: Option<String>,

    /// Filter results by item kind (comma-separated: fn, struct, enum, trait, union, field, variant, const, static, type, macro, use)
    #[arg(long, value_name = "KINDS", help_heading = "Filtering")]
    pub search_kind: Option<String>,

    /// Show all members (fields, variants, methods) of matched types
    #[arg(long)]
    pub members: bool,
}

impl SearchArgs {
    /// Join pattern args with space (AND semantics). Empty string if no patterns.
    pub fn pattern(&self) -> String {
        self.patterns.join(" ")
    }
}

/// Arguments for the `examples` subcommand.
#[derive(Args, Debug, Clone)]
pub struct ExamplesArgs {
    /// Target crate
    #[arg(value_name = "TARGET", default_value = "self")]
    pub crate_name: String,

    /// Grep patterns — multiple args are AND-matched (omit for list mode)
    #[arg(value_name = "PATTERN", num_args = 0..)]
    pub patterns: Vec<String>,

    #[command(flatten)]
    pub global: GlobalArgs,

    /// Path to Cargo.toml
    #[arg(long, help_heading = "Local Workspace")]
    pub manifest_path: Option<String>,

    /// Lines of context around matches: N or BEFORE:AFTER
    #[arg(long, default_value = "2")]
    pub context: String,

    /// Include tests/ directory [default depth: unlimited]
    #[arg(long, num_args(0..=1), default_missing_value = "999", value_name = "DEPTH")]
    pub tests: Option<u32>,

    /// Include benches/ directory [default depth: unlimited]
    #[arg(long, num_args(0..=1), default_missing_value = "999", value_name = "DEPTH")]
    pub benches: Option<u32>,
}

/// Arguments for the `ts` (tree-sitter) subcommand.
#[derive(Args, Debug, Clone)]
pub struct TsArgs {
    /// Target crate (use 'self' for current crate)
    #[arg(value_name = "TARGET")]
    pub crate_name: String,

    /// Tree-sitter S-expression query
    #[arg(value_name = "QUERY")]
    pub query: String,

    #[command(flatten)]
    pub global: GlobalArgs,

    /// Path to Cargo.toml
    #[arg(long, help_heading = "Local Workspace")]
    pub manifest_path: Option<String>,

    /// Show capture names with matched text (for multi-capture queries)
    #[arg(long)]
    pub captures: bool,

    /// Lines of context around matched nodes: N or BEFORE:AFTER
    #[arg(long, default_value = "0")]
    pub context: String,

    /// Only search src/ directory (skip examples, tests, benches)
    #[arg(long)]
    pub src_only: bool,

    /// Limit matches: N (first N) or OFFSET:N (skip OFFSET, show N)
    #[arg(long, value_name = "[OFFSET:]N")]
    pub limit: Option<String>,

    /// Output only file:line locations, no source text
    #[arg(short = 'q', long)]
    pub quiet: bool,
}

/// Arguments for the `summary` subcommand.
#[derive(Args, Debug, Clone)]
pub struct SummaryArgs {
    #[command(flatten)]
    pub target: TargetArgs,

    #[command(flatten)]
    pub global: GlobalArgs,
}

/// Arguments for the `clean` subcommand.
#[derive(Args, Debug, Clone)]
pub struct CleanArgs {
    /// Crate spec to clean (omit to clean all)
    #[arg(value_name = "SPEC")]
    pub spec: Option<String>,
}

impl ExamplesArgs {
    /// Join pattern args with space. None if no patterns (list mode).
    pub fn pattern(&self) -> Option<String> {
        if self.patterns.is_empty() {
            None
        } else {
            Some(self.patterns.join(" "))
        }
    }
}