style: fix remaining lint errors (misspell, shadow, unused, etc.)

- Fix British→American spelling (cancelled→canceled, honour→honor, etc.)
- Remove unused loadSkills method and regex vars
- Fix variable shadowing (err, max)
- Add explicit returns for nakedret
- Use net.JoinHostPort for nosprintfhostport
- Preallocate slices, add nolint:gosmopolitan for CJK test data
- Fix golines and trailing whitespace

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-03-01 00:22:14 +09:00
parent dca46e6799
commit 674d491d37
15 changed files with 55 additions and 69 deletions

View file

@ -4,11 +4,13 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"net"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"time" "time"
@ -216,7 +218,8 @@ func gatewayCmd(debug bool, orchestration bool) error {
if certErr != nil { if certErr != nil {
logger.ErrorCF("miniapp", "Failed to fetch TLS cert", map[string]any{"error": certErr.Error()}) logger.ErrorCF("miniapp", "Failed to fetch TLS cert", map[string]any{"error": certErr.Error()})
} else { } else {
webAppURL = fmt.Sprintf("https://%s:%d/miniapp", hostname, cfg.Gateway.Port) hostPort := net.JoinHostPort(hostname, strconv.Itoa(cfg.Gateway.Port))
webAppURL = "https://" + hostPort + "/miniapp"
cfg.Channels.Telegram.WebAppURL = webAppURL cfg.Channels.Telegram.WebAppURL = webAppURL
tlsCert, tlsKey = certFile, keyFile tlsCert, tlsKey = certFile, keyFile
useTLS = true useTLS = true
@ -268,7 +271,10 @@ func gatewayCmd(debug bool, orchestration bool) error {
cfg.Gateway.Port, cfg.Gateway.Port,
) )
} else { } else {
fmt.Printf("✓ Health endpoints available at http://%s:%d/health and /ready\n", cfg.Gateway.Host, cfg.Gateway.Port) fmt.Printf(
"✓ Health endpoints available at http://%s:%d/health and /ready\n",
cfg.Gateway.Host, cfg.Gateway.Port,
)
} }
go agentLoop.Run(ctx) go agentLoop.Run(ctx)

View file

@ -787,25 +787,6 @@ func (cb *ContextBuilder) AddAssistantMessage(
return messages return messages
} }
func (cb *ContextBuilder) loadSkills() string {
allSkills := cb.skillsLoader.ListSkills()
if len(allSkills) == 0 {
return ""
}
var skillNames []string
for _, s := range allSkills {
skillNames = append(skillNames, s.Name)
}
content := cb.skillsLoader.LoadSkillsForContext(skillNames)
if content == "" {
return ""
}
return "# Skill Definitions\n\n" + content
}
// LoadSkill loads a skill by name, returning its content (with frontmatter stripped) and whether it was found. // LoadSkill loads a skill by name, returning its content (with frontmatter stripped) and whether it was found.
func (cb *ContextBuilder) LoadSkill(name string) (string, bool) { func (cb *ContextBuilder) LoadSkill(name string) (string, bool) {
return cb.skillsLoader.LoadSkill(name) return cb.skillsLoader.LoadSkill(name)

View file

@ -645,7 +645,8 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
lower := strings.ToLower(content) lower := strings.ToLower(content)
// Check for stop keywords // Check for stop keywords
stopKeywords := []string{"stop", "cancel", "abort", "停止", "中止", "やめて"} //nolint:gosmopolitan // intentional CJK stop words //nolint:gosmopolitan // intentional CJK stop words
stopKeywords := []string{"stop", "cancel", "abort", "停止", "中止", "やめて"}
isStop := false isStop := false
for _, kw := range stopKeywords { for _, kw := range stopKeywords {
if lower == kw { if lower == kw {
@ -1999,13 +2000,13 @@ func (al *AgentLoop) runLLMIteration(
if sp, ok := p.(providers.StreamingProvider); ok && sp.CanStream() { if sp, ok := p.(providers.StreamingProvider); ok && sp.CanStream() {
streamCtx, streamCancel := context.WithCancel(ctx) streamCtx, streamCancel := context.WithCancel(ctx)
defer streamCancel() defer streamCancel()
ch, err := sp.ChatStream(streamCtx, messages, providerToolDefs, model, opts_) ch, sErr := sp.ChatStream(streamCtx, messages, providerToolDefs, model, opts_)
if err != nil { if sErr != nil {
return nil, err return nil, sErr
} }
resp, repetition, err := consumeStreamWithRepetitionDetection(ch, streamCancel, 1000, onChunk) resp, repetition, sErr := consumeStreamWithRepetitionDetection(ch, streamCancel, 1000, onChunk)
if err != nil { if sErr != nil {
return nil, err return nil, sErr
} }
if repetition { if repetition {
resp.FinishReason = "repetition_detected" resp.FinishReason = "repetition_detected"
@ -2156,7 +2157,7 @@ func (al *AgentLoop) runLLMIteration(
// blocks so loops inside <think> are caught). Skip when the // blocks so loops inside <think> are caught). Skip when the
// provider already returned native tool calls. // provider already returned native tool calls.
// Streaming providers may have already flagged repetition via // Streaming providers may have already flagged repetition via
// FinishReason="repetition_detected" — honour that too. // FinishReason="repetition_detected" — honor that too.
if response.FinishReason == "repetition_detected" || if response.FinishReason == "repetition_detected" ||
(len(response.ToolCalls) == 0 && utils.DetectRepetitionLoop(response.Content)) { (len(response.ToolCalls) == 0 && utils.DetectRepetitionLoop(response.Content)) {
logger.WarnCF("agent", "Repetition loop detected in LLM response, retrying", logger.WarnCF("agent", "Repetition loop detected in LLM response, retrying",
@ -2603,7 +2604,6 @@ func (al *AgentLoop) runLLMIteration(
al.lastSystemPrompt.Store(newPrompt) al.lastSystemPrompt.Store(newPrompt)
al.promptDirty.Store(false) al.promptDirty.Store(false)
} }
} }
// If max iterations exhausted with tool calls still pending, // If max iterations exhausted with tool calls still pending,
@ -2783,7 +2783,7 @@ func (al *AgentLoop) GetPlanInfo() (hasPlan bool, status string, currentPhase, t
totalPhases = mem.GetTotalPhases() totalPhases = mem.GetTotalPhases()
display = mem.FormatPlanDisplay() display = mem.FormatPlanDisplay()
memory = mem.ReadLongTerm() memory = mem.ReadLongTerm()
return return hasPlan, status, currentPhase, totalPhases, display, memory
} }
// GetPlanStatus returns the current plan status ("interviewing", "executing", "review", etc.) or "". // GetPlanStatus returns the current plan status ("interviewing", "executing", "review", etc.) or "".
@ -2837,7 +2837,7 @@ func (al *AgentLoop) GetContextInfo() (workDir, planWorkDir, workspace string, b
workDir = agent.ContextBuilder.workDir workDir = agent.ContextBuilder.workDir
} }
bootstrap = agent.ContextBuilder.ResolveBootstrapPaths() bootstrap = agent.ContextBuilder.ResolveBootstrapPaths()
return return workDir, planWorkDir, workspace, bootstrap
} }
// GetSystemPrompt returns the system prompt last sent to the LLM. // GetSystemPrompt returns the system prompt last sent to the LLM.

View file

@ -2513,8 +2513,8 @@ func TestAgentLoop_PlanModel_UsedDuringInterviewing(t *testing.T) {
os.MkdirAll(memoryDir, 0o755) os.MkdirAll(memoryDir, 0o755)
memoryPath := filepath.Join(memoryDir, "MEMORY.md") memoryPath := filepath.Join(memoryDir, "MEMORY.md")
memoryContent := "# Active Plan\n\n> Task: Test plan model\n> Status: interviewing\n> Phase: 1\n" memoryContent := "# Active Plan\n\n> Task: Test plan model\n> Status: interviewing\n> Phase: 1\n"
if err := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); err != nil { if wErr := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); wErr != nil {
t.Fatalf("Failed to write MEMORY.md: %v", err) t.Fatalf("Failed to write MEMORY.md: %v", wErr)
} }
_, err = al.ProcessDirectWithChannel( _, err = al.ProcessDirectWithChannel(
@ -2581,8 +2581,8 @@ func TestAgentLoop_PlanModel_NotUsedDuringExecuting(t *testing.T) {
## Phase 1: Build ## Phase 1: Build
- [ ] Run build - [ ] Run build
` `
if err := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); err != nil { if wErr := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); wErr != nil {
t.Fatalf("Failed to write MEMORY.md: %v", err) t.Fatalf("Failed to write MEMORY.md: %v", wErr)
} }
_, err = al.ProcessDirectWithChannel( _, err = al.ProcessDirectWithChannel(
@ -2642,8 +2642,8 @@ func TestAgentLoop_PlanModel_ResolvesProviderForSingleCandidate(t *testing.T) {
os.MkdirAll(memoryDir, 0o755) os.MkdirAll(memoryDir, 0o755)
memoryPath := filepath.Join(memoryDir, "MEMORY.md") memoryPath := filepath.Join(memoryDir, "MEMORY.md")
memoryContent := "# Active Plan\n\n> Task: Test provider resolution\n> Status: interviewing\n> Phase: 1\n" memoryContent := "# Active Plan\n\n> Task: Test provider resolution\n> Status: interviewing\n> Phase: 1\n"
if err := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); err != nil { if wErr := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); wErr != nil {
t.Fatalf("Failed to write MEMORY.md: %v", err) t.Fatalf("Failed to write MEMORY.md: %v", wErr)
} }
_, err = al.ProcessDirectWithChannel( _, err = al.ProcessDirectWithChannel(

View file

@ -146,8 +146,6 @@ var (
reStatus = regexp.MustCompile(`(?m)^> Status:\s*(.+)`) reStatus = regexp.MustCompile(`(?m)^> Status:\s*(.+)`)
rePhase = regexp.MustCompile(`(?m)^> Phase:\s*(\d+)`) rePhase = regexp.MustCompile(`(?m)^> Phase:\s*(\d+)`)
rePhaseHeader = regexp.MustCompile(`(?m)^## Phase (\d+):\s*(.*)`) rePhaseHeader = regexp.MustCompile(`(?m)^## Phase (\d+):\s*(.*)`)
reStepDone = regexp.MustCompile(`(?m)^- \[x\] `)
reStepTodo = regexp.MustCompile(`(?m)^- \[ \] `)
reWorkDir = regexp.MustCompile(`(?m)^> WorkDir:\s*(.+)`) reWorkDir = regexp.MustCompile(`(?m)^> WorkDir:\s*(.+)`)
) )
@ -182,16 +180,16 @@ func (ms *MemoryStore) GetCurrentPhase() int {
func (ms *MemoryStore) GetTotalPhases() int { func (ms *MemoryStore) GetTotalPhases() int {
content := ms.ReadLongTerm() content := ms.ReadLongTerm()
matches := rePhaseHeader.FindAllStringSubmatch(content, -1) matches := rePhaseHeader.FindAllStringSubmatch(content, -1)
max := 0 maxN := 0
for _, m := range matches { for _, m := range matches {
if len(m) >= 2 { if len(m) >= 2 {
n, _ := strconv.Atoi(m[1]) n, _ := strconv.Atoi(m[1])
if n > max { if n > maxN {
max = n maxN = n
} }
} }
} }
return max return maxN
} }
// IsPlanComplete returns true if all steps in all phases are [x]. // IsPlanComplete returns true if all steps in all phases are [x].
@ -638,16 +636,16 @@ func (ms *MemoryStore) getPlanContextFrom(content string) string {
// maxPhaseNumber returns the highest phase number found in content. // maxPhaseNumber returns the highest phase number found in content.
func maxPhaseNumber(content string) int { func maxPhaseNumber(content string) int {
matches := rePhaseHeader.FindAllStringSubmatch(content, -1) matches := rePhaseHeader.FindAllStringSubmatch(content, -1)
max := 0 maxN := 0
for _, m := range matches { for _, m := range matches {
if len(m) >= 2 { if len(m) >= 2 {
n, _ := strconv.Atoi(m[1]) n, _ := strconv.Atoi(m[1])
if n > max { if n > maxN {
max = n maxN = n
} }
} }
} }
return max return maxN
} }
// getPhaseTitle extracts the title of a phase from "## Phase N: Title". // getPhaseTitle extracts the title of a phase from "## Phase N: Title".

View file

@ -26,7 +26,7 @@ import (
// buildInitData constructs a valid initData string from params and a bot token. // buildInitData constructs a valid initData string from params and a bot token.
func buildInitData(params map[string]string, botToken string) string { func buildInitData(params map[string]string, botToken string) string {
// Build data-check-string // Build data-check-string
var pairs []string pairs := make([]string, 0, len(params))
for k, v := range params { for k, v := range params {
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v)) pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
} }

View file

@ -19,7 +19,7 @@ type Event struct {
From string `json:"from,omitempty"` From string `json:"from,omitempty"`
To string `json:"to,omitempty"` To string `json:"to,omitempty"`
Text string `json:"text,omitempty"` Text string `json:"text,omitempty"`
Reason string `json:"reason,omitempty"` // agent_gc: completed | failed | cancelled Reason string `json:"reason,omitempty"` // agent_gc: completed | failed | canceled
Created int64 `json:"created,omitempty"` Created int64 `json:"created,omitempty"`
} }

View file

@ -1086,8 +1086,9 @@ func TestExtractXMLToolCalls_MismatchedCloseTag(t *testing.T) {
} }
} }
func TestStripXMLToolCalls_MismatchedCloseTag(t *testing.T) { //nolint:gosmopolitan // CJK test data func TestStripXMLToolCalls_MismatchedCloseTag(t *testing.T) {
text := `今テスト走らせるね text := `今テスト走らせるね。` + //nolint:gosmopolitan // CJK test data
`
<minimax:toolcall> <minimax:toolcall>
<invoke name="exec"> <invoke name="exec">
<parameter name="command">cd /home/user && pytest</parameter> <parameter name="command">cd /home/user && pytest</parameter>
@ -1098,7 +1099,7 @@ func TestStripXMLToolCalls_MismatchedCloseTag(t *testing.T) { //nolint:gosmopoli
if strings.Contains(got, "toolcall") || strings.Contains(got, "tool_call") { if strings.Contains(got, "toolcall") || strings.Contains(got, "tool_call") {
t.Errorf("should remove XML block, got %q", got) t.Errorf("should remove XML block, got %q", got)
} }
if !strings.Contains(got, "今テスト走らせるね。") { if !strings.Contains(got, "今テスト走らせるね。") { //nolint:gosmopolitan // CJK test data
t.Errorf("should keep text before, got %q", got) t.Errorf("should keep text before, got %q", got)
} }
} }
@ -1163,7 +1164,7 @@ Finished.`
func TestExtractXMLToolCalls_OrphanedClosingTag(t *testing.T) { func TestExtractXMLToolCalls_OrphanedClosingTag(t *testing.T) {
// LLM emits [TOOLCALL] marker + <invoke> with orphaned closing tag (no opening tag) // LLM emits [TOOLCALL] marker + <invoke> with orphaned closing tag (no opening tag)
text := "了解!確認するね。\n[TOOLCALL]\n<invoke name=\"listdir\">\n<parameter name=\"path\">/home/user/workspace</parameter>\n</invoke>\n</minimax:tool_call>" text := "了解!確認するね。\n[TOOLCALL]\n<invoke name=\"listdir\">\n<parameter name=\"path\">/home/user/workspace</parameter>\n</invoke>\n</minimax:tool_call>" //nolint:gosmopolitan // CJK test data
calls := extractXMLToolCalls(text) calls := extractXMLToolCalls(text)
if len(calls) != 1 { if len(calls) != 1 {
@ -1178,12 +1179,12 @@ func TestExtractXMLToolCalls_OrphanedClosingTag(t *testing.T) {
} }
func TestStripXMLToolCalls_OrphanedClosingTag(t *testing.T) { func TestStripXMLToolCalls_OrphanedClosingTag(t *testing.T) {
text := "了解!確認するね。\n[TOOLCALL]\n<invoke name=\"listdir\">\n<parameter name=\"path\">/home/user</parameter>\n</invoke>\n</minimax:tool_call>" text := "了解!確認するね。\n[TOOLCALL]\n<invoke name=\"listdir\">\n<parameter name=\"path\">/home/user</parameter>\n</invoke>\n</minimax:tool_call>" //nolint:gosmopolitan // CJK test data
got := stripXMLToolCalls(text) got := stripXMLToolCalls(text)
if strings.Contains(got, "invoke") || strings.Contains(got, "TOOLCALL") || strings.Contains(got, "minimax") { if strings.Contains(got, "invoke") || strings.Contains(got, "TOOLCALL") || strings.Contains(got, "minimax") {
t.Errorf("should remove orphaned closing tag block, got %q", got) t.Errorf("should remove orphaned closing tag block, got %q", got)
} }
if !strings.Contains(got, "了解") { if !strings.Contains(got, "了解") { //nolint:gosmopolitan // CJK test data
t.Errorf("should keep user-facing text, got %q", got) t.Errorf("should keep user-facing text, got %q", got)
} }
} }

View file

@ -259,7 +259,7 @@ func (p *Provider) CanStream() bool {
// ChatStream opens an SSE connection and returns a channel of StreamEvent. // ChatStream opens an SSE connection and returns a channel of StreamEvent.
// The channel is closed when the stream ends or an error occurs. // The channel is closed when the stream ends or an error occurs.
// Cancelling ctx will abort the HTTP request and close the channel. // Canceling ctx will abort the HTTP request and close the channel.
func (p *Provider) ChatStream( func (p *Provider) ChatStream(
ctx context.Context, ctx context.Context,
messages []Message, messages []Message,
@ -294,7 +294,7 @@ func (p *Provider) ChatStream(
} }
// readSSEIntoChannel reads SSE lines from r and sends StreamEvent values on ch. // readSSEIntoChannel reads SSE lines from r and sends StreamEvent values on ch.
// It returns when the stream ends, an error occurs, or ctx is cancelled. // It returns when the stream ends, an error occurs, or ctx is canceled.
func readSSEIntoChannel(ctx context.Context, r io.Reader, ch chan<- protocoltypes.StreamEvent) { func readSSEIntoChannel(ctx context.Context, r io.Reader, ch chan<- protocoltypes.StreamEvent) {
scanner := bufio.NewScanner(r) scanner := bufio.NewScanner(r)
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)

View file

@ -519,7 +519,7 @@ func TestReadSSEIntoChannel_TextAndToolCalls(t *testing.T) {
} }
func TestReadSSEIntoChannel_ContextCancel(t *testing.T) { func TestReadSSEIntoChannel_ContextCancel(t *testing.T) {
// Simulate a slow SSE stream that gets cancelled. // Simulate a slow SSE stream that gets canceled.
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
// Create a reader that blocks after sending one chunk. // Create a reader that blocks after sending one chunk.

View file

@ -51,7 +51,7 @@ func NewTracker(workspace string) *Tracker {
} }
t.load() t.load()
// Initialise Since if this is a fresh tracker // Initialize Since if this is a fresh tracker
if t.stats.Since.IsZero() { if t.stats.Since.IsZero() {
t.stats.Since = time.Now() t.stats.Since = time.Now()
} }
@ -104,7 +104,7 @@ func (t *Tracker) GetStats() Stats {
return t.stats return t.stats
} }
// Reset zeroes all counters and re-initialises Since. // Reset zeroes all counters and re-initializes Since.
func (t *Tracker) Reset() { func (t *Tracker) Reset() {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()

View file

@ -191,7 +191,7 @@ func (t *BgMonitorTool) actionWatch(ctx context.Context, args map[string]any) *T
IsError: true, IsError: true,
} }
case <-ctx.Done(): case <-ctx.Done():
return ErrorResult("watch cancelled") return ErrorResult("watch canceled")
case <-ticker.C: case <-ticker.C:
// Continue polling // Continue polling
} }

View file

@ -65,7 +65,7 @@ func (m *mockDevTargetManager) GetDevTarget() string {
} }
func (m *mockDevTargetManager) ListDevTargets() []miniapp.DevTarget { func (m *mockDevTargetManager) ListDevTargets() []miniapp.DevTarget {
var out []miniapp.DevTarget out := make([]miniapp.DevTarget, 0, len(m.targets))
for _, dt := range m.targets { for _, dt := range m.targets {
out = append(out, *dt) out = append(out, *dt)
} }

View file

@ -10,9 +10,9 @@ import (
"github.com/sipeed/picoclaw/pkg/providers" "github.com/sipeed/picoclaw/pkg/providers"
) )
// blockingProvider blocks inside Chat until the context is cancelled. // blockingProvider blocks inside Chat until the context is canceled.
// The ready channel is closed the moment Chat is entered, so callers can // The ready channel is closed the moment Chat is entered, so callers can
// synchronise before cancelling the context. // synchronize before canceling the context.
type blockingProvider struct { type blockingProvider struct {
ready chan struct{} ready chan struct{}
} }
@ -179,14 +179,14 @@ func TestSubagentManager_Spawn_SnapshotLiveDuringExecution(t *testing.T) {
} }
// TestSubagentManager_Spawn_CancelledDuringExecution verifies that when the // TestSubagentManager_Spawn_CancelledDuringExecution verifies that when the
// context is cancelled while a subagent's LLM call is in progress, the // context is canceled while a subagent's LLM call is in progress, the
// Broadcaster receives agent_gc with reason="canceled" and the agent is // Broadcaster receives agent_gc with reason="canceled" and the agent is
// removed from the snapshot. // removed from the snapshot.
// //
// Synchronisation: // Synchronization:
// 1. blockingProvider.ready is closed when Chat() is entered (goroutine is // 1. blockingProvider.ready is closed when Chat() is entered (goroutine is
// now blocked inside the LLM call). // now blocked inside the LLM call).
// 2. Only then is the context cancelled, so there is no race between spawn // 2. Only then is the context canceled, so there is no race between spawn
// and cancellation. // and cancellation.
func TestSubagentManager_Spawn_CancelledDuringExecution(t *testing.T) { func TestSubagentManager_Spawn_CancelledDuringExecution(t *testing.T) {
b := orch.NewBroadcaster() b := orch.NewBroadcaster()

View file

@ -59,7 +59,7 @@ func TestStripThinkBlocks_ClosedThenUnclosed(t *testing.T) {
func TestDetectRepetitionLoop_HighRepetition(t *testing.T) { func TestDetectRepetitionLoop_HighRepetition(t *testing.T) {
// Repeat a short phrase many times → should be detected // Repeat a short phrase many times → should be detected
phrase := "結構本格的なコード" phrase := "結構本格的なコード" //nolint:gosmopolitan // CJK test data
repeated := strings.Repeat(phrase, 300) repeated := strings.Repeat(phrase, 300)
if !DetectRepetitionLoop(repeated) { if !DetectRepetitionLoop(repeated) {
t.Fatal("DetectRepetitionLoop should return true for highly repetitive text") t.Fatal("DetectRepetitionLoop should return true for highly repetitive text")