renderSymbolsOnly (fallback for c/cpp/elixir/html/vue) appended each symbol's raw start line plus a literal ' { ... }'. For brace-less languages (Elixir 'def f do', HTML) that is syntactically wrong and wastes tokens. Emit only the accurate symbol list; drop the fabricated line and unused params.
A misspelled or stale symbol_name returned a bare 'not found' error, forcing blind retries. nearestSymbolSuggestion fuzzy-matches the requested name against the file's actual symbols (via existing pkg/fuzzy) and appends 'did you mean: a, b, c?'. The re-parse is served from the parser cache. Adds TestReadSymbolNotFoundSuggestsNearest.
When a pagination cursor footer is emitted it already reports the remaining count; the separate '(truncated, showing subset of N)' prose line duplicated that signal. Suppress the prose line when CursorLine is set; keep it for non-paginated truncation.
A replace with empty new_content failed with a bare 'new_content is required' message, leaving the model to guess. Append a hint to use operation=delete when the intent is removal. Adds TestValidateReplaceEmptySuggestsDelete.
extractGoVarConst used ChildByFieldName("name"), which returns only the first name field, so 'var a, b = 1, 2' and 'const ( C, D = 3, 4 )' surfaced only a/C. Iterate every "name"-field child so each declared identifier becomes its own symbol. Adds TestExtractGoVarConstMultiName.
Three strip=imports/license correctness fixes (all heuristic, keyword/structure-gated):
- stripTSImports now tracks multi-line named imports (import {\n ... \n} from 'x') instead of dropping only the first line and leaving dangling members; side-effect imports without semicolons no longer trigger runaway consumption.
- stripGoImports recognizes all single-import spec forms: import _ "x", import . "x", and aliased import m "x" (previously only import "x"/backtick).
- stripLicense peels a leading shebang (#!) or Go build-constraint (//go:build, // +build) block and preserves it above the license, and now strips // line-comment license headers (not just /* */ and #). Adds tests for each.
FindSymbolRange returned the first AST node matching a name, so file_read with symbol_name silently read the wrong symbol when a name collided (e.g. two String() methods on different receivers, or a func and type sharing a name). New FindSymbolCandidates returns all matches; the file_read handler now errors on ambiguity, listing each candidate's node type and line range so the caller can disambiguate via symbol_kind or line_start/line_end. FindSymbolRange is kept (first match) for callers that don't need ambiguity detection. Adds TestFindSymbolCandidates.
We advertise textDocument.definition.linkSupport=true, so gopls and rust-analyzer return the LocationLink shape (targetUri/targetRange). The old code only tried []Location then a single Location; unmarshaling LocationLink JSON into []Location silently succeeds with zero-value URIs/ranges, producing garbage locations (or a false 'No definition found'). parseDefinitionResult now decodes a unified shape capturing both field sets and normalizes to Location, and surfaces a real parse error instead of swallowing it. Adds TestParseDefinitionResult covering all shapes.
lspHover fell back to AST symbol info on any LSP error, but the '(AST fallback)' marker only showed when verbose=true. In the default mode the model received Node type/Text with no signal the answer was a degraded AST guess. Now always emit '[AST fallback — LSP unavailable]' so the model can decide whether to retry. Includes colocated QF1012 style fixups.
Two AST-summary/query path fixes plus colocated QF1012 style fixups:
- runASTQueryWalk now checks info.Size() > MaxFileSize before os.ReadFile, avoiding a full read of files it will discard.
- generateASTSummary used len(splitLines(...)) for the '(N lines)' header, over-counting newline-terminated files by one (phantom trailing element). New countLines() helper counts via bytes.Count. Adds TestCountLines.
extractErrors is a pure function of the parsed tree (it ignores content), so re-walking the whole tree on every cache hit wasted work on the hottest path (file_read+include_ast, ast_query, edit validation). Store []SyntaxError in CachedTree and return it directly on hits. Adds TestParseCachedErrorsMatch asserting cache-hit errors equal the cache-miss parse.
Mechanical fix applied by golangci-lint --fix. Pre-existing debt that fails the staticcheck QF1012 quickfix check; no behavior change. Clears the lint gate so subsequent commits in this package set pass.
macos-13 Intel runner image retired by GitHub on 2025-12-08; darwin_amd64
build job queued indefinitely with no available runner. Switch to the
standard macos-15-intel label (native x86_64, free for public repos,
supported until Aug 2027). CGO required by go-tree-sitter, so a native
Intel runner is needed.
GoReleaser Pro --split defaults to partitioning by GOOS only, so each
linux matrix runner built both linux/amd64 and linux/arm64. With
CGO_ENABLED=1 (go-tree-sitter), the native gcc cannot assemble the other
arch's CGO runtime (gcc_arm64.S: no such instruction), failing the build.
- .goreleaser.yaml: set partial.by=target (partition by GOOS+GOARCH)
- workflow-prepare.sh: export GGOOS/GGOARCH from the matrix TARGET_* vars
so each native runner builds exactly its own target
GGOOS/GGOARCH are filter-only and do not bleed into before-hooks, unlike
GOOS/GOARCH.
- [x] Remove auto-indentation from text mode edits (caller controls whitespace)
- [x] Add line-ending detection and normalization for both AST and text modes
- [x] Share edit logic via new `spliceContent` function for both modes
- [x] Fix diff to emit "No newline at end of file" markers
- [x] Fix diff to strip raw CR from CRLF file output
- [x] Remove double-unescape of backslash sequences in new_content
- [x] Fix countDiffLines to be hunk-aware (correctly count lines starting with +/-)
- [x] Fix block-comment stripping to remove standalone lines cleanly
- [x] Fix Python license header stripping to preserve separator blank lines
* fix(release): drop broken CGO_ENABLED=0 static build target
smacker/go-tree-sitter requires CGO: the Node type is defined only in
bindings.go behind //go:build cgo, while iter.go references it without
a build tag. With CGO_ENABLED=0 every iter.go:N: undefined: Node fails
the build.
The mcp-filepuff-static target was added in 9205b2b after the last
successful release (v0.2.3) and has never produced an artifact — every
release since has been red. Dropping it restores the working release
shape: darwin_arm64 + linux_amd64 + windows_amd64 CGO binaries, same
as v0.2.3 shipped.
* feat(release): ship darwin_amd64 and linux_arm64 binaries
Shared go-release-cgo.yaml runs each matrix entry natively (no cross-
compile), so adding platforms only needs two edits: a platforms input
in release.yaml and removal of ignore rules in .goreleaser.yaml.
New coverage:
- darwin_amd64 on macos-13 runner (Intel Mac)
- linux_arm64 on ubuntu-24.04-arm runner (ARM servers, Raspberry Pi,
Apple Silicon via Docker/QEMU)
Kept: darwin_arm64, linux_amd64, windows_amd64.
Still skipped: windows_arm64 (uncommon, ARM Windows runner flakier).
.golangci.yml declares version: "2" (new schema) but CI was using
golangci-lint-action@v6, which ships golangci-lint v1.64.8. That
binary is built with go1.24 and cannot load a config targeting
go1.25, so every lint run on main failed with:
can't load config: the Go language version (go1.24) used to build
golangci-lint is lower than the targeted Go version (1.25)
golangci-lint-action@v8 supports golangci-lint v2.x. Pin v2.9.0 to
match the locally-used version and keep the binary toolchain aligned
with the go1.25 project target.
* v2.0: token-optimization overhaul
Additive (backward-compatible flags):
- file_read: skeleton mode, strip (imports/license/block_comments),
compact_line_numbers, 8-char etag with prefix-match compat
- ast_query: format=verbose|compact|location, pagination cursor
- file_search: cluster mode, pagination cursor
- lsp_query (references): compact output
Breaking (v2):
- Preambles removed; opt-in verbose=true restores
- edit_apply: response=count|diff|none, default count
- ping tool removed
- symbol_at/find_definition/find_references merged into lsp_query
- Tool descriptions trimmed -83%, help moved to filepuff://help/<tool>
- Batch file_read dedups by etag
Protocol:
- ResourceLink returned for file_read >64 KiB (force_inline override)
- OnAfterInitialize hook reads capabilities.experimental.filepuff
for session defaults (default_format, default_max_results,
default_cluster, compact_refs, line_numbers,
resource_link_threshold)
* fix: drop --max-total-count from ripgrep args
The flag does not exist in stable ripgrep (confirmed up to 15.1.0 --
"unrecognized flag --max-total-count, similar flags that are
available: --max-count"). Every file_search call failed on hosts with
stock rg. --max-count is per-file, not a drop-in replacement, so rely
on the in-process truncation in parseOutput that was already the
documented safety net.
- [x] Create install.sh script for automated platform detection and installation
- [x] Add checksum verification support in installation script
- [x] Update README with quick install command using curl
- [x] Reorganize installation instructions into quick install and manual sections
- [x] Update binary download URLs to match goreleaser naming convention
- [x] Change installation directory preference from /usr/local/bin to ~/.local/bin
- [x] Remove sudo requirement for installation when possible
- [x] Replace build_flags array with labels map in docker_v2 configuration
- [x] Convert individual --label arguments to key-value pairs
- [x] Maintain all OCI standard image metadata labels
- [x] Keep --pull flag in build_flags for image pulling
- [x] Create semver.yaml with version 1 schema
- [x] Configure patch version triggers (update, initial, fix, chore, docs, test)
- [x] Configure minor version triggers (release, refactor)
- [x] Configure major version triggers (breaking, major)
- [x] Enable force mode with existing and strict settings