diff --git a/internal/routing/snapshot_test.go b/internal/routing/snapshot_test.go new file mode 100644 index 0000000..c79f7df --- /dev/null +++ b/internal/routing/snapshot_test.go @@ -0,0 +1,80 @@ +package routing_test + +import ( + "context" + "encoding/json" + "os" + "sort" + "testing" + + "github.com/mathiasbq/supervisor/internal/registry" + "github.com/mathiasbq/supervisor/internal/skills/debug" + "github.com/mathiasbq/supervisor/internal/skills/retrospective" + "github.com/mathiasbq/supervisor/internal/skills/review" + "github.com/mathiasbq/supervisor/internal/skills/trainer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestToolsListMatchesSupervisorSnapshot pins the four routed skills' tool +// definitions to the supervisor's current advertisement. A deliberate schema +// change must be reflected here by updating testdata/tools_list.snapshot.json. +func TestToolsListMatchesSupervisorSnapshot(t *testing.T) { + complete := func(_ context.Context, _, _, _ string) (string, int64, error) { + return "", 0, nil + } + + reg := registry.New() + reg.Register(review.New(review.Config{ + SkillPrompt: "stub", + DefaultModel: "stub", + CompleteFunc: complete, + })) + reg.Register(debug.New(debug.Config{ + SkillPrompt: "stub", + DefaultModel: "stub", + CompleteFunc: complete, + })) + reg.Register(retrospective.New(retrospective.Config{ + SkillPrompt: "stub", + DefaultModel: "stub", + CompleteFunc: complete, + })) + reg.Register(trainer.New(trainer.Config{ + ReaderPrompt: "stub", + WriterPrompt: "stub", + DefaultModel: "stub", + CompleteFunc: complete, + })) + + wanted := map[string]bool{ + "review": true, + "debug": true, + "retrospective": true, + "trainer": true, + } + var routed []registry.ToolDef + for _, td := range reg.Tools() { + if wanted[td.Name] { + routed = append(routed, td) + } + } + sort.Slice(routed, func(i, j int) bool { return routed[i].Name < routed[j].Name }) + + got, err := json.MarshalIndent(routed, "", " ") + require.NoError(t, err) + + want, err := os.ReadFile("testdata/tools_list.snapshot.json") + require.NoError(t, err) + + // Normalize both via re-encode so whitespace differences don't dominate. + var gotV, wantV any + require.NoError(t, json.Unmarshal(got, &gotV)) + require.NoError(t, json.Unmarshal(want, &wantV)) + + gotN, _ := json.MarshalIndent(gotV, "", " ") + wantN, _ := json.MarshalIndent(wantV, "", " ") + + assert.Equal(t, string(wantN), string(gotN), + "tool advertisement drifted from supervisor snapshot — update testdata/tools_list.snapshot.json deliberately if the schema change is intentional") +} diff --git a/internal/routing/testdata/tools_list.snapshot.json b/internal/routing/testdata/tools_list.snapshot.json new file mode 100644 index 0000000..859602e --- /dev/null +++ b/internal/routing/testdata/tools_list.snapshot.json @@ -0,0 +1,97 @@ +[ + { + "name": "debug", + "description": "Consult a local model to analyse an error and return hypotheses ordered by likelihood, each with a concrete verification step.", + "inputSchema": { + "properties": { + "context": { + "type": "string" + }, + "error": { + "type": "string" + }, + "model": { + "type": "string" + }, + "project_root": { + "type": "string" + }, + "session_id": { + "type": "string" + } + }, + "required": [ + "project_root", + "error" + ], + "type": "object" + } + }, + { + "name": "retrospective", + "description": "Consult a local model to analyse a completed session and identify what is novel or worth preserving as organizational knowledge.", + "inputSchema": { + "type": "object", + "required": [ + "session_id" + ], + "properties": { + "session_id": { + "type": "string" + }, + "model": { + "type": "string" + } + } + } + }, + { + "name": "review", + "description": "Consult a local model for a structured code review of the specified files. Returns findings with severity levels.", + "inputSchema": { + "properties": { + "context": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "model": { + "type": "string" + }, + "project_root": { + "type": "string" + }, + "session_id": { + "type": "string" + } + }, + "required": [ + "project_root", + "files" + ], + "type": "object" + } + }, + { + "name": "trainer", + "description": "Consult a local model to identify learning moments from a session log and suggest knowledge to preserve in the brain.", + "inputSchema": { + "properties": { + "model": { + "type": "string" + }, + "session_id": { + "type": "string" + } + }, + "required": [ + "session_id" + ], + "type": "object" + } + } +]