Files
Maven/internal/router/narrative_test.go
T
claude fe489dff6d Merge task/467 so the sweep tail can reach the three mechanisms (V-528)
attentionq.go, repair.go and internal/router/complaint.go carry the last
hand-written Russian patterns of the V-522 sweep, and they live on task/467.
internal/lexicon, internal/morph and cmd/mavend/topics.go live here. One of
the two had to move.

Four conflicts, and one of them is a real collision rather than a mechanical
one. Both branches wrote the narrative stage 0 rule. This side had
NarrativeQueryGrammars, plural, with the rest-of-day rule beside it and the
verb alternation built from the lexicon; task/467 had NarrativeQueryGrammar,
singular, which extracts the topic into Slots.Text, refuses a bare "расскажи",
and excludes the shapes that are chat ("расскажи о себе", "историю на ночь").
Resolved by keeping this side's container and this side's lexicon-built
pattern, and taking every behaviour only the other side had: the topic slot,
the empty-topic refusal, chatNarrativeTopics, and its wiring position after
TaskCaptureGrammar so "запиши" still beats "расскажи".

The rest: queryFeeds keeps task/467's conditional claim (V-474 supersedes the
unconditional one), rank.go keeps Spoken and drops pluralTasksRU because
say.CountWord is the one copy of Russian count agreement, and vendor/ was
re-vendored — the merged modules.txt claimed replaces for nexus and praxis
that neither go.mod has.

Routing fixture 58/82, unchanged from both sides.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 19:00:32 +04:00

86 lines
3.0 KiB
Go

package router
import (
"context"
"testing"
)
// narrativeRouter wires the grammars in the order the daemon wires them
// (voicewire.go), with the narrative rule last — so a test that passes here is
// a test of the deployed precedence, not of the rule in isolation.
func narrativeRouter(t *testing.T) *Router {
t.Helper()
r := newTestRouter(t, 0.0)
r.grammars = append(r.grammars, SystemTimeDateGrammars()...)
r.grammars = append(r.grammars, AgendaQueryGrammars()...)
r.grammars = append(r.grammars, TaskListGrammar())
r.grammars = append(r.grammars, TaskCaptureGrammar())
r.grammars = append(r.grammars, NarrativeQueryGrammars()[1])
return r
}
// "расскажи про X" and "что дальше?" carried no question mark and no
// interrogative, so nothing at stage 0 claimed them and the model called both
// facts (Vikunja #498, point 1 of #470). The fact write is contained now, but
// the round trip and the wrong fixture score are not.
func TestNarrativeAndRestOfDayRouteToQueryAtStageZero(t *testing.T) {
r := narrativeRouter(t)
for _, u := range []string{
"расскажи про битву при Ватерлоо",
"расскажи мне про Юникод",
"объясни как работает tcp",
"опиши Самару",
"перечисли планеты",
"tell me about the fall of Rome",
"что дальше?",
"и что там дальше",
"что дальше",
"what's next?",
} {
d, err := r.Route(context.Background(), u, refNow())
if err != nil {
t.Fatalf("route(%q): %v", u, err)
}
if d.Intent != IntentQuery {
t.Errorf("route(%q) = %s, want query", u, d.Intent)
}
if d.Stage != 0 {
t.Errorf("route(%q) decided at stage %d, want 0 — the point is to skip the model", u, d.Stage)
}
}
}
// The narrative rule must not take a turn that belongs to something else. A
// capture marker wins because it is what he said, and asking her for a joke is
// chat: the query chain has no source that answers it.
func TestNarrativeGrammarLeavesOtherTurnsAlone(t *testing.T) {
r := narrativeRouter(t)
for _, u := range []string{
"расскажи анекдот",
"расскажи о себе",
"расскажи шутку",
"расскажи",
} {
d, err := r.Route(context.Background(), u, refNow())
if err != nil {
t.Fatalf("route(%q): %v", u, err)
}
if d.Stage == 0 && d.Intent == IntentQuery {
t.Errorf("route(%q) was claimed as a world question at stage 0", u)
}
}
}
// The topic reaches the query chain without the verb that introduced it: the
// search leg wants "битву при Ватерлоо", not "расскажи про битву при Ватерлоо".
func TestNarrativeGrammarKeepsTheTopic(t *testing.T) {
r := narrativeRouter(t)
d, err := r.Route(context.Background(), "расскажи про битву при Ватерлоо", refNow())
if err != nil {
t.Fatal(err)
}
if got, want := d.Slots.Text, "битву при Ватерлоо"; got != want {
t.Errorf("text = %q, want %q", got, want)
}
}