Files
Maven/internal/router/stagezero.go
T
claude 8015fdbb79 Harden semantic boundaries and repair dialogue state
Replace nearest-neighbour personal routing with a frozen class-balanced linear head measured on historical, stratified, cross-validation, holdout, and fresh challenge gates (V-702). Close the four repair handoff holes, preserve nested clarification flows, and route Russian possession statements through structural grammar rather than lexical exceptions (V-573). Owner explicitly requested direct commits to master.
2026-08-13 03:00:31 +04:00

71 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package router
// StageZeroGrammars — the stage 0 rule set, in the order the daemon runs it.
//
// It lives here because it used to live in two places (V-693). `buildRouter` in
// cmd/mavend/voicewire.go held the real set and `baselineGrammars` in
// internal/router/eval/eval_test.go restated it by hand, in the daemon's order,
// with its own comment saying so. Three test files score against the fixture,
// and nothing compared the two lists. By 2026-08-11 they had already drifted:
// BareCaptureGrammar was in the daemon and not in the fixture, so every routing
// measurement scored a set nobody ran. That is the failure CLAUDE.md warned
// about by name, and a diff test would have caught it one grammar late. One
// list cannot drift from itself.
//
// The order is the contract, not the membership. Each rule below says why it
// sits where it sits, and a rule inserted in the wrong place changes which
// utterances the cascade never reaches. Read the comment above a line before
// moving it, and read docs/routing.md before adding one.
//
// The classifier, the extractor, the threshold and the model arm are the
// daemon's to assemble. This function returns the rules and nothing else, so
// the fixture can also run them one at a time and see which of them contend for
// the same utterance, which the cascade hides by stopping at the first match.
func StageZeroGrammars(acts ActMatcher) []Grammar {
grammars := DefaultGrammars(acts)
grammars = append(grammars, SystemTimeDateGrammars()...)
// After the time/date rules on purpose: "какой сегодня день" is a clock
// question and must keep reaching replySystem, while "что у меня сегодня"
// is an agenda question and must not.
grammars = append(grammars, AgendaQueryGrammars()...)
// Same reason as the agenda rules, for the feeds: "что нового в лентах?"
// routed system and answered "пока не умею" (Vikunja #474).
// After the agenda rules, which are the narrower claim, and BEFORE the feed
// and list rules, which are not: "что такое лента" is a definition question
// and the feed rule would take it on the noun alone (V-655).
grammars = append(grammars, WorldQueryGrammars()...)
grammars = append(grammars, FeedQueryGrammar())
// The list side of the same exposure: a phrasing with no possessive in it
// ("список дел") routed system and never reached queryTasks (Vikunja #467).
grammars = append(grammars, TaskListGrammar())
grammars = append(grammars, ListGrammars()...)
grammars = append(grammars, ReminderGrammar())
// Before the capture marker, because "отметь" is a capture verb and "отметь
// второй пункт" is not a note. The Praxis rules are the narrower claim — a
// lifecycle verb AND an item named — so they get first refusal (Vikunja #516).
grammars = append(grammars, PraxisGrammars()...)
// After Praxis, whose bare "закрой" claim this rule cannot reach (it needs the
// board noun), and before the capture marker, which would otherwise read
// "убери из задач купить молоко" as a new task (Vikunja #512).
grammars = append(grammars, TaskStatusGrammar())
// Before the capture markers, which all need an object. A capture verb
// alone is a fact with no key, and the clarify path asks for it rather than
// letting the model invent an answer (Vikunja #557).
grammars = append(grammars, BareCaptureGrammar()...)
// Last, and it matches any utterance shape — its Build is the filter. An
// explicit capture marker beats the model, which called it an act and
// rewrote the task text (Vikunja #467). After the rules above because a
// marker never collides with a clock or agenda question.
grammars = append(grammars, TaskCaptureGrammar())
// After the capture marker, so "запиши" still wins over "расскажи", and
// last overall because it matches on the first word alone: "расскажи про
// X" is a world question the model called a fact (Vikunja #498).
grammars = append(grammars, NarrativeQueryGrammars()...)
// Last because it is deliberately broad over the open remainder of a
// grammatical declaration. Every explicit question, command, capture and
// narrative request above gets first refusal; this catches the Russian
// present-tense possession statement the statistical floors call a query.
grammars = append(grammars, PossessionStatementGrammar())
return grammars
}