Files
Maven/internal/router/taskstatus_test.go
T
claude a97764c5f7 Add seven stage 0 frames and tighten three more (V-720)
MavenHelpGrammar keeps "как отменить напоминание" on SourceSelf, where the
answer names the command Maven accepts, instead of leaking to search.
PublicCurrentVersionGrammar anchors an explicitly current release on
SourceWorld and declines first-person ownership.

AmbiguousFragmentGrammar refuses filler plus an unresolved demonstrative
rather than letting a statistical head invent context.
ImplicitElapsedQueryGrammar reads Russian question word order in "давно я
не тренировался" as recall; the declarative order stays a statement.
ReminderCancellationReportGrammar keeps "я отменил напоминание" in the
non-mutating chat lane.

CommandProhibitionGrammar routes a direct negative command to a sentinel
fn that can never collide with an enabled tool. ActHasEntityTarget stops a
bare verb or a demonstrative-only tail from crossing into Nexus.

Praxis attention now accepts "что там с X" for the four service names only.
taskstatus separates command mood from result words so a first-person
report cannot mutate the board. question.go exports the open-question and
locative shapes the recall gate reads.

--no-verify: master is the working branch this session by the owner's call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 17:18:48 +04:00

144 lines
7.9 KiB
Go

package router
import "testing"
func TestParseTaskStatus(t *testing.T) {
cases := []struct {
utterance string
ok bool
status string
text string
}{
// The shapes that reached nothing before this rule.
{"закрой задачу купить молоко", true, "done", "купить молоко"},
{"задачу купить молоко закрой", true, "done", "купить молоко"},
{"убери из задач купить молоко", true, "dropped", "купить молоко"},
{"убери из моих задач купить молоко", true, "dropped", "купить молоко"},
{"отмени задачу оплатить интернет", true, "dropped", "оплатить интернет"},
{"please close task buy milk", true, "done", "buy milk"},
{"не мог бы ты закрыть задачу купить молоко?", true, "done", "купить молоко"},
// Referents come from the bounded command frame. Interior prepositions
// remain part of a real title; marker verbs and topic prepositions do not.
{"закрой задачу сходить в банк", true, "done", "сходить в банк"},
{"mark task about backups as done", true, "done", "backups"},
// Question-shaped prose containing the same change word is not an
// instruction. This parser feeds a mutating stage-0 decision, so its
// cautious direction is to decline and let the query cascade answer.
{"как отменить задачу купить хлеб?", false, "", ""},
{"как отменить задачу купить хлеб", false, "", ""},
{"отменить задачу купить хлеб? пожалуйста", false, "", ""},
{"how do I cancel task buy bread", false, "", ""},
{"что будет если удалить задачу купить хлеб", false, "", ""},
{"объясни как закрыть задачу купить хлеб", false, "", ""},
// Capture verbs normally outrank question shape. They do not get that
// exception here, because this mutating grammar runs before capture.
{"запиши как отменить задачу купить хлеб", false, "", ""},
{"отметь как отменить задачу купить хлеб", false, "", ""},
// The established state-marker command is not a question: как means
// "as" inside a closed marker frame here.
{"отметь задачу про бэкапы как сделано", true, "done", "бэкапы"},
{"отметь задачу про бэкапы как сделанную", true, "done", "бэкапы"},
// A marker-shaped verb can itself belong to the title. Frame position,
// not global word subtraction, decides which occurrence is control.
{"отметь задачу отметить выходные как сделанную", true, "done", "отметить выходные"},
{"задачу отметить выходные отметь как сделанную", true, "done", "отметить выходные"},
// Token boundaries matter in both directions. Interrogative roots inside
// a word or an indefinite compound do not turn a direct command into a
// question, and an interrogative in the already-commanded task title is
// part of its name.
{"отмени задачу купить какао", true, "dropped", "купить какао"},
{"закрой задачу чтобы купить хлеб", true, "done", "чтобы купить хлеб"},
{"отмени задачу купить что-нибудь", true, "dropped", "купить что-нибудь"},
{"отмени задачу узнать когда рейс", true, "dropped", "узнать когда рейс"},
// The referent may be missing. The turn is still his, and the daemon has
// the list to ask about.
{"закрой задачу", true, "done", ""},
{"убери задачу", true, "dropped", ""},
// No board noun: ordinary speech, and every one of these means something
// else. "закрой" alone belongs to Praxis.
{"готово", false, "", ""},
{"закрой", false, "", ""},
{"убери со стола", false, "", ""},
{"я всё сделал", false, "", ""},
{"закрой шторы в комнате", false, "", ""},
// Reports, plans and desires name a transition but do not address Maven.
// The old mixed-mood accessor lemma-matched every one into a write.
{"я закрыл задачу купить молоко", false, "", ""},
{"я отменил задачу купить молоко", false, "", ""},
{"I closed the task buy milk", false, "", ""},
{"I cancelled the task buy milk", false, "", ""},
{"надо закрыть задачу купить молоко", false, "", ""},
{"я хочу закрыть задачу купить молоко", false, "", ""},
{"I want to close task buy milk", false, "", ""},
// Direct negative authority and the ambiguous don't-forget board idiom
// fail closed before any transition slot is exposed.
{"не закрывай задачу купить молоко", false, "", ""},
{"не закрой задачу купить молоко", false, "", ""},
{"не закрыть задачу купить молоко", false, "", ""},
{"don't close task buy milk", false, "", ""},
{"never cancel task buy milk", false, "", ""},
{"не забудь закрыть задачу купить молоко", false, "", ""},
// The board noun with no status word is a list query, not a move.
{"какие у меня задачи", false, "", ""},
{"добавь в задачи купить молоко", false, "", ""},
// Two transitions in one sentence. Asking beats picking.
{"задачу купить молоко готово убери", false, "", ""},
{"", false, "", ""},
}
for _, c := range cases {
got, ok := ParseTaskStatus(c.utterance)
if ok != c.ok {
t.Errorf("ParseTaskStatus(%q) ok = %v, want %v", c.utterance, ok, c.ok)
continue
}
if !ok {
continue
}
if got.Status != c.status || got.Text != c.text {
t.Errorf("ParseTaskStatus(%q) = %+v, want status %q text %q", c.utterance, got, c.status, c.text)
}
}
}
func TestTaskStatusGrammarFillsTheFnSlot(t *testing.T) {
g := TaskStatusGrammar()
dec, matched, ok := g.Evaluate("закрой задачу купить молоко")
if !matched || !ok {
t.Fatal("structural grammar declined")
}
if dec.Intent != IntentAct || !dec.Slots.HasFn || dec.Slots.Fn != TaskStatusFn {
t.Fatalf("decision = %+v, want act with fn %q", dec, TaskStatusFn)
}
if dec.Slots.Value != "done" || dec.Slots.Text != "купить молоко" {
t.Fatalf("slots = %+v, want value done text \"купить молоко\"", dec.Slots)
}
}
func TestTaskStatusGrammarAcceptsInflectedDoneMarker(t *testing.T) {
g := TaskStatusGrammar()
dec, matched, accepted := g.Evaluate("отметь задачу про бэкапы как сделанную")
if !matched || !accepted {
t.Fatalf("inflected state-marker command matched=%v accepted=%v", matched, accepted)
}
if dec.Intent != IntentAct || dec.Slots.Fn != TaskStatusFn || dec.Slots.Value != "done" || dec.Slots.Text != "бэкапы" {
t.Fatalf("decision = %+v, want task_status/done with the backup task referent", dec)
}
}
func TestTaskStatusGrammarDeclinesQuestionsWithoutMutationSlots(t *testing.T) {
g := TaskStatusGrammar()
for _, utterance := range []string{
"как отменить задачу купить хлеб?",
"how do I drop task buy bread",
"запиши как отменить задачу купить хлеб",
} {
dec, matched, accepted := g.Evaluate(utterance)
if matched || accepted {
t.Errorf("TaskStatusGrammar(%q) matched=%v accepted=%v; a structural refusal must reach the query cascade", utterance, matched, accepted)
}
if dec.Slots.HasFn || dec.Slots.Fn != "" {
t.Errorf("TaskStatusGrammar(%q) exposed mutation slots: %+v", utterance, dec.Slots)
}
}
}