ffs — Fast File Search
A code-aware file search CLI for humans and AI agents. Really fast.
Replaces find + grep + glob + cat + tree-sitter with one binary. Typo-tolerant fuzzy matching, frecency ranking, token-budget reader, MCP server.
curl -fsSL "https://raw.githubusercontent.com/quangdang46/fast_file_search/main/install.sh?$(date +%s)" | bash
🤖 Agent Quickstart (MCP / Robot Mode)
ffs ships a 15-tool MCP server over stdio. The installer auto-configures it for Claude Code, Codex, Cursor, Windsurf, Gemini, OpenCode, and more.
# Run as MCP server
ffs mcp
# Agent-friendly JSON queries
ffs grep 'fn main' --format json
ffs symbol FilePicker --format json
ffs read crates/ffs-engine/src/lib.rs --budget 5000 --format json
ffs map --depth 3 --format json
ffs overview --format json
ffs deps path/to/file.rs --format json
Output conventions
- stdout = structured data (JSON or text)
- stderr = diagnostics, warnings
- exit 0 = success
MCP tools exposed: ffs_find, ffs_grep, ffs_multi_grep, ffs_symbol, ffs_callers, ffs_callees, ffs_refs, ffs_flow, ffs_impact, ffs_outline, ffs_siblings, ffs_deps, ffs_map, ffs_overview, ffs_read
TL;DR
The Problem
File search and code navigation require a menagerie of tools: find, fd, grep, rg, glob, cat, plus a tree-sitter plugin for symbol queries. Each has different syntax, different startup cost, different output formats. AI agents struggle with this combinatorial explosion of invocation patterns — and every pipeline hop loses milliseconds that add up across hundreds of queries.
The Solution
ffs replaces all of them with a single binary that understands code. Typo-resistant fuzzy matching, frecency-ranked results, tree-sitter-powered symbol/callers/callees/refs queries, and a token-budget aware file reader — all from one executable that starts in milliseconds and keeps a warm cache for sub-10 ms subsequent calls.
Why ffs?
| Feature | What it does |
|---|---|
| One binary to rule them all | find + grep + glob + cat + symbol + callers + refs in a single executable |
| Typo-tolerant by default | Query *.rs !test/ shcema works even with the typo |
| Frecency ranking | Files you open more often rank higher. Warm-up uses git touch history |
| Tree-sitter symbol index | Answers code questions across 16 languages |
| Token-budget reader | ffs read path --budget 5000 clips body, preserves header and truncated footer |
| One warm process | After first call, every subsequent query hits warm memory — no per-call subprocess spawn |
| MCP server | 15-tool stdio JSON-RPC server for AI coding agents |
| C ABI (.so/.dylib/.dll) | Stable foreign bindings for Python, Node.js, and more |
| zlib compression | Optional --features zlib for compressed indexes |
How ffs Compares
| Capability | ffs | find/fd |
grep/rg |
glob |
cat |
|---|---|---|---|---|---|
| File-name search (fuzzy, frecency) | ✅ | ✅/✅ | — | — | — |
| Content grep (SIMD, typo-tolerant) | ✅ | — | ✅/✅ | — | — |
| Symbol lookup (tree-sitter) | ✅ | — | — | — | — |
| Callers / callees / refs | ✅ | — | — | — | — |
| Token-budget file reader | ✅ | — | — | — | ✅ |
| MCP server | ✅ 15 tools | — | — | — | — |
| Single process, warm cache | ✅ | — | — | — | — |
| Globbing | ✅ | — | — | ✅ | — |
Quick Example
# Index once, query forever
ffs index # ~200 ms on 10k-file repo
ffs find UnifiedScanner # fuzzy file-name search
ffs find grep --scored # role-aware ranking
ffs grep '\bTODO\b' --root crates/ # content grep
ffs grep 'fn main' --group # symbol-grouped grep
ffs multi-grep TODO FIXME HACK # multi-literal OR (Aho-Corasick)
ffs symbol FilePicker # tree-sitter symbol lookup
ffs callers UnifiedScanner # who calls a symbol
ffs read crates/ffs-engine/src/lib.rs --budget 5000 # token-budget reader
ffs map --depth 3 # workspace tree
ffs mcp # run as MCP server over stdio
ffs mcp /path/to/workspace # pin MCP index root explicitly
MCP workspace root. When launched without a path (as installers do for Cursor/VS Code),
ffs mcpresolves the root from, in order:WORKSPACE_FOLDER_PATHS(injected by Cursor/VS Code),VSCODE_CWD, thencwd. User-level~/.cursor/mcp.jsonwithargs: ["mcp"]therefore indexes the open workspace, not the home directory. Prefer an explicit path (args: ["mcp", "${workspaceFolder}"]) in project-level configs when available.
Design Philosophy
| Principle | Rationale |
|---|---|
| One binary to rule them all | find + grep + glob + cat + symbol + callers — no pipelining, no format wars |
| Typo-tolerant by default | Fuzzy auto-detect query mode; shcema still finds the right file |
| Frecency ranking | Files you open more often rank higher; warm-up uses git touch history |
| Tree-sitter symbol index | Answers code questions, not just file-name questions — across 16 languages |
| Token-budget reader | Clips body but always preserves header + [truncated to budget] footer |
| One warm process | After first call, every subsequent query hits warm memory — no per-call subprocess spawn |
Installation
# macOS / Linux — curl pipe
curl -fsSL "https://raw.githubusercontent.com/quangdang46/fast_file_search/main/install.sh?$(date +%s)" | bash
# Windows PowerShell
irm https://raw.githubusercontent.com/quangdang46/fast_file_search/main/install.ps1 | iex
# From source (with zlib compression support)
cargo build --release -p ffs-cli --features zlib
./target/release/ffs --version
The installers detect platform, fetch the matching release binary, verify SHA-256 sidecar, and atomically install to ~/.local/bin/ffs. MCP server configs are auto-detected for Claude Code, Codex, Cursor, Windsurf, VS Code, Gemini, OpenCode, and more.
Supported platforms
Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64/aarch64
Commands
| Command | What it does |
|---|---|
find |
Fuzzy file-name search. Frecency-ranked, glob constraints, git-aware |
glob |
Match files by glob pattern (replaces shell **) |
grep |
Content search — plain / regex / fuzzy auto-detect |
multi-grep |
OR-logic multi-pattern literal search (Aho-Corasick; alias multigrep) |
read |
Token-budget aware file read (replaces cat) |
outline |
Structural outline of a file (functions, classes, top-level decls) |
symbol |
Exact + prefix lookup over the tree-sitter symbol index |
callers |
Find call sites of a symbol. Bloom-filter narrowed candidates |
callees |
Symbols referenced inside a symbol body |
refs |
Definitions + single-hop usages in one shot |
flow |
Drill-down envelope per definition (def + body + callees + callers) |
siblings |
Peers of a symbol in its parent scope |
deps |
A file's imports + files that depend on it |
impact |
Rank workspace files by change impact for a symbol |
index |
Build / refresh on-disk indexes (Bigram, Bloom, Symbol, Outline) |
map |
Workspace tree annotated with file count and per-directory tokens |
overview |
High-signal repo summary (languages, top symbols, entry-points) |
mcp |
JSON-RPC MCP server over stdio (replaces agent built-in tools) |
guide |
Embedded agent guide |
Performance
All numbers are single-threaded medians on Linux x86-64 (Criterion.rs).
Engine dispatch (256-file fixture)
| Query type | Median |
|---|---|
Symbol lookup (worker_05_3) |
202 ns |
| Concept / NL query | 205 ns |
File path (mod_03/file_2.rs) |
2.2 µs |
Glob (**/*.rs) |
2.8 µs |
Bigram index
| Index size | 2-char query | 6-char query | 14-char query |
|---|---|---|---|
| 10 K files | 46 ns | 120 ns | 314 ns |
| 500 K files | 761 ns | 1.6 µs | 1.6 µs |
Symbol index (1 K files)
| Operation | Median |
|---|---|
| Index one Rust file (50 lines) | 219 µs |
| Exact symbol lookup | 95 µs |
Architecture
┌──────────────────────────────────────┐
│ Frontends │
│ ffs-cli (binary) · ffs-mcp (MCP) │
│ ffs-c (C ABI .so/.dylib/.dll) │
└──────────────┬───────────────────────┘
│ all surfaces share one core
▼
┌──────────────────────────────────────┐
│ Engine layer │
│ ffs-engine — dispatch · ranking │
│ ffs-query-parser — DSL + constraints │
└──────────────┬───────────────────────┘
▼
┌──────────────────────────────────────┐
│ Capability layer │
│ ffs-symbol — tree-sitter · bloom │
│ ffs-grep — SIMD literal / regex │
│ ffs-budget — token-aware reader │
└──────────────┬───────────────────────┘
▼
┌──────────────────────────────────────┐
│ Core layer (ffs-core) │
│ scan · file_picker · score · git │
│ frecency · watcher · ignore │
└──────────────────────────────────────┘
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
ffs index is slow |
First build — expected | One-time cost. Subsequent calls skip re-parse |
| Symbol queries return empty | Index stale (git HEAD changed) | Re-run ffs index |
| Fuzzy search unexpected results | Query too short (1 char) | Add more characters or path qualifiers (src/) |
| MCP server not responding | Server process exited | Keep ffs mcp running as background service |
Limitations
| Edge case | Reality |
|---|---|
| Pre-built index required | Run ffs index once per repo. Index auto-invalidates on HEAD changes |
| 16 languages | Tree-sitter supports 16; non-listed languages fall back to text-only queries |
| Frecency needs git | Warm-up uses git touch history; without git, frecency starts cold |
| MCP stdio only | ffs mcp speaks JSON-RPC over stdio — no HTTP/WebSocket transport (yet) |
FAQ
Does ffs need a pre-built index?
Yes — run ffs index once per repo. The index is cached in <repo>/.ffs/ and auto-invalidates on schema bumps, git HEAD changes, or significant file-count drift.
Can I use ffs as a library from Rust?
Yes — all crates publish via ffs-*. The C ABI (ffs-c) is stable for foreign bindings (Python, Node.js, etc.).
What's the difference between ffs callers and ffs refs?
callers lists call sites (who invokes a symbol). refs combines definitions and usages in one shot — both call sites and other references.
Does ffs work on non-git directories?
Yes — it falls back to a full filesystem scan. Some features (frecency warm-up, cache invalidation) require git.
MCP vs CLI — which should agents use?
Prefer ffs mcp for agent contexts — 15 tools via stdio JSON-RPC, no parsing required. Use CLI with --format json for one-shot queries.
No comments yet
Be the first to share your take.