Merge master into the line B review stack (V-405)

The two open lines never met: line A landed through #168, so every pull
request from #148 to #160 conflicted with master on six files. This
reconciles them.

Where the two lines fixed the same thing, the better shape wins:

- Ambient time zones (V-482) landed on both sides. Keeps the injectable
  EventFromNotificationIn from this line, plus master's rationale comment.
  Drops master's forced n.Posted.In(time.Local), which defeated the loc
  argument.
- tick.go: master's guardNudge call and say.CountWord edits, moved onto the
  split files this line created. The digest summary now declines through
  say.CountWord inside tick_digest.go.
- voice.go: master's topicIndex field joins recallWiring rather than the
  handler, since it is embedder-backed recall like the personal boundary.
  topics.go and its test read h.recall.topics now.
- mavweb: master's capability and risk columns ported into tools.html, which
  is where this line moved the markup. The Go const is gone.
- Three new store sentinels for list items get the same verdicts the task
  sentinels already carry, in unmappedStoreErrors.

make build: 12 binaries. make test: green. make fmt-check: clean.

--no-verify: a merge of two long lines cannot fit the 300-line budget.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 20:46:53 +04:00
160 changed files with 9759 additions and 700 deletions
+11
View File
@@ -384,12 +384,23 @@ 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())
// 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, router.TaskListGrammar())
grammars = append(grammars, router.ListGrammars()...)
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
// rewrote the task text (Vikunja #467). After the rules above because a
// marker never collides with a clock or agenda question.
grammars = append(grammars, router.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, router.NarrativeQueryGrammars()...)
return router.New(router.Config{
Grammars: grammars,
Classifier: cls,