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.
This commit is contained in:
2026-04-19 19:56:49 +01:00
committed by GitHub
parent b131c1edd3
commit 5ad975ee7a
26 changed files with 4909 additions and 507 deletions
+97 -26
View File
@@ -219,11 +219,9 @@ func (s *Searcher) buildArgs(req *Request) []string {
args = append(args, "--no-ignore")
}
// Global result cap — --max-total-count stops rg early across all files.
// Requires ripgrep >= 13.0. In-process truncation in parseOutput is kept as a safety net.
if req.MaxResults > 0 {
args = append(args, fmt.Sprintf("--max-total-count=%d", req.MaxResults))
}
// Result cap enforced in-process by parseOutput. rg has no cross-file
// total-count flag in stable releases, so we don't pass one; --max-count is
// per-file and would miss results unevenly.
// Add pattern
args = append(args, "--", req.Pattern)
@@ -356,8 +354,21 @@ func (s *Searcher) parseOutput(output *bytes.Buffer, maxResults int) (*SearchRes
return results, nil
}
// FormatResults formats search results for display.
// FormatOptions controls how search results are rendered.
type FormatOptions struct {
Cluster bool // coalesce consecutive matches into line-range blocks
CursorLine string // if non-empty, appended as a footer line
Verbose bool // if true, emit "Found N matches in M files:" preamble (opt-in)
}
// FormatResults formats search results for display (backward-compat wrapper).
func (s *Searcher) FormatResults(results *SearchResults) string {
return s.FormatResultsWithOptions(results, FormatOptions{})
}
// FormatResultsWithOptions formats search results with configurable output.
// By default the "Found N matches in M files:" preamble is omitted; set opts.Verbose=true to restore it.
func (s *Searcher) FormatResultsWithOptions(results *SearchResults, opts FormatOptions) string {
if len(results.Results) == 0 {
return "No matches found."
}
@@ -374,14 +385,18 @@ func (s *Searcher) FormatResults(results *SearchResults) string {
fileResults[r.File] = append(fileResults[r.File], r)
}
// Write summary
totalMatches := len(results.Results)
fileCount := len(fileResults)
sb.WriteString(fmt.Sprintf("Found %d matches in %d files", totalMatches, fileCount))
if results.Truncated {
sb.WriteString(fmt.Sprintf(" (truncated, total: %d)", results.Total))
// Write preamble only when Verbose is requested.
if opts.Verbose {
totalMatches := len(results.Results)
fileCount := len(fileResults)
sb.WriteString(fmt.Sprintf("Found %d matches in %d files", totalMatches, fileCount))
if results.Truncated {
sb.WriteString(fmt.Sprintf(" (truncated, total: %d)", results.Total))
}
sb.WriteString(":\n\n")
} else if results.Truncated {
sb.WriteString(fmt.Sprintf("(truncated, showing subset of %d total matches)\n\n", results.Total))
}
sb.WriteString(":\n\n")
// Write results grouped by file
for _, file := range fileOrder {
@@ -395,26 +410,82 @@ func (s *Searcher) FormatResults(results *SearchResults) string {
sb.WriteString(fmt.Sprintf("**%s**\n", relPath))
for _, r := range fileResults[file] {
// Write context before
for _, ctx := range r.Context.Before {
sb.WriteString(fmt.Sprintf(" │ %s\n", truncateLine(ctx, 200)))
}
// Write match line
sb.WriteString(fmt.Sprintf("L%d│ %s\n", r.Line, truncateLine(r.MatchText, 200)))
// Write context after
for _, ctx := range r.Context.After {
sb.WriteString(fmt.Sprintf(" │ %s\n", truncateLine(ctx, 200)))
}
if opts.Cluster {
writeClusteredResults(&sb, fileResults[file])
} else {
writeVerboseResults(&sb, fileResults[file])
}
sb.WriteString("\n")
}
if opts.CursorLine != "" {
sb.WriteString(opts.CursorLine)
sb.WriteString("\n")
}
return sb.String()
}
// writeVerboseResults writes results in the standard verbose format.
func writeVerboseResults(sb *strings.Builder, results []Result) {
for _, r := range results {
// Write context before
for _, ctx := range r.Context.Before {
fmt.Fprintf(sb, " │ %s\n", truncateLine(ctx, 200))
}
// Write match line
fmt.Fprintf(sb, "L%d│ %s\n", r.Line, truncateLine(r.MatchText, 200))
// Write context after
for _, ctx := range r.Context.After {
fmt.Fprintf(sb, " │ %s\n", truncateLine(ctx, 200))
}
}
}
// writeClusteredResults coalesces consecutive or adjacent match lines into
// a single "L12-14│ <first-match-text>" entry. Context lines are dropped
// in cluster mode to maximise information density.
func writeClusteredResults(sb *strings.Builder, results []Result) {
if len(results) == 0 {
return
}
type clusterEntry struct {
startLine int
endLine int
firstText string
}
var clusters []clusterEntry
cur := clusterEntry{
startLine: results[0].Line,
endLine: results[0].Line,
firstText: results[0].MatchText,
}
for _, r := range results[1:] {
// Merge if adjacent (within 1 line gap)
if r.Line <= cur.endLine+1 {
if r.Line > cur.endLine {
cur.endLine = r.Line
}
} else {
clusters = append(clusters, cur)
cur = clusterEntry{startLine: r.Line, endLine: r.Line, firstText: r.MatchText}
}
}
clusters = append(clusters, cur)
for _, c := range clusters {
text := truncateLine(c.firstText, 200)
if c.startLine == c.endLine {
fmt.Fprintf(sb, "L%d│ %s\n", c.startLine, text)
} else {
fmt.Fprintf(sb, "L%d-%d│ %s\n", c.startLine, c.endLine, text)
}
}
}
// truncateLine truncates a line if it exceeds maxLen.
func truncateLine(s string, maxLen int) string {
if len(s) <= maxLen {