upravy projektu a skillu

This commit is contained in:
lachtan
2026-09-02 10:36:37 +02:00
parent 0fa619bbbe
commit f77cc2dcfe
19 changed files with 3875 additions and 52 deletions

164
skills/reflect/README.md Normal file
View File

@@ -0,0 +1,164 @@
# reflect — jak to funguje
Skill hledá v mých vlastních session logách **opakující se chyby**, pojmenuje je a navrhne
opravu. Nálezy pak procházíš ty, jeden po druhém, a rozhoduješ, co se použije.
**Sám od sebe nikdy nic nezmění.**
## Dva režimy
```text
ANALÝZA — denně 03:30, bez tebe REVIEW — jen když napíšeš /reflect
cron → reflect_auto.py ├─ vezme 1 otevřený nález
├─ destiluje session z okna ├─ ukáže diagnózu, důkazy, návrh, diff
├─ LLM tah → nálezy ├─ čeká na tvoje rozhodnutí
├─ zapíše do findings.jsonl ├─ aplikuje → git commit → audit
└─ Telegram (jen když je co) └─ další nález
NEEDITUJE NIC edituje jen to, co schválíš
```
## Nálezy jsou o nedávném chování, ne o celé historii
Běh se dívá jen na **posledních 21 dní** (`--window-days`). Není to úspora — je to
podmínka, aby nález něco znamenal: korpus má 3,5 měsíce a jedna dávka 200 kB, takže
dohánění celé historie po jedné dávce za noc znamenalo, že cursor tři noci stál na
konci května a nálezy z něj se předkládaly jako aktuální.
Dvě hranice, každá jinak:
| | Co dělá |
|---|---|
| **cursor** | podlaha — co se jednou analyzovalo, se neanalyzuje znovu (jinak by se zdvojily počty) |
| **okno** | strop — co je starší než 21 dní, se přeskočí a cursor to mine natrvalo |
Kdybys někdy potřeboval archeologii, vrať `cursor` ve `state.json` a spusť
`--window-days 0`.
## Proč tě to neotravuje každý den
Denně přibude jen pár session — v nich se vzor nepozná, jedna chyba je náhoda. Proto:
| Kdy | Status | Telegram |
|---|---|---|
| vzor poprvé, 1 výskyt | `watch` | ne, jen se počítá |
| vzor podruhé (≥2× a ve ≥2 session) | `open` | ano |
| vzor už jednou opravený se vrátí **po** opravě | `open` + **regrese** | ano |
| vzor už jednou opravený, ale důkazy jsou starší než oprava | `watch` | ne |
| vzor jsi zamítl | `watch` napořád | ne, už nikdy |
Zamítnutí je rozhodnutí, ne odklad — zamítnutý vzor se znovu neotevře.
## Co ti /reflect nabídne
| Napíšeš | Stane se |
|---|---|
| `ok` / `aplikuj` | patch se použije a commitne |
| `uprav: <text>` | přepíšeš návrh vlastními slovy, ukáže se nový diff |
| `přeskoč` | nález zůstane otevřený na příště |
| `zamítni` | nález se zavře natrvalo |
| `konec` | konec review |
Nález, který od analýzy patch nedostal (většina), si ho složí až při review: agent napíše
návrh do JSON a nechá ho ověřit (`--set-patch`). Skript ho **nejdřív ověří proti souboru
a teprve pak uloží**, takže nepoužitelný pokus ve `findings.jsonl` nezůstane — a agent do
store nesahá vůbec. Pak ti ukáže diff a čeká na `ok` jako u každého jiného patche.
Vždy jen **jeden** nález najednou. Nálezy se číslují `1..N` podle pořadí, ne podle
interního id.
## Kde co leží
| Cesta | Co |
|---|---|
| `reflect/findings.jsonl` | všechny nálezy, jeden JSON na řádek |
| `reflect/state.json` | kam se došlo (cursor), použité okno + statistiky běhů |
| `results/<datum>_reflect.md` | plný report jednoho běhu + okno, které pokryl, a kolik výskytů na 100 session mají už známé vzory |
| `log/reflect.log` | audit — každé rozhodnutí, i zamítnutí a přeskočení |
**„Naposledy" u nálezu je nejnovější datum v důkazech**, ne datum, kdy se záznam
naposledy přepsal. Když je starší než okno posledního běhu, je nález **zastaralý**:
analýza tam už nedohlédne, takže se sám nikdy neobnoví — spíš než patch si zaslouží
zamítnutí. `/reflect` ti to u něj řekne.
**Počty v nálezu (`7× ve 4 session`) jsou kumulativní součet napříč běhy** — sečtené
z toho, co model napočítal v jednotlivých oknech, ne měření nad celým korpusem. Skript
u nich hlídá jen to, co hlídat umí: vzor nemůže zasáhnout víc session, než kolikrát
nastal, ani víc, než kolik jich v dávce vůbec bylo.
## Co se o tvém rozhodnutí zapíše
| Rozhodnutí | Do `findings.jsonl` | Do `log/reflect.log` |
|---|---|---|
| složení patche | `patch` + `patch_drafted_at` (= složeno při review, ne modelem) | `DRAFTED <id> [vzor] <soubor>` |
| `ok` | `applied: {at, sha, file}`, `patch` = návrh modelu | `APPLIED <id> [vzor] <soubor> — <sha>` |
| `uprav:` | navíc `applied.new_text` = tvoje verze (návrh modelu zůstává v `patch`) | `APPLIED-EDITED …` |
| `zamítni` | `rejected: {at, reason}`**důvod je povinný** | `REJECTED <id> [vzor] — <důvod>` |
| `přeskoč` | `skipped: {count, last}`, status zůstává `open` | `SKIPPED <id> [vzor] ×N` |
Povinný důvod u zamítnutí není otravování: je to jediná zpětná vazba na kvalitu analýzy.
Z prvních osmi nálezů jsi čtyři zamítl — bez důvodů se z toho čísla nedá poznat, co
v analytickém promptu změnit.
Na dotaz „co jsem už rozhodl" ti to `/reflect` vypíše z logu včetně revert příkazu.
## Jak vrátit změnu zpět
Workspace je git, takže každá aplikovaná oprava je samostatný commit:
```bash
git revert <sha>
```
SHA najdeš v `log/reflect.log` nebo u nálezu ve `findings.jsonl` (`applied.sha`).
Změna žije **jen na serveru** — do trackovacího repa (`src/nanobot`) se musí dotáhnout
zvlášť, jinak ji příští `rsync` skillu přepíše zpátky.
## Ruční spuštění
```bash
cd ~/.nanobot/workspace
# normální běh (to dělá cron)
~/.local/bin/uv run --script skills/reflect/scripts/reflect_auto.py
# jiné okno než výchozích 21 dní
~/.local/bin/uv run --script skills/reflect/scripts/reflect_auto.py --window-days 7
# jen prompty do tmp/, nevolat model; s --all přes celý korpus
~/.local/bin/uv run --script skills/reflect/scripts/reflect_auto.py --dry-run --all
# jak velký je korpus a na kolik dávek vyjde (nevolá model)
~/.local/bin/uv run --script skills/reflect/scripts/reflect_distill.py --stats > /dev/null
```
`--all` ignoruje cursor, takže by znovu přečetl už spočítané session a **nafoukl jim
počty**. V ostrém běhu ho skript odmítne — je jen na `--dry-run`.
**Přerušený běh o hotovou práci nepřijde.** Nálezy i cursor se zapisují po každé dávce
a nová dávka nezačne po 20 minutách (`--deadline-minutes`). S 21denním oknem to vyjde
zpravidla na jednu dávku, takže se tenhle strop ani neuplatní; kdyby zbyla nezpracovaná
dávka, Telegram to řekne i když nálezy nejsou žádné.
Plná cesta k `uv` je tu proto, že v neinteraktivním SSH není v `PATH`. Crontab si
`PATH` nastavuje sám, takže tam stačí `uv run …`.
## Záruky
Nejsou to sliby v promptu, ale kód:
- **Analýza (`reflect_auto.py`) nemá v sobě žádnou cestu k zápisu do cizího souboru.**
Navíc se před a po tahu porovná `git status` celého workspace — kdyby agent přesto něco
zapsal, nálezy se zahodí.
- **Editaci dělá výhradně `reflect_apply.py`, vždy jeden nález.** Agent soubor needituje
sám. Skript odmítne patch, jehož původní text v souboru není nebo je tam vícekrát —
nehádá, kam patřil. Odmítne i nález, který není `open` (tj. nebyl ti předložen).
- **Do `findings.jsonl` píše taky jen `reflect_apply.py`.** I patch složený při review jde
přes něj (`--set-patch`) a projde stejnou kontrolou; agent auditní stopu needituje.
- **Commituje se jen ten jeden dotčený soubor** (`git add -- <file>`), nikdy `git add -A`.
Rozdělaná práce Dreamu a jiných skillů se do commitu nedostane; když je rozdělaný přímo
ten soubor, udělá se nejdřív checkpoint commit, aby byl revert přesný.
- Skill vynechává vlastní session, takže neanalyzuje sám sebe.
- **Každé rozhodnutí nechá záznam**, i to, které nic nezmění: zamítnutí s důvodem,
přeskočení s počítadlem, `uprav:` odděleně od původního návrhu modelu.
Ověřeno testy v `tests/` — včetně toho, že `git revert` vrátí soubor do původního stavu.