[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-5650":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":16,"forks30d":16,"starsTrendScore":19,"compositeScore":20,"rankGlobal":10,"rankLanguage":10,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":24,"hasPages":22,"topics":25,"createdAt":10,"pushedAt":10,"updatedAt":31,"readmeContent":32,"aiSummary":33,"trendingCount":16,"starSnapshotCount":16,"syncStatus":34,"lastSyncTime":35,"discoverSource":36},5650,"fff","dmtrKovalenko\u002Ffff","dmtrKovalenko","The fastest and the most accurate file search toolkit for AI agents, Neovim, Rust, C, and NodeJS","https:\u002F\u002Ffff.dmtrkovalenko.dev\u002F",null,"Rust",8297,320,19,45,0,752,2342,573,93.52,"MIT License",false,"main",true,[26,27,28,29,30],"filesearch","lua","neovim","neovim-plugin","rust","2026-06-12 04:00:26","\u003Cimg alt=\"FFF\" src=\".\u002Fassets\u002Flogo-orange.png\" width=\"300\">\n\n\u003Cp>\n  \u003Ci>A file search toolkit for humans and AI agents. Really fast.\u003C\u002Fi>\n\u003C\u002Fp>\n\nTypo-resistant path and content search, frecency-ranked file access, a background watcher, and a lightweight in-memory content index. Way faster than CLIs like ripgrep and fzf in any long-running process that searches more than once.\n\nOriginally started as [Neovim plugin](#neovim-plugin) people loved, but it turned out that plenty of AI harnesses and code editors need the same thing: accurate, fast file search as a library. That is what fff is.\n\n---\n\nPick what you are interested in:\n\n\u003Cdetails id=\"mcp-server\">\n\u003Csummary>\n\u003Ch2>MCP server\u003C\u002Fh2>\n\u003C\u002Fsummary>\n\nWorks with Claude Code, Codex, OpenCode, Cursor, Cline, and any MCP-capable client. Fewer grep roundtrips, less wasted context, faster answers.\n\n![Benchmark chart comparing FFF against the built-in AI file-search tools](.\u002Fchart.png)\n\n### One-line install\n\nLinux \u002F macOS:\n\n```bash\ncurl -L https:\u002F\u002Fdmtrkovalenko.dev\u002Finstall-fff-mcp.sh | bash\n```\n\nWindows (PowerShell):\n\n```powershell\nirm https:\u002F\u002Fraw.githubusercontent.com\u002FdmtrKovalenko\u002Ffff.nvim\u002Fmain\u002Finstall-mcp.ps1 | iex\n```\n\nThe scripts live at [`install-mcp.sh`](.\u002Finstall-mcp.sh) and [`install-mcp.ps1`](.\u002Finstall-mcp.ps1) if you want to read them first.\n\nIt prints the exact wiring instructions for your client. Once the server is connected, ask the agent to \"use fff\" and it picks up the `ffgrep`, `fffind`, and `fff-multi-grep` tools.\n\n### Recommended agent prompt\n\nDrop this into your project's `CLAUDE.md` or equivalent:\n\n```markdown\nFor any file search or grep in the current git-indexed directory, use fff tools.\n```\n\n### What changes\n\n- Frecency memory. Files you actually open rank higher next time. Warm-up from git touch history runs automatically.\n- Definition-first hinting. Lines that look like code definitions are classified on the Rust side, no regex overhead in your prompt.\n- Smart-case with auto-fuzzy fallback. `IsOffTheRecord` finds snake_case variants; zero-match queries retry as fuzzy and surface the best approximate hits.\n- Git-aware annotations. Modified, untracked, and staged files are tagged so the agent reaches for what you are actively changing.\n\nSource: [`crates\u002Ffff-mcp\u002F`](.\u002Fcrates\u002Ffff-mcp\u002F).\n\n\u003C\u002Fdetails>\n\nThe MCP server gives any agent a file search tool that is faster and more token-efficient than the built-in one.\n\n\u003Cdetails id=\"pi-extension\">\n\u003Csummary>\n\u003Ch2>Pi agent extension\u003C\u002Fh2>\n\u003C\u002Fsummary>\n\n### Install\n\n```bash\npi install npm:@ff-labs\u002Fpi-fff\n```\n\n### Modes\n\nThree operating modes, switchable at runtime with `\u002Ffff-mode`:\n\n| Mode                     | What it does                                                                      |\n| ------------------------ | --------------------------------------------------------------------------------- |\n| `tools-and-ui` (default) | Adds `ffgrep` and `fffind` tools, replaces `@`-mention autocomplete with FFF.     |\n| `tools-only`             | Only tool injection. Keeps pi's native editor autocomplete.                       |\n| `override`               | Replaces pi's built-in `grep`, `find`, and `multi_grep` with FFF implementations. |\n\nEnv vars: `PI_FFF_MODE`, `FFF_FRECENCY_DB`, `FFF_HISTORY_DB`. Flags: `--fff-mode`, `--fff-frecency-db`, `--fff-history-db`.\n\n### Agent-facing tools\n\n- `ffgrep`. Content search. Accepts `path`, `exclude` (comma, space, or array; leading `!` optional), `caseSensitive`, `context`, and cursor pagination. Auto-detects regex, falls back to fuzzy on zero exact matches, rejects `.*`-style wildcard-only patterns up front.\n- `fffind`. Path and filename search. Matches the whole repo-relative path, not just the filename. Frecency-aware. The weak-match detector flags scattered fuzzy noise before it floods the agent's context.\n\n### Commands\n\n- `\u002Ffff-mode [tools-and-ui | tools-only | override]`. Show or switch the mode.\n- `\u002Ffff-health`. Picker, frecency, and git integration status.\n- `\u002Ffff-rescan`. Force a rescan.\n\nSource: [`packages\u002Fpi-fff\u002F`](.\u002Fpackages\u002Fpi-fff\u002F).\n\n\u003C\u002Fdetails>\n\nThe Pi extension swaps pi's native tools for FFF implementations and feeds the interactive editor's `@`-mention autocomplete from the frecency-ranked index.\n\n\u003Cdetails id=\"neovim-plugin\">\n\u003Csummary>\n\u003Ch2>Neovim plugin\u003C\u002Fh2>\n\u003C\u002Fsummary>\n\nDemo on the Linux kernel repo (100k files, 8GB):\n\nhttps:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002F5d0e1ce9-642c-4c44-aa88-01b05bb86abb\n\n### Installation\n\n#### lazy.nvim\n\n```lua\n{\n  'dmtrKovalenko\u002Ffff.nvim',\n  build = function()\n    -- downloads a prebuilt binary or falls back to cargo build\n    require(\"fff.download\").download_or_build_binary()\n  end,\n  -- for nixos:\n  -- build = \"nix run .#release\",\n  opts = {\n    debug = {\n      enabled = true,\n      show_scores = true,\n    },\n  },\n  lazy = false, -- the plugin lazy-initialises itself\n  keys = {\n    { \"ff\", function() require('fff').find_files() end, desc = 'FFFind files' },\n    { \"fg\", function() require('fff').live_grep() end, desc = 'LiFFFe grep' },\n    { \"fz\",\n      function() require('fff').live_grep({ grep = { modes = { 'fuzzy', 'plain' } } }) end,\n      desc = 'Live fffuzy grep',\n    },\n    { \"fc\",\n      function() require('fff').live_grep({ query = vim.fn.expand(\"\u003Ccword>\") }) end,\n      desc = 'Search current word',\n    },\n  },\n}\n```\n\n#### vim.pack\n\n```lua\nvim.pack.add({ 'https:\u002F\u002Fgithub.com\u002FdmtrKovalenko\u002Ffff.nvim' })\n\nvim.api.nvim_create_autocmd('PackChanged', {\n  callback = function(ev)\n    local name, kind = ev.data.spec.name, ev.data.kind\n    if name == 'fff.nvim' and (kind == 'install' or kind == 'update') then\n      if not ev.data.active then vim.cmd.packadd('fff.nvim') end\n      require('fff.download').download_or_build_binary()\n    end\n  end,\n})\n\nvim.g.fff = {\n  lazy_sync = true,\n  debug = { enabled = true, show_scores = true },\n}\n\nvim.keymap.set('n', 'ff', function() require('fff').find_files() end, { desc = 'FFFind files' })\n```\n\n### Public API\n\n```lua\nrequire('fff').find_files()                        -- find files in current repo\nrequire('fff').live_grep()                         -- live content grep\nrequire('fff').scan_files()                        -- force rescan\nrequire('fff').refresh_git_status()                -- refresh git status\nrequire('fff').find_files_in_dir(path)             -- find in a specific dir\nrequire('fff').change_indexing_directory(new_path) -- change root\n```\n\n### Commands\n\n- `:FFFScan`. Rescan files.\n- `:FFFRefreshGit`. Refresh git status.\n- `:FFFClearCache [all|frecency|files]`. Clear caches.\n- `:FFFHealth`. Health check.\n- `:FFFDebug [on|off|toggle]`. Toggle the scoring display.\n- `:FFFOpenLog`. Open `~\u002F.local\u002Fstate\u002Fnvim\u002Flog\u002Ffff.log`.\n\n### Configuration\n\nDefaults are sensible. Override only what you care about.\n\n```lua\nrequire('fff').setup({\n  base_path = vim.fn.getcwd(),\n  prompt = '> ',\n  title = 'FFFiles',\n  max_results = 100,\n  max_threads = 4,\n  lazy_sync = true,\n  prompt_vim_mode = false,\n  layout = {\n    height = 0.8,\n    width = 0.8,\n    prompt_position = 'bottom',   -- or 'top'\n    preview_position = 'right',   -- 'left' | 'right' | 'top' | 'bottom'\n    preview_size = 0.5,\n    flex = { size = 130, wrap = 'top' },\n    show_scrollbar = true,\n    path_shorten_strategy = 'middle_number', -- 'middle_number' | 'middle' | 'end'\n    anchor = 'center',\n  },\n  preview = {\n    enabled = true,\n    max_size = 10 * 1024 * 1024,\n    chunk_size = 8192,\n    binary_file_threshold = 1024,\n    imagemagick_info_format_str = '%m: %wx%h, %[colorspace], %q-bit',\n    line_numbers = false,\n    cursorlineopt = 'both',\n    wrap_lines = false,\n    filetypes = {\n      svg = { wrap_lines = true },\n      markdown = { wrap_lines = true },\n      text = { wrap_lines = true },\n    },\n  },\n  keymaps = {\n    close = '\u003CEsc>',\n    select = '\u003CCR>',\n    select_split = '\u003CC-s>',\n    select_vsplit = '\u003CC-v>',\n    select_tab = '\u003CC-t>',\n    move_up = { '\u003CUp>', '\u003CC-p>' },\n    move_down = { '\u003CDown>', '\u003CC-n>' },\n    preview_scroll_up = '\u003CC-u>',\n    preview_scroll_down = '\u003CC-d>',\n    toggle_debug = '\u003CF2>',\n    cycle_grep_modes = '\u003CS-Tab>',\n    cycle_previous_query = '\u003CC-Up>',\n    toggle_select = '\u003CTab>',\n    send_to_quickfix = '\u003CC-q>',\n    focus_list = '\u003Cleader>l',\n    focus_preview = '\u003Cleader>p',\n  },\n  frecency = {\n    enabled = true,\n    db_path = vim.fn.stdpath('cache') .. '\u002Ffff_nvim',\n  },\n  history = {\n    enabled = true,\n    db_path = vim.fn.stdpath('data') .. '\u002Ffff_queries',\n    min_combo_count = 3,\n    combo_boost_score_multiplier = 100,\n  },\n  git = {\n    status_text_color = false, -- true to color filenames by git status\n  },\n  grep = {\n    max_file_size = 10 * 1024 * 1024,\n    max_matches_per_file = 100,\n    smart_case = true,\n    time_budget_ms = 150,\n    modes = { 'plain', 'regex', 'fuzzy' },\n    trim_whitespace = false,\n  },\n  debug = { enabled = false, show_scores = false },\n  logging = {\n    enabled = true,\n    log_file = vim.fn.stdpath('log') .. '\u002Ffff.log',\n    log_level = 'info',\n  },\n})\n```\n\n### Live grep modes\n\n`\u003CS-Tab>` cycles between `plain`, `regex`, and `fuzzy`. The list is configurable via `grep.modes`, and single-mode setups hide the indicator entirely.\n\nPer-call override:\n\n```lua\nrequire('fff').live_grep({ grep = { modes = { 'fuzzy', 'plain' } } })\nrequire('fff').live_grep({ query = 'search term' }) -- pre-fill\n```\n\n### Constraints\n\nBoth find and grep accept these tokens to refine a query:\n\n- `git:modified`. One of `modified`, `staged`, `deleted`, `renamed`, `untracked`, `ignored`.\n- `test\u002F`. Any deeply nested children of `test\u002F`.\n- `!something`, `!test\u002F`, `!git:modified`. Exclusion.\n- `.\u002F**\u002F*.{rs,lua}`. Any valid glob, powered by [zlob](https:\u002F\u002Fgithub.com\u002FdmtrKovalenko\u002Fzlob).\n\nGrep-only:\n\n- `*.md`, `*.{c,h}`. Extension filter.\n- `src\u002Fmain.rs`. Grep inside a single file.\n\nMix freely: `git:modified src\u002F**\u002F*.rs !src\u002F**\u002Fmod.rs user controller`.\n\n### Multi-select and quickfix\n\n- `\u003CTab>`. Toggle selection (shows a thick `▊` in the signcolumn).\n- `\u003CC-q>`. Send selected files to the quickfix list and close the picker.\n\n### Git status highlighting\n\nSign-column indicators are on by default. To color filename text by git status, set `git.status_text_color = true` and adjust the `hl.git_*` groups. See `:help fff.nvim` for the full list.\n\n### File filtering\n\nFFF honours `.gitignore`. For picker-only ignores that do not touch git, add a sibling `.ignore` file:\n\n```gitignore\n*.md\ndocs\u002Farchive\u002F**\u002F*.md\n```\n\nRun `:FFFScan` to force a rescan.\n\n### Troubleshooting\n\n- `:FFFHealth` verifies picker init, optional dependencies, and DB connectivity.\n- `:FFFOpenLog` opens the log file.\n\n\u003C\u002Fdetails>\n\nThe best file search picker for neovim. Period. Faster and more intuitive queries, frecency ranking, definition classification and much more.\n\n\u003Cdetails id=\"node-sdk\">\n\u003Csummary>\n\u003Ch2>Node & Bun SDK\u003C\u002Fh2>\n\u003C\u002Fsummary>\n\n```bash\nnpm install @ff-labs\u002Ffff-node\n# or\nbun add @ff-labs\u002Ffff-node\n```\n\n```ts\nimport { FileFinder } from \"@ff-labs\u002Ffff-node\";\n\nconst finder = FileFinder.create({ basePath: process.cwd(), aiMode: true });\nif (!finder.ok) throw new Error(finder.error);\nawait finder.value.waitForScan(10_000);\n\nconst files = finder.value.fileSearch(\"incognito profile\", { pageSize: 20 });\nconst hits = finder.value.grep(\"GetOffTheRecordProfile\", {\n  mode: \"plain\",\n  smartCase: true,\n  beforeContext: 1,\n  afterContext: 1,\n  classifyDefinitions: true,\n});\n\nfinder.value.destroy();\n```\n\nEvery method returns a `Result\u003CT>` (`{ ok: true, value } | { ok: false, error }`). Full type reference: [`packages\u002Ffff-node\u002Fsrc\u002Ftypes.ts`](.\u002Fpackages\u002Ffff-node\u002Fsrc\u002Ftypes.ts).\n\n\u003C\u002Fdetails>\n\nTypeScript wrapper over the C library for nodejs and bun. Build custom agent tools, CLIs, or IDE integrations on top of FFF.\n\n\u003Cdetails id=\"rust-crate\">\n\u003Csummary>\n\u003Ch2>Rust crate\u003C\u002Fh2>\n\u003C\u002Fsummary>\n\n### Add the dependency\n\nFFF is written in Rust, so this is the lowest-overhead way to use it.\n\n```toml\n[dependencies]\nfff-search = \"0.6\"\n```\n\nFull API documentation: [docs.rs\u002Ffff-search](https:\u002F\u002Fdocs.rs\u002Ffff-search\u002Flatest\u002Ffff_search\u002F).\n\n\u003C\u002Fdetails>\n\nNative rust crate that is performing all the search. Stable and well documented.\n\n\u003Cdetails id=\"c-library\">\n\u003Csummary>\n\u003Ch2>C library\u003C\u002Fh2>\n\u003C\u002Fsummary>\n\n### Build\n\n```bash\n# Builds only the C cdylib (fastest):\nmake build-c-lib\n\n# or directly with cargo:\ncargo build --release -p fff-c --features zlob\n```\n\nThe output is a `cdylib` (`libfff_c.so` \u002F `libfff_c.dylib` \u002F `fff_c.dll`). The header lives at [`crates\u002Ffff-c\u002Finclude\u002Ffff.h`](.\u002Fcrates\u002Ffff-c\u002Finclude\u002Ffff.h).\n\nPrebuilt binaries for every version, including every commit on main, are on the [releases page](https:\u002F\u002Fgithub.com\u002FdmtrKovalenko\u002Ffff.nvim\u002Freleases). The same binaries also ship inside the `@ff-labs\u002Ffff-bin-*` npm packages.\n\n### Install\n\n```bash\n# System-wide (needs sudo):\nsudo make install\n\n# User-local, no sudo:\nmake install PREFIX=$HOME\u002F.local\n\n# Staged install for packagers:\nmake install DESTDIR=\u002Ftmp\u002Fpkgroot PREFIX=\u002Fusr\n```\n\nDrops `libfff_c.{so,dylib,dll}` into `$(PREFIX)\u002Flib` and the header into `$(PREFIX)\u002Finclude\u002Ffff.h`. Remove with `make uninstall`, which honours the same `PREFIX` and `DESTDIR`.\n\nLink against it after install:\n\n```bash\ncc my_app.c -lfff_c -o my_app\n```\n\nEnsure `$(PREFIX)\u002Flib` is on your runtime library search path (`LD_LIBRARY_PATH` on Linux, `DYLD_LIBRARY_PATH` on macOS, or an entry in `\u002Fetc\u002Fld.so.conf.d\u002F`).\n\n### Minimal example\n\n```c\n#include \u003Cfff.h>\n#include \u003Cstdio.h>\n\nint main(void) {\n    FffResult *res = fff_create_instance(\n        \".\",        \u002F\u002F base_path\n        \"\",         \u002F\u002F frecency_db_path (empty = default)\n        \"\",         \u002F\u002F history_db_path\n        false,      \u002F\u002F use_unsafe_no_lock\n        true,       \u002F\u002F enable_mmap_cache\n        true,       \u002F\u002F enable_content_indexing\n        true,       \u002F\u002F watch\n        false       \u002F\u002F ai_mode\n    );\n    if (!res->success) {\n        fprintf(stderr, \"init failed: %s\\n\", res->error);\n        fff_free_result(res);\n        return 1;\n    }\n    void *handle = res->handle;\n    fff_free_result(res);\n\n    \u002F\u002F Search\n    FffResult *search = fff_search(handle, \"main.rs\", \"\", 0, 0, 20, 100, 3);\n    \u002F\u002F ... read FffSearchResult from search->handle, then fff_free_search_result()\n\n    fff_destroy(handle);\n    return 0;\n}\n```\n\n### Notes\n\n- Every function returning `FffResult*` allocates with Rust's `Box`. Free with `fff_free_result`, do not use malloc's free\n- Payloads (search results, grep results, scan progress) have their own dedicated free functions listed in the header.\n- C strings returned in the `handle` field (e.g. from `fff_get_base_path`) are freed with `fff_free_string`.\n\nSource: [`crates\u002Ffff-c\u002F`](.\u002Fcrates\u002Ffff-c\u002F).\n\n\u003C\u002Fdetails>\n\nStable C ABI. Bind from C\u002FC++, Zig, Go via cgo, Python via ctypes, or anything with C FFI.\n\n---\n\n## What is FFF and why use it over ripgrep or fzf?\n\nFFF is a file search library, not a CLI. Ripgrep and fzf are great tools, but they are command-line programs: every call forks a new process, re-reads `.gitignore`, re-stats directories, and rebuilds whatever state it needs in memory before it can answer. That is fine when you grep once from a shell. It is bad when an editor or an AI agent wants to run hundreds of searches per session.\n\nFFF keeps the index and the file cache resident in one long-lived process and exposes the same Rust core through four thin layers: a native crate (`fff-search`), a C library (`libfff_c`), a Node\u002FBun SDK (`@ff-labs\u002Ffff-node`), and an MCP server. You call `FileFinder.create()` once, then every subsequent search hits warm memory. On a 500k-file Chromium checkout, that is the difference between 3-9 **SECONDS** per ripgrep spawn and sub-10 ms per FFF query.\n\nAlgorithm for fuzzy matching is much more comprehensive than fzf's algorithm it is **typo-resistant** and we provide a query language with additional constraint parsing for prefiltering e.g. \"*.rs !test\u002F shcema\" is a perfectly valid query for fff, but fzf wouldn't find anything even for a single typo in \"shcema\".\n\n### Why a programmatic API matters\n\n- No process spawn. Every call stays in-process and avoids the fork, exec, argv parsing, and stdout pipe setup that dominates short `rg` invocations.\n- One FS walk, metadata collection, and parse of `.gitignore`. The ignore walker runs once at scan time and the result is reused for every search.\n- Results come back as typed objects, not text you have to re-parse. The SDK gives you `{ relativePath, lineNumber, lineContent, gitStatus, totalFrecencyScore, isDefinition, ... }` directly.\n- Cursor pagination that survives across calls. Ripgrep has no concept of \"page 2 of these matches\"; FFF does.\n- A long-lived process opens up optimisations that a one-shot CLI cannot apply: warm caches, incremental re-indexing, cross-query frecency, and shared SIMD state.\n\n### What the core actually does\n\n- **Frecency-ranked fuzzy matching.** Every indexed file carries an access score and a modification score. Searches rank files you have opened recently and frequently above cold results. This is the same idea as VS Code's recently-opened list, but applied to every search result, not just a sidebar.\n- **Typo-resistant matching for both paths and content.** Smith-Waterman fuzzy scoring is available on the grep path; path search uses SIMD-accelerated fuzzy matching (via the [`frizbee`](https:\u002F\u002Fgithub.com\u002Fsaghm\u002Ffrizbee)-derived core) that survives dropped characters and reorderings.\n- **Content grep with three modes.** Plain literal (SIMD memmem), regex (the Rust `regex` crate), and fuzzy (Smith-Waterman per line). Auto-detects which mode to use from the pattern, falls back to fuzzy when a plain search returns zero hits.\n- **Multi-pattern OR search.** SIMD Aho-Corasick for \"find any of these 20 identifiers at once\", which is faster than regex alternation and a lot faster than 20 separate ripgrep runs.\n- **Background file watcher.** The index updates as files change. You never pay for a rescan on the hot path.\n- **Git status awareness.** Modified, staged, untracked, and ignored states are cached and returned with every result, so callers can sort or filter them without shelling out to git. The watcher talks to libgit2 directly instead of spawning the `git` CLI.\n- **Definition classifier.** A byte-level scanner on the Rust side tags lines that start with `struct`, `fn`, `class`, `def`, `impl`, and friends.\n\n### Performance choices that matter\n\n- Efficient memory allocator and memory allocation strategy (see next paragraph). By default we use `mimaloc`\n- Parallel multi thread search pipeline that is not contaganted by the orchistration logic\n- SIMD first algorithms for everything. Efficinet & non-allocating sorting.\n- Platform specific optimizations for FS ([getdents64](https:\u002F\u002Flinux.die.net\u002Fman\u002F2\u002Fgetdents64), NTFS api on windows and others)\n- Lightweight on the flight content index for realtime even typo resistant grep\n- Memory mapped content cache. We store some of the files in virtual memory (the amount is limited)\n- Single contiguous arena storage of string chunks. Significantly reduces the amount of memory to work with and dramatically increases CPU cache hits.\n\n### Memory allocation\n\nYes, fff fundamentally requires more memory than calling a single child process. That is the primary source of the speedup. In practice, alongside one of the most popular file search pickers for Neovim, [fff ends up using less RAM than a burst of ripgrep invocations](https:\u002F\u002Fx.com\u002Fneogoose_btw\u002Fstatus\u002F2041606853155811442).\n\n\nFFF also keeps a content index, around 360 bytes per indexed file, so roughly 36 MB for a 100k-file repo. Not every file is indexed - binaries, oversized files, and anything not eligible for grep are skipped. If even that footprint is too much, the index can be backed by a memory-mapped file instead of anonymous RAM.\n\n### What this means in practice\n\nIf you are building an agent, an IDE extension, a pre-commit check, or any long-running tool that searches the same repository many times, calling FFF as a library is dramatically cheaper than shelling out to ripgrep. The tradeoff is real memory: FFF keeps the index in RAM and warms the content cache. On a 14k-file repo that costs about 26 MB resident. On a 500k-file repo like Chromium, expect a few hundred MB. In exchange, every single search is enriched with git status, frecency ranking, file metadata, timestamps of last access and edit and so on.\n\nIf you are running one grep from a terminal, `rg` is still the right tool. If you run dozens of them inside the same process, FFF will pay for itself starting from the second call. If you work on AI agent fff will finish preparation work before your AI will have a chance to call it.\n\n### How it compares\n\n- **ripgrep**: FFF uses the same underlying regex engine and more advanced plain text matching algorithms. Stores content index and file tree. Main wins on repeated-search workloads. Loses on \"grep once from bash and exit.\"\n- **fzf**: FFF's path search is fuzzy like fzf, but it is also frecency-aware and git-aware, and ships a more typo-tolerant algorithm. fzf is a pure match-and-filter tool; FFF ranks results by how often you actually open them.\n- **Telescope \u002F fzf-lua \u002F snacks.picker**: FFF ships its own Neovim picker with the same ranking the MCP server and SDK use. The picker is optional; the core is the same.\n- **Tantivy or other full-text search engines**: different class of tool. Tantivy indexes documents for query-time scoring at scale. FFF is scoped to one repository and optimised for sub-10 ms response. It does not persist an inverted index on disk.\n\n---\n\n## Repository layout\n\n- `crates\u002Ffff-search`, `crates\u002Ffff-grep`, `crates\u002Ffff-query-parser` - Rust core.\n- `crates\u002Ffff-c` - C FFI used by every language binding.\n- `crates\u002Ffff-nvim` - Lua\u002Fmlua bindings for the Neovim plugin.\n- `crates\u002Ffff-mcp` - MCP server binary.\n- `packages\u002Ffff-node` - Node.js SDK (`@ff-labs\u002Ffff-node`).\n- `packages\u002Ffff-bun` - Bun SDK (`@ff-labs\u002Ffff-node`).\n- `packages\u002Fpi-fff` - pi extension (`@ff-labs\u002Fpi-fff`).\n- `lua\u002F` - Neovim-side plugin code.\n\n## Contributing\n\nBug reports and pull requests welcome. Agentic coding tools are welcome to be used, but human review is mandatory.\n\n## License\n\n[MIT](.\u002FLICENSE) & open source forever.\n","fff 是一个为人类和AI代理设计的文件搜索工具包，特别适用于Neovim、Rust、C和NodeJS环境。它具备抗拼写错误的路径和内容搜索功能，基于频率与最近访问时间排名的文件访问机制，后台监视器以及轻量级内存内容索引。相比像ripgrep和fzf这样的命令行工具，在多次执行搜索任务时表现得更为迅速。适合需要快速准确文件检索的应用场景，如代码编辑器插件或集成到AI开发环境中作为库使用。通过提供MCP服务器支持，fff能够与Claude Code等MCP兼容客户端无缝对接，减少grep往返次数，节省上下文切换成本，从而加快响应速度；同时，也提供了针对Pi agent的扩展安装方式，增强其文件处理能力。",2,"2026-06-11 03:04:30","top_language"]