From f4de2fc5e1fc1c2879968c93ceb84cf6c9e16d57 Mon Sep 17 00:00:00 2001 From: kami Date: Fri, 31 Jul 2026 14:54:20 +0400 Subject: [PATCH] Don't let a verb count as the person being talked about MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The third-person check asks whether anyone else was named before "он". A nudge is mostly verbs, and they were counted as possible people, so "попробуй встать и отдохнуть — у него есть перерыв" passed. Infinitives and imperatives now join past tense as words that cannot be a person. A plain noun before the pronoun still blinds it. That needs a parser, and the comment says so. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ --- internal/phraser/eval/address_time_test.go | 16 ++++++++++++ internal/phraser/eval/checks.go | 30 ++++++++++++++++------ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/internal/phraser/eval/address_time_test.go b/internal/phraser/eval/address_time_test.go index 894a05c..b20c460 100644 --- a/internal/phraser/eval/address_time_test.go +++ b/internal/phraser/eval/address_time_test.go @@ -24,3 +24,19 @@ func TestAddressTimeWordDoesNotBlind(t *testing.T) { } } } + +// TestAddressVerbIsNotAnAntecedent — a nudge is mostly verbs, and a verb is +// never who "он" refers to. This exact string passed the check before. +func TestAddressVerbIsNotAnAntecedent(t *testing.T) { + s := "попробуй встать и отдохнуть — у него есть перерыв" + if r := checkAddress(s); r.Pass { + t.Errorf("checkAddress(%q) passed, want a third-person failure", s) + } + // Still missed, and this is the documented hole: "выпей воды, он не пил" has + // a real noun ("воды") before the pronoun, so the scan believes somebody + // else was named. Telling that apart needs a parser, not a suffix rule. + // A named third party still wins over the verbs around it. + if r := checkAddress("сервис упал, он не отвечает"); !r.Pass { + t.Errorf("checkAddress on a real third party failed: %s", r.Detail) + } +} diff --git a/internal/phraser/eval/checks.go b/internal/phraser/eval/checks.go index 838cbd3..ade60b7 100644 --- a/internal/phraser/eval/checks.go +++ b/internal/phraser/eval/checks.go @@ -340,7 +340,9 @@ func prevWord(words []string, i int) string { // - it only looks BACKWARD. "Он не отвечает, сервис упал" names the subject // after the pronoun and is flagged wrongly. // - any noun earlier in the message counts as an antecedent, even when it is -// not one ("после обеда он не ел" reads as legitimate and is missed). The +// not one ("после обеда он не ел", "выпей воды, он не пил" — both missed). +// Verbs and time words no longer count, which covers the usual nudge, but a +// plain noun before the pronoun still blinds it. The // common time words are stoplisted so the usual nudge opening does not // blind it, but a message with any other noun in front still slips through. // This is the check's real hole; widening it further would start flagging @@ -407,14 +409,26 @@ var notAnAntecedent = map[string]bool{ "твой": true, "твоя": true, "твоё": true, "твое": true, "твои": true, "твою": true, } -// looksPastVerb — a past-tense verb needs a subject of its own, so it is not an -// antecedent either. Keeps "сервис упал, он не отвечает" working off "сервис". -func looksPastVerb(w string) bool { - if len([]rune(w)) < 3 { +// looksVerb — a verb is never the thing "он" refers to, so it must not count as +// an antecedent. Past tense keeps "сервис упал, он не отвечает" working off +// "сервис"; the infinitive and imperative endings are here because a nudge is +// mostly made of them ("попробуй встать и отдохнуть — у него есть перерыв" +// slipped through with "попробуй" taken for the person being talked about). +func looksVerb(w string) bool { + r := []rune(w) + if len(r) < 3 { return false } - return strings.HasSuffix(w, "л") || strings.HasSuffix(w, "ла") || - strings.HasSuffix(w, "ло") || strings.HasSuffix(w, "ли") + for _, suf := range []string{ + "л", "ла", "ло", "ли", // past tense + "ть", "ться", "ти", "чь", // infinitive + "й", "йся", "йте", // imperative + } { + if strings.HasSuffix(w, suf) { + return true + } + } + return false } func checkAddress(body string) Result { @@ -441,7 +455,7 @@ func checkAddress(body string) Result { if !unicode.Is(unicode.Cyrillic, []rune(p)[0]) && !isLatinWord(p) { continue // punctuation } - if notAnAntecedent[p] || prepositions[p] || thirdPersonHim[p] || looksPastVerb(p) { + if notAnAntecedent[p] || prepositions[p] || thirdPersonHim[p] || looksVerb(p) { continue } named = true