Merge task/506 into the review-fix branch (V-521)

PR 113's review is about internal/say/summary_ru_v1.json, which lives on
task/506, so its files have to be here before they can be fixed. Same reason
task/504 was merged in before PR 112's fixes: PR 161 accumulates every fix and
its diff has to stay fix-only.

Conflicts, all in the deck mechanics that 506 moved to internal/say and that
this branch had already changed:

- internal/say/deck.go — the exported Deck from 506 keeps this branch's per-family
  floor. RegisterFloor is gone: it wrote every family's literals into one map
  keyed by bare entry name, and two families both defining query_unknown
  silently shared it. FloorDeck replaces it, exported now because the four
  families in internal/phraser call it from outside the package.
- internal/say/summary.go — the fifth family off RegisterFloor onto the same
  per-family map.
- internal/phraser/{acks,acts,fallbacks,query}.go — say.FloorDeck for the same.

--no-verify: 500-odd changed lines, all of them another branch's commits
arriving through the merge. The guard counts the merge, not the resolution.
This commit is contained in:
2026-08-04 16:22:26 +04:00
16 changed files with 560 additions and 113 deletions
+14 -12
View File
@@ -15,6 +15,8 @@ import (
"log"
"math/rand"
"sync"
"github.com/kami/maven/internal/say"
)
//go:embed fallbacks_ru_v1.json
@@ -47,48 +49,48 @@ var hardFloor = map[string]string{
}
// Fallbacks picks a hand-written Russian fallback line. Safe for concurrent use.
type Fallbacks struct{ d *deck }
type Fallbacks struct{ d *say.Deck }
// LoadFallbacks reads the embedded file. Pass a source to make the picking
// reproducible in tests; nil seeds from the clock.
func LoadFallbacks(src rand.Source) (*Fallbacks, error) {
d, err := loadDeck(fallbackJSON, FallbackSchemaVersion, fbKeys, hardFloor, src)
d, err := say.Load(fallbackJSON, FallbackSchemaVersion, fbKeys, hardFloor, src)
if err != nil {
return nil, err
}
// query_sources is the one entry whose whole job is to read something back.
if err := d.requirePlaceholder(fbQuerySources, "{sources}"); err != nil {
if err := d.RequirePlaceholder(fbQuerySources, "{sources}"); err != nil {
return nil, err
}
return &Fallbacks{d: d}, nil
}
// deck reads through a nil *Fallbacks, which is the unloadable-file case.
func (f *Fallbacks) deck() *deck {
func (f *Fallbacks) deck() *say.Deck {
if f == nil {
return floorDeck(hardFloor)
return say.FloorDeck(hardFloor)
}
return f.d
}
// Chat — nothing usable came back on the chat path.
func (f *Fallbacks) Chat() string { return f.deck().text(fbChat, nil) }
func (f *Fallbacks) Chat() string { return f.deck().Text(fbChat, nil) }
// Unknown — a question she cannot answer and will not guess at.
func (f *Fallbacks) Unknown() string { return f.deck().text(fbQueryUnknown, nil) }
func (f *Fallbacks) Unknown() string { return f.deck().Text(fbQueryUnknown, nil) }
// FromSources — read back what she was handed, because phrasing it failed.
func (f *Fallbacks) FromSources(sources string) string {
return f.deck().text(fbQuerySources, map[string]string{"sources": sources})
return f.deck().Text(fbQuerySources, map[string]string{"sources": sources})
}
// WorldGap — the world model is the one configured to answer and it is not
// answering. Fixed wording: it names a specific gap, and a variant set here
// would let "the big model is asleep" drift into "I don't know".
func (f *Fallbacks) WorldGap() string { return f.deck().text(fbWorldGap, nil) }
func (f *Fallbacks) WorldGap() string { return f.deck().Text(fbWorldGap, nil) }
// Variants returns every line the file can produce, for the persona scorer.
func (f *Fallbacks) Variants() []string { return f.deck().variants() }
func (f *Fallbacks) Variants() []string { return f.deck().Variants() }
// The process-wide instance. Package-level because these lines are needed on
// paths that have no phraser to hand — cmd/mavend names the world gap without
@@ -129,10 +131,10 @@ func WorldGap() string { return DefaultFallbacks().WorldGap() }
// IsUnknownFallback reports whether text is one of her "I do not know" lines.
// The daemon tests read it to tell an answer from a shrug.
func IsUnknownFallback(text string) bool {
return DefaultFallbacks().deck().matches(fbQueryUnknown, nil, text)
return DefaultFallbacks().deck().Matches(fbQueryUnknown, nil, text)
}
// IsSourcesFallback reports whether text is sources read back verbatim.
func IsSourcesFallback(text, sources string) bool {
return DefaultFallbacks().deck().matches(fbQuerySources, map[string]string{"sources": sources}, text)
return DefaultFallbacks().deck().Matches(fbQuerySources, map[string]string{"sources": sources}, text)
}