package router import ( "strings" "github.com/kami/maven/internal/lexicon" "github.com/kami/maven/internal/morph" ) // A reminder needs something to say at the hour, and the gate that checks for // one was reading a slot that is never empty. // // gateLLMDecision has asked about a subjectless reminder since V-383, on the // test `d.Slots.Text == ""`. Measured on the box on 05-08-2026: "напомни" alone // routes to IntentReminder with `Text:напомни`, because fillSlots hands the text // slot the utterance when the model names nothing narrower. So the slot was // never empty, the gate never fired, and the turn reached actionReminder and // answered "не получилось разобрать время напоминания." — a parse error for a // request she never finished asking about. "ну напомни же" did the same. // // The fix is to ask what the text slot CONTAINS rather than whether it is set. // Two closed classes answer that and no third mechanism is needed: the reminder // verbs are her own vocabulary (lexicon.ReminderVerbs), and the particles and // politeness words cannot be the subject of anything (lexicon.IsFillerParticle). // A verb is matched by lemma through morph.SameWord, so "напоминай" and // "напомнить" need no entry of their own. // // Deliberately NOT reusing cmd/mavend/reminderbody.go, which strips the same // marker: that function also strips the time words, so "напомни завтра" would // read as subjectless there. Asking is right when he named no subject, and wrong // when he named a day — the reminder for tomorrow is the one whose subject she // should ask about, not one she should treat as noise. // reminderHasSubject reports whether a reminder's text names anything to say at // the hour. False for "напомни", "напомни мне", "ну напомни же"; true for // "напомни позвонить маме" and for "напомни завтра", where the day is a subject // she can ask nothing better about. func reminderHasSubject(text string) bool { for _, f := range strings.Fields(strings.ToLower(text)) { w := strings.Trim(f, " ,.;:!?—-«»\"'()") if w == "" || lexicon.IsFillerParticle(w) { continue } if isReminderVerb(w) { continue } return true } return false } // isReminderVerb matches one of her reminder imperatives by lemma. Lemma and not // prefix: "напоминание" is a noun he can perfectly well ask to be reminded // about, and a stem test would eat it. func isReminderVerb(word string) bool { for _, v := range lexicon.ReminderVerbs() { if word == v || morph.SameWord(word, v) { return true } } return false }