Drop the three-layer Claude subprocess orchestration (local model →
Claude verifier → cloud escalation). Skills now call LiteLLM directly
and return plain text to Claude Code, which decides what to do with it.
- Delete executor, orchestrator, verifier, result, attempts packages
- Simplify LiteLLMExecutor: Run(Request)→Result becomes Complete(model,sys,user)→(string,int64,error)
- Replace ExecutorFn with CompleteFunc in all 6 skill configs
- Rewrite all skill handlers to call Complete and return {"text","model","duration_ms"}
- Simplify config/models: remove Verifier/LlamaSwapURL, add ModelFor
- Bump version to v0.5.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
74 lines
2.3 KiB
Go
74 lines
2.3 KiB
Go
// internal/skills/trainer/handlers_test.go
|
|
package trainer_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/mathiasbq/supervisor/internal/session"
|
|
"github.com/mathiasbq/supervisor/internal/skills/trainer"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestTrainerToolRegistered(t *testing.T) {
|
|
sk := trainer.New(trainer.Config{ReaderPrompt: "r", WriterPrompt: "w"})
|
|
names := make([]string, 0)
|
|
for _, tool := range sk.Tools() {
|
|
names = append(names, tool.Name)
|
|
}
|
|
assert.Contains(t, names, "trainer")
|
|
}
|
|
|
|
func TestTrainerRequiresSessionID(t *testing.T) {
|
|
sk := trainer.New(trainer.Config{ReaderPrompt: "r", WriterPrompt: "w"})
|
|
_, err := sk.Handle(context.Background(), "trainer", json.RawMessage(`{}`))
|
|
assert.ErrorContains(t, err, "session_id")
|
|
}
|
|
|
|
func TestTrainerCallsReaderThenWriter(t *testing.T) {
|
|
sessDir := t.TempDir()
|
|
require.NoError(t, session.Append(sessDir, "sess-1", session.Entry{
|
|
SessionID: "sess-1", Skill: "tdd", Phase: "red", FinalStatus: "ok",
|
|
Message: "wrote failing test", FilePath: "internal/foo/foo_test.go",
|
|
}))
|
|
|
|
callCount := 0
|
|
var readerTask, writerTask string
|
|
|
|
fakeFn := func(_ context.Context, _, sys, user string) (string, int64, error) {
|
|
callCount++
|
|
if callCount == 1 {
|
|
// reader call
|
|
readerTask = user
|
|
return "1 sft candidate found: first-pass clean TDD", 60, nil
|
|
}
|
|
// writer call
|
|
writerTask = user
|
|
return "written 1 knowledge entry to brain/knowledge/tdd-patterns.md", 70, nil
|
|
}
|
|
|
|
sk := trainer.New(trainer.Config{
|
|
ReaderPrompt: "reader rules",
|
|
WriterPrompt: "writer rules",
|
|
CompleteFunc: fakeFn,
|
|
SessionsDir: sessDir,
|
|
BrainDir: t.TempDir(),
|
|
})
|
|
out, err := sk.Handle(context.Background(), "trainer", json.RawMessage(`{"session_id":"sess-1"}`))
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 2, callCount, "complete must be called exactly twice: reader then writer")
|
|
assert.Contains(t, readerTask, "role: reader")
|
|
assert.Contains(t, readerTask, "sess-1")
|
|
assert.Contains(t, readerTask, "wrote failing test")
|
|
assert.Contains(t, writerTask, "role: writer")
|
|
assert.Contains(t, writerTask, "sft candidate")
|
|
|
|
var result map[string]any
|
|
require.NoError(t, json.Unmarshal(out, &result))
|
|
assert.Contains(t, result["reader_analysis"], "sft candidate")
|
|
assert.Contains(t, result["writer_output"], "knowledge entry")
|
|
}
|