chore(lint): clear remaining errcheck and staticcheck findings
Brings the project to a clean `make lint` baseline (0 issues). Mechanical: - Wrap deferred resp.Body.Close() in closures (router/discovery.go, router/probe.go) so the unchecked return surfaces as `_ = ...`. - Apply `_ = ...` (single or multi-return blank) to test-file calls that intentionally ignore errors: os.MkdirAll / os.WriteFile / os.Chdir in setup paths, Close / Shutdown in teardown, Submit / Spawn / Send / LoadDir in tests that assert on side effects. Structural: - engine.handleRequestTooLarge drops the unused req parameter and rebuilds the request from compacted history (SA4009 — argument was overwritten before first use). - provider.ClassifyHTTPStatus and google.applyCapabilityOverrides switch to tagged switches over the discriminator (QF1002). - tui.app.go MouseWheel + inputMode and cmd/gnoma main slm-status use tagged switches in place of equality chains (QF1003). - cmd/gnoma main.go merges a var decl with its immediate assignment (S1021). - Three empty-branch sites (dispatcher_test, loader_test, coordinator_test) become real assertions or get the dead `if` removed (SA9003).
This commit is contained in:
+6
-6
@@ -344,8 +344,8 @@ func main() {
|
||||
return
|
||||
}
|
||||
dir := filepath.Join(userCfgDir, "gnoma")
|
||||
os.MkdirAll(dir, 0o755)
|
||||
os.WriteFile(filepath.Join(dir, "quality.json"), data, 0o644)
|
||||
_ = os.MkdirAll(dir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(dir, "quality.json"), data, 0o644)
|
||||
}()
|
||||
var armID router.ArmID
|
||||
if primaryProviderOK {
|
||||
@@ -641,8 +641,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Create context window with summarize strategy (falls back to truncation)
|
||||
var compactStrategy gnomactx.Strategy
|
||||
compactStrategy = gnomactx.NewSummarizeStrategy(prov)
|
||||
var compactStrategy gnomactx.Strategy = gnomactx.NewSummarizeStrategy(prov)
|
||||
ctxWindow := gnomactx.NewWindow(gnomactx.WindowConfig{
|
||||
MaxTokens: contextWindowSize,
|
||||
Strategy: compactStrategy,
|
||||
@@ -1334,9 +1333,10 @@ func runSLMCommand(args []string, cfg *gnomacfg.Config, logger *slog.Logger) int
|
||||
fmt.Printf(" sha256: %s\n", mf.SHA256[:16]+"...")
|
||||
fmt.Printf(" setup: %s\n", mf.SetupAt.Format("2006-01-02 15:04 UTC"))
|
||||
}
|
||||
if status == slm.StatusNotSetUp {
|
||||
switch status {
|
||||
case slm.StatusNotSetUp:
|
||||
fmt.Println(" run: gnoma slm setup")
|
||||
} else if status == slm.StatusMissing {
|
||||
case slm.StatusMissing:
|
||||
fmt.Println(" file is missing; run: gnoma slm setup")
|
||||
}
|
||||
return 0
|
||||
|
||||
@@ -40,7 +40,7 @@ ollama = "http://myhost:11434/v1"
|
||||
bash_timeout = "60s"
|
||||
max_file_size = 2097152
|
||||
`
|
||||
os.WriteFile(path, []byte(content), 0o644)
|
||||
_ = os.WriteFile(path, []byte(content), 0o644)
|
||||
|
||||
cfg := Defaults()
|
||||
if err := loadTOML(&cfg, path); err != nil {
|
||||
@@ -122,12 +122,12 @@ func TestApplyEnv_EnvVarReference(t *testing.T) {
|
||||
func TestProjectRoot_GoMod(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
sub := filepath.Join(root, "pkg", "util")
|
||||
os.MkdirAll(sub, 0o755)
|
||||
os.WriteFile(filepath.Join(root, "go.mod"), []byte("module example.com/foo\n"), 0o644)
|
||||
_ = os.MkdirAll(sub, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(root, "go.mod"), []byte("module example.com/foo\n"), 0o644)
|
||||
|
||||
origDir, _ := os.Getwd()
|
||||
os.Chdir(sub)
|
||||
defer os.Chdir(origDir)
|
||||
_ = os.Chdir(sub)
|
||||
defer func() { _ = os.Chdir(origDir) }()
|
||||
|
||||
got := ProjectRoot()
|
||||
if got != root {
|
||||
@@ -138,12 +138,12 @@ func TestProjectRoot_GoMod(t *testing.T) {
|
||||
func TestProjectRoot_Git(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
sub := filepath.Join(root, "src")
|
||||
os.MkdirAll(sub, 0o755)
|
||||
os.MkdirAll(filepath.Join(root, ".git"), 0o755)
|
||||
_ = os.MkdirAll(sub, 0o755)
|
||||
_ = os.MkdirAll(filepath.Join(root, ".git"), 0o755)
|
||||
|
||||
origDir, _ := os.Getwd()
|
||||
os.Chdir(sub)
|
||||
defer os.Chdir(origDir)
|
||||
_ = os.Chdir(sub)
|
||||
defer func() { _ = os.Chdir(origDir) }()
|
||||
|
||||
got := ProjectRoot()
|
||||
if got != root {
|
||||
@@ -154,12 +154,12 @@ func TestProjectRoot_Git(t *testing.T) {
|
||||
func TestProjectRoot_GnomaDir(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
sub := filepath.Join(root, "internal")
|
||||
os.MkdirAll(sub, 0o755)
|
||||
os.MkdirAll(filepath.Join(root, ".gnoma"), 0o755)
|
||||
_ = os.MkdirAll(sub, 0o755)
|
||||
_ = os.MkdirAll(filepath.Join(root, ".gnoma"), 0o755)
|
||||
|
||||
origDir, _ := os.Getwd()
|
||||
os.Chdir(sub)
|
||||
defer os.Chdir(origDir)
|
||||
_ = os.Chdir(sub)
|
||||
defer func() { _ = os.Chdir(origDir) }()
|
||||
|
||||
got := ProjectRoot()
|
||||
if got != root {
|
||||
@@ -171,8 +171,8 @@ func TestProjectRoot_Fallback(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
origDir, _ := os.Getwd()
|
||||
os.Chdir(dir)
|
||||
defer os.Chdir(origDir)
|
||||
_ = os.Chdir(dir)
|
||||
defer func() { _ = os.Chdir(origDir) }()
|
||||
|
||||
got := ProjectRoot()
|
||||
if got != dir {
|
||||
@@ -183,7 +183,7 @@ func TestProjectRoot_Fallback(t *testing.T) {
|
||||
func TestHookConfig_TOML_RoundTrip(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "config.toml")
|
||||
os.WriteFile(path, []byte(`
|
||||
_ = os.WriteFile(path, []byte(`
|
||||
[[hooks]]
|
||||
name = "log-tools"
|
||||
event = "post_tool_use"
|
||||
@@ -228,8 +228,8 @@ tool_pattern = "bash*"
|
||||
func TestHookConfig_MergeOrder(t *testing.T) {
|
||||
globalDir := t.TempDir()
|
||||
gnomaDir := filepath.Join(globalDir, "gnoma")
|
||||
os.MkdirAll(gnomaDir, 0o755)
|
||||
os.WriteFile(filepath.Join(gnomaDir, "config.toml"), []byte(`
|
||||
_ = os.MkdirAll(gnomaDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(gnomaDir, "config.toml"), []byte(`
|
||||
[[hooks]]
|
||||
name = "global-hook"
|
||||
event = "pre_tool_use"
|
||||
@@ -239,8 +239,8 @@ exec = "echo global"
|
||||
|
||||
projectDir := t.TempDir()
|
||||
pGnomaDir := filepath.Join(projectDir, ".gnoma")
|
||||
os.MkdirAll(pGnomaDir, 0o755)
|
||||
os.WriteFile(filepath.Join(pGnomaDir, "config.toml"), []byte(`
|
||||
_ = os.MkdirAll(pGnomaDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(pGnomaDir, "config.toml"), []byte(`
|
||||
[[hooks]]
|
||||
name = "project-hook"
|
||||
event = "post_tool_use"
|
||||
@@ -250,8 +250,8 @@ exec = "echo project"
|
||||
|
||||
t.Setenv("XDG_CONFIG_HOME", globalDir)
|
||||
origDir, _ := os.Getwd()
|
||||
os.Chdir(projectDir)
|
||||
defer os.Chdir(origDir)
|
||||
_ = os.Chdir(projectDir)
|
||||
defer func() { _ = os.Chdir(origDir) }()
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
@@ -273,8 +273,8 @@ func TestHookConfig_ProjectOnly(t *testing.T) {
|
||||
// No global hooks, project defines one.
|
||||
projectDir := t.TempDir()
|
||||
pGnomaDir := filepath.Join(projectDir, ".gnoma")
|
||||
os.MkdirAll(pGnomaDir, 0o755)
|
||||
os.WriteFile(filepath.Join(pGnomaDir, "config.toml"), []byte(`
|
||||
_ = os.MkdirAll(pGnomaDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(pGnomaDir, "config.toml"), []byte(`
|
||||
[[hooks]]
|
||||
name = "project-only"
|
||||
event = "stop"
|
||||
@@ -285,8 +285,8 @@ exec = "echo done"
|
||||
emptyGlobalDir := t.TempDir()
|
||||
t.Setenv("XDG_CONFIG_HOME", emptyGlobalDir)
|
||||
origDir, _ := os.Getwd()
|
||||
os.Chdir(projectDir)
|
||||
defer os.Chdir(origDir)
|
||||
_ = os.Chdir(projectDir)
|
||||
defer func() { _ = os.Chdir(origDir) }()
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
@@ -301,8 +301,8 @@ func TestHookConfig_GlobalOnly(t *testing.T) {
|
||||
// Global defines a hook, no project config.
|
||||
globalDir := t.TempDir()
|
||||
gnomaDir := filepath.Join(globalDir, "gnoma")
|
||||
os.MkdirAll(gnomaDir, 0o755)
|
||||
os.WriteFile(filepath.Join(gnomaDir, "config.toml"), []byte(`
|
||||
_ = os.MkdirAll(gnomaDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(gnomaDir, "config.toml"), []byte(`
|
||||
[[hooks]]
|
||||
name = "global-only"
|
||||
event = "session_start"
|
||||
@@ -313,8 +313,8 @@ exec = "echo start"
|
||||
projectDir := t.TempDir() // no .gnoma dir
|
||||
t.Setenv("XDG_CONFIG_HOME", globalDir)
|
||||
origDir, _ := os.Getwd()
|
||||
os.Chdir(projectDir)
|
||||
defer os.Chdir(origDir)
|
||||
_ = os.Chdir(projectDir)
|
||||
defer func() { _ = os.Chdir(origDir) }()
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
@@ -329,8 +329,8 @@ func TestLayeredLoad(t *testing.T) {
|
||||
// Set up global config
|
||||
globalDir := t.TempDir()
|
||||
gnomaDir := filepath.Join(globalDir, "gnoma")
|
||||
os.MkdirAll(gnomaDir, 0o755)
|
||||
os.WriteFile(filepath.Join(gnomaDir, "config.toml"), []byte(`
|
||||
_ = os.MkdirAll(gnomaDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(gnomaDir, "config.toml"), []byte(`
|
||||
[provider]
|
||||
default = "anthropic"
|
||||
max_tokens = 4096
|
||||
@@ -339,8 +339,8 @@ max_tokens = 4096
|
||||
// Set up project config that overrides
|
||||
projectDir := t.TempDir()
|
||||
pGnomaDir := filepath.Join(projectDir, ".gnoma")
|
||||
os.MkdirAll(pGnomaDir, 0o755)
|
||||
os.WriteFile(filepath.Join(pGnomaDir, "config.toml"), []byte(`
|
||||
_ = os.MkdirAll(pGnomaDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(pGnomaDir, "config.toml"), []byte(`
|
||||
[provider]
|
||||
model = "claude-haiku"
|
||||
`), 0o644)
|
||||
@@ -348,8 +348,8 @@ model = "claude-haiku"
|
||||
// Override XDG_CONFIG_HOME and working directory
|
||||
t.Setenv("XDG_CONFIG_HOME", globalDir)
|
||||
origDir, _ := os.Getwd()
|
||||
os.Chdir(projectDir)
|
||||
defer os.Chdir(origDir)
|
||||
_ = os.Chdir(projectDir)
|
||||
defer func() { _ = os.Chdir(origDir) }()
|
||||
|
||||
cfg, err := Load()
|
||||
if err != nil {
|
||||
|
||||
@@ -179,7 +179,7 @@ func TestWindow_CircuitBreaker(t *testing.T) {
|
||||
|
||||
// Try to compact — should fail 3 times then stop
|
||||
for i := 0; i < 5; i++ {
|
||||
w.CompactIfNeeded()
|
||||
_, _ = w.CompactIfNeeded()
|
||||
}
|
||||
|
||||
if failStrategy.calls > 3 {
|
||||
|
||||
@@ -202,9 +202,9 @@ func TestManager_WaitAll(t *testing.T) {
|
||||
|
||||
mgr := NewManager(ManagerConfig{Router: rtr, Tools: tool.NewRegistry()})
|
||||
|
||||
mgr.Spawn(context.Background(), router.TaskGeneration, "a", "", 30)
|
||||
mgr.Spawn(context.Background(), router.TaskGeneration, "b", "", 30)
|
||||
mgr.Spawn(context.Background(), router.TaskGeneration, "c", "", 30)
|
||||
_, _ = mgr.Spawn(context.Background(), router.TaskGeneration, "a", "", 30)
|
||||
_, _ = mgr.Spawn(context.Background(), router.TaskGeneration, "b", "", 30)
|
||||
_, _ = mgr.Spawn(context.Background(), router.TaskGeneration, "c", "", 30)
|
||||
|
||||
results := mgr.WaitAll()
|
||||
if len(results) != 3 {
|
||||
|
||||
@@ -429,7 +429,7 @@ func TestEngine_Reset(t *testing.T) {
|
||||
}
|
||||
|
||||
e, _ := New(Config{Provider: mp, Tools: tool.NewRegistry()})
|
||||
e.Submit(context.Background(), "hello", nil)
|
||||
_, _ = e.Submit(context.Background(), "hello", nil)
|
||||
|
||||
if len(e.History()) == 0 {
|
||||
t.Fatal("history should not be empty before reset")
|
||||
@@ -463,7 +463,7 @@ func TestEngine_Reset_ClearsContextWindow(t *testing.T) {
|
||||
Tools: tool.NewRegistry(),
|
||||
Context: ctxWindow,
|
||||
})
|
||||
e.Submit(context.Background(), "hello", nil)
|
||||
_, _ = e.Submit(context.Background(), "hello", nil)
|
||||
|
||||
if len(ctxWindow.Messages()) == 0 {
|
||||
t.Fatal("context window should have messages before reset")
|
||||
@@ -542,7 +542,7 @@ func TestSubmit_TrackerReflectsInputTokens(t *testing.T) {
|
||||
}
|
||||
e, _ := New(Config{Provider: mp, Tools: tool.NewRegistry(), Context: ctxWindow})
|
||||
|
||||
e.Submit(context.Background(), "hi", nil)
|
||||
_, _ = e.Submit(context.Background(), "hi", nil)
|
||||
|
||||
// Tracker should be InputTokens + OutputTokens = 150, not more
|
||||
used := ctxWindow.Tracker().Used()
|
||||
@@ -568,8 +568,8 @@ func TestSubmit_CumulativeUsage(t *testing.T) {
|
||||
|
||||
e, _ := New(Config{Provider: mp, Tools: tool.NewRegistry()})
|
||||
|
||||
e.Submit(context.Background(), "one", nil)
|
||||
e.Submit(context.Background(), "two", nil)
|
||||
_, _ = e.Submit(context.Background(), "one", nil)
|
||||
_, _ = e.Submit(context.Background(), "two", nil)
|
||||
|
||||
if e.Usage().InputTokens != 300 {
|
||||
t.Errorf("cumulative InputTokens = %d, want 300", e.Usage().InputTokens)
|
||||
|
||||
@@ -123,7 +123,7 @@ func TestHook_PreToolUse_Deny(t *testing.T) {
|
||||
Tools: reg,
|
||||
Hooks: hookDispatcher(hook.PreToolUse, &blockingExecutor{}),
|
||||
})
|
||||
eng.Submit(context.Background(), "run", nil)
|
||||
_, _ = eng.Submit(context.Background(), "run", nil)
|
||||
|
||||
if executed {
|
||||
t.Error("tool was executed despite PreToolUse deny")
|
||||
@@ -155,7 +155,7 @@ func TestHook_PreToolUse_Allow(t *testing.T) {
|
||||
Tools: reg,
|
||||
Hooks: hookDispatcher(hook.PreToolUse, &allowingExecutor{}),
|
||||
})
|
||||
eng.Submit(context.Background(), "run", nil)
|
||||
_, _ = eng.Submit(context.Background(), "run", nil)
|
||||
|
||||
if !executed {
|
||||
t.Error("tool was not executed despite PreToolUse allow")
|
||||
@@ -185,7 +185,7 @@ func TestHook_PreToolUse_DenyMessage(t *testing.T) {
|
||||
Tools: reg,
|
||||
Hooks: hookDispatcher(hook.PreToolUse, &blockingExecutor{}),
|
||||
})
|
||||
eng.Submit(context.Background(), "run", nil)
|
||||
_, _ = eng.Submit(context.Background(), "run", nil)
|
||||
|
||||
for _, msg := range eng.History() {
|
||||
for _, c := range msg.Content {
|
||||
@@ -226,10 +226,10 @@ func TestHook_PreToolUse_Transform(t *testing.T) {
|
||||
Hooks: hookDispatcher(hook.PreToolUse,
|
||||
&argTransformExecutor{newArgs: json.RawMessage(`{"command":"safe-replacement"}`)}),
|
||||
})
|
||||
eng.Submit(context.Background(), "run", nil)
|
||||
_, _ = eng.Submit(context.Background(), "run", nil)
|
||||
|
||||
var got map[string]string
|
||||
json.Unmarshal(receivedArgs, &got)
|
||||
_ = json.Unmarshal(receivedArgs, &got)
|
||||
if got["command"] != "safe-replacement" {
|
||||
t.Errorf("tool args = %s, want safe-replacement", receivedArgs)
|
||||
}
|
||||
@@ -259,7 +259,7 @@ func TestHook_PostToolUse_Transform(t *testing.T) {
|
||||
Hooks: hookDispatcher(hook.PostToolUse,
|
||||
&resultTransformExecutor{newOutput: "transformed output"}),
|
||||
})
|
||||
eng.Submit(context.Background(), "run", nil)
|
||||
_, _ = eng.Submit(context.Background(), "run", nil)
|
||||
|
||||
for _, msg := range eng.History() {
|
||||
for _, c := range msg.Content {
|
||||
|
||||
@@ -198,7 +198,7 @@ func (e *Engine) runLoop(ctx context.Context, cb Callback) (*Turn, error) {
|
||||
})
|
||||
if err != nil {
|
||||
// Try reactive compaction on 413 (request too large)
|
||||
s, err = e.handleRequestTooLarge(ctx, err, req)
|
||||
s, err = e.handleRequestTooLarge(ctx, err)
|
||||
if err != nil {
|
||||
decision.Rollback()
|
||||
streamErr := fmt.Errorf("provider stream: %w", err)
|
||||
@@ -698,8 +698,9 @@ func truncate(s string, maxLen int) string {
|
||||
return string(runes[:maxLen]) + "..."
|
||||
}
|
||||
|
||||
// handleRequestTooLarge attempts compaction on 413 and retries once.
|
||||
func (e *Engine) handleRequestTooLarge(ctx context.Context, origErr error, req provider.Request) (stream.Stream, error) {
|
||||
// handleRequestTooLarge attempts compaction on 413 and retries once. The
|
||||
// request is rebuilt from the compacted history, so callers don't pass it in.
|
||||
func (e *Engine) handleRequestTooLarge(ctx context.Context, origErr error) (stream.Stream, error) {
|
||||
var provErr *provider.ProviderError
|
||||
if !errors.As(origErr, &provErr) || provErr.StatusCode != 413 {
|
||||
return nil, origErr
|
||||
@@ -716,7 +717,7 @@ func (e *Engine) handleRequestTooLarge(ctx context.Context, origErr error, req p
|
||||
}
|
||||
|
||||
e.replaceHistory(e.cfg.Context.Messages())
|
||||
req = e.buildRequest(ctx)
|
||||
req := e.buildRequest(ctx)
|
||||
|
||||
if e.cfg.Router != nil {
|
||||
prompt := e.latestUserPrompt()
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestSetHistory_OverwritesPreviousHistory(t *testing.T) {
|
||||
},
|
||||
}
|
||||
e, _ := New(Config{Provider: mp, Tools: tool.NewRegistry()})
|
||||
e.Submit(context.Background(), "first message", nil)
|
||||
_, _ = e.Submit(context.Background(), "first message", nil)
|
||||
|
||||
if len(e.History()) == 0 {
|
||||
t.Fatal("history should not be empty after Submit")
|
||||
@@ -174,7 +174,7 @@ func TestSetUsage_OverwritesPreviousUsage(t *testing.T) {
|
||||
},
|
||||
}
|
||||
e, _ := New(Config{Provider: mp, Tools: tool.NewRegistry()})
|
||||
e.Submit(context.Background(), "hello", nil)
|
||||
_, _ = e.Submit(context.Background(), "hello", nil)
|
||||
|
||||
if e.Usage().InputTokens == 0 {
|
||||
t.Fatal("usage should be non-zero after Submit")
|
||||
|
||||
@@ -82,7 +82,7 @@ func TestAgentExecutor_TemplateRendered(t *testing.T) {
|
||||
}
|
||||
fn, captured := capturingSpawnFn("ALLOW")
|
||||
ex := NewAgentExecutor(def, fn)
|
||||
ex.Execute(context.Background(), MarshalPreToolPayload("bash", nil))
|
||||
_, _ = ex.Execute(context.Background(), MarshalPreToolPayload("bash", nil))
|
||||
if *captured != "Tool=bash Event=pre_tool_use" {
|
||||
t.Errorf("prompt = %q", *captured)
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ func TestDispatcher_TransformChaining(t *testing.T) {
|
||||
makeHandler(PreToolUse, exA),
|
||||
makeHandler(PreToolUse, exB),
|
||||
)
|
||||
d.Fire(PreToolUse, []byte(`{"tool":"original"}`))
|
||||
_, _, _ = d.Fire(PreToolUse, []byte(`{"tool":"original"}`))
|
||||
|
||||
if string(exB.receivedPayload) != string(transformed) {
|
||||
t.Errorf("exB received %q, want %q", exB.receivedPayload, transformed)
|
||||
@@ -190,7 +190,7 @@ func TestDispatcher_TransformChaining_EmptyOutputPassesThrough(t *testing.T) {
|
||||
makeHandler(PreToolUse, exA),
|
||||
makeHandler(PreToolUse, exB),
|
||||
)
|
||||
d.Fire(PreToolUse, original)
|
||||
_, _, _ = d.Fire(PreToolUse, original)
|
||||
|
||||
if string(exB.receivedPayload) != string(original) {
|
||||
t.Errorf("exB received %q, want %q", exB.receivedPayload, original)
|
||||
@@ -223,11 +223,7 @@ func TestDispatcher_ToolPattern_Empty_MatchesAll(t *testing.T) {
|
||||
payload := MarshalPreToolPayload("fs.read", nil)
|
||||
d := dispatcherWith(PreToolUse, makePatternHandler("", ex))
|
||||
_, action, _ := d.Fire(PreToolUse, payload)
|
||||
if action != Allow {
|
||||
// empty pattern + Deny → resolveAction sees Deny → Deny
|
||||
// wait, empty pattern means fire for all tools
|
||||
}
|
||||
// correct: empty pattern fires → Deny
|
||||
// Empty pattern fires for all tools; the Deny handler must resolve to Deny.
|
||||
if action != Deny {
|
||||
t.Errorf("empty pattern matches all: action = %v, want Deny", action)
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ func TestPromptExecutor_TemplateRendered(t *testing.T) {
|
||||
Exec: "Tool={{.Tool}} Event={{.Event}}",
|
||||
}
|
||||
ex := NewPromptExecutor(def, capturingStreamer)
|
||||
ex.Execute(context.Background(), MarshalPreToolPayload("bash", nil))
|
||||
_, _ = ex.Execute(context.Background(), MarshalPreToolPayload("bash", nil))
|
||||
capturedPrompt = capturingStreamer.prompt
|
||||
if capturedPrompt == "" {
|
||||
t.Fatal("prompt not captured")
|
||||
|
||||
@@ -17,7 +17,7 @@ func writeMCPServer(t *testing.T, tools []MCPTool, callResult string) string {
|
||||
|
||||
// Write response payloads as files.
|
||||
initResult := `{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"test-server","version":"1.0.0"}}`
|
||||
os.WriteFile(filepath.Join(dir, "init.json"), []byte(initResult), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "init.json"), []byte(initResult), 0o644)
|
||||
|
||||
toolsJSON, err := json.Marshal(struct {
|
||||
Tools []MCPTool `json:"tools"`
|
||||
@@ -25,8 +25,8 @@ func writeMCPServer(t *testing.T, tools []MCPTool, callResult string) string {
|
||||
if err != nil {
|
||||
t.Fatalf("marshal tools: %v", err)
|
||||
}
|
||||
os.WriteFile(filepath.Join(dir, "tools.json"), toolsJSON, 0o644)
|
||||
os.WriteFile(filepath.Join(dir, "call.json"), []byte(callResult), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "tools.json"), toolsJSON, 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "call.json"), []byte(callResult), 0o644)
|
||||
|
||||
// The script uses pure bash for JSON parsing — no python3 or jq dependency.
|
||||
// We extract "method" and "id" with grep since the JSON-RPC format is predictable.
|
||||
@@ -79,7 +79,7 @@ func TestClient_Initialize(t *testing.T) {
|
||||
}
|
||||
|
||||
client := NewClient(tr, logger)
|
||||
defer client.Close()
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
t.Fatalf("Initialize: %v", err)
|
||||
@@ -117,7 +117,7 @@ func TestClient_ListTools(t *testing.T) {
|
||||
}
|
||||
|
||||
client := NewClient(tr, logger)
|
||||
defer client.Close()
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
t.Fatalf("Initialize: %v", err)
|
||||
@@ -159,7 +159,7 @@ func TestClient_CallTool(t *testing.T) {
|
||||
}
|
||||
|
||||
client := NewClient(tr, logger)
|
||||
defer client.Close()
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
t.Fatalf("Initialize: %v", err)
|
||||
@@ -210,7 +210,7 @@ echo "{\"jsonrpc\":\"2.0\",\"id\":$id,\"error\":{\"code\":-32000,\"message\":\"i
|
||||
}
|
||||
|
||||
client := NewClient(tr, logger)
|
||||
defer client.Close()
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
err := client.Initialize(ctx)
|
||||
if err == nil {
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestManager_StartAll_RegistersTools(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("StartAll: %v", err)
|
||||
}
|
||||
defer mgr.Shutdown()
|
||||
defer func() { _ = mgr.Shutdown() }()
|
||||
|
||||
// Tools should be registered with mcp__ prefix.
|
||||
if _, ok := reg.Get("mcp__git__status"); !ok {
|
||||
@@ -77,7 +77,7 @@ func TestManager_StartAll_ReplaceDefault(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("StartAll: %v", err)
|
||||
}
|
||||
defer mgr.Shutdown()
|
||||
defer func() { _ = mgr.Shutdown() }()
|
||||
|
||||
// The "bash" tool should now be the MCP adapter, not the mock.
|
||||
bashTool, ok := reg.Get("bash")
|
||||
@@ -111,7 +111,7 @@ func TestManager_StartAll_BadCommand(t *testing.T) {
|
||||
}, reg)
|
||||
if err == nil {
|
||||
t.Error("expected error for bad command")
|
||||
mgr.Shutdown()
|
||||
_ = mgr.Shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ func TestManager_StartAll_ReplaceDefault_PicksMatchingTool(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("StartAll: %v", err)
|
||||
}
|
||||
defer mgr.Shutdown()
|
||||
defer func() { _ = mgr.Shutdown() }()
|
||||
|
||||
// fs.read and fs.write should be replaced.
|
||||
if fsRead, ok := reg.Get("fs.read"); !ok {
|
||||
|
||||
@@ -105,7 +105,7 @@ func TestAdapter_Execute(t *testing.T) {
|
||||
}
|
||||
|
||||
client := NewClient(tr, logger)
|
||||
defer client.Close()
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
t.Fatalf("Initialize: %v", err)
|
||||
@@ -140,7 +140,7 @@ func TestAdapter_Execute_MultipleTextBlocks(t *testing.T) {
|
||||
}
|
||||
|
||||
client := NewClient(tr, logger)
|
||||
defer client.Close()
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
t.Fatalf("Initialize: %v", err)
|
||||
@@ -180,16 +180,16 @@ while IFS= read -r line; do
|
||||
esac
|
||||
done
|
||||
`
|
||||
os.WriteFile(script, []byte(content), 0o755)
|
||||
_ = os.WriteFile(script, []byte(content), 0o755)
|
||||
|
||||
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
|
||||
tr := NewTransport("bash", []string{script}, nil, logger)
|
||||
|
||||
ctx := context.Background()
|
||||
tr.Start(ctx)
|
||||
_ = tr.Start(ctx)
|
||||
client := NewClient(tr, logger)
|
||||
defer client.Close()
|
||||
client.Initialize(ctx)
|
||||
defer func() { _ = client.Close() }()
|
||||
_ = client.Initialize(ctx)
|
||||
|
||||
a := NewAdapter("err", MCPTool{Name: "broken", InputSchema: json.RawMessage(`{}`)}, client)
|
||||
result, err := a.Execute(ctx, json.RawMessage(`{}`))
|
||||
|
||||
@@ -74,7 +74,7 @@ func TestTransport_Call_Success(t *testing.T) {
|
||||
if err := tr.Start(ctx); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
defer tr.Close()
|
||||
defer func() { _ = tr.Close() }()
|
||||
|
||||
result, err := tr.Call(ctx, "tools/list", nil)
|
||||
if err != nil {
|
||||
@@ -101,7 +101,7 @@ func TestTransport_Call_RPCError(t *testing.T) {
|
||||
if err := tr.Start(ctx); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
defer tr.Close()
|
||||
defer func() { _ = tr.Close() }()
|
||||
|
||||
_, err := tr.Call(ctx, "nonexistent", nil)
|
||||
if err == nil {
|
||||
@@ -131,7 +131,7 @@ func TestTransport_Call_Timeout(t *testing.T) {
|
||||
if err := tr.Start(ctx); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
defer tr.Close()
|
||||
defer func() { _ = tr.Close() }()
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
defer cancel()
|
||||
@@ -161,7 +161,7 @@ echo "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"val\":\"$TEST_MCP_VAR\"}}"
|
||||
if err := tr.Start(ctx); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
defer tr.Close()
|
||||
defer func() { _ = tr.Close() }()
|
||||
|
||||
result, err := tr.Call(ctx, "test", nil)
|
||||
if err != nil {
|
||||
@@ -205,7 +205,7 @@ echo "$line" > "` + filepath.Join(dir, "received.json") + `"
|
||||
|
||||
// Give the script a moment to write the file.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
tr.Close()
|
||||
_ = tr.Close()
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(dir, "received.json"))
|
||||
if err != nil {
|
||||
@@ -233,7 +233,7 @@ func TestTransport_MultipleCalls(t *testing.T) {
|
||||
if err := tr.Start(ctx); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
defer tr.Close()
|
||||
defer func() { _ = tr.Close() }()
|
||||
|
||||
// First call.
|
||||
r1, err := tr.Call(ctx, "first", nil)
|
||||
@@ -242,7 +242,7 @@ func TestTransport_MultipleCalls(t *testing.T) {
|
||||
}
|
||||
|
||||
var p1 struct{ Step string }
|
||||
json.Unmarshal(r1, &p1)
|
||||
_ = json.Unmarshal(r1, &p1)
|
||||
if p1.Step != "first" {
|
||||
t.Errorf("call 1 step = %q, want %q", p1.Step, "first")
|
||||
}
|
||||
@@ -254,7 +254,7 @@ func TestTransport_MultipleCalls(t *testing.T) {
|
||||
}
|
||||
|
||||
var p2 struct{ Step string }
|
||||
json.Unmarshal(r2, &p2)
|
||||
_ = json.Unmarshal(r2, &p2)
|
||||
if p2.Step != "second" {
|
||||
t.Errorf("call 2 step = %q, want %q", p2.Step, "second")
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func writePluginWithSkill(t *testing.T, dir, pluginName, skillName, skillContent
|
||||
t.Helper()
|
||||
pluginDir := filepath.Join(dir, pluginName)
|
||||
skillsDir := filepath.Join(pluginDir, "skills")
|
||||
os.MkdirAll(skillsDir, 0o755)
|
||||
_ = os.MkdirAll(skillsDir, 0o755)
|
||||
|
||||
m := Manifest{
|
||||
Name: pluginName,
|
||||
@@ -45,8 +45,8 @@ func writePluginWithSkill(t *testing.T, dir, pluginName, skillName, skillContent
|
||||
},
|
||||
}
|
||||
data, _ := marshalManifest(m)
|
||||
os.WriteFile(filepath.Join(pluginDir, "plugin.json"), data, 0o644)
|
||||
os.WriteFile(filepath.Join(skillsDir, skillName+".md"), []byte(skillContent), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(pluginDir, "plugin.json"), data, 0o644)
|
||||
_ = os.WriteFile(filepath.Join(skillsDir, skillName+".md"), []byte(skillContent), 0o644)
|
||||
}
|
||||
|
||||
func marshalManifest(m Manifest) ([]byte, error) {
|
||||
@@ -122,8 +122,8 @@ func TestLoader_Discover_SkipsInvalidManifest(t *testing.T) {
|
||||
|
||||
// Write an invalid plugin (bad JSON).
|
||||
badDir := filepath.Join(globalDir, "bad")
|
||||
os.MkdirAll(badDir, 0o755)
|
||||
os.WriteFile(filepath.Join(badDir, "plugin.json"), []byte(`{invalid`), 0o644)
|
||||
_ = os.MkdirAll(badDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(badDir, "plugin.json"), []byte(`{invalid`), 0o644)
|
||||
|
||||
loader := NewLoader(testLogger())
|
||||
plugins, err := loader.Discover(globalDir, filepath.Join(dir, "project"))
|
||||
@@ -274,8 +274,9 @@ func TestLoader_Load_TOFU_RecordsPinOnFirstLoad(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if len(result.Skills)+len(result.Hooks)+len(result.MCPServers) != 0 {
|
||||
// No capabilities declared, but plugin should still have been processed.
|
||||
// Plugin declares no capabilities, but TOFU must still record a pin.
|
||||
if n := len(result.Skills) + len(result.Hooks) + len(result.MCPServers); n != 0 {
|
||||
t.Errorf("plugin with no capabilities produced %d entries", n)
|
||||
}
|
||||
if _, ok := pins.Get("newbie"); !ok {
|
||||
t.Error("TOFU did not record a pin for the new plugin")
|
||||
|
||||
@@ -10,15 +10,15 @@ func TestManager_Install(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
globalDir := filepath.Join(dir, "global")
|
||||
projectDir := filepath.Join(dir, "project")
|
||||
os.MkdirAll(globalDir, 0o755)
|
||||
os.MkdirAll(projectDir, 0o755)
|
||||
_ = os.MkdirAll(globalDir, 0o755)
|
||||
_ = os.MkdirAll(projectDir, 0o755)
|
||||
|
||||
// Create a source plugin directory.
|
||||
srcDir := filepath.Join(dir, "src", "my-plugin")
|
||||
os.MkdirAll(srcDir, 0o755)
|
||||
_ = os.MkdirAll(srcDir, 0o755)
|
||||
m := Manifest{Name: "my-plugin", Version: "1.0.0", Description: "Test plugin"}
|
||||
data, _ := marshalJSON(m)
|
||||
os.WriteFile(filepath.Join(srcDir, "plugin.json"), data, 0o644)
|
||||
_ = os.WriteFile(filepath.Join(srcDir, "plugin.json"), data, 0o644)
|
||||
|
||||
mgr := NewManager(globalDir, projectDir, testLogger())
|
||||
|
||||
@@ -38,14 +38,14 @@ func TestManager_Install_ProjectScope(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
globalDir := filepath.Join(dir, "global")
|
||||
projectDir := filepath.Join(dir, "project")
|
||||
os.MkdirAll(globalDir, 0o755)
|
||||
os.MkdirAll(projectDir, 0o755)
|
||||
_ = os.MkdirAll(globalDir, 0o755)
|
||||
_ = os.MkdirAll(projectDir, 0o755)
|
||||
|
||||
srcDir := filepath.Join(dir, "src", "proj-plugin")
|
||||
os.MkdirAll(srcDir, 0o755)
|
||||
_ = os.MkdirAll(srcDir, 0o755)
|
||||
m := Manifest{Name: "proj-plugin", Version: "1.0.0"}
|
||||
data, _ := marshalJSON(m)
|
||||
os.WriteFile(filepath.Join(srcDir, "plugin.json"), data, 0o644)
|
||||
_ = os.WriteFile(filepath.Join(srcDir, "plugin.json"), data, 0o644)
|
||||
|
||||
mgr := NewManager(globalDir, projectDir, testLogger())
|
||||
|
||||
@@ -62,18 +62,18 @@ func TestManager_Install_ProjectScope(t *testing.T) {
|
||||
func TestManager_Install_AlreadyInstalled(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
globalDir := filepath.Join(dir, "global")
|
||||
os.MkdirAll(globalDir, 0o755)
|
||||
_ = os.MkdirAll(globalDir, 0o755)
|
||||
|
||||
srcDir := filepath.Join(dir, "src", "dup")
|
||||
os.MkdirAll(srcDir, 0o755)
|
||||
_ = os.MkdirAll(srcDir, 0o755)
|
||||
m := Manifest{Name: "dup", Version: "1.0.0"}
|
||||
data, _ := marshalJSON(m)
|
||||
os.WriteFile(filepath.Join(srcDir, "plugin.json"), data, 0o644)
|
||||
_ = os.WriteFile(filepath.Join(srcDir, "plugin.json"), data, 0o644)
|
||||
|
||||
mgr := NewManager(globalDir, filepath.Join(dir, "project"), testLogger())
|
||||
|
||||
// First install.
|
||||
mgr.Install(srcDir, "user")
|
||||
_ = mgr.Install(srcDir, "user")
|
||||
|
||||
// Second install should fail.
|
||||
err := mgr.Install(srcDir, "user")
|
||||
@@ -85,10 +85,10 @@ func TestManager_Install_AlreadyInstalled(t *testing.T) {
|
||||
func TestManager_Install_NoManifest(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
globalDir := filepath.Join(dir, "global")
|
||||
os.MkdirAll(globalDir, 0o755)
|
||||
_ = os.MkdirAll(globalDir, 0o755)
|
||||
|
||||
srcDir := filepath.Join(dir, "src", "empty")
|
||||
os.MkdirAll(srcDir, 0o755)
|
||||
_ = os.MkdirAll(srcDir, 0o755)
|
||||
|
||||
mgr := NewManager(globalDir, filepath.Join(dir, "project"), testLogger())
|
||||
err := mgr.Install(srcDir, "user")
|
||||
@@ -100,14 +100,14 @@ func TestManager_Install_NoManifest(t *testing.T) {
|
||||
func TestManager_Uninstall(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
globalDir := filepath.Join(dir, "global")
|
||||
os.MkdirAll(globalDir, 0o755)
|
||||
_ = os.MkdirAll(globalDir, 0o755)
|
||||
|
||||
// Pre-install a plugin.
|
||||
pluginDir := filepath.Join(globalDir, "to-remove")
|
||||
os.MkdirAll(pluginDir, 0o755)
|
||||
_ = os.MkdirAll(pluginDir, 0o755)
|
||||
m := Manifest{Name: "to-remove", Version: "1.0.0"}
|
||||
data, _ := marshalJSON(m)
|
||||
os.WriteFile(filepath.Join(pluginDir, "plugin.json"), data, 0o644)
|
||||
_ = os.WriteFile(filepath.Join(pluginDir, "plugin.json"), data, 0o644)
|
||||
|
||||
mgr := NewManager(globalDir, filepath.Join(dir, "project"), testLogger())
|
||||
|
||||
|
||||
@@ -83,18 +83,18 @@ func ClassifyHTTPError(status int, message string) (ErrorKind, bool) {
|
||||
|
||||
// ClassifyHTTPStatus returns the ErrorKind and retryability for an HTTP status code.
|
||||
func ClassifyHTTPStatus(status int) (ErrorKind, bool) {
|
||||
switch {
|
||||
case status == 401 || status == 403:
|
||||
switch status {
|
||||
case 401, 403:
|
||||
return ErrAuth, false
|
||||
case status == 400:
|
||||
case 400:
|
||||
return ErrBadRequest, false
|
||||
case status == 404:
|
||||
case 404:
|
||||
return ErrNotFound, false
|
||||
case status == 429 || status == 529:
|
||||
case 429, 529:
|
||||
return ErrTransient, true
|
||||
case status == 500 || status == 502 || status == 503:
|
||||
case 500, 502, 503:
|
||||
return ErrTransient, true
|
||||
case status == 504:
|
||||
case 504:
|
||||
return ErrOverloaded, true
|
||||
default:
|
||||
if status >= 500 {
|
||||
|
||||
@@ -140,15 +140,14 @@ func inferGoogleModelCapabilities(m *genai.Model) provider.Capabilities {
|
||||
}
|
||||
|
||||
// Model-specific overrides based on model name
|
||||
name := m.Name
|
||||
switch {
|
||||
case name == "gemini-2.5-pro", name == "gemini-2.5-flash":
|
||||
switch m.Name {
|
||||
case "gemini-2.5-pro", "gemini-2.5-flash":
|
||||
caps.ContextWindow = 1048576
|
||||
caps.MaxOutput = 65536
|
||||
case name == "gemini-2.0-pro", name == "gemini-2.0-flash":
|
||||
case "gemini-2.0-pro", "gemini-2.0-flash":
|
||||
caps.ContextWindow = 1048576
|
||||
caps.MaxOutput = 8192
|
||||
case name == "gemini-1.5-pro", name == "gemini-1.5-flash":
|
||||
case "gemini-1.5-pro", "gemini-1.5-flash":
|
||||
caps.ContextWindow = 1048576
|
||||
caps.MaxOutput = 8192
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestSubprocessStream_EchoShell(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
defer func() { _ = s.Close() }()
|
||||
|
||||
var texts []string
|
||||
for s.Next() {
|
||||
@@ -57,7 +57,7 @@ func TestSubprocessStream_ContextCancel(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
defer func() { _ = s.Close() }()
|
||||
|
||||
cancel()
|
||||
// Drain — should stop quickly due to context cancellation.
|
||||
@@ -76,7 +76,7 @@ func TestSubprocessStream_ProcessError(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
defer func() { _ = s.Close() }()
|
||||
|
||||
for s.Next() {
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func DiscoverOllama(ctx context.Context, baseURL string, toolCache map[string]bo
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ollama not reachable at %s: %w", baseURL, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("ollama returned %d", resp.StatusCode)
|
||||
@@ -115,7 +115,7 @@ func DiscoverLlamaCpp(ctx context.Context, baseURL string) ([]DiscoveredModel, e
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("llama.cpp not reachable at %s: %w", baseURL, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("llama.cpp returned %d", resp.StatusCode)
|
||||
|
||||
@@ -25,7 +25,7 @@ func probeLlamaCppToolSupport(ctx context.Context, baseURL string) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return false
|
||||
@@ -73,7 +73,7 @@ func probeOllamaToolSupport(ctx context.Context, baseURL, modelName string) bool
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return false
|
||||
|
||||
@@ -13,7 +13,7 @@ func TestProbeLlamaCppToolSupport_SupportsTools(t *testing.T) {
|
||||
t.Errorf("unexpected path %q", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{
|
||||
_, _ = w.Write([]byte(`{
|
||||
"chat_template": "...",
|
||||
"chat_template_caps": {
|
||||
"supports_tools": true,
|
||||
@@ -34,7 +34,7 @@ func TestProbeLlamaCppToolSupport_SupportsTools(t *testing.T) {
|
||||
func TestProbeLlamaCppToolSupport_NoToolSupport(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{
|
||||
_, _ = w.Write([]byte(`{
|
||||
"chat_template": "...",
|
||||
"chat_template_caps": {
|
||||
"supports_tools": false,
|
||||
@@ -55,7 +55,7 @@ func TestProbeLlamaCppToolSupport_NoCaps(t *testing.T) {
|
||||
// Old llama.cpp version that doesn't return chat_template_caps
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"chat_template": "...", "total_slots": 1}`))
|
||||
_, _ = w.Write([]byte(`{"chat_template": "...", "total_slots": 1}`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
@@ -75,7 +75,7 @@ func TestProbeLlamaCppToolSupport_ServerDown(t *testing.T) {
|
||||
func TestProbeLlamaCppToolSupport_ToolsWithoutToolCalls(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{
|
||||
_, _ = w.Write([]byte(`{
|
||||
"chat_template_caps": {
|
||||
"supports_tools": true,
|
||||
"supports_tool_calls": false
|
||||
@@ -96,7 +96,7 @@ func TestProbeOllamaToolSupport_HasTools(t *testing.T) {
|
||||
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{
|
||||
_, _ = w.Write([]byte(`{
|
||||
"details": {"family": "qwen2", "parameter_size": "7B"},
|
||||
"capabilities": ["completion", "tools"]
|
||||
}`))
|
||||
@@ -112,7 +112,7 @@ func TestProbeOllamaToolSupport_HasTools(t *testing.T) {
|
||||
func TestProbeOllamaToolSupport_NoTools(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{
|
||||
_, _ = w.Write([]byte(`{
|
||||
"details": {"family": "phi", "parameter_size": "3B"},
|
||||
"capabilities": ["completion"]
|
||||
}`))
|
||||
@@ -129,7 +129,7 @@ func TestProbeOllamaToolSupport_NoCapsField(t *testing.T) {
|
||||
// Old Ollama version without capabilities
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"details": {"family": "llama"}}`))
|
||||
_, _ = w.Write([]byte(`{"details": {"family": "llama"}}`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ func TestLocal_SendWhileBusy(t *testing.T) {
|
||||
eng, _ := engine.New(engine.Config{Provider: mp, Tools: tool.NewRegistry()})
|
||||
sess := NewLocal(LocalConfig{Engine: eng, Provider: "test", Model: "model"})
|
||||
|
||||
sess.Send("first")
|
||||
_ = sess.Send("first")
|
||||
|
||||
// Try to send while still processing
|
||||
err := sess.Send("second")
|
||||
@@ -151,7 +151,7 @@ func TestLocal_Cancel(t *testing.T) {
|
||||
eng, _ := engine.New(engine.Config{Provider: mp, Tools: tool.NewRegistry()})
|
||||
sess := NewLocal(LocalConfig{Engine: eng, Provider: "test", Model: "model"})
|
||||
|
||||
sess.Send("slow task")
|
||||
_ = sess.Send("slow task")
|
||||
|
||||
// Read a few events then cancel
|
||||
evts := sess.Events()
|
||||
@@ -203,12 +203,12 @@ func TestLocal_StatusTracking(t *testing.T) {
|
||||
sess := NewLocal(LocalConfig{Engine: eng, Provider: "test", Model: "mock-model"})
|
||||
|
||||
// Turn 1
|
||||
sess.Send("one")
|
||||
_ = sess.Send("one")
|
||||
for range sess.Events() {
|
||||
}
|
||||
|
||||
// Turn 2
|
||||
sess.Send("two")
|
||||
_ = sess.Send("two")
|
||||
for range sess.Events() {
|
||||
}
|
||||
|
||||
|
||||
@@ -73,9 +73,9 @@ func TestSessionStore_Load_CorruptMetadata(t *testing.T) {
|
||||
store := session.NewSessionStore(root, 3, slog.Default())
|
||||
|
||||
dir := filepath.Join(root, ".gnoma", "sessions", "corrupt-sess")
|
||||
os.MkdirAll(dir, 0o755)
|
||||
os.WriteFile(filepath.Join(dir, "metadata.json"), []byte("not json"), 0o644)
|
||||
os.WriteFile(filepath.Join(dir, "messages.json"), []byte("[]"), 0o644)
|
||||
_ = os.MkdirAll(dir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(dir, "metadata.json"), []byte("not json"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "messages.json"), []byte("[]"), 0o644)
|
||||
|
||||
_, err := store.Load("corrupt-sess")
|
||||
if err == nil {
|
||||
@@ -87,9 +87,9 @@ func TestSessionStore_List_SortedByUpdatedAt(t *testing.T) {
|
||||
store := makeStore(t)
|
||||
now := time.Now().UTC()
|
||||
|
||||
store.Save(makeSnap("sess-old", now.Add(-2*time.Hour)))
|
||||
store.Save(makeSnap("sess-new", now))
|
||||
store.Save(makeSnap("sess-mid", now.Add(-1*time.Hour)))
|
||||
_ = store.Save(makeSnap("sess-old", now.Add(-2*time.Hour)))
|
||||
_ = store.Save(makeSnap("sess-new", now))
|
||||
_ = store.Save(makeSnap("sess-mid", now.Add(-1*time.Hour)))
|
||||
|
||||
list, err := store.List()
|
||||
if err != nil {
|
||||
@@ -131,7 +131,7 @@ func TestSessionStore_Prune_RemovesOldest(t *testing.T) {
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
id := fmt.Sprintf("sess-%03d", i)
|
||||
store.Save(makeSnap(id, now.Add(time.Duration(i)*time.Minute)))
|
||||
_ = store.Save(makeSnap(id, now.Add(time.Duration(i)*time.Minute)))
|
||||
}
|
||||
|
||||
list, err := store.List()
|
||||
|
||||
@@ -61,8 +61,8 @@ func TestRegistry_OverridePrecedence(t *testing.T) {
|
||||
writeSkillFile(t, dir2, "shared.md", "---\nname: shared\ndescription: from dir2\n---\nbody2\n")
|
||||
|
||||
reg := NewRegistry()
|
||||
reg.LoadDir(dir1, "user")
|
||||
reg.LoadDir(dir2, "project")
|
||||
_ = reg.LoadDir(dir1, "user")
|
||||
_ = reg.LoadDir(dir2, "project")
|
||||
|
||||
sk := reg.Get("shared")
|
||||
if sk == nil {
|
||||
@@ -90,7 +90,7 @@ func TestRegistry_Names_Sorted(t *testing.T) {
|
||||
writeSkillFile(t, dir, "middle.md", "---\nname: middle\n---\nbody\n")
|
||||
|
||||
reg := NewRegistry()
|
||||
reg.LoadDir(dir, "test")
|
||||
_ = reg.LoadDir(dir, "test")
|
||||
|
||||
names := reg.Names()
|
||||
if len(names) != 3 {
|
||||
@@ -116,7 +116,7 @@ func TestRegistry_All_ReturnsCopy(t *testing.T) {
|
||||
writeSkillFile(t, dir, "a.md", "---\nname: aaa\n---\nbody\n")
|
||||
|
||||
reg := NewRegistry()
|
||||
reg.LoadDir(dir, "test")
|
||||
_ = reg.LoadDir(dir, "test")
|
||||
|
||||
all := reg.All()
|
||||
if len(all) != 1 {
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
func TestDownload_Success(t *testing.T) {
|
||||
content := []byte("hello llamafile")
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(content)
|
||||
_, _ = w.Write(content)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
@@ -94,7 +94,7 @@ func TestDownload_ContextCancel(t *testing.T) {
|
||||
func TestDownload_NilProgress(t *testing.T) {
|
||||
content := []byte("data")
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(content)
|
||||
_, _ = w.Write(content)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
@@ -111,8 +111,8 @@ func TestHashFile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Write(content)
|
||||
f.Close()
|
||||
_, _ = f.Write(content)
|
||||
_ = f.Close()
|
||||
|
||||
h := sha256.Sum256(content)
|
||||
want := hex.EncodeToString(h[:])
|
||||
|
||||
@@ -76,7 +76,7 @@ func TestManager_StatusMissing(t *testing.T) {
|
||||
func TestManager_Setup(t *testing.T) {
|
||||
content := []byte("fake llamafile binary")
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Write(content)
|
||||
_, _ = w.Write(content)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
func makeTestStore(t *testing.T) *persist.Store {
|
||||
t.Helper()
|
||||
s := persist.New("test-coord-" + t.Name())
|
||||
t.Cleanup(func() { os.RemoveAll(s.Dir()) })
|
||||
t.Cleanup(func() { _ = os.RemoveAll(s.Dir()) })
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -26,10 +26,8 @@ func TestListResultsTool_EmptyStore(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(result.Output, "no results") && result.Output != "" {
|
||||
// both "no results" message and empty string are acceptable
|
||||
}
|
||||
// Verify it doesn't error on empty store
|
||||
// Empty store: either a "no results" message or empty output is acceptable;
|
||||
// verify only that we don't surface a hard error.
|
||||
if strings.Contains(result.Output, "error") {
|
||||
t.Errorf("unexpected error output for empty store: %s", result.Output)
|
||||
}
|
||||
|
||||
+16
-16
@@ -321,9 +321,9 @@ func TestGlobTool_Interface(t *testing.T) {
|
||||
|
||||
func TestGlobTool_MatchFiles(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main"), 0o644)
|
||||
os.WriteFile(filepath.Join(dir, "test.go"), []byte("package main"), 0o644)
|
||||
os.WriteFile(filepath.Join(dir, "readme.md"), []byte("# readme"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "test.go"), []byte("package main"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "readme.md"), []byte("# readme"), 0o644)
|
||||
|
||||
g := NewGlobTool()
|
||||
result, err := g.Execute(context.Background(), mustJSON(t, globArgs{Pattern: "*.go", Path: dir}))
|
||||
@@ -357,12 +357,12 @@ func TestGlobTool_NoMatches(t *testing.T) {
|
||||
|
||||
func TestGlobTool_Doublestar(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
os.MkdirAll(filepath.Join(dir, "internal", "foo"), 0o755)
|
||||
os.MkdirAll(filepath.Join(dir, "cmd", "bar"), 0o755)
|
||||
os.WriteFile(filepath.Join(dir, "main.go"), []byte(""), 0o644)
|
||||
os.WriteFile(filepath.Join(dir, "internal", "foo", "foo.go"), []byte(""), 0o644)
|
||||
os.WriteFile(filepath.Join(dir, "cmd", "bar", "bar.go"), []byte(""), 0o644)
|
||||
os.WriteFile(filepath.Join(dir, "cmd", "bar", "bar_test.go"), []byte(""), 0o644)
|
||||
_ = os.MkdirAll(filepath.Join(dir, "internal", "foo"), 0o755)
|
||||
_ = os.MkdirAll(filepath.Join(dir, "cmd", "bar"), 0o755)
|
||||
_ = os.WriteFile(filepath.Join(dir, "main.go"), []byte(""), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "internal", "foo", "foo.go"), []byte(""), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "cmd", "bar", "bar.go"), []byte(""), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "cmd", "bar", "bar_test.go"), []byte(""), 0o644)
|
||||
|
||||
g := NewGlobTool()
|
||||
|
||||
@@ -441,9 +441,9 @@ func TestGrepTool_SingleFile(t *testing.T) {
|
||||
|
||||
func TestGrepTool_Directory(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
os.WriteFile(filepath.Join(dir, "a.go"), []byte("func main() {}\nfunc helper() {}"), 0o644)
|
||||
os.WriteFile(filepath.Join(dir, "b.go"), []byte("func test() {}"), 0o644)
|
||||
os.WriteFile(filepath.Join(dir, "c.txt"), []byte("func ignored() {}"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "a.go"), []byte("func main() {}\nfunc helper() {}"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "b.go"), []byte("func test() {}"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "c.txt"), []byte("func ignored() {}"), 0o644)
|
||||
|
||||
g := NewGrepTool()
|
||||
|
||||
@@ -537,9 +537,9 @@ func TestLSTool_Interface(t *testing.T) {
|
||||
|
||||
func TestLSTool_ListDirectory(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
os.WriteFile(filepath.Join(dir, "hello.go"), []byte("package main"), 0o644)
|
||||
os.WriteFile(filepath.Join(dir, "readme.md"), []byte("# readme"), 0o644)
|
||||
os.MkdirAll(filepath.Join(dir, "subdir"), 0o755)
|
||||
_ = os.WriteFile(filepath.Join(dir, "hello.go"), []byte("package main"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "readme.md"), []byte("# readme"), 0o644)
|
||||
_ = os.MkdirAll(filepath.Join(dir, "subdir"), 0o755)
|
||||
|
||||
l := NewLSTool()
|
||||
result, err := l.Execute(context.Background(), mustJSON(t, lsArgs{Path: dir}))
|
||||
@@ -591,7 +591,7 @@ func TestLSTool_DirectoryNotFound(t *testing.T) {
|
||||
|
||||
func TestLSTool_ShowsSizes(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
os.WriteFile(filepath.Join(dir, "small.txt"), []byte("hi"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(dir, "small.txt"), []byte("hi"), 0o644)
|
||||
|
||||
l := NewLSTool()
|
||||
result, err := l.Execute(context.Background(), mustJSON(t, lsArgs{Path: dir}))
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func TestStore_SaveSkipsSmallContent(t *testing.T) {
|
||||
s := persist.New("test-session-001")
|
||||
t.Cleanup(func() { os.RemoveAll(s.Dir()) })
|
||||
t.Cleanup(func() { _ = os.RemoveAll(s.Dir()) })
|
||||
|
||||
path, ok := s.Save("bash", "call-001", "small output")
|
||||
if ok {
|
||||
@@ -24,7 +24,7 @@ func TestStore_SaveSkipsSmallContent(t *testing.T) {
|
||||
|
||||
func TestStore_SavePersistsLargeContent(t *testing.T) {
|
||||
s := persist.New("test-session-002")
|
||||
t.Cleanup(func() { os.RemoveAll(s.Dir()) })
|
||||
t.Cleanup(func() { _ = os.RemoveAll(s.Dir()) })
|
||||
|
||||
content := strings.Repeat("x", 1024)
|
||||
path, ok := s.Save("fs.grep", "call-002", content)
|
||||
@@ -45,7 +45,7 @@ func TestStore_SavePersistsLargeContent(t *testing.T) {
|
||||
|
||||
func TestStore_ListFilters(t *testing.T) {
|
||||
s := persist.New("test-session-003")
|
||||
t.Cleanup(func() { os.RemoveAll(s.Dir()) })
|
||||
t.Cleanup(func() { _ = os.RemoveAll(s.Dir()) })
|
||||
|
||||
bigContent := strings.Repeat("y", 1024)
|
||||
s.Save("bash", "c1", bigContent)
|
||||
@@ -71,7 +71,7 @@ func TestStore_ListFilters(t *testing.T) {
|
||||
|
||||
func TestStore_ReadValidatesPath(t *testing.T) {
|
||||
s := persist.New("test-session-004")
|
||||
t.Cleanup(func() { os.RemoveAll(s.Dir()) })
|
||||
t.Cleanup(func() { _ = os.RemoveAll(s.Dir()) })
|
||||
|
||||
// Path outside session dir must be rejected
|
||||
_, err := s.Read("/etc/passwd")
|
||||
|
||||
@@ -162,7 +162,7 @@ func TestStubTool_Execute(t *testing.T) {
|
||||
execFn: func(ctx context.Context, args json.RawMessage) (Result, error) {
|
||||
called = true
|
||||
var input struct{ Value string }
|
||||
json.Unmarshal(args, &input)
|
||||
_ = json.Unmarshal(args, &input)
|
||||
return Result{
|
||||
Output: "processed: " + input.Value,
|
||||
Metadata: map[string]any{"key": "val"},
|
||||
|
||||
+6
-4
@@ -575,9 +575,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
case tea.MouseWheelMsg:
|
||||
if msg.Button == tea.MouseWheelUp {
|
||||
switch msg.Button {
|
||||
case tea.MouseWheelUp:
|
||||
m.scrollOffset += 3
|
||||
} else if msg.Button == tea.MouseWheelDown {
|
||||
case tea.MouseWheelDown:
|
||||
m.scrollOffset -= 3
|
||||
if m.scrollOffset < 0 {
|
||||
m.scrollOffset = 0
|
||||
@@ -847,7 +848,8 @@ Mark anything you're unsure about with TODO. Be terse — directive-style bullet
|
||||
|
||||
func (m Model) submitInput(input string) (tea.Model, tea.Cmd) {
|
||||
// Prepend mode prefix and reset mode before dispatching.
|
||||
if m.inputMode == "command" {
|
||||
switch m.inputMode {
|
||||
case "command":
|
||||
if strings.TrimSpace(input) == "" {
|
||||
m.inputMode = ""
|
||||
m.suggestions = nil
|
||||
@@ -860,7 +862,7 @@ func (m Model) submitInput(input string) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
input = "/" + strings.TrimSpace(input)
|
||||
} else if m.inputMode == "execute" {
|
||||
case "execute":
|
||||
if strings.TrimSpace(input) == "" {
|
||||
m.inputMode = ""
|
||||
m.input.SetPromptFunc(2, func(info textarea.PromptInfo) string {
|
||||
|
||||
Reference in New Issue
Block a user