Commit Graph
100 Commits
Author SHA1 Message Date
lukaszraczylo 5c9b0f967f chore(brew): publish mcp-filepuff cask to the tap 2026-10-03 23:12:15 +01:00
lukaszraczylo cefd6ba7b1 ci(deps): update dependencies with Renovate [skip ci] 2026-10-03 18:44:28 +01:00
lukaszraczyloandGitHub d82479c02e Update go.mod and go.sum (#43) 2026-09-30 04:13:22 +01:00
lukaszraczylo f6ba02e892 ci(lint): migrate golangci config to the v2 schema
The config used v1 keys under a v2 header, so golangci-lint config verify
rejected it and the Lint job failed. This change moves the exclusions to
linters.exclusions and the output order to output.sort-order.

The old linter settings never took effect. Enforcing them reports 62 issues
in the current code. Only the staticcheck setting stays, because it reports
none. The gocyclo, errcheck and govet settings need a separate cleanup.
2026-09-30 01:42:00 +01:00
lukaszraczylo 2344b38d59 ci: build with Go 1.26 to match go.mod
The go.mod file requires Go 1.26.0. The release job runs Go 1.25 with
GOTOOLCHAIN=local, so it fails before it runs any test. The lint and
security jobs use the same pin. This change sets all three to 1.26.
2026-09-30 01:35:43 +01:00
lukaszraczylo 2158228ddb perf(tools): lower default result sizes to cut response tokens
The default limits let broad queries flood the model context. This
change sets smaller defaults. An explicit parameter still overrides each one.

- file_search: max_results 1000 to 50, context_lines 2 to 0.
- ast_query: max_results 100 to 50.
- ast_query compact output omits the node type unless verbose is set.

On a fixed seven-call suite, response size fell from 198529 to 61960 bytes.
2026-09-30 00:20:19 +01:00
lukaszraczyloandGitHub f4f5135ac2 Update go.mod and go.sum (#42) 2026-09-28 04:15:14 +01:00
lukaszraczyloandGitHub 964e987776 Update go.mod and go.sum (#41) 2026-09-24 04:11:15 +01:00
lukaszraczyloandGitHub a7fde76871 Update go.mod and go.sum (#40) 2026-09-16 04:11:14 +01:00
lukaszraczyloandGitHub ed8611d07b Update go.mod and go.sum (#39) 2026-09-09 04:11:28 +01:00
lukaszraczyloandGitHub 700c6e818c Update go.mod and go.sum (#38) 2026-09-03 05:25:49 +01:00
lukaszraczyloandGitHub 25ee197acd Update go.mod and go.sum (#37) 2026-08-12 04:44:27 +01:00
lukaszraczyloandGitHub a7dcab5146 Update go.mod and go.sum (#36) 2026-08-07 04:58:19 +01:00
lukaszraczyloandGitHub d9d8332f77 Update go.mod and go.sum (#35) 2026-07-24 06:46:58 +01:00
lukaszraczyloandGitHub a4bfdefdab Update go.mod and go.sum (#34) 2026-07-10 05:23:17 +01:00
lukaszraczyloandGitHub 32b0a7f69a Update go.mod and go.sum (#33) 2026-07-09 05:20:53 +01:00
lukaszraczyloandGitHub 76316de768 Update go.mod and go.sum (#32) 2026-07-07 05:22:27 +01:00
lukaszraczyloandGitHub 9abab7a550 Update go.mod and go.sum (#31) 2026-06-26 05:30:00 +01:00
lukaszraczylo 658c895318 fix(lsp): detect, evict, and respawn crashed language servers
A crashed language server stayed cached with ready==true: readLoop exited and
drained in-flight calls but never cleared the running flag, and GetServer keyed
reuse on ready alone. Every subsequent hover/definition/references then passed
the running guard, wrote to a dead stdin, and blocked the full request timeout —
permanently, since active use kept the server off the idle reaper. A single
crash wedged that language at ~10s/call with no self-heal.

- readLoop now flips running=false on exit, so Call fails fast instead of hanging.
- GetServer treats ready && client.IsRunning() as the liveness condition; a stale
  entry is evicted (closed async — its process has already exited) and a fresh
  server is spawned in its place.
- drainPending's send is now non-blocking, matching the dispatch path, so a
  duplicate-id race can't deadlock it under c.mu.

Test (proven to fail without the fix): a client whose process exits is marked
not-running and its next Call fails fast rather than blocking.
2026-06-21 02:25:23 +01:00
lukaszraczylo d24c8218fd refactor: remove dead scaffolding from parser and edit
edit.ValidateLanguage had zero callers (only its own godoc referenced it) —
removed.

yaml_json.go: contrary to the audit's claim, the file is NOT entirely dead —
ParseYAML/ParseJSON are live via the Parse() dispatch (parser.go:198-200) and
are kept with their tests. Removed only the genuinely unreachable scaffolding
that nothing constructs or calls: the YAMLNode/JSONNode types, WalkYAML,
GetYAMLNodeText, GetYAMLNodeLocation, QueryYAML, QueryJSON, ValidateYAML,
ValidateJSON, and ToSitterTree, plus their now-orphaned tests. YAML/JSON parsing
behavior is unchanged (TestParseYAML/TestParseJSON/TestRegistryParse_YAML_JSON
still pass).
2026-06-21 02:21:37 +01:00
lukaszraczylo f4c34c41e4 test(edit): cover selector_index bounds, overlap, and CRLF splice
Add behavior tests for the highest-risk under-tested mutation paths: findExactText
(overlapping-match index resolution + out-of-range/empty/not-found errors),
findRegexPattern (non-overlapping index resolution + out-of-range/invalid-regex/
not-found errors), and spliceContent's CRLF pullback (an edit ending between \r
and \n must not orphan a bare LF). These resolve the byte range that gets mutated,
so wrong handling means wrong-location or line-ending-corrupting writes.
2026-06-21 02:18:15 +01:00
lukaszraczylo 749076518d perf(lsp): default find_references to compact output
References defaulted to one line per reference; compact (one line per file,
columns collapsed) existed only as opt-in. Compact preserves every line:col pair
with zero information loss while cutting roughly one line per reference for the
LLM consumer. Default it to true; compact=false and the session compact_refs
pref still select the verbose per-reference layout (explicit override wins).
2026-06-21 02:16:40 +01:00
lukaszraczylo 664b7dac45 perf(file_read): compact line-number prefix by default
The default line prefix was the padded '  42| ' form; the compact '42|' form
existed only as opt-in (compact_line_numbers). The padding and trailing space
carry no information an LLM needs, costing ~3 chars/line — ~300 tokens on a
400-line read, the highest-volume path in the server. Flip the default to
compact; compact_line_numbers=false, mode/line_numbers=full, and the session
'full' pref all still restore the padded alignment (explicit override wins).

Updates the two tests that pinned the old default and refreshes stale comments.
2026-06-21 02:15:49 +01:00
lukaszraczylo d0c07f0157 fix(edit): re-validate path immediately before write (TOCTOU hardening)
edit_apply validated the target with IsPathAllowed in the handler, then later
wrote it via os.WriteFile on the raw path after reading+parsing+diffing. A
symlink swapped in during that window could redirect the write outside the
workspace root. Inject the admission check into the edit Engine
(NewEngineWithValidator) and re-run it inside a new writeValidatedFile helper
immediately before the write — IsPathAllowed resolves symlinks at write time, so
a swapped-in link now resolving outside the root is rejected. This narrows the
TOCTOU window to near-zero while still permitting legitimate in-workspace symlink
edits (unlike a blanket O_NOFOLLOW). writeValidatedFile also dedups the
perm-preserving write shared by AST and text modes.

Tests: rejected-path edit leaves the file untouched; file mode is preserved.
2026-06-21 02:12:57 +01:00
lukaszraczylo a841210092 perf(lsp): emit workspace-relative paths in definition/references
lsp_query printed absolute paths from URIToFile (e.g. the full
/Users/.../internal/... prefix repeated per result) while file_search and the
AST summary already relativize. On a find_references across many files that
~60-char root prefix repeats per file group — wasted tokens for the LLM
consumer. Extract a Server.relPath helper (dedups the AST summary's inline
block) and apply it to definition and references display paths; the absolute
path is kept where the file is actually re-read for previews.

Adds a relPath unit test (in-workspace relativized, out-of-workspace unchanged).
2026-06-21 02:03:43 +01:00
lukaszraczylo 98a499ab20 fix(parser): guard FindNodeAtPosition against position underflow
line/col are 1-indexed and converted via uint32(line-1); a caller passing 0
wrapped to 4294967295, feeding a garbage Point into tree-sitter. Reachable from
the LSP-down AST fallback with caller-supplied coordinates. Reject non-positive
positions up front. Adds tests (the function was previously 0% covered).
2026-06-21 02:01:50 +01:00
lukaszraczylo 8d940022b7 fix(search): drop matches outside the workspace root (symlink escape)
file_search runs ripgrep with --follow on by default, so a symlink inside the
workspace pointing outside it (e.g. link -> /etc) caused rg to traverse out and
return matches from external files — leaking their content to the caller despite
the workspace-scoping contract. parseOutput emitted match paths verbatim with no
re-validation.

Filter every match through IsPathAllowed (resolving rg's workspace-relative path
against the root first); IsPathAllowed's EvalSymlinks rejects the escape while
still permitting legitimate worktree siblings. Defense in depth that also covers
--no-ignore and any other traversal that reaches outside the root.

Adds a security regression test (proven to fail without the filter).
2026-06-21 01:59:53 +01:00
lukaszraczylo 99de8a93b9 test(search): cover cross-file context attribution in parseOutput
parseOutput's pendingFile guard attaches before-context only when it belongs to the same file as the following match. Add tests for both the cross-file leak guard (fileA context must not attach to a fileB match) and the same-file positive case. Test-only; no behavior change.
2026-06-21 00:07:08 +01:00
lukaszraczylo a11623cb0d perf(edit_apply): drop redundant Diff: label; docs(file_read): note strip renumbering
edit_apply response=diff prefixed the fenced ```diff block with a 'Diff:'
label that the fence and the diff's own ---/+++ headers already convey — pure
token cost per diff response. Extract fencedDiff() (also dedups the diff and
default branches) and drop the label. Adds a test guard.

Also document that strip renumbers lines from 1 over the stripped content, so
the model doesn't cite original-file line numbers after a stripped read.
2026-06-20 02:12:46 +01:00
lukaszraczylo 5f0bb90e28 fix(ast_query): reliable first-page pagination cursor
The page-1 walk was capped at max_results via SkipAll, but buildASTCursorFooter
derives the remaining count from the total number of matches seen. Capping
truncated that total, so on page 1 the cursor fired only when the last parsed
file happened to overshoot the cap — nondeterministic — and reported a wrong
remaining count when it did. Same failure class as the file_search first-page
cursor bug.

Always walk the full tree (pages 2+ already did). The common case where matches
fit in one page already visited everything (SkipAll never triggered), and parse
results are LRU-cached, so the extra cost falls only on multi-page queries and is
mostly file I/O.

Adds a strict regression test asserting page 1 emits a cursor with an accurate
remaining count; the existing pagination test silently skipped when no cursor.
2026-06-20 01:44:47 +01:00
lukaszraczylo 62971c9f8c fix(ast_query): skip dependency dirs (node_modules/vendor) by default
ast_query walked every language-matching file under the search root, including
node_modules, vendor and bower_components — parsing third-party code and
flooding results with irrelevant matches (large token cost on every call,
worst for JS/TS node_modules). Skip these by name during the walk, alongside
the existing hidden-dir skip.

The walk root is now exempt from both skips, so targeting a dependency dir
explicitly (paths:["vendor/foo"]) still works — previously naming a hidden or
dependency dir as the root would have skipped everything. Documents the scope
in the tool description and help resource.

Adds a regression test covering default-skip and explicit-target inclusion.
2026-06-20 01:15:37 +01:00
lukaszraczylo 99514ff8d6 fix(parser): extract C++ in-class methods correctly
extractCFunction/extractCFunctionDecl scanned for the first 'identifier' in the declarator, which for C++ methods grabbed a parameter name (e.g. method setName surfaced as 'n') or, when the name was a field_identifier with no parameter identifier, dropped the symbol entirely. cDeclaratorName now descends the declarator field-chain to the real name (identifier/field_identifier/qualified_identifier/destructor/operator); in-class definitions are kinded as methods and qualified Class::method (out-of-line Class::method definitions detected via '::'). Adds TestExtractCppMethods.
2026-06-20 00:54:27 +01:00
lukaszraczylo 2fb9d3e379 fix(search): emit pagination cursor on first truncated page
On the first page (offset==0) ripgrep capped results at exactly max_results
and set Truncated=true, but the cursor-footer guard (len>max_results) was
false, so the response said "(truncated...)" with no cursor — the caller
was told more matches exist but given no way to fetch them. Pagination only
worked from page 2. Emit a cursor on the first page using rg's total match
count for the remaining estimate.

Adds a strict regression test (asserts, not skips); the existing pagination
test was silently skipping its round-trip because page 1 never had a cursor.
2026-06-20 00:44:41 +01:00
lukaszraczylo 3195e353e2 fix(util): rune-safe truncation across formatters
firstLineOf (query) and truncateString (edit) sliced strings at a raw byte
offset, splitting a multibyte UTF-8 character into invalid bytes in
LLM-facing output (ast_query compact format and edit not-found errors are
hot paths). Extract the rune-boundary backoff already used by search's
truncateLine into util.RuneBoundary and route all three sites through it.

Adds table-driven tests for the primitive and a multibyte case for firstLineOf.
2026-06-20 00:44:38 +01:00
lukaszraczylo 549ec2a177 fix(search): truncate match lines on a rune boundary
truncateLine byte-sliced s[:maxLen-3], which can split a multibyte UTF-8 character and emit an invalid sequence (mojibake) on long non-ASCII match lines. Back off to the nearest rune boundary via utf8.RuneStart. Adds TestTruncateLineRuneSafe.
2026-06-20 00:33:34 +01:00
lukaszraczylo 96db1c7749 fix(config): clamp max_edit_size and max_search_results
Only max_file_size and max_parse_size were clamped after reading .mcp-filepuff.json; max_edit_size and max_search_results were unbounded, letting a config file request excessive memory. Clamp max_edit_size to 100MB and max_search_results to 100k (and floor negatives to 0). Adds TestLoadClampsExcessiveLimits.
2026-06-20 00:33:34 +01:00
lukaszraczylo 5452cfce30 fix(parser): deterministic order for remaining doc-comment tags
FormatDocComment emitted leftover tags by ranging a map, so multi-tag comments (@example/@see/@deprecated) rendered in random order run-to-run — nondeterministic output that defeats caching/diffing. Sort the remaining tag names before emitting.
2026-06-20 00:33:34 +01:00
lukaszraczylo 85a539b18b fix(parser): exclude function-local var/const from symbols
extractGoSymbols/extractJSSymbols matched var/const/let at any tree depth, so every function-local var, const (Go) and let/const/var (JS/TS) was emitted as a top-level symbol — flooding include_ast/ast_query symbol lists with locals (inaccurate + token-wasteful). Skip declarations nested inside a function/block body (isNestedInBody); module-level and 'export const' declarations are still kept. Adds TestExtractSymbolsExcludesLocals.
2026-06-20 00:33:33 +01:00
lukaszraczylo 084c1fd2b2 test(edit): cover CRLF line-ending preservation on apply
Locks the documented guarantee that inserting LF content into a CRLF file normalizes to CRLF, with no bare-LF leaks. Closes a test gap on a stated design goal.
2026-06-20 00:33:33 +01:00
lukaszraczylo 2e444401f5 fix(parser): name Elixir structs after their enclosing module
extractElixirStruct hardcoded Name="defstruct", so every Elixir struct surfaced as a symbol literally called "defstruct" — useless for navigation and colliding across modules. Walk ancestors to the enclosing defmodule and name the struct %ModuleName{}. Adds TestExtractElixirStructNamedByModule.
2026-06-20 00:33:33 +01:00
lukaszraczylo 1b41e43bf3 feat(lsp): capture server stderr to diagnose init failures
drainStderr discarded all server stderr, so an LSP init failure (e.g. 'command not found', missing module) surfaced as an opaque 'initialization failed'. Retain a bounded tail (StderrCaptureMax=4096B, oldest dropped) under a mutex and attach it to the init-failure error context. Adds TestClientStderrCapture.
2026-06-20 00:33:33 +01:00
lukaszraczylo 13b1a22102 perf(parser): denser symbols-only skeleton fallback
The fallback skeleton (c/cpp/elixir/html/vue) is not valid source, so the '// ' comment framing and '(line N)' wording just cost tokens. Use a compact 'kind name :line' form. ~15-25% fewer tokens on fallback skeletons; output stays accurate.
2026-06-20 00:33:33 +01:00
lukaszraczylo d0f649a9b8 docs(cursor): justify the ignored json.Marshal error in Encode
Per the project's error-handling rule, an ignored error needs a comment. Marshalling the two-scalar payload struct cannot fail; document why the error is dropped. No behavior change.
2026-06-20 00:33:33 +01:00
lukaszraczylo 5214ad2b66 fix(parser): keep signature in decorated Python skeleton
skeletonPythonNode took lines[0] of a decorated_definition, which is the first @decorator line, dropping the def/class signature entirely. Now emit the decorator line(s) then recurse into the wrapped definition so its signature is preserved. Adds skeleton_test.go (decorated + plain function).
2026-06-20 00:33:33 +01:00
lukaszraczylo 80c3f03f62 fix(lsp): non-blocking response dispatch to avoid client deadlock
readLoop sent the response on the pending channel while holding c.mu. A misbehaving server sending a duplicate response for the same id would block on the buffered(1) channel under the lock, deadlocking all future dispatches. Use a non-blocking send (matching the notification path).
2026-06-20 00:33:33 +01:00
lukaszraczylo 61b04b46cd fix(metrics): ignore negative Counter.Add to keep counters monotonic
Counter.Add applied any delta, so a negative value would decrement a counter that must be monotonic, corrupting rate()-style downstream math. Ignore delta<0. Adds TestCounterRejectsNegativeAdd.
2026-06-20 00:33:32 +01:00
lukaszraczylo debe79e33e feat(search): bound default result count and add include_hidden
Two file_search gaps:
- max_results defaulted to 0 (unbounded), so a broad pattern returned every match — unbounded memory + token blowup. The configured MaxSearchResults (default 1000) existed but was dead; wire it as the builtIn default. Explicit max_results still wins, and the cursor footer pages the rest.
- search.Request.IncludeHidden was a dead field with no tool param. Add an include_hidden bool (default false) so dotfiles (.github/, etc.) are searchable.
2026-06-20 00:33:32 +01:00
lukaszraczylo dfd68f08c9 feat(ast_query): default to compact format for token efficiency
ast_query defaulted to verbose output (full code + up to 500 chars/match + captures), which is very token-heavy on multi-match queries. Default to compact (one line per match: file:line (kind) firstline) — roughly 5-10x fewer tokens on typical queries — while verbose remains available per-call (format=verbose) or via the default_format session pref. Updates tool description, help resource, and TestASTQueryFormatCompactDefault.
2026-06-20 00:33:32 +01:00
lukaszraczylo b2c4101b5b fix(edit): correct diff @@ hunk headers at file boundaries
hunkBounds approximated one-sided start lines by reusing the opposite side's number, and re-assigned it every iteration inside the count==0 guard. For pure insertion/deletion hunks (e.g. writing into an empty file, or deleting a whole file) this produced wrong headers like '@@ -2,0 +1,2 @@' instead of '@@ -0,0 +1,2 @@'. flattenDiffLines now records both running line numbers on every line, so hunkBounds takes the start from the first line directly and applies GNU's zero-count convention (line-before-change, clamped to 0). Adds TestGenerateDiffHunkHeaderBounds.
2026-06-20 00:33:32 +01:00
lukaszraczylo 53bc06df7f refactor(ast_query): emit cursor footer from formatter, not string-replace
handleASTQuery rebuilt the formatter's '[remaining: N]' placeholder string and strings.ReplaceAll'd it with the cursor line, plus a fallback append — fragile coupling to the formatter's exact output. New FormatResultsWithCursor appends the prebuilt '[cursor: ..., remaining: N]' footer in place of the bare hint when results are truncated; the handler just passes it through. FormatResultsWithOptions is unchanged (delegates with empty cursor). Adds TestFormatResultsWithCursor.
2026-06-20 00:33:32 +01:00
lukaszraczylo cc1661d295 feat(lsp): query by symbol_name; document ast_query match limits
Two ast_query/lsp_query ergonomics improvements that share the tool registration and help files:

- lsp_query accepts symbol_name (+symbol_kind) and resolves the position via AST (resolveLSPSymbolPosition), so callers need not know line:col; line/column are no longer required. SymbolCandidate gains NameLine/NameColumn for the precise identifier position. Ambiguous/unknown names error with candidates / a 'did you mean' hint. Adds TestResolveLSPSymbolPosition.
- ast_query help + tool description now document that matching is keyword/kind-based, not structural: pattern arg/return constraints are not enforced; use name_exact/name_matches/kind_in to filter. Fixes a misleading 'functions returning error' example.
2026-06-20 00:33:32 +01:00
lukaszraczylo b6489ee4bc docs(lsp): clarify drainStderr cancellation relies on pipe EOF
The goroutine parks in the blocking stderr.Read, so closing c.done does not interrupt it; termination relies on the read erroring at pipe EOF when the process is killed. The c.done select is only a best-effort fast path.
2026-06-20 00:33:32 +01:00
lukaszraczylo 5ed14457f1 fix(parser): stop fabricating { ... } in fallback skeleton
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.
2026-06-20 00:33:32 +01:00
lukaszraczylo 1c26f8d105 feat(read): suggest nearest symbols when symbol_name not found
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.
2026-06-20 00:33:32 +01:00
lukaszraczylo 7b8914017c fix(search): drop redundant truncation prose when cursor present
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.
2026-06-20 00:33:31 +01:00
lukaszraczylo 5827290ece fix(edit): point empty-replace error at operation=delete
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.
2026-06-20 00:33:31 +01:00
lukaszraczylo b0c1fc0480 fix(parser): extract all names from grouped var/const specs
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.
2026-06-20 00:33:31 +01:00
lukaszraczylo a0b41a6eb8 fix(parser): improve strip accuracy for imports and license
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.
2026-06-20 00:33:31 +01:00
lukaszraczylo c261fb8c76 fix(parser): detect ambiguous symbol_name reads
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.
2026-06-20 00:33:31 +01:00
lukaszraczylo ff37be1f17 fix(lsp): parse LocationLink in definition results
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.
2026-06-20 00:33:31 +01:00
lukaszraczylo 36deb948c8 feat(lsp): always signal AST fallback when LSP unavailable
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.
2026-06-20 00:33:31 +01:00
lukaszraczylo 833e898d20 fix(server): skip oversized files in AST walk, fix line count
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.
2026-06-20 00:33:31 +01:00
lukaszraczylo 02d54b56bb perf(parser): cache syntax errors to skip walk on cache hit
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.
2026-06-20 00:33:31 +01:00
lukaszraczylo e11f140997 style: use fmt.Fprintf instead of WriteString(fmt.Sprintf) (staticcheck QF1012)
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.
2026-06-20 00:33:30 +01:00
lukaszraczyloandGitHub aa7d884b0c Update go.mod and go.sum (#30) 2026-06-17 05:32:41 +01:00
lukaszraczyloandGitHub 0fe678cd60 Update go.mod and go.sum (#29) 2026-06-09 05:26:55 +01:00
lukaszraczyloandGitHub 70484d20c3 Update go.mod and go.sum (#28) 2026-05-29 05:31:00 +01:00
lukaszraczylo 6e0c5247c1 ci: replace retired macos-13 runner with macos-15-intel for darwin_amd64
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.
2026-05-29 01:43:22 +01:00
lukaszraczylo 7774dc29d1 fix(release): partition CGO split builds by target to stop cross-arch failure
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.
2026-05-29 00:57:29 +01:00
lukaszraczylo 9af2801b1b refactor(edit): remove auto-indentation and add line-ending normalization
- [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
2026-05-29 00:17:36 +01:00
lukaszraczyloandGitHub f1643e7b81 Fix/release drop broken static (#13)
* 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).
2026-04-19 20:22:55 +01:00
lukaszraczyloandGitHub 1fe8db9012 ci: bump golangci-lint-action to v8 with pinned v2.9.0 (#12)
.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.
2026-04-19 20:02:54 +01:00
lukaszraczyloandGitHub 5ad975ee7a V2/token optimization (#11)
* 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.
2026-04-19 19:56:49 +01:00
lukaszraczyloandGitHub b131c1edd3 Update go.mod and go.sum (#10) 2026-04-15 05:02:41 +01:00
lukaszraczyloandGitHub 7829ae4891 Update go.mod and go.sum (#9) 2026-04-09 04:58:25 +01:00
lukaszraczyloandGitHub 0d91e94416 Update go.mod and go.sum (#8) 2026-04-05 05:01:32 +01:00
lukaszraczyloandGitHub 7ad20c33f9 Update go.mod and go.sum (#7) 2026-03-27 03:58:58 +00:00
lukaszraczyloandGitHub 90244fda67 Update go.mod and go.sum (#6) 2026-03-20 03:48:30 +00:00
lukaszraczyloandGitHub 9a9d81f2a4 Update go.mod and go.sum (#5) 2026-03-15 03:59:02 +00:00
lukaszraczyloandGitHub 78c5fc7236 Update go.mod and go.sum (#4) 2026-03-13 03:48:53 +00:00
lukaszraczylo ccfbdc513f fixup! fixup! fixup! fixup! Add Docker usage instructions to README 2026-03-12 19:21:13 +00:00
lukaszraczylo db803c6950 fixup! fixup! fixup! Add Docker usage instructions to README 2026-03-12 10:42:39 +00:00
lukaszraczylo 34d4401280 fixup! fixup! Add Docker usage instructions to README 2026-03-11 13:18:59 +00:00
lukaszraczylo 267378181c fixup! Add Docker usage instructions to README 2026-03-11 02:53:12 +00:00
lukaszraczylo 2b83fccb35 Add Docker usage instructions to README 2026-03-07 18:55:05 +00:00
lukaszraczylo 996be39314 Add Docker image support with distroless container
- Dockerfile: distroless base (glibc) for CGO binary
- GoReleaser: Docker build + push to ghcr.io/lukaszraczylo/filepuff-mcp
- Cosign signing for Docker manifests
2026-03-07 18:47:22 +00:00
lukaszraczyloandGitHub 50da24756f Update go.mod and go.sum (#3) 2026-03-07 03:42:02 +00:00
lukaszraczyloandGitHub a778e0b28b Update go.mod and go.sum (#2) 2026-02-28 03:37:42 +00:00
lukaszraczylo e2dc4a1f40 multi-fixes 2026-02-22 15:24:48 +00:00
lukaszraczylo 982c2c8b44 fixup! Update, bugfixes on diff and edit handling 2026-02-22 14:03:54 +00:00
lukaszraczylo 6980d3b294 Update, bugfixes on diff and edit handling 2026-02-18 21:49:55 +00:00
lukaszraczylo 9205b2bc26 feat(docs, ci, config): add comprehensive documentation and tooling
- [x] Add API reference documentation with tool descriptions and examples
- [x] Add ERROR_CODES reference with error descriptions and remediation steps
- [x] Add PERFORMANCE tuning guide with caching and optimization details
- [x] Add GitHub Actions workflows for linting and security scanning
- [x] Add golangci-lint configuration with comprehensive linter settings
- [x] Add pre-commit hooks configuration for local development
- [x] Add API documentation generator tool (cmd/docgen)
- [x] Update Go version from 1.24 to 1.25 across workflows
- [x] Add static build configuration to goreleaser
- [x] Add metrics package with Prometheus-style metric types
- [x] Add parser benchmarks for performance testing
- [x] Add LSP manager integration tests
- [x] Add server integration tests with MCP protocol flow testing
- [x] Extract regex cache to shared utility package
- [x] Add context cancellation handling in AST queries
- [x] Add graceful shutdown with timeout to server
- [x] Add configurable max parse size (MaxParseSize)
- [x] Add Config.Validate() method with comprehensive checks
- [x] Add parser cache statistics tracking
- [x] Add file permission preservation in edit operations
- [x] Improve line splitting for large files with bufio.Scanner
- [x] Add comprehensive config tests for edge cases
- [x] Update Makefile with new targets and documentation
2026-02-18 21:49:54 +00:00
lukaszraczyloandGitHub 143a166249 Update go.mod and go.sum (#1) 2026-02-16 03:53:38 +00:00
lukaszraczylo 9d84c1253b test(edit): add comprehensive selector specificity tests
- [x] Add sortBySpecificity and shouldPrefer helper functions for node preference logic
- [x] Implement isDeclarationLike pattern matching for declaration/statement node types
- [x] Add AtLine selector specificity logic to prefer smallest meaningful nodes
- [x] Add TestResolveSelectorAtLineSpecificity to verify correct node selection
- [x] Add TestRegressionInsertAfterAtLine to prevent file corruption on insertions
- [x] Add TestRegressionInsertBeforeAtLine to verify insert-before ordering
- [x] Add TestRegressionNestedStructures to ensure nested nodes select correctly
- [x] Add TestRegressionPreservesFileIntegrity to verify unrelated content preservation
- [x] Add TestRegressionMultipleConstBlocks for multi-block const handling
- [x] Add TestSortBySpecificity unit test for sorting logic
2026-01-23 20:56:26 +00:00
lukaszraczylo b8d868115c feat(parser): add Elixir language support
- [x] Add Elixir documentation extraction (@doc and @moduledoc attributes)
- [x] Add Elixir symbol extraction (modules, functions, macros, structs, protocols)
- [x] Add tree-sitter Elixir language parser integration
- [x] Add Elixir language detection for .ex and .exs file extensions
- [x] Add Elixir symbol extraction tests
- [x] Update language support table in README
- [x] Improve install script with package manager detection and LSP installation
- [x] Fix shell script portability (replace echo -e with printf)
- [x] Fix checksum verification in install script for macOS/Linux compatibility
2026-01-23 20:31:08 +00:00
lukaszraczylo ac1b81b70e fixup! fixup! build(goreleaser): enable CGO and remove Docker builds 2026-01-18 19:38:29 +00:00
lukaszraczylo f216380d37 fixup! build(goreleaser): enable CGO and remove Docker builds 2026-01-18 19:10:41 +00:00
lukaszraczylo 3be03aef7b build(goreleaser): enable CGO and remove Docker builds
- [x] Enable CGO_ENABLED=1 for builds
- [x] Add platform ignore rules for unsupported architectures
- [x] Remove Docker build configuration (dockers_v2)
- [x] Remove Docker signing configuration
- [x] Add platform compatibility check in install script
2026-01-18 19:08:10 +00:00
lukaszraczylo 5800c689e9 build(install): add automated installation script and update README
- [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
2026-01-18 19:05:12 +00:00
lukaszraczylo 81f146e78d build(goreleaser): migrate docker labels from build_flags to labels config
- [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
2026-01-18 19:00:36 +00:00