docs: make Chinese README the default
Build & Publish / Build MCP aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build Neovim aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build Neovim aarch64-linux-android (push) Waiting to run
Build & Publish / Build Neovim aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build Neovim aarch64-unknown-linux-gnu (push) Waiting to run
Build & Publish / Build Neovim aarch64-unknown-linux-musl (push) Waiting to run
Build & Publish / Build Neovim x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build Neovim x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build Neovim x86_64-unknown-linux-gnu (push) Waiting to run
Build & Publish / Build Neovim x86_64-unknown-linux-musl (push) Waiting to run
Build & Publish / Build C FFI aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build C FFI aarch64-linux-android (push) Waiting to run
Build & Publish / Build C FFI aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build C FFI aarch64-unknown-linux-gnu (push) Waiting to run
Build & Publish / Build C FFI aarch64-unknown-linux-musl (push) Waiting to run
Build & Publish / Build C FFI x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build C FFI x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build C FFI x86_64-unknown-linux-gnu (push) Waiting to run
Build & Publish / Build C FFI x86_64-unknown-linux-musl (push) Waiting to run
Build & Publish / Build MCP aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build MCP aarch64-unknown-linux-gnu (push) Waiting to run
Build & Publish / Build Python wheels x86_64 (macos-latest) (push) Waiting to run
Build & Publish / Build Python wheels x86_64 (ubuntu-latest) (push) Waiting to run
Build & Publish / Release (push) Blocked by required conditions
Build & Publish / Publish Python wheels to PyPI (push) Blocked by required conditions
Build & Publish / Publish Rust crates (push) Blocked by required conditions
Build & Publish / Publish npm packages (push) Blocked by required conditions
Spelling / Spell Check with Typos (push) Waiting to run
Build & Publish / Build MCP aarch64-unknown-linux-musl (push) Waiting to run
Build & Publish / Build MCP x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build MCP x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build MCP x86_64-unknown-linux-gnu (push) Waiting to run
Build & Publish / Build MCP x86_64-unknown-linux-musl (push) Waiting to run
Build & Publish / Build Python wheels aarch64 (macos-latest) (push) Waiting to run
Build & Publish / Build Python wheels aarch64 (ubuntu-latest) (push) Waiting to run
Build & Publish / Build Python wheels x86_64 (windows-latest) (push) Waiting to run
Build & Publish / Build Python sdist (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 10:17:28 +00:00
parent 2871a2fedb
commit e25890c8a2
+203 -210
View File
@@ -1,29 +1,35 @@
<!-- WEHUB_ZH_README -->
> [!NOTE]
> 本文档由 WeHub 基于上游 README 翻译整理,属于社区翻译,非官方中文文档。
> [English](./README.en.md) · [原始项目](https://github.com/dmtrKovalenko/fff) · [上游 README](https://github.com/dmtrKovalenko/fff/blob/HEAD/README.md)
> 原作者、版权与许可证归属以原始项目及本仓库 LICENSE 文件为准。
<img alt="FFF" src="./assets/logo-orange.png" width="300">
<p>
<i>A file search toolkit for humans and AI agents. Really fast.</i>
<i>面向人类与 AI agent 的文件搜索工具包。速度真的很快。</i>
</p>
Typo-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.
抗拼写错误的路径与内容搜索、按使用频率(frecency)排序的文件访问、后台监视器,以及轻量级内存内容索引。在需要多次搜索的长期运行进程中,比 ripgrep、fzf 等 CLI 快得多。
Powers file search in [opencode](http://github.com/anomalyco/opencode/), [nushell](https://github.com/nushell/nushell), and many more amazing projects!
[opencode](http://github.com/anomalyco/opencode/), [nushell](https://github.com/nushell/nushell), 以及更多优秀项目提供文件搜索能力!
Originally 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.
最初作为备受喜爱的 [Neovim 插件](#neovim-plugin) 起步,但事实证明许多 AI harness 与代码编辑器都需要同样的东西:作为库提供的准确、快速的文件搜索。这就是 fff。
---
Pick what you are interested in:
选择你感兴趣的内容:
<details id="mcp-server">
<summary>
<h2>MCP server</h2>
</summary>
Works with Claude Code, Codex, OpenCode, Cursor, Cline, and any MCP-capable client. Fewer grep roundtrips, less wasted context, faster answers.
可与 Claude CodeCodexOpenCodeCursorCline 及任何支持 MCP 的客户端配合使用。减少 grep 往返次数、节省浪费的上下文,更快得到答案。
![Benchmark chart comparing FFF against the built-in AI file-search tools](./chart.png)
![将 FFF 与内置 AI 文件搜索工具进行对比的基准测试图表](./chart.png)
### One-line install
### 一行安装
Linux / macOS:
@@ -37,7 +43,7 @@ Windows (PowerShell):
irm https://raw.githubusercontent.com/dmtrKovalenko/fff.nvim/main/install-mcp.ps1 | iex
```
The scripts live at [`install-mcp.sh`](./install-mcp.sh) and [`install-mcp.ps1`](./install-mcp.ps1) if you want to read them first. They print the exact wiring instructions for your client.
若想先阅读脚本,可前往 [`install-mcp.sh`](./install-mcp.sh) [`install-mcp.ps1`](./install-mcp.ps1)。它们会打印针对你客户端的精确接入说明。
### Homebrew (macOS / Linux)
@@ -46,81 +52,81 @@ brew install dmtrKovalenko/fff/fff-mcp
brew upgrade fff-mcp # after new stable releases
```
Formula lives in [`Formula/fff-mcp.rb`](./Formula/fff-mcp.rb) in this repo and is **auto-bumped on every stable release** (see `bump-homebrew-formula` in [`.github/workflows/release.yaml`](./.github/workflows/release.yaml)). Installs the prebuilt `fff-mcp` binary from [GitHub releases](https://github.com/dmtrKovalenko/fff.nvim/releases).
Formula 位于本仓库的 [`Formula/fff-mcp.rb`](./Formula/fff-mcp.rb),且**每次稳定版发布时自动更新**(见 [`.github/workflows/release.yaml`](./.github/workflows/release.yaml) 中的 `bump-homebrew-formula`)。从 [GitHub releases](https://github.com/dmtrKovalenko/fff.nvim/releases). 安装预构建的 `fff-mcp` 二进制文件。
Once the server is connected, ask the agent to "use fff" and it picks up the `ffgrep`, `fffind`, and `fff-multi-grep` tools.
服务器连接后,让 agent「使用 fff」,它会启用 `ffgrep``fffind` `fff-multi-grep` 工具。
### Recommended agent prompt
### 推荐的 agent 提示词
Drop this into your project's `CLAUDE.md` or equivalent:
将其放入项目的 `CLAUDE.md` 或等效文件中:
```markdown
For any file search or grep in the current git-indexed directory, use fff tools.
```
### What changes
### 有哪些变化
- Frecency memory. Files you actually open rank higher next time. Warm-up from git touch history runs automatically.
- Definition-first hinting. Lines that look like code definitions are classified on the Rust side, no regex overhead in your prompt.
- Smart-case with auto-fuzzy fallback. `IsOffTheRecord` finds snake_case variants; zero-match queries retry as fuzzy and surface the best approximate hits.
- Git-aware annotations. Modified, untracked, and staged files are tagged so the agent reaches for what you are actively changing.
- 使用频率(frecency)记忆。你实际打开过的文件下次排名更高。会自动从 git touch 历史进行预热。
- 定义优先(definition-first)提示。看起来像代码定义的行在 Rust 侧完成分类,提示词中无需正则开销。
- 智能大小写匹配,自动模糊回退。`IsOffTheRecord` 可找到 snake_case 变体;零匹配查询会以模糊搜索重试,并呈现最佳近似命中。
- Git 感知标注。已修改、未跟踪和已暂存的文件会被标记,便于 agent 定位你正在积极修改的内容。
Source: [`crates/fff-mcp/`](./crates/fff-mcp/).
源码:[`crates/fff-mcp/`](./crates/fff-mcp/)
</details>
The MCP server gives any agent a file search tool that is faster and more token-efficient than the built-in one.
MCP 服务器为任意 agent 提供比内置方案更快、更省 token 的文件搜索工具。
<details id="pi-extension">
<summary>
<h2>Pi agent extension</h2>
</summary>
### Install
### 安装
```bash
pi install npm:@ff-labs/pi-fff
```
### Modes
### 模式
Three operating modes, switchable at runtime with `/fff-mode`:
三种运行模式,可在运行时通过 `/fff-mode` 切换:
| Mode | What it does |
| 模式 | 作用 |
| ------------------------ | --------------------------------------------------------------------------------- |
| `tools-and-ui` (default) | Adds `ffgrep` and `fffind` tools, replaces `@`-mention autocomplete with FFF. |
| `tools-only` | Only tool injection. Keeps pi's native editor autocomplete. |
| `override` | Replaces pi's built-in `grep`, `find`, and `multi_grep` with FFF implementations. |
| `tools-and-ui` (default) | 添加 `ffgrep` `fffind` 工具,用 FFF 替换 `@` 提及自动补全。 |
| `tools-only` | 仅注入工具。保留 pi 原生编辑器自动补全。 |
| `override` | 用 FFF 实现替换 pi 内置的 `grep``find` `multi_grep` |
Env vars: `PI_FFF_MODE`, `FFF_FRECENCY_DB`, `FFF_HISTORY_DB`. Flags: `--fff-mode`, `--fff-frecency-db`, `--fff-history-db`.
环境变量:`PI_FFF_MODE``FFF_FRECENCY_DB``FFF_HISTORY_DB`。标志:`--fff-mode``--fff-frecency-db``--fff-history-db`
### Agent-facing tools
### 面向 agent 的工具
- `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.
- `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.
- `ffgrep`。内容搜索。接受 `path``exclude`(逗号、空格或数组;前导 `!` 可选)、`caseSensitive``context`,以及游标分页。自动检测正则,零精确匹配时回退到模糊搜索, upfront 拒绝仅含 `.*` 风格通配符的模式。
- `fffind`。路径与文件名搜索。匹配整个仓库相对路径,而非仅文件名。具备使用频率感知。弱匹配检测器会在散乱的模糊噪声淹没 agent 上下文之前将其标记出来。
### Commands
### 命令
- `/fff-mode [tools-and-ui | tools-only | override]`. Show or switch the mode.
- `/fff-health`. Picker, frecency, and git integration status.
- `/fff-rescan`. Force a rescan.
- `/fff-mode [tools-and-ui | tools-only | override]`。显示或切换模式。
- `/fff-health`。选择器、使用频率与 Git 集成状态。
- `/fff-rescan`。强制重新扫描。
Source: [`packages/pi-fff/`](./packages/pi-fff/).
源码:[`packages/pi-fff/`](./packages/pi-fff/)
</details>
The Pi extension swaps pi's native tools for FFF implementations and feeds the interactive editor's `@`-mention autocomplete from the frecency-ranked index.
Pi 扩展用 FFF 实现替换 pi 的原生工具,并从按使用频率排序的索引为交互式编辑器的 `@` 提及自动补全提供数据。
<details id="neovim-plugin">
<summary>
<h2>fff.nvim</h2>
</summary>
Demo on the Linux kernel repo (100k files, 8GB):
在 Linux 内核仓库上的演示(10 万文件,8GB):
https://github.com/user-attachments/assets/5d0e1ce9-642c-4c44-aa88-01b05bb86abb
### Installation
### 安装
#### lazy.nvim
@@ -179,7 +185,7 @@ vim.g.fff = {
vim.keymap.set('n', 'ff', function() require('fff').find_files() end, { desc = 'FFFind files' })
```
### Public API
### 公开 API
```lua
require('fff').find_files() -- find files in current repo
@@ -197,7 +203,7 @@ require('fff').content_search(query, opts) -- programmatic grep
#### `file_search(query, opts)`
Returns a structured result `{ items, scores, total_matched, total_files?, total_dirs?, location? }`. Each item has a `type` field (`"file"` or `"directory"`) and `name` / `relative_path`. File items also expose `size`, `modified`, `git_status`, `is_binary`, and frecency scores.
返回结构化结果 `{ items, scores, total_matched, total_files?, total_dirs?, location? }`。每项包含 `type` 字段(`"file"` `"directory"`)以及 `name` / `relative_path`。文件项还会暴露 `size``modified``git_status``is_binary` 和使用频率分数。
```lua
local r = require('fff').file_search('button', {
@@ -216,7 +222,7 @@ end
#### `content_search(query, opts)`
Returns a `GrepResult` `{ items, total_matched, total_files_searched, total_files, filtered_file_count, next_file_offset, regex_fallback_error? }`. Each match item has `relative_path`, `name`, `line_number`, `col`, `line_content`, `match_ranges`, plus the same file metadata as `file_search`.
返回一个 `GrepResult` `{ items, total_matched, total_files_searched, total_files, filtered_file_count, next_file_offset, regex_fallback_error? }`。每个匹配项包含 `relative_path``name``line_number``col``line_content``match_ranges`,以及与 `file_search` 相同的文件元数据。
```lua
local r = require('fff').content_search('TODO', {
@@ -236,29 +242,29 @@ for _, m in ipairs(r.items) do
end
```
Both functions accept the same constraint syntax as the UI pickers (e.g. `git:modified`, `*.rs`, `!test/`, glob patterns).
两个函数均接受与 UI 选择器相同的约束语法(例如 `git:modified``*.rs``!test/`glob 模式)。
#### `cwd` and indexing
#### `cwd` 与索引
Both `file_search` and `content_search` honour an optional `cwd` field. The first call to either function lazily initialises the picker at `config.base_path` (your Neovim cwd by default).
`file_search` `content_search` 均尊重可选的 `cwd` 字段。首次调用任一函数时,会在 `config.base_path`(默认为你 Neovim cwd)处惰性初始化选择器。
- If `cwd` matches the currently indexed root, the call returns immediately against the existing index.
- If `cwd` differs, the picker is re-indexed at the new root and the call **blocks** (default up to 10 s) until the new picker is installed and its initial scan completes — so callers always get results from the right tree.
- If the index is still warming up after a `change_indexing_directory`, you can pass `wait_for_index_ms = N` to block for up to `N` ms regardless of whether `cwd` triggered the swap. Pass `0` to skip waiting entirely (useful for fire-and-forget calls where partial results are acceptable).
- Invalid or non-existent `cwd` paths return an empty result and emit an error via `vim.notify`.
- `cwd` 与当前已索引的根目录一致,则调用会立即针对现有索引返回结果。
- `cwd` 不同,选择器会在新根目录下重新建立索引,且调用会**阻塞**(默认最长 10 秒),直至新选择器安装完成且其初次扫描结束——这样调用方总能从正确的目录树获得结果。
- 若在 `change_indexing_directory` 之后索引仍在预热,可传入 `wait_for_index_ms = N`,无论 `cwd` 是否触发了切换,均可阻塞等待最长 `N` ms。传入 `0` 可完全跳过等待(适用于“即发即忘”、可接受部分结果的调用)。
- 无效或不存在的 `cwd` 路径会返回空结果,并通过 `vim.notify` 输出错误。
### Commands
### 命令
- `:FFFScan`. Rescan files.
- `:FFFRefreshGit`. Refresh git status.
- `:FFFClearCache [all|frecency|files]`. Clear caches.
- `:FFFHealth`. Health check.
- `:FFFDebug [on|off|toggle]`. Toggle the scoring display.
- `:FFFOpenLog`. Open `~/.local/state/nvim/log/fff.log`.
- `:FFFScan`。重新扫描文件。
- `:FFFRefreshGit`。刷新 git 状态。
- `:FFFClearCache [all|frecency|files]`。清除缓存。
- `:FFFHealth`。健康检查。
- `:FFFDebug [on|off|toggle]`。切换评分显示。
- `:FFFOpenLog`。打开 `~/.local/state/nvim/log/fff.log`
### Configuration
### 配置
Defaults are sensible. Override only what you care about.
默认配置已足够合理。仅覆盖你关心的项即可。
```lua
require('fff').setup({
@@ -379,36 +385,36 @@ require('fff').setup({
})
```
### Live grep modes
### 实时 grep 模式
`<S-Tab>` cycles between `plain`, `regex`, and `fuzzy`. The list is configurable via `grep.modes`, and single-mode setups hide the indicator entirely.
`<S-Tab>` 会在 `plain``regex` `fuzzy` 之间循环切换。列表可通过 `grep.modes` 配置;若仅配置单一模式,则完全隐藏指示器。
Per-call override:
每次调用覆盖:
```lua
require('fff').live_grep({ grep = { modes = { 'fuzzy', 'plain' } } })
require('fff').live_grep({ query = 'search term' }) -- pre-fill
```
### Constraints
### 约束
Both find and grep accept these tokens to refine a query:
find grep 均接受下列标记以细化查询:
- `git:modified`. One of `modified`, `staged`, `deleted`, `renamed`, `untracked`, `ignored`.
- `test/`. Any deeply nested children of `test/`.
- `!something`, `!test/`, `!git:modified`. Exclusion.
- `./**/*.{rs,lua}`. Any valid glob, powered by [zlob](https://github.com/dmtrKovalenko/zlob).
- `git:modified``modified``staged``deleted``renamed``untracked``ignored` 之一。
- `test/``test/` 下任意深层子项。
- `!something``!test/``!git:modified`。排除。
- `./**/*.{rs,lua}`。任意有效的 glob,由 [zlob](https://github.com/dmtrKovalenko/zlob).
Grep-only:
仅 grep
- `*.md`, `*.{c,h}`. Extension filter.
- `src/main.rs`. Grep inside a single file.
- `*.md``*.{c,h}`。扩展名过滤。
- `src/main.rs`。在单个文件内 grep。
Mix freely: `git:modified src/**/*.rs !src/**/mod.rs user controller`.
可自由组合:`git:modified src/**/*.rs !src/**/mod.rs user controller`
### Open in invoking window
### 在调用窗口中打开
By default fff.nvim will try to open a file in the most suitable window, so any non-file buffers are not affected. You can customize or disable this by providing:
默认情况下,fff.nvim 会尝试在最合适的窗口中打开文件,因此不会影响任何非文件缓冲区。你可以通过提供以下配置来自定义或禁用该行为:
```lua
require('fff').setup({
@@ -418,22 +424,22 @@ require('fff').setup({
})
```
Caveat: the chosen file replaces the buffer in the invoking window even if it's a non-modifiable / special buftype. `winfixbuf` windows still fall back to `:split` to avoid `E1513`.
注意:即使调用窗口为不可修改/特殊 buftype,所选文件仍会替换其中的缓冲区。`winfixbuf` 窗口仍会回退到 `:split`,以避免 `E1513`
### Multi-select and quickfix
### 多选与 quickfix
- `<Tab>`. Toggle selection (shows a thick `▊` in the signcolumn).
- `<C-q>`. Send selected files to the quickfix list and close the picker.
- `<Tab>`。切换选中状态(在 signcolumn 中显示粗 `▊`)。
- `<C-q>`。将所选文件发送到 quickfix 列表并关闭选择器。
### Git status highlighting
### Git 状态高亮
Sign-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.
默认启用 sign-column 指示器。若要按 git 状态为文件名文本着色,请设置 `git.status_text_color = true` 并调整 `hl.git_*` 组。完整列表见 `:help fff.nvim`
### Float colors
### 浮动窗口颜色
The picker maps its float content to `NormalFloat` (via `hl.normal`) and the border to `FloatBorder`. Default `FloatBorder` links to `NormalFloat`, so border and content share a background out of the box and the picker reads as a single popup. Override `hl.normal = 'Normal'` to make the picker blend with the editor instead.
选择器通过 `hl.normal` 将其浮动内容映射到 `NormalFloat`,将边框映射到 `FloatBorder`。默认情况下 `FloatBorder` 关联到 `NormalFloat`,因此边框与内容默认共享背景,选择器呈现为单一弹出层。可覆盖 `hl.normal = 'Normal'`,使选择器与编辑器融为一体。
For finer control, set `hl.winhl` to override the per-window `winhighlight`. It accepts either a single string applied to every picker window, or a table with optional `prompt`, `list`, `preview`, and `file_info` keys. Missing keys fall back to the default built from `hl.normal`, `hl.border`, and `hl.title`.
若要更精细控制,可设置 `hl.winhl` 以覆盖每个窗口的 `winhighlight`。它既可接受应用于每个选择器窗口的单个字符串,也可接受包含可选键 `prompt``list``preview` `file_info` 的表。缺失的键将回退到由 `hl.normal``hl.border` `hl.title` 构建的默认值。
```lua
-- Apply the same winhighlight to all picker windows
@@ -449,52 +455,48 @@ hl = {
}
```
### File info panel
### 文件信息面板
Enable with `debug.enabled = true`. The panel sits above the preview and shows
file metadata, score breakdown, timestamps and the full absolute path. It
adapts to the panel width: at narrow widths sections stack vertically (B2),
at wide widths sections render as a two-column grid (H2). Each section can be
disabled individually via `debug.show_file_info`.
通过 `debug.enabled = true` 启用。该面板位于预览上方,显示文件元数据、得分明细、时间戳和完整绝对路径。它会根据面板宽度自适应:较窄时各部分垂直堆叠(B2),较宽时以两列网格(H2)排列。可通过 `debug.show_file_info` 单独禁用各个区块。
Customise the panel via `hl`:
通过 `hl` 自定义面板:
| key | default | used for |
| ----------------------- | ----------------- | ---------------------------------- |
| `file_info_section` | `Title` | section header label |
| `file_info_separator` | `FloatBorder` | dashes that act as section borders |
| `file_info_label` | `Comment` | row labels (Size, Type, Git, ...) |
| `file_info_value` | `Normal` fg | plain values |
| `file_info_value_dim` | `NonText` | dim values, separators inside rows |
| `file_info_size` | `Number` | file size value |
| `file_info_type` | `Type` | filetype value |
| `file_info_path` | `Directory` | full path |
| `file_info_total_score` | bold + `Number` | total score (bold) |
| `file_info_match_type` | bold + `Special` | match type (bold) |
| `file_info_score_pos` | `DiagnosticOk` | positive score components |
| `file_info_score_neg` | `DiagnosticError` | negative score components |
| `file_info_section` | `Title` | 区块标题标签 |
| `file_info_separator` | `FloatBorder` | 用作区块边界的虚线 |
| `file_info_label` | `Comment` | 行标签(SizeTypeGit 等) |
| `file_info_value` | `Normal` fg | 普通值 |
| `file_info_value_dim` | `NonText` | 暗淡值、行内分隔符 |
| `file_info_size` | `Number` | 文件大小值 |
| `file_info_type` | `Type` | 文件类型值 |
| `file_info_path` | `Directory` | 完整路径 |
| `file_info_total_score` | bold + `Number` | 总分(粗体) |
| `file_info_match_type` | bold + `Special` | 匹配类型(粗体) |
| `file_info_score_pos` | `DiagnosticOk` | 正向得分分量 |
| `file_info_score_neg` | `DiagnosticError` | 负向得分分量 |
### File filtering
### 文件过滤
FFF honours `.gitignore`. For picker-only ignores that do not touch git, add a sibling `.ignore` file:
FFF 遵循 `.gitignore`。对于仅作用于选择器、不影响 git 的忽略规则,请添加同级的 `.ignore` 文件:
```gitignore
*.md
docs/archive/**/*.md
```
Run `:FFFScan` to force a rescan.
运行 `:FFFScan` 以强制重新扫描。
### Troubleshooting
### 故障排除
- `:FFFHealth` verifies picker init, optional dependencies, and DB connectivity.
- `:FFFOpenLog` opens the current session's log file.
- Historical log files are stored near the main log file `<state>/log/fff+<UTC-timestamp>+<pid>.log` (up to 20 files)
- For a crash backtrace, run `lldb -- nvim` or `gdb -- nvim` and reproduce
- `:FFFHealth` 验证选择器初始化、可选依赖项及数据库连接。
- `:FFFOpenLog` 打开当前会话的日志文件。
- 历史日志文件保存在主日志文件 `<state>/log/fff+<UTC-timestamp>+<pid>.log` 附近(最多 20 个文件)
- 若要获取崩溃回溯,请运行 `lldb -- nvim` `gdb -- nvim` 并复现问题
</details>
The best file search picker for neovim. Period. Faster and more intuitive queries, frecency ranking, definition classification and much more.
Neovim 最好的文件搜索选择器,没有之一。查询更快、更直观,支持 frecency(频率+新近性)排序、定义分类等丰富功能。
<details id="node-sdk">
<summary>
@@ -529,38 +531,38 @@ const rustFiles = finder.value.glob("**/*.rs", { pageSize: 100 });
finder.value.destroy();
```
Every method returns a `Result<T>` (`{ ok: true, value } | { ok: false, error }`). Full type reference: [`packages/fff-node/src/types.ts`](./packages/fff-node/src/types.ts).
每个方法均返回 `Result<T>``{ ok: true, value } | { ok: false, error }`)。完整类型参考:[`packages/fff-node/src/types.ts`](./packages/fff-node/src/types.ts)
</details>
TypeScript wrapper over the C library for nodejs and bun. Build custom agent tools, CLIs, or IDE integrations on top of FFF.
面向 nodejs bun 的 C 库 TypeScript 封装。可在 FFF 之上构建自定义 agent 工具、CLI 或 IDE 集成。
<details id="rust-crate">
<summary>
<h2>Rust crate</h2>
</summary>
### Add the dependency
### 添加依赖
FFF is written in Rust, so this is the lowest-overhead way to use it.
FFF 由 Rust 编写,因此这是开销最低的用法。
```toml
[dependencies]
fff-search = "0.6"
```
Full API documentation: [docs.rs/fff-search](https://docs.rs/fff-search/latest/fff_search/).
完整 API 文档:[docs.rs/fff-search](https://docs.rs/fff-search/latest/fff_search/).
</details>
Native rust crate that is performing all the search. Stable and well documented.
执行所有搜索的原生 Rust crate。稳定且文档完善。
<details id="c-library">
<summary>
<h2>C library</h2>
</summary>
### Build
### 构建
```bash
# Builds only the C cdylib (fastest):
@@ -570,16 +572,15 @@ make build-c-lib
cargo build --release -p fff-c --features zlob
```
> The `zlob` feature (requires the [Zig](https://ziglang.org) toolchain) switches both
> glob matching **and** filesystem traversal to [zlob](https://github.com/dmtrKovalenko/zlob)'s
> native parallel walker. Without it, the default build uses the pure-Rust
> [`ignore`](https://crates.io/crates/ignore) (ripgrep) walker and `globset`.
> `zlob` 功能(需要 [Zig](https://ziglang.org) 工具链)会将 glob 匹配**以及**文件系统遍历都切换为 [zlob](https://github.com/dmtrKovalenko/zlob)'s
> 原生并行遍历器。若不启用,默认构建使用纯 Rust 的
> [`ignore`](https://crates.io/crates/ignore)ripgrep)遍历器与 `globset`。
The output is a `cdylib` (`libfff_c.so` / `libfff_c.dylib` / `fff_c.dll`). The header lives at [`crates/fff-c/include/fff.h`](./crates/fff-c/include/fff.h).
输出为 `cdylib``libfff_c.so` / `libfff_c.dylib` / `fff_c.dll`)。头文件位于 [`crates/fff-c/include/fff.h`](./crates/fff-c/include/fff.h)
Prebuilt binaries for every version, including every commit on main, are on the [releases page](https://github.com/dmtrKovalenko/fff.nvim/releases). The same binaries also ship inside the `@ff-labs/fff-bin-*` npm packages.
每个版本(包括 main 上的每次提交)的预构建二进制文件可在 [releases page](https://github.com/dmtrKovalenko/fff.nvim/releases). 获取。相同二进制文件也会随 `@ff-labs/fff-bin-*` npm 包一同发布。
### Install
### 安装
```bash
# System-wide (needs sudo):
@@ -592,17 +593,17 @@ make install PREFIX=$HOME/.local
make install DESTDIR=/tmp/pkgroot PREFIX=/usr
```
Drops `libfff_c.{so,dylib,dll}` into `$(PREFIX)/lib` and the header into `$(PREFIX)/include/fff.h`. Remove with `make uninstall`, which honours the same `PREFIX` and `DESTDIR`.
`libfff_c.{so,dylib,dll}` 安装到 `$(PREFIX)/lib`,将头文件安装到 `$(PREFIX)/include/fff.h`。使用 `make uninstall` 卸载,该命令遵循相同的 `PREFIX` `DESTDIR`
Link against it after install:
安装后与之链接:
```bash
cc my_app.c -lfff_c -o my_app
```
Ensure `$(PREFIX)/lib` is on your runtime library search path (`LD_LIBRARY_PATH` on Linux, `DYLD_LIBRARY_PATH` on macOS, or an entry in `/etc/ld.so.conf.d/`).
确保 `$(PREFIX)/lib` 位于运行时库搜索路径中(Linux 上为 `LD_LIBRARY_PATH`macOS 上为 `DYLD_LIBRARY_PATH`,或在 `/etc/ld.so.conf.d/` 中添加条目)。
### Minimal example
### 最小示例
```c
#include <fff.h>
@@ -636,11 +637,9 @@ int main(void) {
}
```
### Versioned options struct (preferred)
### 版本化 options 结构体(推荐)
For instance creation use [`FffCreateOptions`](./crates/fff-c/include/fff.h) — a
versioned struct that evolves without ABI breaks. C99 designated
initializers keep call sites readable and zero-init unspecified fields:
创建实例请使用 [`FffCreateOptions`](./crates/fff-c/include/fff.h) —— 一种可在不破坏 ABI 的情况下演进的版本化结构体。C99 指定初始化器(designated initializers)使调用点保持可读,未指定字段零初始化:
```c
FffResult *res = fff_create_instance_with(&(FffCreateOptions){
@@ -653,42 +652,39 @@ FffResult *res = fff_create_instance_with(&(FffCreateOptions){
});
```
### Glob-only search
### Glob 搜索
`fff_glob` filters indexed files by a single glob pattern, ranks by frecency,
paginates — bypasses the regular query parser entirely. Use this when you
already have a literal glob (`*.rs`, `**/*.test.ts`, `src/**`) and don't want
fuzzy matching layered on top.
`fff_glob` 按单个 glob 模式过滤已索引文件,按访问频率(frecency)排序并分页——完全绕过常规查询解析器。当你已有字面量 glob(`*.rs``**/*.test.ts``src/**`),且不希望在其上叠加模糊匹配时使用。
```c
FffResult *res = fff_glob(handle, "**/*.rs", "", 0, 0, 100);
// FffSearchResult in res->handle, free with fff_free_search_result.
```
### Notes
### 说明
- Every function returning `FffResult*` allocates with Rust's `Box`. Free with `fff_free_result`, do not use malloc's free
- Payloads (search results, grep results, scan progress) have their own dedicated free functions listed in the header.
- C strings returned in the `handle` field (e.g. from `fff_get_base_path`) are freed with `fff_free_string`.
- 每个返回 `FffResult*` 的函数均使用 Rust `Box` 分配内存。请用 `fff_free_result` 释放,不要使用 malloc free
- 各类载荷(搜索结果、grep 结果、扫描进度)在头文件中列出了各自的专用释放函数。
- `handle` 字段中返回的 C 字符串(例如来自 `fff_get_base_path`)需用 `fff_free_string` 释放。
Source: [`crates/fff-c/`](./crates/fff-c/).
源码:[`crates/fff-c/`](./crates/fff-c/)
</details>
Stable C ABI. Bind from C/C++, Zig, Go via cgo, Python via ctypes, or anything with C FFI.
稳定的 C ABI(应用程序二进制接口)。可从 C/C++Zig、通过 cgo 的 Go、通过 ctypes 的 Python,或任何支持 C FFI 的语言绑定。
<details id="python-bindings">
<summary>
<h2>Python bindings</h2>
<h2>Python 绑定</h2>
</summary>
### Install
### 安装
```bash
pip install fff-search
```
Or build and install from source:
或从源码构建并安装:
```bash
cd packages/fff-python
@@ -696,7 +692,7 @@ uv sync --all-extras
uv run maturin develop --release
```
### Basic usage
### 基本用法
```python
from fff import FileFinder
@@ -711,11 +707,9 @@ with FileFinder("/path/to/project", watch=False) as finder:
hits = finder.grep("class Profile", mode="plain", before_context=1, after_context=1)
```
### Async usage
### 异步用法
`wait_for_scan` is a coroutine that polls scan status and yields to the event
loop, so it never blocks other tasks. Use `wait_for_scan_blocking` from
synchronous code.
`wait_for_scan` 是一个协程,会轮询扫描状态并让出给事件循环,因此不会阻塞其他任务。在同步代码中使用 `wait_for_scan_blocking`
```python
import asyncio
@@ -730,96 +724,95 @@ async def main():
asyncio.run(main())
```
### What you get
### 你将获得
- `search`, `glob`, `directory_search`, `mixed_search`frecency-ranked fuzzy file/dir search
- `grep` / `multi_grep`plain, regex, or fuzzy content search with context lines and cursor pagination
- `track_query` / `get_historical_query`optional frecency and query-history databases
- `reindex`, `refresh_git_status`, `scan_progress`, `health_check`lifecycle and diagnostics
- `search``glob``directory_search``mixed_search`按访问频率排序的模糊文件/目录搜索
- `grep` / `multi_grep`纯文本、正则或模糊内容搜索,支持上下文行与游标分页
- `track_query` / `get_historical_query`可选的访问频率与查询历史数据库
- `reindex``refresh_git_status``scan_progress``health_check`生命周期与诊断
Typed result objects (`FileItem`, `Score`, `GrepMatch`, …) with `py.typed`
stubs included. Ships as an `abi3` wheel compatible with Python 3.10+.
类型化结果对象(`FileItem``Score``GrepMatch`,…),附带 `py.typed` 桩代码。以兼容 Python 3.10+ 的 `abi3` wheel 形式发布。
Source: [`packages/fff-python/`](./packages/fff-python/).
源码:[`packages/fff-python/`](./packages/fff-python/)
</details>
Native Python bindings built with PyO3. Use them for notebooks, agent scripts, or any Python tool that needs fast file search.
基于 PyO3 构建的原生 Python 绑定。适用于 notebookagent 脚本或任何需要快速文件搜索的 Python 工具。
---
## What is FFF and why use it over ripgrep or fzf?
## FFF 是什么?为何选用它而非 ripgrep fzf
FFF 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.
FFF 是一个文件搜索库,而非 CLIRipgrep fzf 都是优秀工具,但它们是命令行程序:每次调用都会 fork 新进程、重新读取 `.gitignore`、重新 stat 目录,并在能给出答案前于内存中重建所需状态。在 shell 里 grep 一次时这没问题;但当编辑器或 AI agent 希望在单次会话中执行数百次搜索时,这就很糟糕。
FFF 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/Bun SDK (`@ff-labs/fff-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.
FFF 将索引与文件缓存驻留在同一长生命周期进程中,并通过四层薄封装暴露同一 Rust 核心:原生 crate`fff-search`)、C 库(`libfff_c`)、Node/Bun SDK`@ff-labs/fff-node`)以及 MCP 服务器。你只需调用一次 `FileFinder.create()`,此后每次搜索都命中热内存。在约 50 万文件的 Chromium checkout 上,这意味着每次 spawn ripgrep 需 39 **SECONDS**,而每次 FFF 查询低于 10 ms。
Algorithm 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/ shcema" is a perfectly valid query for fff, but fzf wouldn't find anything even for a single typo in "shcema".
模糊匹配算法远比 fzf 更全面,具备**抗拼写错误(typo-resistant)**能力;我们还提供带额外约束解析的查询语言用于预过滤,例如 "\*.rs !test/ shcema" 对 fff 是完全合法的查询,但 fzf 即使在 "shcema" 中仅有一处拼写错误也找不到任何结果。
### Why a programmatic API matters
### 为何程序化 API 很重要
- No process spawn. Every call stays in-process and avoids the fork, exec, argv parsing, and stdout pipe setup that dominates short `rg` invocations.
- 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.
- Results come back as typed objects, not text you have to re-parse. The SDK gives you `{ relativePath, lineNumber, lineContent, gitStatus, totalFrecencyScore, isDefinition, ... }` directly.
- Cursor pagination that survives across calls. Ripgrep has no concept of "page 2 of these matches"; FFF does.
- 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.
- 无进程 spawn。每次调用均在进程内完成,避免短时 `rg` 调用中占主导地位的 forkexecargv 解析与 stdout 管道设置。
- 仅一次 FS 遍历、元数据收集与 `.gitignore` 解析。ignore walker 在扫描时运行一次,结果供每次搜索复用。
- 结果以类型化对象返回,而非需重新解析的文本。SDK 直接提供 `{ relativePath, lineNumber, lineContent, gitStatus, totalFrecencyScore, isDefinition, ... }`
- 跨调用仍有效的游标分页。Ripgrep 没有「这些匹配的第 2 页」概念;FFF 有。
- 长生命周期进程可实现一次性 CLI 无法应用的优化:热缓存、增量重索引、跨查询 frecency 与共享 SIMD 状态。
### What the core actually does
### 核心实际做了什么
- **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.
- **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://github.com/saghen/frizbee)-derived core) that survives dropped characters and reorderings.
- **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.
- **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.
- **Background file watcher.** The index updates as files change. You never pay for a rescan on the hot path.
- **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.
- **Definition classifier.** A byte-level scanner on the Rust side tags lines that start with `struct`, `fn`, `class`, `def`, `impl`, and friends.
- **按访问频率排序的模糊匹配(Frecency-ranked fuzzy matching)。** 每个已索引文件携带访问分数与修改分数。搜索会将你最近且频繁打开的文件排在冷门结果之上。这与 VS Code 的最近打开列表思路相同,但应用于每条搜索结果,而非仅侧边栏。
- **路径与内容均支持抗拼写错误匹配。** grep 路径可用 Smith-Waterman 模糊评分;路径搜索使用 SIMD 加速模糊匹配(通过 [`frizbee`](https://github.com/saghen/frizbee)-derived core)),可容忍字符缺失与重排。
- **三种模式的内容 grep。** 纯字面量(SIMD memmem)、正则(Rust `regex` crate)与模糊(逐行 Smith-Waterman)。根据模式自动检测使用哪种模式;纯文本搜索零命中时回退到模糊模式。
- **多模式 OR 搜索。** SIMD Aho-Corasick 用于「一次找出这 20 个标识符中的任意一个」,比正则择快得多,也远快于 20 次独立 ripgrep 运行。
- **后台文件监视器。** 文件变更时索引自动更新。热路径上无需为重新扫描付出代价。
- **Git 状态感知。** 已修改、已暂存、未跟踪与已忽略状态会被缓存并与每条结果一并返回,调用方可排序或过滤而无需 shell 调用 git。监视器直接与 libgit2 通信,而非 spawn `git` CLI
- **定义分类器。** Rust 端的字节级扫描器为以 `struct``fn``class``def``impl` 等开头的行打标签。
### Performance choices that matter
### 重要的性能取舍
- Efficient memory allocator and memory allocation strategy (see next paragraph). By default we use `mimaloc`
- Parallel multi thread search pipeline that is not contaganted by the orchistration logic
- SIMD first algorithms for everything. Efficinet & non-allocating sorting.
- Platform specific optimizations for FS ([getdents64](https://linux.die.net/man/2/getdents64), NTFS api on windows and others)
- Lightweight on the flight content index for realtime even typo resistant grep
- Memory mapped content cache. We store some of the files in virtual memory (the amount is limited)
- Single contiguous arena storage of string chunks. Significantly reduces the amount of memory to work with and dramatically increases CPU cache hits.
- 高效的内存分配器与分配策略(见下一段)。默认使用 `mimaloc`
- 并行多线程搜索流水线,不受编排逻辑拖累
- 一切优先采用 SIMD 算法。高效且无分配的排序。
- 针对 FS 的平台特定优化([getdents64](https://linux.die.net/man/2/getdents64), NTFS api on windows and others)
- 轻量即时内容索引,支持实时且抗拼写错误的 grep
- 内存映射内容缓存。我们将部分文件存入虚拟内存(数量有限)
- 字符串块的单一连续 arena 存储。显著减少需操作的内存量,并大幅提升 CPU 缓存命中。
### Memory allocation
### 内存分配
Yes, 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://x.com/neogoose_btw/status/2041606853155811442).
是的,fff 本质上比调用单个子进程需要更多内存。这正是速度提升的主要来源。实践中,在与 Neovim 最受欢迎的文件搜索选择器之一配合使用时,[fff 最终占用的 RAM 少于一波 ripgrep 调用](https://x.com/neogoose_btw/status/2041606853155811442).
FFF 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.
FFF 还维护内容索引,每个已索引文件约 360 字节,因此 10 万文件规模的仓库约 36 MB。并非每个文件都会被索引——二进制文件、超大文件以及不适合 grep 的内容会被跳过。若即便如此 footprint 仍过大,索引可改用内存映射文件而非匿名 RAM 承载。
### What this means in practice
### 实际含义
If 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.
如果你在构建 agent、IDE 扩展、pre-commit 检查,或任何会反复搜索同一仓库的长驻工具,将 FFF 作为库调用,远比通过 shell 调用 ripgrep 便宜得多。代价是真实的内存占用:FFF 将索引保留在 RAM 中,并预热内容缓存。在约 1.4 万文件的仓库上,常驻内存大约 26 MB。在像 Chromium 这样约 50 万文件的仓库上,预计需要数百 MB。作为交换,每一次搜索都会附带 git 状态、frecency(使用频率)排序、文件元数据、最近访问与编辑时间戳等信息。
If 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.
如果你只是在终端里跑一次 grep`rg` 仍是合适之选。如果在同一进程里跑几十次,从第二次调用起 FFF 就能回本。如果你在开发 AI agentfff 会在你的 AI 有机会调用它之前就完成准备工作。
### How it compares
### 与其他工具对比
- **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."
- **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.
- **Telescope / fzf-lua / 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.
- **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.
- **ripgrep**FFF 使用相同的底层正则引擎,以及更先进的纯文本匹配算法。它会存储内容索引和文件树。在重复搜索场景下优势明显;在「从 bash 里 grep 一次就退出」的场景下则不如 ripgrep。
- **fzf**FFF 的路径搜索与 fzf 一样是模糊匹配,但它还支持 frecency 感知和 git 感知,并内置更容忍拼写错误的算法。fzf 是纯匹配与过滤工具;FFF 会按你实际打开文件的频率对结果排序。
- **Telescope / fzf-lua / snacks.picker**FFF 自带 Neovim picker,排序逻辑与 MCP server SDK 一致。picker 是可选的;核心能力相同。
- **Tantivy 或其他全文搜索引擎**:属于不同类别的工具。Tantivy 为大规模查询时评分而索引文档。FFF 限定在单个仓库,并针对亚 10 ms 响应进行优化。它不会在磁盘上持久化倒排索引。
---
## Repository layout
## 仓库结构
- `crates/fff-search`, `crates/fff-grep`, `crates/fff-query-parser` - Rust core.
- `crates/fff-c` - C FFI used by every language binding.
- `crates/fff-nvim` - Lua/mlua bindings for the Neovim plugin.
- `crates/fff-mcp` - MCP server binary.
- `packages/fff-node` - Node.js SDK (`@ff-labs/fff-node`).
- `packages/fff-bun` - Bun SDK (`@ff-labs/fff-bun`).
- `packages/pi-fff` - pi extension (`@ff-labs/pi-fff`).
- `lua/` - Neovim-side plugin code.
- `crates/fff-search``crates/fff-grep``crates/fff-query-parser` Rust 核心。
- `crates/fff-c` — 各语言绑定共用的 C FFI。
- `crates/fff-nvim` — Neovim 插件的 Lua/mlua 绑定。
- `crates/fff-mcp` MCP server 二进制。
- `packages/fff-node` Node.js SDK`@ff-labs/fff-node`)。
- `packages/fff-bun` Bun SDK`@ff-labs/fff-bun`)。
- `packages/pi-fff` pi 扩展(`@ff-labs/pi-fff`)。
- `lua/` Neovim 端插件代码。
## Contributing
## 参与贡献
Bug reports and pull requests welcome. Agentic coding tools are welcome to be used, but human review is mandatory.
欢迎提交 bug 报告和 pull request。可以使用 agentic coding 工具,但必须经过人工审核。
## License
## 许可证
[MIT](./LICENSE) & open source forever.
[MIT](./LICENSE) & 永久开源。