diff --git a/cmd/docgen/main.go b/cmd/docgen/main.go index da5e71e..0806233 100644 --- a/cmd/docgen/main.go +++ b/cmd/docgen/main.go @@ -211,7 +211,7 @@ func GenerateMarkdown() string { var sb strings.Builder sb.WriteString("# MCP Filepuff API Reference\n\n") - sb.WriteString(fmt.Sprintf("> Auto-generated on %s\n\n", time.Now().Format("2006-01-02"))) + fmt.Fprintf(&sb, "> Auto-generated on %s\n\n", time.Now().Format("2006-01-02")) sb.WriteString("This document provides detailed API documentation for all MCP tools available in filepuff.\n\n") // Table of Contents @@ -228,9 +228,9 @@ func GenerateMarkdown() string { } for _, cat := range categoryOrder { - sb.WriteString(fmt.Sprintf("### %s\n", cat)) + fmt.Fprintf(&sb, "### %s\n", cat) for _, tool := range categories[cat] { - sb.WriteString(fmt.Sprintf("- [`%s`](#%s)\n", tool.Name, tool.Name)) + fmt.Fprintf(&sb, "- [`%s`](#%s)\n", tool.Name, tool.Name) } sb.WriteString("\n") } @@ -240,11 +240,11 @@ func GenerateMarkdown() string { sb.WriteString("## Tool Reference\n\n") for _, cat := range categoryOrder { - sb.WriteString(fmt.Sprintf("### %s\n\n", cat)) + fmt.Fprintf(&sb, "### %s\n\n", cat) for _, tool := range categories[cat] { - sb.WriteString(fmt.Sprintf("#### `%s`\n\n", tool.Name)) - sb.WriteString(fmt.Sprintf("%s\n\n", tool.Description)) + fmt.Fprintf(&sb, "#### `%s`\n\n", tool.Name) + fmt.Fprintf(&sb, "%s\n\n", tool.Description) // Read-only indicator if tool.ReadOnly { @@ -263,8 +263,8 @@ func GenerateMarkdown() string { if param.Required { required = "**Yes**" } - sb.WriteString(fmt.Sprintf("| `%s` | `%s` | %s | %s |\n", - param.Name, param.Type, required, param.Description)) + fmt.Fprintf(&sb, "| `%s` | `%s` | %s | %s |\n", + param.Name, param.Type, required, param.Description) } sb.WriteString("\n") } else { @@ -285,7 +285,7 @@ func GenerateMarkdown() string { if len(tool.Notes) > 0 { sb.WriteString("**Notes:**\n\n") for _, note := range tool.Notes { - sb.WriteString(fmt.Sprintf("- %s\n", note)) + fmt.Fprintf(&sb, "- %s\n", note) } sb.WriteString("\n") } diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index 650ed5a..534273a 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -278,27 +278,27 @@ func (r *Registry) Expose() string { // Export counters for _, c := range r.counters { if c.help != "" { - sb.WriteString(fmt.Sprintf("# HELP %s %s\n", c.name, c.help)) + fmt.Fprintf(&sb, "# HELP %s %s\n", c.name, c.help) } - sb.WriteString(fmt.Sprintf("# TYPE %s counter\n", c.name)) - sb.WriteString(fmt.Sprintf("%s%s %d\n", c.name, formatLabels(c.labels), c.value.Load())) + fmt.Fprintf(&sb, "# TYPE %s counter\n", c.name) + fmt.Fprintf(&sb, "%s%s %d\n", c.name, formatLabels(c.labels), c.value.Load()) } // Export gauges for _, g := range r.gauges { if g.help != "" { - sb.WriteString(fmt.Sprintf("# HELP %s %s\n", g.name, g.help)) + fmt.Fprintf(&sb, "# HELP %s %s\n", g.name, g.help) } - sb.WriteString(fmt.Sprintf("# TYPE %s gauge\n", g.name)) - sb.WriteString(fmt.Sprintf("%s%s %d\n", g.name, formatLabels(g.labels), g.value.Load())) + fmt.Fprintf(&sb, "# TYPE %s gauge\n", g.name) + fmt.Fprintf(&sb, "%s%s %d\n", g.name, formatLabels(g.labels), g.value.Load()) } // Export histograms for _, h := range r.histograms { if h.help != "" { - sb.WriteString(fmt.Sprintf("# HELP %s %s\n", h.name, h.help)) + fmt.Fprintf(&sb, "# HELP %s %s\n", h.name, h.help) } - sb.WriteString(fmt.Sprintf("# TYPE %s histogram\n", h.name)) + fmt.Fprintf(&sb, "# TYPE %s histogram\n", h.name) // Cumulative bucket counts var cumulative int64 @@ -306,26 +306,26 @@ func (r *Registry) Expose() string { cumulative += h.counts[i].Load() labelStr := formatLabels(h.labels) if labelStr == "" { - sb.WriteString(fmt.Sprintf("%s_bucket{le=\"%g\"} %d\n", h.name, bound, cumulative)) + fmt.Fprintf(&sb, "%s_bucket{le=\"%g\"} %d\n", h.name, bound, cumulative) } else { // Insert le label into existing labels - sb.WriteString(fmt.Sprintf("%s_bucket%s %d\n", h.name, - strings.Replace(labelStr, "}", fmt.Sprintf(",le=\"%g\"}", bound), 1), cumulative)) + fmt.Fprintf(&sb, "%s_bucket%s %d\n", h.name, + strings.Replace(labelStr, "}", fmt.Sprintf(",le=\"%g\"}", bound), 1), cumulative) } } // +Inf bucket cumulative += h.counts[len(h.buckets)].Load() labelStr := formatLabels(h.labels) if labelStr == "" { - sb.WriteString(fmt.Sprintf("%s_bucket{le=\"+Inf\"} %d\n", h.name, cumulative)) + fmt.Fprintf(&sb, "%s_bucket{le=\"+Inf\"} %d\n", h.name, cumulative) } else { - sb.WriteString(fmt.Sprintf("%s_bucket%s %d\n", h.name, - strings.Replace(labelStr, "}", ",le=\"+Inf\"}", 1), cumulative)) + fmt.Fprintf(&sb, "%s_bucket%s %d\n", h.name, + strings.Replace(labelStr, "}", ",le=\"+Inf\"}", 1), cumulative) } // Sum and count - sb.WriteString(fmt.Sprintf("%s_sum%s %g\n", h.name, formatLabels(h.labels), h.Sum())) - sb.WriteString(fmt.Sprintf("%s_count%s %d\n", h.name, formatLabels(h.labels), h.count.Load())) + fmt.Fprintf(&sb, "%s_sum%s %g\n", h.name, formatLabels(h.labels), h.Sum()) + fmt.Fprintf(&sb, "%s_count%s %d\n", h.name, formatLabels(h.labels), h.count.Load()) } return sb.String() diff --git a/internal/parser/skeleton.go b/internal/parser/skeleton.go index d7342d7..03c6029 100644 --- a/internal/parser/skeleton.go +++ b/internal/parser/skeleton.go @@ -44,11 +44,11 @@ func SkeletonFile(ctx context.Context, reg *Registry, filename string, content [ func renderSymbolsOnly(syms []protocol.Symbol, _ string, lang protocol.Language, content []byte) string { lines := strings.Split(string(content), "\n") var sb strings.Builder - sb.WriteString(fmt.Sprintf("// skeleton unavailable for %s — symbol list only\n", lang)) + fmt.Fprintf(&sb, "// skeleton unavailable for %s — symbol list only\n", lang) for _, s := range syms { - sb.WriteString(fmt.Sprintf("// %s %s (line %d)\n", s.Kind, s.Name, s.Location.Line)) + fmt.Fprintf(&sb, "// %s %s (line %d)\n", s.Kind, s.Name, s.Location.Line) if s.Doc != "" { - sb.WriteString(fmt.Sprintf("// doc: %s\n", s.Doc)) + fmt.Fprintf(&sb, "// doc: %s\n", s.Doc) } if s.Location.Line >= 1 && s.Location.Line <= len(lines) { sb.WriteString(lines[s.Location.Line-1] + " { ... }\n") diff --git a/internal/query/query.go b/internal/query/query.go index 15ee659..af6d023 100644 --- a/internal/query/query.go +++ b/internal/query/query.go @@ -486,7 +486,7 @@ func FormatResultsWithOptions(results []MatchResult, maxResults int, format stri // Emit preamble only when verbose=true is explicitly passed (opt-in, default off). wantVerbose := len(verbose) > 0 && verbose[0] if wantVerbose { - sb.WriteString(fmt.Sprintf("Found %d match(es):\n", renderCount)) + fmt.Fprintf(&sb, "Found %d match(es):\n", renderCount) } switch format { @@ -498,13 +498,13 @@ func FormatResultsWithOptions(results []MatchResult, maxResults int, format stri nodeType = r.Node.Type() } firstLine := firstLineOf(r.Text, 80) - sb.WriteString(fmt.Sprintf("%s:%d (%s) %s\n", r.File, r.Location.Line, nodeType, firstLine)) + fmt.Fprintf(&sb, "%s:%d (%s) %s\n", r.File, r.Location.Line, nodeType, firstLine) } case "location": for i := 0; i < renderCount; i++ { r := results[i] - sb.WriteString(fmt.Sprintf("%s:%d\n", r.File, r.Location.Line)) + fmt.Fprintf(&sb, "%s:%d\n", r.File, r.Location.Line) } default: // "verbose" @@ -514,7 +514,7 @@ func FormatResultsWithOptions(results []MatchResult, maxResults int, format stri if r.Node != nil { nodeType = r.Node.Type() } - sb.WriteString(fmt.Sprintf("**%s:%d** (%s)\n", r.File, r.Location.Line, nodeType)) + fmt.Fprintf(&sb, "**%s:%d** (%s)\n", r.File, r.Location.Line, nodeType) // Truncate very long text text := r.Text @@ -538,7 +538,7 @@ func FormatResultsWithOptions(results []MatchResult, maxResults int, format stri if len(capText) > 50 { capText = capText[:50] + "..." } - sb.WriteString(fmt.Sprintf("$%s=%s", name, capText)) + fmt.Fprintf(&sb, "$%s=%s", name, capText) } sb.WriteString("\n") } @@ -549,7 +549,7 @@ func FormatResultsWithOptions(results []MatchResult, maxResults int, format stri if remaining > 0 { // Caller must embed the cursor token; we just append the remaining count hint. // The actual [cursor: ...] line is written by the handler after calling MakeCursor. - sb.WriteString(fmt.Sprintf("[remaining: %d]\n", remaining)) + fmt.Fprintf(&sb, "[remaining: %d]\n", remaining) } return sb.String() diff --git a/internal/search/search.go b/internal/search/search.go index 11c5832..378aae5 100644 --- a/internal/search/search.go +++ b/internal/search/search.go @@ -389,13 +389,13 @@ func (s *Searcher) FormatResultsWithOptions(results *SearchResults, opts FormatO if opts.Verbose { totalMatches := len(results.Results) fileCount := len(fileResults) - sb.WriteString(fmt.Sprintf("Found %d matches in %d files", totalMatches, fileCount)) + fmt.Fprintf(&sb, "Found %d matches in %d files", totalMatches, fileCount) if results.Truncated { - sb.WriteString(fmt.Sprintf(" (truncated, total: %d)", results.Total)) + fmt.Fprintf(&sb, " (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)) + fmt.Fprintf(&sb, "(truncated, showing subset of %d total matches)\n\n", results.Total) } // Write results grouped by file @@ -408,7 +408,7 @@ func (s *Searcher) FormatResultsWithOptions(results *SearchResults, opts FormatO } } - sb.WriteString(fmt.Sprintf("**%s**\n", relPath)) + fmt.Fprintf(&sb, "**%s**\n", relPath) if opts.Cluster { writeClusteredResults(&sb, fileResults[file]) diff --git a/internal/server/session_integration_test.go b/internal/server/session_integration_test.go index 919bfd9..41a6d1b 100644 --- a/internal/server/session_integration_test.go +++ b/internal/server/session_integration_test.go @@ -199,7 +199,7 @@ func TestSessionPrefsASTQueryMaxResults(t *testing.T) { var sb strings.Builder sb.WriteString("package main\n\n") for i := 0; i < 5; i++ { - sb.WriteString(fmt.Sprintf("func Fn%c() {}\n\n", rune('A'+i))) + fmt.Fprintf(&sb, "func Fn%c() {}\n\n", rune('A'+i)) } testFile := filepath.Join(tmpDir, "many.go") if err := os.WriteFile(testFile, []byte(sb.String()), 0600); err != nil { @@ -281,7 +281,7 @@ func TestSessionPrefsMaxResultsExplicitOverride(t *testing.T) { var sb strings.Builder sb.WriteString("package main\n\n") for i := 0; i < 5; i++ { - sb.WriteString(fmt.Sprintf("func Fn%c() {}\n\n", rune('A'+i))) + fmt.Fprintf(&sb, "func Fn%c() {}\n\n", rune('A'+i)) } testFile := filepath.Join(tmpDir, "many.go") if err := os.WriteFile(testFile, []byte(sb.String()), 0600); err != nil { diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 172b5a7..cf87df9 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -77,24 +77,24 @@ func (e *StructuredError) Error() string { var sb strings.Builder // Error code and message - sb.WriteString(fmt.Sprintf("[%d] %s", e.Code, e.Message)) + fmt.Fprintf(&sb, "[%d] %s", e.Code, e.Message) // Context if available if len(e.Context) > 0 { sb.WriteString("\nContext:") for k, v := range e.Context { - sb.WriteString(fmt.Sprintf("\n %s: %v", k, v)) + fmt.Fprintf(&sb, "\n %s: %v", k, v) } } // Remediation if available if e.Remediation != "" { - sb.WriteString(fmt.Sprintf("\nHow to fix: %s", e.Remediation)) + fmt.Fprintf(&sb, "\nHow to fix: %s", e.Remediation) } // Underlying cause if available if e.Cause != nil { - sb.WriteString(fmt.Sprintf("\nCaused by: %v", e.Cause)) + fmt.Fprintf(&sb, "\nCaused by: %v", e.Cause) } return sb.String() @@ -215,7 +215,7 @@ func captureStack(skip int) string { for { frame, more := frames.Next() if !strings.Contains(frame.File, "runtime/") { - sb.WriteString(fmt.Sprintf("\n %s:%d %s", frame.File, frame.Line, frame.Function)) + fmt.Fprintf(&sb, "\n %s:%d %s", frame.File, frame.Line, frame.Function) } if !more { break