Files
claude 33032b859a strings family 5: the confirmation answers move, the prompt does not (V-505)
The task asked to decide first whether this family should move at all. It
moves, but only half of it, and the half that stays put is the important one.

The prompt is already in acts_ru_v1.json. act_confirm and act_confirm_entity
went there with family 4, which is where they belong: the sentence he has to
hear before he says yes is an act line, and it loads with {name} required, so a
variant that dropped the capability cannot exist. Nothing about that needed
redoing.

What was left in cmd/mavend/confirm.go is the answers. Those are now
confirm_ru_v1.json: cancelled, the two routine answers, and the four
propose-gap lines. Every entry is fixed at one wording. He answered a question
about one specific thing, so variety buys nothing here and costs the property
that matters, which is that the same act reports the same outcome every time.
The three propose lines that name the verb have {name} required, for the same
reason the prompt does.

Two literals also stopped being duplicates. The confirmed tool run said
"готово." and "не получилось выполнить команду." word for word from the acts
family, so it now reports through ActDone and ActFail rather than keeping a
second copy to drift from.

Family 5 was the last one open. The persona scorer sweeps the new variants with
the other five, and the single-variant-means-fixed test now covers it.
2026-08-05 16:07:50 +04:00

140 lines
5.4 KiB
Go

package phraser
// The confirmation answers — what she says once he has answered a confirm, and
// what she says when an act names a verb she may not run.
//
// Fifth family on the shared deck (deck.go). They were literals in
// cmd/mavend/confirm.go.
//
// The prompt is deliberately not here. act_confirm and act_confirm_entity are in
// acts_ru_v1.json, where they belong: the sentence he has to hear before he says
// yes is an act line, and it already loads with its {name} placeholder required.
// Vikunja #505 asked whether this family should move at all. It moves, but only
// the answers, and every entry is fixed. He answered a question about one
// specific thing, so variety here buys nothing and costs the one property that
// matters: the same act reports the same outcome every time.
//
// The tool-confirm success and failure lines are not here either. They said
// "готово." and "не получилось выполнить команду." in two places, which is the
// acts family word for word, so the confirmed run now reports through ActDone
// and ActFail rather than keeping a second copy that can drift.
import (
_ "embed"
"log"
"math/rand"
"sync"
"github.com/kami/maven/internal/say"
)
//go:embed confirm_ru_v1.json
var confirmJSON []byte
// ConfirmSchemaVersion — this family's own version.
const ConfirmSchemaVersion = 1
// The entry keys.
const (
// ConfirmCancelled — he said no. It names no capability, because the
// prompt he answered named one and nothing ran.
ConfirmCancelled = "confirm_cancelled"
// ConfirmRoutineAuthed — he said yes to a proposed routine out loud, which
// is not an acceptance. A room mic cannot hand the tick loop a standing new
// reason to speak, so the row stays proposed and this line points at the
// page where the accept button is gated (Vikunja #367).
ConfirmRoutineAuthed = "confirm_routine_authed"
ConfirmRoutineNo = "confirm_routine_no"
// The propose-gap lines: an act whose verb is not on the allowlist. She
// drafts the registration and says so. She never enables it.
ProposeNoVerb = "propose_no_verb"
ProposeFailed = "propose_failed"
ProposeNew = "propose_new"
ProposeAlready = "propose_already"
)
var confirmKeys = []string{
ConfirmCancelled, ConfirmRoutineAuthed, ConfirmRoutineNo,
ProposeNoVerb, ProposeFailed, ProposeNew, ProposeAlready,
}
// confirmFloor — the literal each key falls back to when the file is unusable.
// These are the exact strings that lived in cmd/mavend/confirm.go.
var confirmFloor = map[string]string{
ConfirmCancelled: "отменила.",
ConfirmRoutineAuthed: "поняла — подтверди на странице рутин, и начну напоминать.",
ConfirmRoutineNo: "хорошо, не буду.",
ProposeNoVerb: "не разобрала команду — попробуй иначе.",
ProposeFailed: "команды «{name}» нет в списке разрешённых.",
ProposeNew: "команды «{name}» нет в списке. Предложила её добавить — включи через клиент.",
ProposeAlready: "команды «{name}» пока нет в списке — она уже предложена, включи через клиент.",
}
// Confirms picks a hand-written Russian confirmation answer. Safe for
// concurrent use.
type Confirms struct{ d *say.Deck }
// LoadConfirms reads the embedded file. Pass a source to make the picking
// reproducible in tests; nil seeds from the clock.
func LoadConfirms(src rand.Source) (*Confirms, error) {
d, err := say.Load(confirmJSON, ConfirmSchemaVersion, confirmKeys, confirmFloor, src)
if err != nil {
return nil, err
}
// Every propose line that names the verb must keep naming it. A variant that
// dropped {name} would tell him a command is not allowed without saying
// which one, and the whole point of the line is that he goes and enables it.
for _, key := range []string{ProposeFailed, ProposeNew, ProposeAlready} {
if err := d.RequirePlaceholder(key, "{name}"); err != nil {
return nil, err
}
}
return &Confirms{d: d}, nil
}
// deck reads through a nil *Confirms, which is the unloadable-file case.
func (c *Confirms) deck() *say.Deck {
if c == nil {
return say.FloorDeck(confirmFloor)
}
return c.d
}
// Say returns the line for key, with the verb filled into the frame.
func (c *Confirms) Say(key string, vars map[string]string) string {
return c.deck().Text(key, vars)
}
// Variants returns every line the file can produce, for the persona scorer.
func (c *Confirms) Variants() []string { return c.deck().Variants() }
var (
confirmOnce sync.Once
confirmsDeck *Confirms
)
// DefaultConfirms returns the shared instance, loading it on first use. A broken
// file logs once and leaves a nil *Confirms, which still answers from
// confirmFloor: a daemon must not fail to boot over its own copy deck.
func DefaultConfirms() *Confirms {
confirmOnce.Do(func() {
c, err := LoadConfirms(nil)
if err != nil {
log.Printf("phraser: confirmation answers unavailable, using the built-in lines: %v", err)
return
}
confirmsDeck = c
})
return confirmsDeck
}
// C — one confirmation answer, the way every caller says it.
func C(key string, vars map[string]string) string { return DefaultConfirms().Say(key, vars) }
// IsC reports whether text is a line key could have produced, for the tests.
func IsC(key string, vars map[string]string, text string) bool {
return DefaultConfirms().deck().Matches(key, vars, text)
}