Compare commits

..

18 Commits

Author SHA1 Message Date
claude 8b9e8e9f4e Merge pull request 'Reminders: spelled-out times fail, the body keeps the marker, and the page shows UTC' (#134) from task/469-reminders-spelled-out-times-fail-the-bod into master 2026-08-04 18:23:29 +02:00
claude b338d9bb40 Merge pull request 'Bug: the Praxis attention capability is unreachable from a question' (#133) from task/475-bug-the-praxis-attention-capability-is-u into master 2026-08-04 18:23:24 +02:00
claude 6878e12d37 Merge pull request 'Bug: a transient complaint is stored as a durable fact at confidence 1.00' (#132) from task/481-bug-a-transient-complaint-is-stored-as-a into master 2026-08-04 18:23:20 +02:00
claude 8d46ee39e0 Merge pull request 'Bug: the router transliterates Latin entity names into Cyrillic before Nexus sees them' (#131) from task/476-bug-the-router-transliterates-latin-enti into master 2026-08-04 18:23:16 +02:00
claude 6eba79b332 Merge pull request 'Decide whether a parked clarify question should survive a restart' (#130) from task/385-decide-whether-a-parked-clarify-question into master 2026-08-04 18:23:12 +02:00
claude 3f60ec3994 Merge pull request 'Backfill routines accepted before the fire-forever fix' (#129) from task/377-backfill-routines into master 2026-08-04 18:23:08 +02:00
claude 14ea06712e Merge pull request 'Weather: the 6-city match table has no geocoder behind it' (#128) from task/421-weather-geocoder into master 2026-08-04 18:23:03 +02:00
claude 42d3feadd2 Merge pull request 'No read path for delivery_attempts — the outbox is durable but invisible' (#127) from task/390-no-read-path-for-delivery-attempts into master 2026-08-04 18:22:58 +02:00
claude 2a2f706b74 Merge pull request 'Recall fixture: filler note ids can collide with case ids and split the two backends' (#126) from task/386-recall-fixture-filler-note-ids into master 2026-08-04 18:22:52 +02:00
claude e082e06868 Merge pull request 'Bug: morning.Item has no required/optional flag, so behaviour 1 of task 280 cannot hold' (#125) from task/473-bug-morning-item-has-no-required-flag into master 2026-08-04 18:22:45 +02:00
claude 88dc4e1383 Merge pull request 'Bug: make simulate routes with an empty seed set, so a green run proves less than it looks' (#124) from task/465-bug-make-simulate-routes-with-an-empty into master 2026-08-04 18:22:37 +02:00
claude 60759a991e Merge pull request 'Bug: spoken task capture is dead — the router calls the marker an act, and capture only rides the note intent' (#123) from task/467-bug-spoken-task-capture-is-dead into master 2026-08-04 18:22:28 +02:00
claude 9537346441 Merge pull request 'Bug: a pending clarify is global, so one unanswerable question swallows the next three utterances from anybody' (#122) from task/466-bug-a-pending-clarify-is-global-so-one-u into master 2026-08-04 18:21:49 +02:00
claude c6be818f13 Merge pull request 'Bug: pattern.Detect has no minimum-interval floor, so four fast taps mint a permanent false routine' (#121) from task/468-bug-pattern-detect-has-no-minimum-interv into master 2026-08-04 18:21:38 +02:00
claude b5575a9402 Merge pull request 'Bug: CheckFeminine flags second-person masculine verbs as self-reference' (#120) from task/462-bug-checkfeminine-flags-second-person-ma into master 2026-08-04 18:21:27 +02:00
claude fcda5e3d2c Merge pull request 'safeKey drops Cyrillic, so Russian calendar events on one day collide' (#119) from task/443-safekey-drops-cyrillic-so-russian-calend into master 2026-08-04 18:21:15 +02:00
claude bdafc82e35 Merge pull request 'llama-server core-dumps on every SIGTERM, so each mavgpud yield writes a core file' (#116) from task/491-llama-server-core-dumps-on-every-sigterm into master 2026-08-04 14:09:25 +02:00
claude c69023c310 Merge PR #117 into task/491 (V-383) 2026-08-04 14:08:59 +02:00
7 changed files with 5 additions and 165 deletions
+3 -27
View File
@@ -145,13 +145,6 @@ func (h *reactiveHandler) actionQuery(ctx context.Context, dec router.Decision)
continue
}
if reply, ok := src.answer(h, ctx, t); ok {
// Which source claimed is the one thing about a query turn that was
// invisible from outside: /trace is the nudge-rule trace and carries
// no query-source field, so a wrong answer could not be told from a
// wrongly-ordered chain (Vikunja #474). Only the name is logged —
// the utterance and the answer are already on the voice lines above
// and below this one.
log.Printf("voice: query claimed by source %q", src.name)
return reply
}
}
@@ -290,17 +283,9 @@ func (h *reactiveHandler) queryFeeds(ctx context.Context, t *queryTurn) (string,
return "", false
}
if !h.feedsOn {
// Claim only when nothing below can read the world. The reason this
// source used to claim unconditionally was that general knowledge would
// answer "что нового?" with an invented news bulletin — true, and it
// stopped being the only alternative on 2026-08-02, when live search
// took the lead. With SearXNG or the ZIMs configured, "что происходит
// в новостях про искусственный интеллект?" has a real answer below,
// and a configuration status is the wrong thing to say instead
// (Vikunja #474).
if h.search != nil || h.kiwix != nil {
return "", false
}
// Claim the turn rather than fall through: "не читаю ленты" is true, and
// letting general knowledge answer "что нового?" would be an invented
// news bulletin.
return "я пока не читаю ленты — они не настроены.", true
}
// By source, not the last 200 notes of any kind: a busy day of voice notes
@@ -337,15 +322,6 @@ func (h *reactiveHandler) queryFeeds(ctx context.Context, t *queryTurn) (string,
// h.now(), not time.Now(): the handler's clock is the injected one, so this
// source can be tested at a fixed time like the rest.
func (h *reactiveHandler) queryCalendar(ctx context.Context, t *queryTurn) (string, bool) {
// A day word is all this source matches on, so any question that merely
// names a day reached it first. "какая сегодня погода в Москве?" answered
// "на 02.08.2026 ничего нет." (Vikunja #474). Weather is asked about a day
// far more often than the calendar is, and the weather source sits right
// below, so the calendar steps aside on weather wording — the same bail-out
// queryHome already does for the same reason.
if isWeatherQuery(t.dec.Utterance) {
return "", false
}
date, ok := router.ParseCalendarDate(t.dec.Utterance, h.now())
if !ok {
return "", false
-19
View File
@@ -194,22 +194,3 @@ func TestFeedWorkerFetcherIsAllowlisted(t *testing.T) {
t.Fatal("the poller fetched a private address")
}
}
// TestQueryFeedsPassesWhenTheWorldCanAnswer — the defect (Vikunja #474). The
// deployed box has no feeds block and does have SearXNG, and "что происходит
// сейчас в новостях про искусственный интеллект?" got a configuration status
// instead of the live answer sitting one source below.
func TestQueryFeedsPassesWhenTheWorldCanAnswer(t *testing.T) {
h := buildFeedHandler(t, false)
h.search = &searchWiring{max: 3, runes: 1500}
if reply, ok := askFeeds(t, h, "что нового в лентах?"); ok {
t.Fatalf("feeds off with a search configured must fall through, got %q", reply)
}
// With nothing below that reads the world, the honest status is still said:
// general knowledge would otherwise answer with an invented bulletin.
h.search = nil
if reply, ok := askFeeds(t, h, "что нового в лентах?"); !ok || !strings.Contains(reply, "не настроены") {
t.Fatalf("no search and no ZIMs: reply = %q, ok = %v", reply, ok)
}
}
-3
View File
@@ -382,9 +382,6 @@ func buildRouter(emb router.Embedder, acts router.ActMatcher, threshold float64,
// question and must keep reaching replySystem, while "что у меня сегодня"
// is an agenda question and must not.
grammars = append(grammars, router.AgendaQueryGrammars()...)
// Same reason as the agenda rules, for the feeds: "что нового в лентах?"
// routed system and answered "пока не умею" (Vikunja #474).
grammars = append(grammars, router.FeedQueryGrammar())
grammars = append(grammars, router.ReminderGrammar())
// 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
+1 -33
View File
@@ -1,11 +1,6 @@
package main
import (
"context"
"testing"
"github.com/kami/maven/internal/router"
)
import "testing"
// TestExtractWeatherLocation — any place he names comes through, not just the
// six that used to be in a table (Vikunja #421).
@@ -36,30 +31,3 @@ func TestExtractWeatherLocation(t *testing.T) {
}
}
}
// TestCalendarStepsAsideForWeather — the defect (Vikunja #474). "какая сегодня
// погода в Москве?" answered "на 02.08.2026 ничего нет.": the calendar matches
// on a day word alone, and it sits above the weather source.
func TestCalendarStepsAsideForWeather(t *testing.T) {
h, api := contQueryHandler()
for _, u := range []string{
"какая сегодня погода в Москве?",
"будет дождь завтра?",
"сколько градусов сегодня?",
} {
if reply, ok := h.queryCalendar(context.Background(), &queryTurn{
dec: router.Decision{Intent: router.IntentQuery, Utterance: u},
}); ok {
t.Errorf("the calendar claimed %q with %q", u, reply)
}
}
if api.events != 0 {
t.Errorf("CalendarEvents called %d times for weather questions, want 0", api.events)
}
// The agenda question it exists for still reaches it.
if _, ok := h.queryCalendar(context.Background(), &queryTurn{
dec: router.Decision{Intent: router.IntentQuery, Utterance: "что у меня сегодня?"},
}); !ok {
t.Fatal("the calendar stopped answering the agenda question")
}
}
-1
View File
@@ -236,7 +236,6 @@ func newBaselineRouter(t *testing.T, emb router.Embedder, llmR *router.LLMRouter
// Same order as buildRouter (voicewire.go). The fixture is only worth
// anything while its grammar set is the daemon's grammar set.
grammars = append(grammars, router.AgendaQueryGrammars()...)
grammars = append(grammars, router.FeedQueryGrammar())
grammars = append(grammars, router.ReminderGrammar())
return router.New(router.Config{
Grammars: grammars,
-55
View File
@@ -1,55 +0,0 @@
package router
import (
"context"
"testing"
)
func feedRouter(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, FeedQueryGrammar())
return r
}
// The documented utterance of task 258 step 1 routed system and answered
// "пока не умею отвечать на этот вопрос.", while the same question worded with
// "новостях" worked (Vikunja #474).
func TestFeedQuestionsRouteToQuery(t *testing.T) {
r := feedRouter(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.Intent != IntentQuery {
t.Errorf("route(%q) = %s, want query", u, d.Intent)
}
}
}
// The greeting and the statement keep their intents. "что нового?" is the most
// common opener in the language, and a rule that claimed it would answer hello
// with a configuration status.
func TestFeedGrammarLeavesTheGreetingAlone(t *testing.T) {
r := feedRouter(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 {
t.Errorf("route(%q) was claimed at stage 0 as %s", u, d.Intent)
}
}
}
+1 -27
View File
@@ -208,39 +208,13 @@ func AgendaQueryGrammars() []Grammar {
}
}
// FeedQueryGrammar — stage-0 rule for "что нового в лентах?", routed to
// IntentQuery so it reaches queryFeeds.
//
// Same shape of defect as the agenda grammars: the model calls it system, and
// replySystem has no feeds arm, so the documented utterance of task 258 step 1
// answered "пока не умею отвечать на этот вопрос." while the same question
// worded with "новостях" worked (Vikunja #474).
//
// An ask word at the front and a feed noun after it are both required, which
// is the same pair ParseFeedQuery wants. "что нового?" on its own is a greeting
// — the most common opener in the language — and vagueNouns in feeds.go exists
// to keep it out of the feed reader; routing it to query here would put it
// back. "у меня новая лента в инстаграме" carries the noun without the ask and
// stays the statement it is.
func FeedQueryGrammar() Grammar {
return Grammar{
Name: "feed-query",
// (\s|[?!.]|$) rather than \b, which is ASCII-only and never fires next
// to a Cyrillic letter.
Pattern: regexp.MustCompile(`(?i)^\s*(что|какие|расскажи|покажи|почитай|прочитай)\s+.*(лент|новостн)[а-я]*(\s|[?!.]|$)`),
Build: agendaQueryBuild,
}
}
// dayWordPattern — the day words an agenda question can name. Weekdays appear
// in the accusative and prepositional forms the questions actually use ("в
// среду", "на среде"), which is why the stems carry an inflection tail rather
// than a fixed ending.
const dayWordPattern = `(сегодня|завтра|послезавтра|выходн[а-я]+|недел[а-я]+|понедельник[а-я]*|вторник[а-я]*|сред[ауые][а-я]*|четверг[а-я]*|пятниц[ауые][а-я]*|суббот[ауые][а-я]*|воскресень[ея][а-я]*)`
// agendaQueryBuild — shared Build for the agenda grammars and the feed one,
// which all do the same single thing: keep the utterance out of IntentSystem
// and let the query chain decide who answers it. Confidence 1.0 on
// agendaQueryBuild — shared Build for the agenda grammars. Confidence 1.0 on
// the intent only: the utterance travels intact and the query chain's own
// matchers decide the rest.
func agendaQueryBuild(m []string) (Decision, bool) {