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
+10 -8
View File
@@ -16,6 +16,8 @@ import (
"log"
"math/rand"
"sync"
"github.com/kami/maven/internal/say"
)
//go:embed ack_ru_v1.json
@@ -93,12 +95,12 @@ var ackFloor = map[string]string{
}
// Acks picks a hand-written Russian acknowledgement. Safe for concurrent use.
type Acks struct{ d *deck }
type Acks struct{ d *say.Deck }
// LoadAcks reads the embedded file. Pass a source to make the picking
// reproducible in tests; nil seeds from the clock.
func LoadAcks(src rand.Source) (*Acks, error) {
d, err := loadDeck(ackJSON, AckSchemaVersion, ackKeys, ackFloor, src)
d, err := say.Load(ackJSON, AckSchemaVersion, ackKeys, ackFloor, src)
if err != nil {
return nil, err
}
@@ -109,7 +111,7 @@ func LoadAcks(src rand.Source) (*Acks, error) {
{AckFactKey, "{key}"}, {AckFactValue, "{key}"}, {AckFactValue, "{value}"},
{AckAct, "{fn}"}, {AckTask, "{text}"}, {AckTaskUrgent, "{text}"},
} {
if err := d.requirePlaceholder(req.key, req.ph); err != nil {
if err := d.RequirePlaceholder(req.key, req.ph); err != nil {
return nil, err
}
}
@@ -117,9 +119,9 @@ func LoadAcks(src rand.Source) (*Acks, error) {
}
// deck reads through a nil *Acks, which is the unloadable-file case.
func (a *Acks) deck() *deck {
func (a *Acks) deck() *say.Deck {
if a == nil {
return floorDeck(ackFloor)
return say.FloorDeck(ackFloor)
}
return a.d
}
@@ -127,11 +129,11 @@ func (a *Acks) deck() *deck {
// Say returns one line for key, with his data filled into the frame. Pass nil
// when the entry takes none.
func (a *Acks) Say(key string, vars map[string]string) string {
return a.deck().text(key, vars)
return a.deck().Text(key, vars)
}
// Variants returns every line the file can produce, for the persona scorer.
func (a *Acks) Variants() []string { return a.deck().variants() }
func (a *Acks) Variants() []string { return a.deck().Variants() }
var (
ackOnce sync.Once
@@ -158,5 +160,5 @@ func Ack(key string, vars map[string]string) string { return DefaultAcks().Say(k
// IsAck reports whether text is a line key could have produced. For the daemon
// tests, which can no longer compare against one literal.
func IsAck(key string, vars map[string]string, text string) bool {
return DefaultAcks().deck().matches(key, vars, text)
return DefaultAcks().deck().Matches(key, vars, text)
}