87 lines
4.8 KiB
Markdown
87 lines
4.8 KiB
Markdown
# /remind Skill — Top 5 Priorities (Merged from Two Audits)
|
||
|
||
**Datum:** 2026-06-02
|
||
**Model:** GLM-5.1:cloud
|
||
**Zdroje:** `results/2026-06-02_remind-skill-analysis-and-improvements.md` + `results/remind-skill-audit-2026-06-02.md`
|
||
|
||
---
|
||
|
||
## 1. Stabilní ID + dedup fix (nahradit SHA1[:8] + substring match)
|
||
|
||
**Problém:** Dva propojené bugy:
|
||
- `.reminder_state.json` používá `SHA1(text)[:8]` jako dedup klíč — 4 bajty hex, kolize při ~65k reminderů. Změna textu (i překlep) vytvoří nový klíč → duplikátní doručení.
|
||
- `remove` matchuje substring — `remove --keyword "boty"` smaže i "koupit boty pro dědu".
|
||
- `cmd_remove` používá `is not` identity check — funguje jen díky referenční shodě, po refaktoringu (deep copy, reload) se rozbije.
|
||
|
||
**Řešení:**
|
||
- Přidat `id` pole (UUID nebo short hash z text+timestamp) přiřazené při `add`.
|
||
- `remove` i `update` primárně přes `--id`, `--keyword` jako fallback.
|
||
- State file klíč → stabilní `id` místo SHA1[:8].
|
||
- `cmd_remove` filtrovat přes `id` nebo index, ne přes `is not`.
|
||
|
||
**Dopad:** Zabrání tichým datovým ztrátám a duplikátům. Bez toho je celý skill nespolehlivý.
|
||
|
||
---
|
||
|
||
## 2. Garbage collection `at` reminderů + deduplikace při doručení
|
||
|
||
**Problém:** Dva propojené bugy:
|
||
- Jednorázové `at` remindery zůstávají v `reminder.yaml` navždy. Po odeslání se jen přestanou spouštět, ale loadují se každý minutovým cronem. Po měsících tam budou stovky mrtvých záznamů.
|
||
- `should_fire()` má 60s okno — s minutovým cronem může `at` reminder doručit dvakrát (např. při dvojím spuštění cronu nebo časovém posunu). Log ukazuje, že to zatím proběhlo OK, ale není to garantováno.
|
||
|
||
**Řešení:**
|
||
- `remind_send.py` po úspěšném doručení `at` reminderu: buď ho smazat z YAML, nebo přidat `purge` subcommand pro ruční cleanup.
|
||
- Zužit existující state file pro dedup: zúžit okno na `0 <= delta < 30` a kontrolovat, zda už byl ve stejném minutovém okně doručen (state file už existuje, jen má špatný klíč — viz bod 1).
|
||
- Alternativně: SQLite state tabulka `fired(text, scheduled_at, fired_at)` s `PRIMARY KEY(text, scheduled_at)`.
|
||
|
||
**Dopad:** Zabrání spamu a nekonečnému růstu YAML souboru.
|
||
|
||
---
|
||
|
||
## 3. Atomic writes + odstranění ruční YAML konstrukce
|
||
|
||
**Problém:** Dva propojené problémy v `remind_edit.py`:
|
||
- Zápis do `reminder.yaml` je neatomický — `with open(REMINDER_FILE, "w")` může při crashu zanechat prázdný/s poškozený soubor = ztráta všech reminderů.
|
||
- `format_reminder()` ručně skládá YAML stringy (`f"- text: {text}"`) — neescapuje speciální znaky (uvozovky, dvojtečky, newlines), nedrží konzistentní odsazení, duplikuje logiku ruamel.yaml.
|
||
|
||
**Řešení:**
|
||
- Atomic write: `tmp = path.with_suffix(".tmp")` → `yaml.dump(data, f)` → `os.replace(tmp, path)`.
|
||
- Nahradit `format_reminder()` builděním dictu a `yaml.dump()` celého dokumentu. Použít `ruamel.yaml.scalarstring.LiteralScalarString` přímo z knihovny (smazat vlastní třídu).
|
||
- Přidat validaci před zápisem (schema check).
|
||
|
||
**Dopad:** Zabrání ztrátě dat a tichým YAML parse chybám. Největší robustness win s minimálním úsilím.
|
||
|
||
---
|
||
|
||
## 4. `update` příkaz + `list` příkaz
|
||
|
||
**Problém:**
|
||
- `remind_edit.py` má jen `add` a `remove`. Změna času = smazat a vytvořit znovu — rizikové (viz bod 1, substring match).
|
||
- `list` je dokumentovaný v SKILL.md, ale v kódu neexistuje. Uživatel (nebo LLM) nemá jak zkontrolovat aktuální stav.
|
||
|
||
**Řešení:**
|
||
- Přidat `update --id X [--cron ...] [--at ...] [--random-* ...]` — najde reminder a upraví jen zadaná pole.
|
||
- Přidat `list` — vypíše všechny remindery s ID, textem a typem schedule.
|
||
- Přidat `--dry-run` k `add` a `update` pro bezpečné testování.
|
||
|
||
**Dopad:** Zásadní UX zlepšení, snižuje riziko chyb při úpravách, doplňuje chybějící dokumentovanou funkci.
|
||
|
||
---
|
||
|
||
## 5. Testy pro `remind_edit.py` a `remind_send.py`
|
||
|
||
**Problém:** Testy pokrývají jen `random_times.py`. Dva hlavní skripty (CRUD operace, dedup, fire detection, YAML I/O) nemají žádné testy. Jakákoliv změna v bodech 1–4 bez testů = riziko regresí.
|
||
|
||
**Řešení:** Přidat unit testy pro:
|
||
- `cmd_add` s různými kombinacemi flagů (cron, at, random)
|
||
- `cmd_remove` s 0/1/N matches, substring kolize
|
||
- `should_fire` s různými typy reminderů a okraji časových oken
|
||
- Dedup state management (nový i starý formát)
|
||
- Atomic write (crash uprostřed zápisu)
|
||
- Validace `at` v budoucnosti
|
||
|
||
**Dopad:** Bez testů je jakýkoliv refaktoring hazard. S testy se body 1–4 dají implementovat bez strachu z regresí.
|
||
|
||
---
|
||
|
||
*Zbylé návrhy (SQLite backend, pause/resume, cron until, retry TTL, log enrichment, chat_id z configu, per-reminder TZ) jsou P2–P3 — užitečné, ale nejsou blokátory.* |