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.
This commit is contained in:
2026-06-20 00:44:38 +01:00
parent 549ec2a177
commit 3195e353e2
6 changed files with 64 additions and 8 deletions
+2 -6
View File
@@ -10,10 +10,10 @@ import (
"os/exec"
"path/filepath"
"strings"
"unicode/utf8"
json "github.com/goccy/go-json"
"github.com/lukaszraczylo/mcp-filepuff/internal/config"
"github.com/lukaszraczylo/mcp-filepuff/internal/util"
"github.com/lukaszraczylo/mcp-filepuff/pkg/errors"
"github.com/lukaszraczylo/mcp-filepuff/pkg/protocol"
)
@@ -495,9 +495,5 @@ func truncateLine(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
end := max(maxLen-3, 0)
for end > 0 && !utf8.RuneStart(s[end]) {
end--
}
return s[:end] + "..."
return s[:util.RuneBoundary(s, maxLen-3)] + "..."
}