diff options
| author | Paul Buetow <paul@buetow.org> | 2025-11-02 23:42:15 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-11-02 23:42:15 +0200 |
| commit | 35e1de6f975088ade5dbf0af533fe6fdac8fcc94 (patch) | |
| tree | c9fc9b6ad86cc10a777b3f510c3c4b2d62c41ebd /internal | |
| parent | c60d5703d25b7d76d1da2f368b0632f08a161644 (diff) | |
some linter fixes
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/hexaiaction/cmdentry.go | 4 | ||||
| -rw-r--r-- | internal/hexaiaction/cmdentry_test.go | 6 | ||||
| -rw-r--r-- | internal/hexaiaction/run.go | 12 | ||||
| -rw-r--r-- | internal/hexaiaction/tui_delegate.go | 2 | ||||
| -rw-r--r-- | internal/hexaicli/run.go | 35 | ||||
| -rw-r--r-- | internal/hexaicli/run_test.go | 2 | ||||
| -rw-r--r-- | internal/hexaicli/testhelpers_test.go | 10 | ||||
| -rw-r--r-- | internal/hexailsp/run.go | 6 | ||||
| -rw-r--r-- | internal/llm/copilot.go | 20 | ||||
| -rw-r--r-- | internal/llm/copilot_http_test.go | 16 | ||||
| -rw-r--r-- | internal/llm/ollama.go | 14 | ||||
| -rw-r--r-- | internal/llm/openai.go | 14 | ||||
| -rw-r--r-- | internal/llm/openai_http_test.go | 14 | ||||
| -rw-r--r-- | internal/llm/openai_sse_negative_test.go | 4 | ||||
| -rw-r--r-- | internal/llm/openrouter.go | 12 | ||||
| -rw-r--r-- | internal/llm/openrouter_test.go | 4 | ||||
| -rw-r--r-- | internal/lsp/codeaction_prompts_test.go | 2 | ||||
| -rw-r--r-- | internal/lsp/completion_messages_test.go | 2 | ||||
| -rw-r--r-- | internal/stats/stats.go | 14 | ||||
| -rw-r--r-- | internal/tmux/status_more_test.go | 10 | ||||
| -rw-r--r-- | internal/version.go | 2 |
21 files changed, 134 insertions, 71 deletions
diff --git a/internal/hexaiaction/cmdentry.go b/internal/hexaiaction/cmdentry.go index ca33443..7d91ab2 100644 --- a/internal/hexaiaction/cmdentry.go +++ b/internal/hexaiaction/cmdentry.go @@ -160,8 +160,8 @@ func catFileTo(w io.Writer, path string) error { // echoThrough no longer used in tmux-only flow, but kept for potential reuse. func echoThrough(infile, outfile string, stdin io.Reader, stdout io.Writer) error { - var in io.Reader = stdin - var out io.Writer = stdout + in := stdin + out := stdout if infile != "" { f, err := os.Open(infile) if err != nil { diff --git a/internal/hexaiaction/cmdentry_test.go b/internal/hexaiaction/cmdentry_test.go index 9c896f6..71ed9db 100644 --- a/internal/hexaiaction/cmdentry_test.go +++ b/internal/hexaiaction/cmdentry_test.go @@ -24,7 +24,11 @@ func TestPersistStdin_WritesFile(t *testing.T) { t.Fatalf("write src: %v", err) } f, _ := os.Open(src) - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + t.Errorf("failed to close temp file: %v", err) + } + }() if err := persistStdin(path, f); err != nil { t.Fatalf("persistStdin: %v", err) } diff --git a/internal/hexaiaction/run.go b/internal/hexaiaction/run.go index 2a1f940..bf355b0 100644 --- a/internal/hexaiaction/run.go +++ b/internal/hexaiaction/run.go @@ -36,7 +36,7 @@ func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error { stats.SetWindow(time.Duration(cfg.StatsWindowMinutes) * time.Minute) } if err := cfg.Validate(); err != nil { - fmt.Fprintf(stderr, logging.AnsiBase+"hexai-tmux-action: %v"+logging.AnsiReset+"\n", err) + _, _ = fmt.Fprintf(stderr, logging.AnsiBase+"hexai-tmux-action: %v"+logging.AnsiReset+"\n", err) return err } // Enable custom action submenu with configurable hotkey @@ -50,7 +50,7 @@ func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error { } cli, err := newClientFromApp(cfg) if err != nil { - fmt.Fprintf(stderr, logging.AnsiBase+"hexai-tmux-action: LLM disabled: %v"+logging.AnsiReset+"\n", err) + _, _ = fmt.Fprintf(stderr, logging.AnsiBase+"hexai-tmux-action: LLM disabled: %v"+logging.AnsiReset+"\n", err) return err } primaryModel := strings.TrimSpace(reqOptsFrom(cfg).model) @@ -61,7 +61,7 @@ func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error { var client chatDoer = cli parts, err := ParseInput(stdin) if err != nil { - fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: failed to read input"+logging.AnsiReset) + _, _ = fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: failed to read input"+logging.AnsiReset) return err } if strings.TrimSpace(parts.Selection) == "" { @@ -75,7 +75,7 @@ func Run(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer) error { if err != nil { return err } - io.WriteString(stdout, out) + _, _ = io.WriteString(stdout, out) return nil } @@ -123,7 +123,7 @@ func executeAction(ctx context.Context, kind ActionKind, parts InputParts, cfg a func handleRewriteAction(ctx context.Context, parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (string, error) { instr, cleaned := ExtractInstruction(parts.Selection) if strings.TrimSpace(instr) == "" { - fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: no inline instruction found; echoing input"+logging.AnsiReset) + _, _ = fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: no inline instruction found; echoing input"+logging.AnsiReset) return parts.Selection, nil } return runWithTimeout(ctx, timeout10s, func(cctx context.Context) (string, error) { @@ -169,7 +169,7 @@ func handleCustomAction(ctx context.Context, parts InputParts, cfg appconfig.App func handleCustomPromptAction(ctx context.Context, parts InputParts, cfg appconfig.App, client chatDoer, stderr io.Writer) (string, error) { prompt, err := editor.OpenTempAndEdit(nil) if err != nil || strings.TrimSpace(prompt) == "" { - fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: custom prompt canceled or empty; echoing input"+logging.AnsiReset) + _, _ = fmt.Fprintln(stderr, logging.AnsiBase+"hexai-tmux-action: custom prompt canceled or empty; echoing input"+logging.AnsiReset) return parts.Selection, nil } return runWithTimeout(ctx, timeout10s, func(cctx context.Context) (string, error) { diff --git a/internal/hexaiaction/tui_delegate.go b/internal/hexaiaction/tui_delegate.go index 46d40cb..0b34d7e 100644 --- a/internal/hexaiaction/tui_delegate.go +++ b/internal/hexaiaction/tui_delegate.go @@ -31,5 +31,5 @@ func (oneLineDelegate) Render(w io.Writer, m list.Model, index int, listItem lis if index == m.Index() { cursor = cursorStyle.Render("> ") } - fmt.Fprintf(w, "%s%s%s", cursor, title, hot) + _, _ = fmt.Fprintf(w, "%s%s%s", cursor, title, hot) } diff --git a/internal/hexaicli/run.go b/internal/hexaicli/run.go index e2aa9a2..7b360e9 100644 --- a/internal/hexaicli/run.go +++ b/internal/hexaicli/run.go @@ -170,13 +170,13 @@ func Run(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io. } jobs, err := buildCLIJobs(cfg) if err != nil { - fmt.Fprintf(stderr, logging.AnsiBase+"hexai: LLM disabled: %v"+logging.AnsiReset+"\n", err) + _, _ = fmt.Fprintf(stderr, logging.AnsiBase+"hexai: LLM disabled: %v"+logging.AnsiReset+"\n", err) return err } if selected := selectionFromContext(ctx); len(selected) > 0 { jobs, err = filterJobsBySelection(jobs, selected) if err != nil { - fmt.Fprintf(stderr, logging.AnsiBase+"hexai: %v"+logging.AnsiReset+"\n", err) + _, _ = fmt.Fprintf(stderr, logging.AnsiBase+"hexai: %v"+logging.AnsiReset+"\n", err) return err } } @@ -193,12 +193,12 @@ func Run(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io. } } if rerr != nil { - fmt.Fprintln(stderr, logging.AnsiBase+rerr.Error()+logging.AnsiReset) + _, _ = fmt.Fprintln(stderr, logging.AnsiBase+rerr.Error()+logging.AnsiReset) return rerr } msgs := buildMessagesFromConfig(cfg, input) if err := runCLIJobs(ctx, jobs, msgs, input, stdout, stderr); err != nil { - fmt.Fprintf(stderr, logging.AnsiBase+"hexai: error: %v"+logging.AnsiReset+"\n", err) + _, _ = fmt.Fprintf(stderr, logging.AnsiBase+"hexai: error: %v"+logging.AnsiReset+"\n", err) return err } return nil @@ -209,14 +209,14 @@ func Run(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io. func RunWithClient(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer, client llm.Client) error { input, err := readInput(stdin, args) if err != nil { - fmt.Fprintln(stderr, logging.AnsiBase+err.Error()+logging.AnsiReset) + _, _ = fmt.Fprintln(stderr, logging.AnsiBase+err.Error()+logging.AnsiReset) return err } req := requestArgs{model: strings.TrimSpace(client.DefaultModel())} printProviderInfo(stderr, client, req.model) msgs := buildMessages(input) if err := runChat(ctx, client, req, msgs, input, stdout, stderr); err != nil { - fmt.Fprintf(stderr, logging.AnsiBase+"hexai: error: %v"+logging.AnsiReset+"\n", err) + _, _ = fmt.Fprintf(stderr, logging.AnsiBase+"hexai: error: %v"+logging.AnsiReset+"\n", err) return err } return nil @@ -620,12 +620,21 @@ func runChat(ctx context.Context, client llm.Client, req requestArgs, msgs []llm var output string if s, ok := client.(llm.Streamer); ok { var b strings.Builder + var streamErr error if err := s.ChatStream(ctx, msgs, func(chunk string) { + if streamErr != nil { + return + } b.WriteString(chunk) - fmt.Fprint(out, chunk) + if _, err := fmt.Fprint(out, chunk); err != nil { + streamErr = err + } }, req.options...); err != nil { return err } + if streamErr != nil { + return streamErr + } output = b.String() } else { txt, err := client.Chat(ctx, msgs, req.options...) @@ -633,7 +642,9 @@ func runChat(ctx context.Context, client llm.Client, req requestArgs, msgs []llm return err } output = txt - fmt.Fprint(out, output) + if _, err := fmt.Fprint(out, output); err != nil { + return err + } } dur := time.Since(start) // Contribute to global stats and update tmux status @@ -655,8 +666,10 @@ func runChat(ctx context.Context, client llm.Client, req requestArgs, msgs []llm } } scopeRPM := float64(scopeReqs) / minsWin - fmt.Fprintf(errw, "\n"+logging.AnsiBase+"done provider=%s model=%s time=%s in_bytes=%d out_bytes=%d | global Σ reqs=%d rpm=%.2f"+logging.AnsiReset+"\n", - client.Name(), model, dur.Round(time.Millisecond), sent, recv, snap.Global.Reqs, snap.RPM) + if _, err := fmt.Fprintf(errw, "\n"+logging.AnsiBase+"done provider=%s model=%s time=%s in_bytes=%d out_bytes=%d | global Σ reqs=%d rpm=%.2f"+logging.AnsiReset+"\n", + client.Name(), model, dur.Round(time.Millisecond), sent, recv, snap.Global.Reqs, snap.RPM); err != nil { + return err + } _ = tmux.SetStatus(tmux.FormatGlobalStatusColored(snap.Global.Reqs, snap.RPM, snap.Global.Sent, snap.Global.Recv, client.Name(), model, scopeRPM, scopeReqs, snap.Window)) return nil } @@ -666,7 +679,7 @@ func printProviderInfo(errw io.Writer, client llm.Client, model string) { if strings.TrimSpace(model) == "" { model = client.DefaultModel() } - fmt.Fprintf(errw, logging.AnsiBase+"provider=%s model=%s"+logging.AnsiReset+"\n", client.Name(), model) + _, _ = fmt.Fprintf(errw, logging.AnsiBase+"provider=%s model=%s"+logging.AnsiReset+"\n", client.Name(), model) } // newClientFromConfig is kept for tests; delegates to llmutils. diff --git a/internal/hexaicli/run_test.go b/internal/hexaicli/run_test.go index 991965e..43576cf 100644 --- a/internal/hexaicli/run_test.go +++ b/internal/hexaicli/run_test.go @@ -146,7 +146,7 @@ model = "gpt-x" t.Fatalf("expected error due to missing API key") } // Accept either explicit "LLM disabled" or a generic provider error emitted by Run. - if !(strings.Contains(errb.String(), "LLM disabled") || strings.Contains(errb.String(), "openai error") || strings.Contains(errb.String(), "hexai: error:")) { + if !strings.Contains(errb.String(), "LLM disabled") && !strings.Contains(errb.String(), "openai error") && !strings.Contains(errb.String(), "hexai: error:") { t.Fatalf("expected disabled-or-error message, got %q", errb.String()) } } diff --git a/internal/hexaicli/testhelpers_test.go b/internal/hexaicli/testhelpers_test.go index 4cc04f7..16f1639 100644 --- a/internal/hexaicli/testhelpers_test.go +++ b/internal/hexaicli/testhelpers_test.go @@ -25,7 +25,9 @@ func setStdin(t *testing.T, content string) (func(), *os.File) { old := os.Stdin os.Stdin = f restore := func() { - f.Close() + if err := f.Close(); err != nil { + t.Errorf("failed to close temp stdin file: %v", err) + } os.Stdin = old } return restore, f @@ -71,7 +73,11 @@ func writeTOML(t *testing.T, path string, m map[string]string) { if err != nil { t.Fatalf("create: %v", err) } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + t.Errorf("failed to close temp TOML file: %v", err) + } + }() for k, v := range m { if _, err := f.WriteString(k + " = \"" + v + "\"\n"); err != nil { t.Fatalf("write: %v", err) diff --git a/internal/hexailsp/run.go b/internal/hexailsp/run.go index f0ab404..b7f777b 100644 --- a/internal/hexailsp/run.go +++ b/internal/hexailsp/run.go @@ -37,7 +37,11 @@ func RunWithConfig(logPath string, configPath string, stdin io.Reader, stdout io if err != nil { logger.Fatalf("failed to open log file: %v", err) } - defer f.Close() + defer func() { + if err := f.Close(); err != nil { + logger.Printf("failed to close log file: %v", err) + } + }() logger.SetOutput(f) } logging.Bind(logger) diff --git a/internal/llm/copilot.go b/internal/llm/copilot.go index d3b1a9d..b439ed3 100644 --- a/internal/llm/copilot.go +++ b/internal/llm/copilot.go @@ -118,7 +118,11 @@ func (c copilotClient) Chat(ctx context.Context, messages []Message, opts ...Req logging.Logf("llm/copilot ", "%shttp error after %s: %v%s", logging.AnsiRed, time.Since(start), err, logging.AnsiBase) return "", err } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + logging.Logf("llm/copilot", "failed to close response body: %v", err) + } + }() if err := handleCopilotNon2xx(resp, start); err != nil { return "", err } @@ -144,7 +148,7 @@ func buildCopilotChatRequest(o Options, messages []Message, defaultTemp *float64 req := copilotChatRequest{Model: o.Model} req.Messages = make([]copilotMessage, len(messages)) for i, m := range messages { - req.Messages[i] = copilotMessage{Role: m.Role, Content: m.Content} + req.Messages[i] = copilotMessage(m) } if o.Temperature != 0 { req.Temperature = &o.Temperature @@ -220,7 +224,11 @@ func (c *copilotClient) ensureSession(ctx context.Context) error { if err != nil { return err } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + logging.Logf("llm/copilot", "failed to close response body: %v", err) + } + }() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("copilot token http error: %d", resp.StatusCode) } @@ -354,7 +362,11 @@ func (c copilotClient) CodeCompletion(ctx context.Context, prompt string, suffix if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + logging.Logf("llm/copilot", "failed to close response body: %v", err) + } + }() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("copilot codex http error: %d", resp.StatusCode) } diff --git a/internal/llm/copilot_http_test.go b/internal/llm/copilot_http_test.go index 9dd4aee..1371f71 100644 --- a/internal/llm/copilot_http_test.go +++ b/internal/llm/copilot_http_test.go @@ -73,8 +73,8 @@ func TestCopilot_CodeCompletion_Success(t *testing.T) { if r.URL.Host == "copilot-proxy.githubusercontent.com" && strings.HasSuffix(r.URL.Path, "/v1/engines/copilot-codex/completions") { rw := httptest.NewRecorder() // two choices for index 0 and 1 - rw.WriteString("data: {\"choices\":[{\"index\":0,\"text\":\"A\"}]}\n") - rw.WriteString("data: {\"choices\":[{\"index\":1,\"text\":\"B\"}]}\n") + _, _ = rw.WriteString("data: {\"choices\":[{\"index\":0,\"text\":\"A\"}]}\n") + _, _ = rw.WriteString("data: {\"choices\":[{\"index\":1,\"text\":\"B\"}]}\n") res := rw.Result() res.StatusCode = 200 return res, nil @@ -164,7 +164,7 @@ func TestCopilot_Chat_DecodeError_StatusOK(t *testing.T) { } // Chat returns 200 but invalid JSON; expect decode error srv := newIPv4Server(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "{invalid") + _, _ = io.WriteString(w, "{invalid") })) defer srv.Close() c := newCopilot(srv.URL, "gpt-4o-mini", "KEY", f64p(0.1)).(copilotClient) @@ -197,9 +197,9 @@ func TestCopilot_CodeCompletion_MalformedAndEmpty(t *testing.T) { if r.URL.Host == "copilot-proxy.githubusercontent.com" && strings.HasSuffix(r.URL.Path, "/v1/engines/copilot-codex/completions") { rw := httptest.NewRecorder() // malformed line - rw.WriteString("data: {bad}\n") + _, _ = rw.WriteString("data: {bad}\n") // done; should produce empty suggestions - rw.WriteString("data: [DONE]\n") + _, _ = rw.WriteString("data: [DONE]\n") res := rw.Result() res.StatusCode = 200 return res, nil @@ -226,9 +226,9 @@ func TestCopilot_CodeCompletion_MalformedAndEmpty(t *testing.T) { } if r.URL.Host == "copilot-proxy.githubusercontent.com" && strings.HasSuffix(r.URL.Path, "/v1/engines/copilot-codex/completions") { rw := httptest.NewRecorder() - rw.WriteString("data: {bad}\n") - rw.WriteString("data: {\"choices\":[{\"index\":0,\"text\":\"OK\"}]}\n") - rw.WriteString("data: [DONE]\n") + _, _ = rw.WriteString("data: {bad}\n") + _, _ = rw.WriteString("data: {\"choices\":[{\"index\":0,\"text\":\"OK\"}]}\n") + _, _ = rw.WriteString("data: [DONE]\n") res := rw.Result() res.StatusCode = 200 return res, nil diff --git a/internal/llm/ollama.go b/internal/llm/ollama.go index 374a771..f355166 100644 --- a/internal/llm/ollama.go +++ b/internal/llm/ollama.go @@ -81,7 +81,11 @@ func (c ollamaClient) Chat(ctx context.Context, messages []Message, opts ...Requ logging.Logf("llm/ollama ", "%shttp error after %s: %v%s", logging.AnsiRed, time.Since(start), err, logging.AnsiBase) return "", err } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + logging.Logf("llm/ollama", "failed to close response body: %v", err) + } + }() if err := handleOllamaNon2xx(resp, start); err != nil { return "", err } @@ -129,7 +133,11 @@ func (c ollamaClient) ChatStream(ctx context.Context, messages []Message, onDelt logging.Logf("llm/ollama ", "%shttp error after %s: %v%s", logging.AnsiRed, time.Since(start), err, logging.AnsiBase) return err } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + logging.Logf("llm/ollama", "failed to close response body: %v", err) + } + }() if err := handleOllamaNon2xx(resp, start); err != nil { return err } @@ -172,7 +180,7 @@ func buildOllamaRequest(o Options, messages []Message, defaultTemp *float64, str req := ollamaChatRequest{Model: o.Model, Stream: stream} req.Messages = make([]oaMessage, len(messages)) for i, m := range messages { - req.Messages[i] = oaMessage{Role: m.Role, Content: m.Content} + req.Messages[i] = oaMessage(m) } optsMap := map[string]any{} if o.Temperature != 0 { diff --git a/internal/llm/openai.go b/internal/llm/openai.go index c284bb3..b97111d 100644 --- a/internal/llm/openai.go +++ b/internal/llm/openai.go @@ -121,7 +121,11 @@ func (c openAIClient) Chat(ctx context.Context, messages []Message, opts ...Requ logging.Logf("llm/openai ", "%shttp error after %s: %v%s", logging.AnsiRed, time.Since(start), err, logging.AnsiBase) return "", err } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + logging.Logf("llm/openai", "failed to close response body: %v", err) + } + }() if err := handleOpenAINon2xx(resp, start, "llm/openai ", "openai"); err != nil { return "", err } @@ -172,7 +176,11 @@ func (c openAIClient) ChatStream(ctx context.Context, messages []Message, onDelt logging.Logf("llm/openai ", "%shttp error after %s: %v%s", logging.AnsiRed, time.Since(start), err, logging.AnsiBase) return err } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + logging.Logf("llm/openai", "failed to close response body: %v", err) + } + }() if err := handleOpenAINon2xx(resp, start, "llm/openai ", "openai"); err != nil { return err } @@ -200,7 +208,7 @@ func buildOAChatRequest(o Options, messages []Message, defaultTemp *float64, str req := oaChatRequest{Model: o.Model, Stream: stream} req.Messages = make([]oaMessage, len(messages)) for i, m := range messages { - req.Messages[i] = oaMessage{Role: m.Role, Content: m.Content} + req.Messages[i] = oaMessage(m) } if o.Temperature != 0 { req.Temperature = &o.Temperature diff --git a/internal/llm/openai_http_test.go b/internal/llm/openai_http_test.go index affcae9..d0fc828 100644 --- a/internal/llm/openai_http_test.go +++ b/internal/llm/openai_http_test.go @@ -45,8 +45,8 @@ func TestOpenAI_ChatStream_SSE(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Return SSE-like stream w.Header().Set("Content-Type", "text/event-stream") - io.WriteString(w, "data: {\"choices\":[{\"delta\":{\"content\":\"Hi\"}}]}\n\n") - io.WriteString(w, "data: [DONE]\n") + _, _ = io.WriteString(w, "data: {\"choices\":[{\"delta\":{\"content\":\"Hi\"}}]}\n\n") + _, _ = io.WriteString(w, "data: [DONE]\n") })) defer srv.Close() c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient) @@ -71,8 +71,8 @@ func TestOpenAI_ChatStream_SSE_ErrorChunk(t *testing.T) { } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") - io.WriteString(w, "data: {\"error\":{\"message\":\"oops\"}}\n\n") - io.WriteString(w, "data: [DONE]\n") + _, _ = io.WriteString(w, "data: {\"error\":{\"message\":\"oops\"}}\n\n") + _, _ = io.WriteString(w, "data: [DONE]\n") })) defer srv.Close() c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient) @@ -104,8 +104,8 @@ func TestOpenAI_ChatStream_SSE_EmptyDelta_NoError(t *testing.T) { } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") - io.WriteString(w, "data: {\\\"choices\\\":[{\\\"delta\\\":{\\\"content\\\":\\\"\\\"}}]}\\n\\n") - io.WriteString(w, "data: [DONE]\\n") + _, _ = io.WriteString(w, "data: {\\\"choices\\\":[{\\\"delta\\\":{\\\"content\\\":\\\"\\\"}}]}\\n\\n") + _, _ = io.WriteString(w, "data: [DONE]\\n") })) defer srv.Close() c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient) @@ -126,7 +126,7 @@ func TestOpenAI_Chat_DecodeError_StatusOK(t *testing.T) { // Return status 200 but invalid JSON body; Chat should return an error srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) - io.WriteString(w, "{invalid") + _, _ = io.WriteString(w, "{invalid") })) defer srv.Close() c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient) diff --git a/internal/llm/openai_sse_negative_test.go b/internal/llm/openai_sse_negative_test.go index de2ff71..7f4f7db 100644 --- a/internal/llm/openai_sse_negative_test.go +++ b/internal/llm/openai_sse_negative_test.go @@ -16,8 +16,8 @@ func TestOpenAI_ChatStream_SSE_MalformedChunk(t *testing.T) { // Malformed JSON chunk should be skipped; no onDelta calls; no error. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") - io.WriteString(w, "data: {not json}\n\n") - io.WriteString(w, "data: [DONE]\n") + _, _ = io.WriteString(w, "data: {not json}\n\n") + _, _ = io.WriteString(w, "data: [DONE]\n") })) defer srv.Close() c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient) diff --git a/internal/llm/openrouter.go b/internal/llm/openrouter.go index f03844a..4aae398 100644 --- a/internal/llm/openrouter.go +++ b/internal/llm/openrouter.go @@ -65,7 +65,11 @@ func (c openRouterClient) Chat(ctx context.Context, messages []Message, opts ... logging.Logf("llm/openrouter ", "%shttp error after %s: %v%s", logging.AnsiRed, time.Since(start), err, logging.AnsiBase) return "", err } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + logging.Logf("llm/openrouter", "failed to close response body: %v", err) + } + }() if err := handleOpenAINon2xx(resp, start, "llm/openrouter ", "openrouter"); err != nil { return "", err } @@ -111,7 +115,11 @@ func (c openRouterClient) ChatStream(ctx context.Context, messages []Message, on logging.Logf("llm/openrouter ", "%shttp error after %s: %v%s", logging.AnsiRed, time.Since(start), err, logging.AnsiBase) return err } - defer resp.Body.Close() + defer func() { + if err := resp.Body.Close(); err != nil { + logging.Logf("llm/openrouter", "failed to close response body: %v", err) + } + }() if err := handleOpenAINon2xx(resp, start, "llm/openrouter ", "openrouter"); err != nil { return err } diff --git a/internal/llm/openrouter_test.go b/internal/llm/openrouter_test.go index 2a07be0..f8efe16 100644 --- a/internal/llm/openrouter_test.go +++ b/internal/llm/openrouter_test.go @@ -75,8 +75,8 @@ func TestOpenRouter_ChatStream_SendsHeaders(t *testing.T) { acceptHeader = r.Header.Get("Accept") referer = r.Header.Get("HTTP-Referer") w.Header().Set("Content-Type", "text/event-stream") - io.WriteString(w, "data: {\"choices\":[{\"delta\":{\"content\":\"hi\"}}]}\n\n") - io.WriteString(w, "data: [DONE]\n") + _, _ = io.WriteString(w, "data: {\"choices\":[{\"delta\":{\"content\":\"hi\"}}]}\n\n") + _, _ = io.WriteString(w, "data: [DONE]\n") })) defer srv.Close() diff --git a/internal/lsp/codeaction_prompts_test.go b/internal/lsp/codeaction_prompts_test.go index c5fd5e2..8b07dfe 100644 --- a/internal/lsp/codeaction_prompts_test.go +++ b/internal/lsp/codeaction_prompts_test.go @@ -59,7 +59,7 @@ func TestResolveCodeAction_UsesDiagnosticsPrompts(t *testing.T) { if cap.msgs[0].Content != "DSYS" || cap.msgs[1].Role != "user" { t.Fatalf("unexpected diagnostics prompts: %#v", cap.msgs) } - if got := cap.msgs[1].Content; !(contains(got, "oops1") && contains(got, "oops2") && contains(got, "var a")) { + if got := cap.msgs[1].Content; !contains(got, "oops1") || !contains(got, "oops2") || !contains(got, "var a") { t.Fatalf("diagnostics/user content mismatch: %q", got) } } diff --git a/internal/lsp/completion_messages_test.go b/internal/lsp/completion_messages_test.go index f0c693c..bc02645 100644 --- a/internal/lsp/completion_messages_test.go +++ b/internal/lsp/completion_messages_test.go @@ -89,7 +89,7 @@ func contains(s, sub string) bool { } func stringIndex(s, sub string) int { - return len([]rune(s[:])) - len([]rune(s[:])) + (func() int { return intIndex(s, sub) })() + return intIndex(s, sub) } func intIndex(s, sub string) int { return Index(s, sub) } diff --git a/internal/stats/stats.go b/internal/stats/stats.go index a8390ef..a98b2c5 100644 --- a/internal/stats/stats.go +++ b/internal/stats/stats.go @@ -88,7 +88,7 @@ func Update(ctx context.Context, provider, model string, sentBytes, recvBytes in if err != nil { return err } - defer f.Close() + defer func() { _ = f.Close() }() unlock, err := acquireFileLock(ctx, f) if err != nil { return err @@ -131,21 +131,21 @@ func Update(ctx context.Context, provider, model string, sentBytes, recvBytes in enc := json.NewEncoder(tmp) enc.SetEscapeHTML(false) if err := enc.Encode(&sf); err != nil { - tmp.Close() - os.Remove(tmp.Name()) + _ = tmp.Close() + _ = os.Remove(tmp.Name()) return err } if err := tmp.Sync(); err != nil { - tmp.Close() - os.Remove(tmp.Name()) + _ = tmp.Close() + _ = os.Remove(tmp.Name()) return err } if err := tmp.Close(); err != nil { - os.Remove(tmp.Name()) + _ = os.Remove(tmp.Name()) return err } if err := os.Rename(tmp.Name(), path); err != nil { - os.Remove(tmp.Name()) + _ = os.Remove(tmp.Name()) return err } return nil diff --git a/internal/tmux/status_more_test.go b/internal/tmux/status_more_test.go index deaf57d..39cbe45 100644 --- a/internal/tmux/status_more_test.go +++ b/internal/tmux/status_more_test.go @@ -22,16 +22,16 @@ func TestFormatLLMStatsStatusColored_Basic(t *testing.T) { func TestFormatGlobalStatusColored_NarrowAndMaxLen(t *testing.T) { // Narrow mode should elide the tail - os.Setenv("HEXAI_TMUX_STATUS_NARROW", "1") - defer os.Unsetenv("HEXAI_TMUX_STATUS_NARROW") + _ = os.Setenv("HEXAI_TMUX_STATUS_NARROW", "1") + defer func() { _ = os.Unsetenv("HEXAI_TMUX_STATUS_NARROW") }() s := FormatGlobalStatusColored(10, 3.3, 1000, 2000, "prov", "model", 1.1, 4, 30*time.Minute) if containsAll(s, []string{"|", "prov:model"}) { t.Fatalf("narrow mode should not include tail: %q", s) } // Max length should also drop the tail when it would overflow - os.Unsetenv("HEXAI_TMUX_STATUS_NARROW") - os.Setenv("HEXAI_TMUX_STATUS_MAXLEN", "10") - defer os.Unsetenv("HEXAI_TMUX_STATUS_MAXLEN") + _ = os.Unsetenv("HEXAI_TMUX_STATUS_NARROW") + _ = os.Setenv("HEXAI_TMUX_STATUS_MAXLEN", "10") + defer func() { _ = os.Unsetenv("HEXAI_TMUX_STATUS_MAXLEN") }() s2 := FormatGlobalStatusColored(10, 3.3, 1000, 2000, "prov", "model", 1.1, 4, 30*time.Minute) if containsAll(s2, []string{"|", "prov:model"}) { t.Fatalf("maxlen should drop tail when overflowing: %q", s2) diff --git a/internal/version.go b/internal/version.go index 021cc9d..052c674 100644 --- a/internal/version.go +++ b/internal/version.go @@ -1,4 +1,4 @@ // Summary: Hexai semantic version identifier used by CLI and LSP binaries. package internal -const Version = "0.15.1" +const Version = "0.15.2" |
