User asked the code stays easy to fix/extend as the feature surface grows.
This commit refactors what was duplicated, centralises what was scattered,
adds tests where there were none, and writes the architecture doc that
contributors will need.
Deduplication
- tts::play_wav extracted to tts/mod.rs as pub(crate). Was identical in
piper.rs and silero.rs (~25 lines × 2). Both backends now call super::play_wav.
Centralised env config
- new crates/jarvis-core/src/runtime_config.rs — single doc-file for every
JARVIS_* / GROQ_* / OLLAMA_* env var. Includes:
- ENV_* constants with doc comments
- get(name) / get_bool(name, default) / get_parse(name, default) helpers
- feature-flag wrappers: llm_tts_enabled(), llm_router_enabled(),
llm_router_threshold()
- log_effective_config() prints active values on startup
- migrated llm_fallback (JARVIS_LLM_TTS), llm_router (JARVIS_LLM_ROUTER,
JARVIS_LLM_ROUTER_THRESHOLD) to use the new helpers. Pattern set for
future migrations.
Lua boilerplate killer
- new crates/jarvis-core/src/lua/api/cmd.rs exposing:
jarvis.cmd.ok(msg?) — play_ok + speak + {chain=false}
jarvis.cmd.chain_ok(msg?) — same but chain=true
jarvis.cmd.error(msg?) — play_error + speak + {chain=false}
jarvis.cmd.not_found(msg?) — play_not_found + speak + {chain=false}
jarvis.cmd.silent_ok / silent_error
- refactored 5 packs (daily_briefing/off, memory_pack/list, pomodoro/stop,
habit_nudge/stop_all, scheduler/clear) — each lost 3-4 lines of repetitive
play_*/speak/return boilerplate. Pattern for future packs documented in
ARCHITECTURE.md.
Tests (was 32, now 49)
- long_term_memory: 8 new tests for normalize_key, search_in (rank, limit,
empty), build_context_from (empty + populated), serde round-trips for
MemoryRecord and Store. Extracted pure logic (search_in, build_context_from)
into pub(crate) functions to enable testing without global state.
- profiles: 6 new tests for Profile::allows_command (empty/whitelist/blacklist/
deny-wins-over-allow), serde round-trip with all fields + minimal-fields
tolerance via #[serde(default)].
- runtime_config: 2 tests for get_bool / get_parse defaults.
- All 49 tests pass.
New mood/energy log pack
- resources/commands/mood_log/ with 2 commands:
mood.record "запиши настроение 7" / "сегодня мне грустно" → stores
timestamped entry via jarvis.memory.remember
mood.recap "как прошла неделя" → LLM summarises last 30 entries
- Showcase: composes memory + llm + cmd helpers in <30 lines per script.
ARCHITECTURE.md (new, 250 lines)
- Crate layout, data flow diagram (mic→action 10 steps), per-module
responsibility table, configuration layers, TTS pipeline diagram,
Lua sandbox details with API quick-ref, background-services overview,
"how to add a pack/feature/TTS backend" recipes, test coverage map,
build instructions with MSVC env, git workflow with Forgejo NO_PROXY trick.
- Aimed at someone who just cloned the repo and needs to fix a bug fast.
Build: cargo build --release -p jarvis-app -p jarvis-gui both green.
Tests: 49/49 jarvis-core unit tests pass.
33 lines
1.3 KiB
Lua
33 lines
1.3 KiB
Lua
-- Pull all mood_* entries, ask LLM to summarize trends.
|
||
local all = jarvis.memory.all()
|
||
local moods = {}
|
||
for _, rec in ipairs(all) do
|
||
if rec.key:sub(1, 5) == "mood_" then
|
||
table.insert(moods, rec.value)
|
||
end
|
||
end
|
||
|
||
if #moods == 0 then
|
||
return jarvis.cmd.not_found("Записей о настроении пока нет.")
|
||
end
|
||
|
||
-- Trim to most-recent-ish 30 entries (memory.all isn't ordered, but mood_<ts> keys sort)
|
||
table.sort(moods)
|
||
local recent = {}
|
||
local start = math.max(1, #moods - 30 + 1)
|
||
for i = start, #moods do
|
||
table.insert(recent, moods[i])
|
||
end
|
||
|
||
local joined = table.concat(recent, "\n")
|
||
|
||
local summary = jarvis.llm({
|
||
{ role = "system", content = "Ты — психотерапевт-помощник. На основе записей пользователя о настроении дай короткую сводку (3-5 предложений) на русском: общий тон, тренды, выдели хороший/плохой день. Без банальностей, без советов." },
|
||
{ role = "user", content = "Записи:\n" .. joined }
|
||
}, { max_tokens = 250, temperature = 0.5 })
|
||
|
||
if not summary or summary == "" then
|
||
return jarvis.cmd.error("Не получилось обобщить. Возможно, нет связи с LLM.")
|
||
end
|
||
|
||
return jarvis.cmd.ok(summary)
|