clippy 1.98 added chunks_exact_to_as_chunks, which fails CI under
-D warnings. SEEN_WORDS is a multiple of 8, so the remainder is
always empty and the fixed-size chunk keeps codegen identical or
better.
Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
rmcp 1.7.0 aborts startup on any pre-initialize request except ping, so a
client probing with server/discover (SEP-1442 stateless spec) killed the
process before it could fall back to the legacy handshake.
Wrap the stdio transport so unsupported pre-init requests get -32601 and the
server keeps waiting for initialize. Full stateless support is out of scope.
Closes#797
Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
* fix: raise LMDB max_readers to avoid MDB_READERS_FULL (#783)
heed's default reader table is 126 slots and fff opened envs in default
TLS mode, so each long-lived reader thread pinned a slot for its lifetime.
Long-lived embedders (Neovim, node agents) sharing one lock file across
many processes/threads exhausted the table with MDB_READERS_FULL.
Raise max_readers to 1024 (slots are ~64B, cost negligible) and expose
FFF_LMDB_MAX_READERS for hosts to tune. NOTLS left for maintainer.
Closes#783
* fix: open LMDB envs with MDB_NOTLS so reader slots free on txn drop (#783)
Reader slots are now tied to txn objects instead of pinned per thread
for the thread's lifetime, so long-lived embedders no longer accumulate
slots. Env/RoTxn become WithoutTls-typed; RwTxn is unaffected.
* fix(build): link advapi32 on Windows for lmdb-master-sys
mdb_env_setup_locks references InitializeSecurityDescriptor /
SetSecurityDescriptorDacl but lmdb-master-sys's build script never
links advapi32; minimal test binaries fail with LNK2019 without it.
* fix(test): link advapi32 in lmdb repro test binary on Windows
The test links heed directly and rustc elides the unused fff lib, so
build-script link flags never reach this binary; declare the dependency
on advapi32 (mdb_env_setup_locks security-descriptor APIs) in the test.
---------
Co-authored-by: Dmitriy Kovalenko <dmtr.kovalenko@outlook.com>
* fix(ci): build the workspace once per e2e job
* perf(ci): cache rust compilations with sccache
* fix(ci): stop sccache idling out mid-compile and cancel superseded runs
* fix(tests): poll for the new root index instead of a fixed sleep
* perf(ci): cache release target builds with sccache
clear_cache('files') drops the Rust picker but core.lua kept
state.file_picker_initialized set, so ensure_initialized short-circuited
on state.initialized and never rebuilt it. Every later FFFScan/search then
ran against a dropped picker (watcher logs "File picker not initialized"),
which SIGSEGVs on Linux.
Split ensure_initialized: one-time setup stays gated on state.initialized,
picker creation is gated separately on state.file_picker_initialized.
clear_cache now clears that flag via mark_file_picker_uninitialized so the
next ensure_initialized rebuilds the picker.
Closes#772
Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
find_files, grep, and multi_grep only read the filesystem but declared
no MCP tool annotations, so plan-mode / read-only clients blocked them.
Add annotations(read_only_hint = true, destructive_hint = false,
open_world_hint = false) to each #[tool].
Closes#771
Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
ffgrep/fff-multi-grep passed the documented `limit` only as
`maxMatchesPerFile`, so matches spread across files could return a full
SDK page (default 50) instead of `limit`. Pass `limit` as `pageSize`
too, on both grep tools and the fuzzy fallback, so the cap applies to
total matches per page; excess stays retrievable via the existing
cursor. Also clamp `context` to a bounded non-negative integer so a
large value cannot multiply output size past the model window.
Closes#768
Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
* chore: point residual repo refs at dmtrKovalenko/fff
Update install URLs, download targets, and docs after the GitHub
rename from fff.nvim to fff. Preserve the existing Neovim package
name (fff.nvim) in lazy/vim.pack snippets so upgraders keep the same
install dir and lockfile identity.
* chore(docs): align Neovim package name with repo basename
Drop the explicit fff.nvim package name so lazy/vim.pack use fff from
the repository URL. Mild migration cost for existing installs (new
plugin dir + clean of the old one). Drop this commit to keep the
lowest-pain name=fff.nvim install snippets from the previous commit.
* fix(grep): keep FilePath scope in regex/literal fallback (#756)
The literal/regex fallback rebuilt the query with empty constraints,
dropping an explicit inline FilePath scope. In regex mode a top-level
alternation then leaked matches into files outside the pinned path.
Preserve FilePath constraints in the fallback query.
Closes#756
* chore: cargo fmt (#756)
---------
Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
The no_content_indexing field lacked its #[arg(long = ...)] attribute,
so clap treated it as a positional with a SetTrue action. Debug builds
panicked on any invocation; release builds exposed [NO_CONTENT_INDEXING]
as a positional and rejected --no-content-indexing.
Closes#754
Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
Pi reloads extension modules with jiti moduleCache:false, so loadSdk()
re-executes a dynamic import of the fff-bun module graph on every /reload.
The fff-bun graph top-level awaits a type:file import of the native .so,
which never resolves when re-imported inside the Bun-compiled pi binary,
leaving the reload screen stuck forever (pi awaits session_start handlers
without a timeout).
Cache the first import on globalThis so reloads reuse it.
Closes#757
Co-authored-by: chenydev <chenydev@users.noreply.github.com>
* fix(nvim): refuse fs-root/home index at Lua level before FFI (#745)
Opening nvim at `/` with lazy=false crashed the whole neovim process on
CI-cross-compiled aarch64 .so binaries: the init_file_picker FFI call
SIGSEGVs instead of returning Error::FilesystemRoot cleanly. A SIGSEGV
is a hardware signal that the pcall around init_file_picker cannot catch.
Mirror the Rust refusal (file_picker.rs:862) in Lua and bail before
crossing the FFI boundary, so the crashing path is never reached. Same
guard added to change_indexing_directory for the :cd-into-root case.
Honors enable_fs_root_scanning / enable_home_dir_scanning overrides.
* chore(nvim): trim comments in fs-root/home refusal guard
---------
Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
Expose home-dir scanning as --fff-enable-home-scan flag + FFF_ENABLE_HOME_SCAN
env, default true. resolveBoolOpt takes a fallback and accepts 0/false. Threaded
through the main finder and AuxFinderPool.
Cache os.homedir() once per process in src/paths.ts. AuxOpts.onHomeDirScan fires
whenever the agent spawns an aux picker rooted at or above $HOME. On session
start from $HOME, notify the user and track scan progress with a 1s poller that
clears the footer once the scan settles; the interval is unref'd and cleared on
shutdown and in destroyFinder().
Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dmtrKovalenko <16926049+dmtrKovalenko@users.noreply.github.com>
* fix: Correctly handle empty directories during the scan
Closes#725
Before we have completely ignored empty directories partially as a
feature cause usually they do not contain anything useful but there is a
bug #725 that we need to fix and it definetely makes sense to show empty
directories in the dir search
* fix: Gitignore incompatbility
Closes https://github.com/dmtrKovalenko/fff/issues/723 fixed in zlob
* more efficient way to track subdirs
* fix(pi-fff): dedup concurrent aux finders and bound grep time (#746)
Concurrent AuxFinderPool.acquire() calls for the same root each started a
full scan because entries was populated only after waitForScan() resolved.
Coalesce in-flight creations by root via a pending map. Also pass a finite
timeBudgetMs to native grep (sync call, uninterruptible by AbortSignal) and
skip the fuzzy fallback when the exact pass left a nextCursor.
Refs #746
* Apply suggestions from code review
Co-authored-by: Dmitriy Kovalenko <dmitriy@iusevimbtw.com>
* fix typo
Co-authored-by: Dmitriy Kovalenko <dmitriy@iusevimbtw.com>
---------
Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
Co-authored-by: Dmitriy Kovalenko <dmitriy@iusevimbtw.com>
Per pi extension docs, each guideline in promptGuidelines is appended
to the flat Guidelines block without tool grouping, so the LLM cannot
tell which tool owns which bullet. Prefix each entry with the dynamic
tool name (toolNames.grep / toolNames.find / toolNames.multiGrep) so
guidelines stay correct in override mode too.
Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
git2 0.20.4 carries two informational=unsound advisories that surface in
downstream cargo-audit/cargo-deny runs. fff does not call the affected
APIs (Remote::list, Blame::blame_buffer), but bumping clears the noise
for consumers.
0.21.0 changes StatusEntry::path() to return Result<&str, git2::Error>
instead of Option<&str>; adjust the two callers in fff-core.
Closes#733
Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
* fix(fff-mcp): compare CARGO_PKG_VERSION to stable release tag (#721)
The update check compared the embedded 40-char git SHA prefix against
the release tag string, which never matched a semver tag. It also hit
/releases?per_page=1 (prerelease-inclusive) while install-mcp.sh is
pinned to a stable tag, so the suggested installer could not silence
the notice when the API returned a nightly.
Compare CARGO_PKG_VERSION against /releases/latest (stable-only) with
a leading `v` stripped from the tag.
Closes#721
* test(fff-node): retry watch subscribe in exit test
---------
Co-authored-by: gustav-fff <286169375+gustav-fff@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
fff-core gates indexing of $HOME and / behind enable_home_dir_scanning and
enable_fs_root_scanning, and every other surface (C, python, nvim, node/bun)
exposes them. fff-mcp did not, so it aborts at startup whenever an editor or
agent launches it from a home directory.
Add --enable-home-scan and --enable-root-scan, also settable via
FFF_ENABLE_HOME_SCAN and FFF_ENABLE_ROOT_SCAN. Both default off, so the guard
is unchanged unless opted into.
Allow users to pass a custom border table instead of just preset
strings. Supports a table { border_chars, junction_chars } and falls
back to existing preset system when a string is passed