src is a fast, single-binary CLI for interrogating source code in parallel.
It is built for agent workflows and for engineers who want structured answers instead of stitching together find, grep, cat, and ad hoc scripts.
The output is YAML by default, JSON when requested, and designed to be easy for both humans and tools to consume.
Most codebase inspection ends up looking like this:
find . -name "*.ts"
grep -R "createInvoice" .
sed -n '120,180p' src/payments/service.ts
sed -n '40,90p' src/orders/controller.tssrc collapses that into one tool with a consistent output shape:
- project tree for fast orientation
- globbed file lists
- full-file search results or scoped context windows
- exact multi-file line extraction
- internal dependency graphs
- symbol extraction across supported languages
- caller tracing for a symbol name
- codebase stats for sizing and hotspot detection
cargo install --path .Or build locally:
cargo build --release
# binary at target/release/srcsrc
src -g "*.rs"
src -f "TODO|FIXME"
src -f "createInvoice|finalizeInvoice" -g "*.ts" -c
src --lines "src/main.rs:1:40 src/cli.rs:220:293"
src --graph -g "*.{ts,tsx}"
src --symbols -g "*.rs" --compact
src --callers process_file -g "*.rs"
src --statsCount where a state hook or factory shows up:
src -g "*.{ts,tsx}" \
-f "useMemberStore|create" \
-c -L 8Actual output:
meta:
elapsedMs: 26
filesScanned: 16
filesMatched: 4
totalMatches: 16
files:
- path: src/api/memberApiClient.ts
count: 2
- path: src/main.tsx
count: 2
- path: src/screens/ClassesScreen.tsx
count: 6
- path: src/stores/memberAppStore.ts
count: 6This is the fast first pass before reading anything.
Start from a single line number and pull the whole symbol:
src --lines "src/main.rs:185:185" --auto-expandActual output:
meta:
elapsedMs: 1
filesMatched: 1
files:
- path: src/main.rs
chunks:
- startLine: 157
endLine: 203
content: |
157. fn execute(args: cli::CliArgs) -> i32 {
158. let root = Path::new(&args.root);
159. let format = resolve_format(&args);
...
184. if !args.lines.is_empty() {
185. execute_lines(&args, root, &cancelled, start, format)
186. } else if args.graph {
...
203. }This is useful when another tool or stack trace only gives you a line number.
Trace a helper through the codebase:
src --callers process_file -g "*.rs"Actual output:
meta:
elapsedMs: 9
filesScanned: 27
filesMatched: 8
totalMatches: 11
declarations:
- path: src/count.rs
line: 33
signature: "fn process_file(file_path: &str, root: &Path, matcher: &Matcher) -> Option<CountEntry> {"
- path: src/graph.rs
line: 38
signature: fn process_file(
callers:
- path: src/count.rs
sites:
- line: 23
content: process_file(file_path, root, matcher)
- path: src/searcher.rs
sites:
- line: 96
content: process_file(file_path, root, matcher, line_numbers, context)This is the quickest way to answer "where is this declared, and who actually uses it?"
When a full file is too noisy:
src -f "with_comments|with_tests" -g "*.rs" -C 2 -L 2Actual output:
files:
- path: src/cli.rs
chunks:
- startLine: 143
endLine: 148
content: |
143. }
144. "--compact" => compact = true,
145. "--with-comments" => with_comments = true,
146. "--with-tests" => with_tests = true,
147. "--auto-expand" => auto_expand = true,
148. "--output" | "-o" => {Use -C when you want grep-like focus but still need structured output.
Use the graph when you need to understand which files depend on which local modules:
src --graph -g "*.rs" -L 8Actual output:
meta:
elapsedMs: 4
filesScanned: 27
filesMatched: 8
graph:
- file: src/alias.rs
imports:
- src/file_reader.rs
- file: src/callers.rs
imports:
- src/file_reader.rs
- src/models.rs
- src/path_helper.rs
- src/searcher.rs
- src/symbols.rs
- file: src/count.rs
imports:
- src/file_reader.rs
- src/models.rs
- src/path_helper.rs
- src/searcher.rsThis is useful before changing a shared module because it shows internal coupling without external package noise.
Use symbols to get the public shape of files before reading implementations:
src --symbols -g "*.rs" --compact -L 5Actual output:
meta:
elapsedMs: 4
filesScanned: 27
filesMatched: 5
symbols:
- path: src/callers.rs
- fn find_callers :13:99
- path: src/cli.rs
- struct CliArgs :2:25
- enum OutputFormatArg :28:31
- enum CliAction :34:38
- fn parse_args :40:219
- fn print_help :221:293
- path: src/count.rs
- fn count_matches :11:31
- fn process_file :33:51This gives you the outline first: file, declaration kind, name, and line range.
Instead of three separate file reads, batch exact ranges into one --lines call:
src --lines "src/main.rs:157:203 src/cli.rs:221:293 src/models.rs:95:116"Actual output shape:
meta:
filesMatched: 3
files:
- path: src/cli.rs
chunks:
- startLine: 221
endLine: 293
content: |
221. pub fn print_help() {
...
- path: src/main.rs
chunks:
- startLine: 157
endLine: 203
content: |
157. fn execute(args: cli::CliArgs) -> i32 {
...
- path: src/models.rs
chunks:
- startLine: 95
endLine: 116
content: |
95. pub enum OutputPayload {
...This is the core agent workflow: one command returns several focused source ranges in a stable, structured response.
| Mode | Command | What it returns |
|---|---|---|
| Tree | src |
Directory hierarchy of source files |
| Glob | src -g "*.ts" |
Flat file list |
| Find | src -f "auth | token" |
Matching files with full contents |
| Find with context | src -f "auth | token" -C 3 |
Matching files with focused chunks |
| Count | src -f "auth | token" -c |
Match counts per file |
| Lines | src --lines "a.rs:1:30 b.ts:40:90" |
Exact ranges from multiple files |
| Lines auto-expand | src --lines "a.rs:88:88" --auto-expand |
Full enclosing symbol for the referenced line |
| Graph | src --graph |
Project-internal dependency/import map |
| Symbols | src --symbols -g "*.rs" |
Symbol declarations with ranges |
| Compact symbols | src --symbols --compact |
Condensed declaration listing |
| Callers | src --callers handleAuth |
Declarations plus call sites |
| Stats | src --stats |
File, line, byte, and hotspot summary |
| Flag | Meaning |
|---|---|
--dir, -d <path> |
Scan another repo without changing directories |
--glob, -g <pattern> |
Restrict by file pattern; repeatable; supports brace expansion (*.{ts,tsx}) |
--find, -f <pattern> |
Search contents; | works as a literal OR |
--regex, -E |
Treat --find as regex |
--count, -c |
Return counts instead of file contents |
--context, -C <n> |
Return match windows instead of full files |
--lines "<specs>" |
Extract exact file ranges in one call |
--auto-expand |
Expand a --lines location to the enclosing symbol |
--graph |
Build an internal dependency graph |
--symbols, -s |
Extract declarations |
--compact |
Condense symbol output for scanning |
--with-comments |
Include doc comments in symbol output |
--with-tests |
Include test files normally skipped by source scanning |
--callers <name> |
Find declaration(s) and call sites for a symbol |
--limit, -L <n> |
Cap result size |
--json |
Emit JSON instead of YAML |
--output, -o <path> |
Save results as an artifact |
YAML is the default because it is readable and works well for LLM pipelines:
meta:
elapsedMs: 16
filesScanned: 60
filesMatched: 28
totalMatches: 44
files:
- path: src/payments/service.ts
chunks:
- startLine: 118
endLine: 132
content: |
118. export async function createInvoice(...) {
...Switch to JSON when you want to pipe the results somewhere else:
src --symbols -g "*.rs" --json
src --stats -o stats.yamlImport resolution and symbol extraction currently support:
- Rust
- TypeScript / JavaScript
- C#
- Go
- Java
- Kotlin
- Ruby
- Python
Other file types still work with tree, glob, find, lines, and stats modes.
Use these in order when you are dropped into an unfamiliar repo:
src --statssrc --graph -g "*.{ts,tsx}"orsrc --graph -g "*.rs"src --symbols --compact -g "*.{ts,tsx}"orsrc --symbols --compact -g "*.rs"src -f "termA|termB" -csrc --lines "file:line:line" --auto-expandsrc --callers symbolName
That sequence gets you from orientation to exact code with very little waste.
src is implemented in Rust and keeps the runtime simple:
rayonfor parallel scanningmemmap2for fast file access on larger filesregexfor regex search modememchrfor fast newline counting
The current codebase is roughly 14.5k lines, with language handlers split by platform-specific parser in src/lang/.
cargo testMIT