KeiSeiKit-1.0/_primitives
Parfii-bot 26dc8c85f7 feat(kei-buddy): AskLanguage i18n + real proposeTopicSources + voice handling
Three follow-up atomics on top of the contacts/topics/sync wave.

## 1. AskLanguage state + ru/en localisation (default en)

New state `AskLanguage` inserted between `Intro` and `AskName`. Intro now
sends a bilingual greeting + language picker. AskLanguage parses
en/english/1/ru/русский/2/etc → persona_patch{"language":"<code>"} →
transitions to AskName with that language's prompt.

All later prompts (AskName / AskTone / AskInterests / AskHobbies /
TopicSpecifics / TopicNowLater / TopicResearch / AskSchedule / Ready)
read persona.language via Lang::from_persona and dispatch through
Strings::* helpers — two language tables, no fallthrough.

Back-compat migration: existing chats without `language` key (like the
user currently in topic_now_later) get an implicit "ru" patch on next
turn so their Russian onboarding continues without regression.

New files: strings.rs (164), machine_lang.rs (145).
Modified: state.rs (+AskLanguage variant), machine.rs (Intro→AskLanguage,
AskLanguage arm, migration guard), machine_helpers.rs, machine_tests.rs.

5 new tests (intro_to_ask_language, ask_language_en, ask_language_ru,
ask_language_invalid, migration_sets_ru_when_language_missing).

## 2. Real proposeTopicSources — removed TODO(phase2) stub

machine_lang.rs::step_topic_research now calls
extractor.extract(prompt, topic_title) with a {name, url, why} schema.
Parses JSON, formats numbered source list, transitions to TopicSources.

Failure paths (LLM error, empty array): graceful fallback prompt asking
user to suggest their own — still transitions to TopicSources so flow
doesn't deadlock.

3 new tests in machine_tests_topic_research.rs:
topic_research_yes_proposes_sources,
topic_research_yes_empty_sources_still_advances,
topic_research_no_skips_topic_sources.

## 3. Voice-message handling (Telegram voice/audio → STT → text pipeline)

kei-telegram-webhook: added Voice/Audio sub-structs on Message and
WebhookEvent::Voice variant. classify() detects message.voice OR
message.audio. 2 new tests in event.rs.

kei-buddy/src/voice.rs (178 LOC):
VoiceHandler { bot_token, stt: Arc<dyn SttBackend>, http }
transcribe_file(file_id, mime_type) does:
  1. GET https://api.telegram.org/bot{token}/getFile?file_id=...
  2. GET https://api.telegram.org/file/bot{token}/{file_path}
  3. SttRequest { audio_bytes, mime_type, language: None } → backend.transcribe
  4. Returns transcript text.
2 wiremock tests (download chain + 500 error mapping).

serve.rs adds voice: Option<Arc<VoiceHandler>> to BuddyContext;
on_event Voice arm: whitelist check → transcribe → handle_text (same
pipeline as if user typed). Voice unavailable: warn + ignore.

serve_runner.rs builds VoiceHandler from KEI_BUDDY_STT_BACKEND env.

kei-stt added as optional dep gated by serve feature. Default backend
whisper-local (no extra build deps).

TTS reply path deferred (next atomic).

## Verify

  * cargo check --workspace: PASS
  * cargo test -p kei-buddy --lib: 55 passed / 0 failed (was 41 → 50 → 53 → 55)
  * cargo test -p kei-telegram-webhook --lib: 7 passed (was 5, +2 voice)
  * cargo build -p kei-buddy --release: PASS (23.7s)

NOT deployed yet — three new things to roll out next:
  * новые миграции (нет — БД без изменений)
  * новые env: KEI_BUDDY_STT_BACKEND (optional)
  * установка faster-whisper / piper-tts на сервер для STT
    (без него Voice event просто warn-логируется и игнорируется)
2026-05-12 17:49:06 +08:00
..
_rust feat(kei-buddy): AskLanguage i18n + real proposeTopicSources + voice handling 2026-05-12 17:49:06 +08:00
templates feat(live-graph): WebSocket activity stream — orchestrator-centric live view 2026-05-02 13:30:24 +08:00
design-scrape.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
figma-tokens.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
frontend-inspect.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
harden-base.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
kei-ci-lint.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
kei-docs-scaffold.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
kei-doctor.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
kei-sleep-queue.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
kei-sleep-setup.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
kei-sleep-sync.sh feat(sleep-sync): mirror time-metrics + ledger snapshots, surface in Phase B report 2026-05-02 04:02:28 +08:00
live-preview.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
log-ship.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
MANIFEST.toml KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
metrics-scrape.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
provision-hetzner.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
provision-vultr.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
README.md KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
screenshot-decode.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00
tomd.sh KeiSeiKit-public — clean state 2026-05-01 12:09:03 +08:00

_primitives — first-class building blocks

_primitives/ holds standalone utilities that agents, hooks, and skills (including /compose-solution) depend on. Unlike _blocks/ (behavioral markdown) or _manifests/ (agent TOML), primitives are executable shell programs installed at $HOME/.claude/agents/_primitives/ by install.sh.

Current primitives

Primitive Purpose Invocation
tomd.sh Universal non-native-format → markdown converter (PDF, DOCX, XLSX, PPTX, CSV, images, code). ~/.claude/agents/_primitives/tomd.sh <file>

tomd.sh is a first-class primitive. Universal non-native-format → markdown converter with configurable cache directory (KEISEI_TOMD_CACHE) and KeiSeiKit-style error tags ([tomd]).

Hook integration

hooks/tomd-preread.sh is a PreToolUse(Read) hook that auto-redirects Claude to the converted markdown when a Read targets .docx / .doc / .xlsx / .pptx / .csv. Cached under $KEISEI_TOMD_CACHE (default /tmp/keisei-tomd-cache).

/compose-solution discovery

Phase 3 prior-art sweep greps _primitives/ alongside _blocks/, _manifests/, skills/, _bridges/, hooks/. If a user task involves file-format parsing, the meta-composer surfaces tomd automatically — reuse over rewrite (RULE "No Patching").