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:
parent
b90f49970e
commit
d8f00d4148
15 changed files with 55 additions and 69 deletions
|
|
@ -4,11 +4,13 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -216,7 +218,8 @@ func gatewayCmd(debug bool, orchestration bool) error {
|
|||
if certErr != nil {
|
||||
logger.ErrorCF("miniapp", "Failed to fetch TLS cert", map[string]any{"error": certErr.Error()})
|
||||
} 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
|
||||
tlsCert, tlsKey = certFile, keyFile
|
||||
useTLS = true
|
||||
|
|
@ -268,7 +271,10 @@ func gatewayCmd(debug bool, orchestration bool) error {
|
|||
cfg.Gateway.Port,
|
||||
)
|
||||
} 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)
|
||||
|
|
|
|||
|
|
@ -787,25 +787,6 @@ func (cb *ContextBuilder) AddAssistantMessage(
|
|||
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.
|
||||
func (cb *ContextBuilder) LoadSkill(name string) (string, bool) {
|
||||
return cb.skillsLoader.LoadSkill(name)
|
||||
|
|
|
|||
|
|
@ -645,7 +645,8 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
|||
lower := strings.ToLower(content)
|
||||
|
||||
// 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
|
||||
for _, kw := range stopKeywords {
|
||||
if lower == kw {
|
||||
|
|
@ -1999,13 +2000,13 @@ func (al *AgentLoop) runLLMIteration(
|
|||
if sp, ok := p.(providers.StreamingProvider); ok && sp.CanStream() {
|
||||
streamCtx, streamCancel := context.WithCancel(ctx)
|
||||
defer streamCancel()
|
||||
ch, err := sp.ChatStream(streamCtx, messages, providerToolDefs, model, opts_)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
ch, sErr := sp.ChatStream(streamCtx, messages, providerToolDefs, model, opts_)
|
||||
if sErr != nil {
|
||||
return nil, sErr
|
||||
}
|
||||
resp, repetition, err := consumeStreamWithRepetitionDetection(ch, streamCancel, 1000, onChunk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
resp, repetition, sErr := consumeStreamWithRepetitionDetection(ch, streamCancel, 1000, onChunk)
|
||||
if sErr != nil {
|
||||
return nil, sErr
|
||||
}
|
||||
if repetition {
|
||||
resp.FinishReason = "repetition_detected"
|
||||
|
|
@ -2156,7 +2157,7 @@ func (al *AgentLoop) runLLMIteration(
|
|||
// blocks so loops inside <think> are caught). Skip when the
|
||||
// provider already returned native tool calls.
|
||||
// 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" ||
|
||||
(len(response.ToolCalls) == 0 && utils.DetectRepetitionLoop(response.Content)) {
|
||||
logger.WarnCF("agent", "Repetition loop detected in LLM response, retrying",
|
||||
|
|
@ -2603,7 +2604,6 @@ func (al *AgentLoop) runLLMIteration(
|
|||
al.lastSystemPrompt.Store(newPrompt)
|
||||
al.promptDirty.Store(false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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()
|
||||
display = mem.FormatPlanDisplay()
|
||||
memory = mem.ReadLongTerm()
|
||||
return
|
||||
return hasPlan, status, currentPhase, totalPhases, display, memory
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
bootstrap = agent.ContextBuilder.ResolveBootstrapPaths()
|
||||
return
|
||||
return workDir, planWorkDir, workspace, bootstrap
|
||||
}
|
||||
|
||||
// GetSystemPrompt returns the system prompt last sent to the LLM.
|
||||
|
|
|
|||
|
|
@ -2513,8 +2513,8 @@ func TestAgentLoop_PlanModel_UsedDuringInterviewing(t *testing.T) {
|
|||
os.MkdirAll(memoryDir, 0o755)
|
||||
memoryPath := filepath.Join(memoryDir, "MEMORY.md")
|
||||
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 {
|
||||
t.Fatalf("Failed to write MEMORY.md: %v", err)
|
||||
if wErr := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); wErr != nil {
|
||||
t.Fatalf("Failed to write MEMORY.md: %v", wErr)
|
||||
}
|
||||
|
||||
_, err = al.ProcessDirectWithChannel(
|
||||
|
|
@ -2581,8 +2581,8 @@ func TestAgentLoop_PlanModel_NotUsedDuringExecuting(t *testing.T) {
|
|||
## Phase 1: Build
|
||||
- [ ] Run build
|
||||
`
|
||||
if err := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); err != nil {
|
||||
t.Fatalf("Failed to write MEMORY.md: %v", err)
|
||||
if wErr := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); wErr != nil {
|
||||
t.Fatalf("Failed to write MEMORY.md: %v", wErr)
|
||||
}
|
||||
|
||||
_, err = al.ProcessDirectWithChannel(
|
||||
|
|
@ -2642,8 +2642,8 @@ func TestAgentLoop_PlanModel_ResolvesProviderForSingleCandidate(t *testing.T) {
|
|||
os.MkdirAll(memoryDir, 0o755)
|
||||
memoryPath := filepath.Join(memoryDir, "MEMORY.md")
|
||||
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 {
|
||||
t.Fatalf("Failed to write MEMORY.md: %v", err)
|
||||
if wErr := os.WriteFile(memoryPath, []byte(memoryContent), 0o644); wErr != nil {
|
||||
t.Fatalf("Failed to write MEMORY.md: %v", wErr)
|
||||
}
|
||||
|
||||
_, err = al.ProcessDirectWithChannel(
|
||||
|
|
|
|||
|
|
@ -146,8 +146,6 @@ var (
|
|||
reStatus = regexp.MustCompile(`(?m)^> Status:\s*(.+)`)
|
||||
rePhase = regexp.MustCompile(`(?m)^> Phase:\s*(\d+)`)
|
||||
rePhaseHeader = regexp.MustCompile(`(?m)^## Phase (\d+):\s*(.*)`)
|
||||
reStepDone = regexp.MustCompile(`(?m)^- \[x\] `)
|
||||
reStepTodo = regexp.MustCompile(`(?m)^- \[ \] `)
|
||||
reWorkDir = regexp.MustCompile(`(?m)^> WorkDir:\s*(.+)`)
|
||||
)
|
||||
|
||||
|
|
@ -182,16 +180,16 @@ func (ms *MemoryStore) GetCurrentPhase() int {
|
|||
func (ms *MemoryStore) GetTotalPhases() int {
|
||||
content := ms.ReadLongTerm()
|
||||
matches := rePhaseHeader.FindAllStringSubmatch(content, -1)
|
||||
max := 0
|
||||
maxN := 0
|
||||
for _, m := range matches {
|
||||
if len(m) >= 2 {
|
||||
n, _ := strconv.Atoi(m[1])
|
||||
if n > max {
|
||||
max = n
|
||||
if n > maxN {
|
||||
maxN = n
|
||||
}
|
||||
}
|
||||
}
|
||||
return max
|
||||
return maxN
|
||||
}
|
||||
|
||||
// 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.
|
||||
func maxPhaseNumber(content string) int {
|
||||
matches := rePhaseHeader.FindAllStringSubmatch(content, -1)
|
||||
max := 0
|
||||
maxN := 0
|
||||
for _, m := range matches {
|
||||
if len(m) >= 2 {
|
||||
n, _ := strconv.Atoi(m[1])
|
||||
if n > max {
|
||||
max = n
|
||||
if n > maxN {
|
||||
maxN = n
|
||||
}
|
||||
}
|
||||
}
|
||||
return max
|
||||
return maxN
|
||||
}
|
||||
|
||||
// getPhaseTitle extracts the title of a phase from "## Phase N: Title".
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import (
|
|||
// buildInitData constructs a valid initData string from params and a bot token.
|
||||
func buildInitData(params map[string]string, botToken string) string {
|
||||
// Build data-check-string
|
||||
var pairs []string
|
||||
pairs := make([]string, 0, len(params))
|
||||
for k, v := range params {
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ type Event struct {
|
|||
From string `json:"from,omitempty"`
|
||||
To string `json:"to,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"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1086,8 +1086,9 @@ func TestExtractXMLToolCalls_MismatchedCloseTag(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestStripXMLToolCalls_MismatchedCloseTag(t *testing.T) { //nolint:gosmopolitan // CJK test data
|
||||
text := `今テスト走らせるね。
|
||||
func TestStripXMLToolCalls_MismatchedCloseTag(t *testing.T) {
|
||||
text := `今テスト走らせるね。` + //nolint:gosmopolitan // CJK test data
|
||||
`
|
||||
<minimax:toolcall>
|
||||
<invoke name="exec">
|
||||
<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") {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1163,7 +1164,7 @@ Finished.`
|
|||
|
||||
func TestExtractXMLToolCalls_OrphanedClosingTag(t *testing.T) {
|
||||
// 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)
|
||||
if len(calls) != 1 {
|
||||
|
|
@ -1178,12 +1179,12 @@ func TestExtractXMLToolCalls_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)
|
||||
if strings.Contains(got, "invoke") || strings.Contains(got, "TOOLCALL") || strings.Contains(got, "minimax") {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ func (p *Provider) CanStream() bool {
|
|||
|
||||
// ChatStream opens an SSE connection and returns a channel of StreamEvent.
|
||||
// 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(
|
||||
ctx context.Context,
|
||||
messages []Message,
|
||||
|
|
@ -294,7 +294,7 @@ func (p *Provider) ChatStream(
|
|||
}
|
||||
|
||||
// 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) {
|
||||
scanner := bufio.NewScanner(r)
|
||||
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
||||
|
|
|
|||
|
|
@ -519,7 +519,7 @@ func TestReadSSEIntoChannel_TextAndToolCalls(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())
|
||||
|
||||
// Create a reader that blocks after sending one chunk.
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ func NewTracker(workspace string) *Tracker {
|
|||
}
|
||||
t.load()
|
||||
|
||||
// Initialise Since if this is a fresh tracker
|
||||
// Initialize Since if this is a fresh tracker
|
||||
if t.stats.Since.IsZero() {
|
||||
t.stats.Since = time.Now()
|
||||
}
|
||||
|
|
@ -104,7 +104,7 @@ func (t *Tracker) GetStats() Stats {
|
|||
return t.stats
|
||||
}
|
||||
|
||||
// Reset zeroes all counters and re-initialises Since.
|
||||
// Reset zeroes all counters and re-initializes Since.
|
||||
func (t *Tracker) Reset() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ func (t *BgMonitorTool) actionWatch(ctx context.Context, args map[string]any) *T
|
|||
IsError: true,
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ErrorResult("watch cancelled")
|
||||
return ErrorResult("watch canceled")
|
||||
case <-ticker.C:
|
||||
// Continue polling
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ func (m *mockDevTargetManager) GetDevTarget() string {
|
|||
}
|
||||
|
||||
func (m *mockDevTargetManager) ListDevTargets() []miniapp.DevTarget {
|
||||
var out []miniapp.DevTarget
|
||||
out := make([]miniapp.DevTarget, 0, len(m.targets))
|
||||
for _, dt := range m.targets {
|
||||
out = append(out, *dt)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ import (
|
|||
"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
|
||||
// synchronise before cancelling the context.
|
||||
// synchronize before canceling the context.
|
||||
type blockingProvider struct {
|
||||
ready chan struct{}
|
||||
}
|
||||
|
|
@ -179,14 +179,14 @@ func TestSubagentManager_Spawn_SnapshotLiveDuringExecution(t *testing.T) {
|
|||
}
|
||||
|
||||
// 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
|
||||
// removed from the snapshot.
|
||||
//
|
||||
// Synchronisation:
|
||||
// Synchronization:
|
||||
// 1. blockingProvider.ready is closed when Chat() is entered (goroutine is
|
||||
// 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.
|
||||
func TestSubagentManager_Spawn_CancelledDuringExecution(t *testing.T) {
|
||||
b := orch.NewBroadcaster()
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func TestStripThinkBlocks_ClosedThenUnclosed(t *testing.T) {
|
|||
|
||||
func TestDetectRepetitionLoop_HighRepetition(t *testing.T) {
|
||||
// Repeat a short phrase many times → should be detected
|
||||
phrase := "結構本格的なコード"
|
||||
phrase := "結構本格的なコード" //nolint:gosmopolitan // CJK test data
|
||||
repeated := strings.Repeat(phrase, 300)
|
||||
if !DetectRepetitionLoop(repeated) {
|
||||
t.Fatal("DetectRepetitionLoop should return true for highly repetitive text")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue