Files
nanobot-runtime/results/remind-skill-audit-2026-06-02.md
2026-06-10 06:39:52 +02:00

141 lines
7.3 KiB
Markdown

# Deep Research Audit: /remind Skill
**Datum:** 2026-06-02
**Model:** GLM-5.1:cloud
**Scope:** SKILL.md, remind_edit.py, remind_send.py, random_times.py, testy, reminder.yaml, .reminder_state.json, log, crontab
---
## 🔴 Kritické problémy
### 1. Žádný `update` příkaz
`remind_edit.py` má jen `list`, `add`, `remove`. Když chceš změnit čas existujícího reminderu, musíš ho smazat a vytvořit znovu. To je nebezpečné — `remove` matchuje substring, takže při recreate můžeš trefit špatný záznam nebo vytvořit duplikát.
**Návrh:** Přidat `update --keyword "..." --cron/--at/--random-*` příkaz, který najde reminder a upraví jen zadaná pole.
### 2. `at` remindery se nikdy nesmažou (no garbage collection)
Jednorázové `at` remindery zůstávají v `reminder.yaml` navždy. Po odeslání se jen přestanou spouštět, ale leží v YAML a loadují se každý minutovým cronem. Po čase tam bude stovky mrtvých záznamů.
**Návrh:** `remind_send.py` by měl po úspěšném doručení `at` reminderu zapsat flag nebo rovnou zavolat `remind_edit.py remove`. Nebo lépe — přidat `purge` subcommand, který smaže všechny `at` remindery s `at_time < now`.
### 3. Žádné stabilní ID — `remove` matchuje substring
`remove --keyword "boty"` by smazal "objednat boty xshoes", ale taky "koupit boty pro dědu". Substring match na textu je křehký.
**Návrh:** Přidat `id` pole (hash nebo UUID) přiřazené při `add`. `remove` i `update` by primárně pracovaly s `--id`. `--keyword` by zůstal jako fallback.
### 4. Deduplikace přes SHA1(text)[:8] — kolize a křehkost
`.reminder_state.json` klíč je `hashlib.sha1(text.encode())[:8]` — 4 bajty hex. Při ~65k reminderů je kolize pravděpodobná. Horší: když se text změní (i jen překlep), dedup key se změní a reminder se odešle znovu.
**Návrh:** Použít stabilní `id` z bodu 3 jako klíč do state. SHA1[:8] zahodit.
---
## 🟡 Střední problémy
### 5. Hardcoded `CHAT_ID` v remind_send.py
`CHAT_ID = "8826147089"` je natvrdo v kódu. Když se změní uživatel nebo přidá druhý, musí se upravovat zdroják.
**Návrh:** Číst `chat_id` z `config.json` (tam už je token), nebo z `reminder.yaml` jako globální `default_chat_id`.
### 6. Žádná validace `at` časů v budoucnosti
`remind_edit.py` přijme `--at "2020-01-01T00:00:00"` bez chyby. Zápis v minulosti nedává smysl a nikdy se nespustí.
**Návrh:** Validovat `at > now()` v `cmd_add`. Případně alespoň varování na stderr.
### 7. Žádný max-retry / TTL pro neodeslané remindery
Když Telegram API vrátí chybu, `remind_send.py` zkusí znovu příští minutu — ale jen pokud `last` state nebyl nastaven. Když selže 100x po sobě, zkusí to 100x. Žádný TTL ani exponential backoff.
**Návrh:** Přidat retry count do state. Po 3 selháních označit jako `failed` a přestat zkoušet. Nebo jednoduše: po 5 minutách od first fire time přestat retryovat.
### 8. Identity check bug v `cmd_remove`
```python
data["reminders"] = [r for r in data["reminders"] if r is not removed]
```
`is not` je identity check. Funguje, protože `matches[0]` je reference na stejný dict v seznamu, ale je to křehké — jakýkoliv refaktoring (deep copy, reload) to rozbije.
**Návrh:** Použít index nebo `id`-based filter.
### 9. Chybí dokumentace k `at_times` (multi-at)
SKILL.md dokumentuje `--at` jako "repeatable", ale `remind_send.py` zpracovává `at_times` pole, zatímco SKILL.md ho nezmíní jako samostatný koncept. Uživatel (nebo LLM) může být zmatený.
**Návrh:** Doplnit SKILL.md o příklad multi-at.
---
## 🔵 Zlepšení kódu
### 10. Přechod z YAML na SQLite
YAML je lidsky čitelný, ale:
- Atomic write přes `.tmp` + `os.replace` je správný, ale zbytečně složitý
- YAML nemá schema, snadno se rozbije ruční editací
- Dotazy (list, search) vyžadují full load
**Návrh:** Přesunout data do `db/reminders.sqlite` (konvence `db/*.sqlite`). YAML nechat jako read-only export nebo zahodit. `remind_edit.py` by pracoval s SQLite, `remind_send.py` taky. Výhody: ID autoincrement, atomicity zdarma, snadný search, žádný parse overhead.
### 11. Cachování Telegram tokenu
`_telegram_token()` čte a parsuje `config.json` každou minutu. Soubor se nemění.
**Návrh:** Načíst jednou při startu, cachovat v modulu. Nebo ještě lépe — environment variable `TELEGRAM_BOT_TOKEN`.
### 12. Log enrichment
`reminder.log` má jen `timestamp text`. Chybí: delivery status, fire time vs actual send time, reminder ID.
**Návrh:** Formát: `{ts} {id} {fire_time} {status} {text}`
### 13. `--dry-run` flag pro `add`
Užitečné pro LLM skill workflow — ukáže, co by se přidalo, bez zápisu.
### 14. Test coverage — chybí testy pro remind_edit.py a remind_send.py
Testy pokrývají jen `random_times.py`. `remind_edit.py` (CRUD) a `remind_send.py` (dedup, fire detection) nemají žádné testy.
**Návrh:** Přidat unit testy pro:
- `cmd_add` s různými kombinacemi flagů
- `cmd_remove` s 0/1/N matches
- `_due_fire` s různými typy reminderů
- Dedup state management
---
## 🟢 Chybějící funkce
### 15. Pause / disable reminder
Nemáš způsob jak reminder dočasně vypnout bez smazání. Běžný use case: "nech mě týden na pokoji".
**Návrh:** Přidat `enabled: true/false` pole. `remind_send.py` by skipoval `enabled: false`. Příkaz `remind_edit.py pause --id X` / `resume --id X`.
### 16. Cron s end date
Cron remindery běží navždy. Chybí `until` datum pro cron (podobně jako `random``from`/`until`).
**Návrh:** Přidat `until` pole na úroveň reminderu. `remind_send.py` by po `until` datumu reminder přeskočil.
### 17. Snooze
Když reminder přijde a uživatel není připraven, nemá jak ho odložit. To by vyžadovalo interakci s Telegram botem (callback button), což je mimo současný scope, ale je to přirozené rozšíření.
### 18. `list --due` nebo `list --next`
Užitečné zobrazit jen remindery, které se spustí v následujících N hodin. SKILL.md to neumožňuje.
**Návrh:** Přidat `list --due-within 2h` nebo `list --next 5`.
### 19. Per-reminder timezone
SKILL.md říká "Timezone is always Europe/Prague". To je OK pro jednoho uživatele, ale kód je tight-coupled — `TZ` je konstanta v `remind_send.py`. Pro multi-user by to muselo být konfigurovatelné.
---
## 📋 Prioritizovaný implementační plán
| Priorita | Co | Proč |
|----------|----|------|
| **P0** | Stabilní ID + dedup fix (body 3, 4) | Bez toho hrozí kolize a duplikátní doručení |
| **P0** | Garbage collection `at` reminderů (bod 2) | YAML poroste donekonečna |
| **P0** | Identity check fix v remove (bod 8) | Tichý bug, dnes funguje náhodou |
| **P1** | `update` příkaz (bod 1) | Zásadní UX zlepšení, snižuje riziko chyb |
| **P1** | Validace `at` v budoucnosti (bod 6) | Prevence nesmyslných vstupů |
| **P1** | Retry TTL (bod 7) | Prevence nekonečných retry |
| **P2** | SQLite backend (bod 10) | Architektonické zlepšení, ale není urgentní |
| **P2** | Testy pro edit/send (bod 14) | Spolehlivost |
| **P2** | `pause`/`resume` (bod 15) | Užitečná funkce |
| **P2** | `until` pro cron (bod 16) | Užitečná funkce |
| **P3** | Chat ID z configu (bod 5) | Multi-user příprava |
| **P3** | Log enrichment (bod 12) | Debugovatelnost |
| **P3** | `--dry-run` (bod 13) | Vývojářská ergonomie |
| **P3** | `list --due` (bod 18) | Nice-to-have |