diff --git a/cmd/mavend/recall.go b/cmd/mavend/recall.go new file mode 100644 index 0000000..0027adf --- /dev/null +++ b/cmd/mavend/recall.go @@ -0,0 +1,25 @@ +package main + +import "github.com/kami/maven/internal/memory" + +// bestRecall is the read side of the long-term memory store: the top hit's +// stored text when it clears the confidence gate. This recalls across BOTH +// notes and facts (facts aren't in the notes table, so this is the only path +// that can answer "when did I last …?" from a captured fact). A note hit here +// is redundant with the notes-RAG path — by design; the two indexes can diverge +// once the backend is swapped for a persistent/external store. ok=false when +// there's no hit above the threshold or the hit carries no text. +func bestRecall(results []memory.Result, min float64) (string, bool) { + if len(results) == 0 { + return "", false + } + top := results[0] + if top.Score < min { + return "", false + } + text := top.Meta["text"] + if text == "" { + return "", false + } + return text, true +} diff --git a/cmd/mavend/recall_test.go b/cmd/mavend/recall_test.go new file mode 100644 index 0000000..d527a50 --- /dev/null +++ b/cmd/mavend/recall_test.go @@ -0,0 +1,45 @@ +package main + +import ( + "testing" + + "github.com/kami/maven/internal/memory" +) + +func TestBestRecall(t *testing.T) { + const min = 0.55 + + t.Run("empty results", func(t *testing.T) { + if _, ok := bestRecall(nil, min); ok { + t.Error("empty results returned ok") + } + }) + + t.Run("top below threshold", func(t *testing.T) { + res := []memory.Result{{Score: 0.4, Meta: map[string]string{"text": "выпил воды"}}} + if _, ok := bestRecall(res, min); ok { + t.Error("below-threshold hit returned ok") + } + }) + + t.Run("hit without text meta", func(t *testing.T) { + res := []memory.Result{{Score: 0.9, Meta: map[string]string{"type": "fact"}}} + if _, ok := bestRecall(res, min); ok { + t.Error("textless hit returned ok") + } + }) + + t.Run("clearing hit returns its text", func(t *testing.T) { + res := []memory.Result{ + {Score: 0.82, Meta: map[string]string{"text": "выпил воды в три часа", "type": "fact"}}, + {Score: 0.60, Meta: map[string]string{"text": "другое"}}, + } + got, ok := bestRecall(res, min) + if !ok { + t.Fatal("clearing hit not returned") + } + if got != "выпил воды в три часа" { + t.Errorf("wrong text: %q", got) + } + }) +}