git:20260927.2455443 to git:20260928.dabacde

2 added, 0 removed. Audit A to A.

# Konfigurace Claude Code
Moje osobní konfigurace Claude Code – pravidla, skilly, hooky a status line, sdílená pro inspiraci.
- **Slug:** `claude`
- **Struktura:** root
- **Repozitář:** https://github.com/jantichy/claude
## Výjimky z obecných pravidel
- **Blok metadat je tady, ne v kořenovém `CLAUDE.md`**, jak jinak velí `~/.claude/STRUCTURE.md`. Kořenový soubor je uživatelský a rozbaluje se do každé session v každém projektu – metadata tohohle repozitáře tam nepatří, mátla by v cizím projektu.
- **`/attack`, `/release` ani `/evaluate` se tu nikdy nepouštějí.** Repozitář je konfigurace, ne aplikace – není co spustit, kam nasadit, a tedy ani žádný provoz, který by šlo vyhodnotit. **Posledním krokem osy, na který se tu dojde, je `/implement`**; kontrolní kroky se pouštějí dál a běh uzavírá `/cleanup` a po něm `/merge`, stojí-li práce na větvi – spouštěčem obou není pozice v cyklu, ale konec session, respektive pokyn uživatele. Zapsáno schválně, ne odvozeno (`~/.claude/RULES.md`, *Zapiš i to, co vědomě nemáš*).
- **Standardní soubory leží v kořeni, ne v `docs/`** – režim `root` podle `~/.claude/STRUCTURE.md`, *Dva režimy umístění*. Repozitář není vyvíjený projekt, ale konfigurace, takže by `docs/` byl prázdný obal nad čtyřmi soubory. `rules.md` tu není a nezakládá se: pravidla téhle vrstvy jsou samy jejím obsahem, ne meta-vrstvou nad ním.
- **Repozitář je veřejný, takže `todo.md`, `backlog.md`, `done.md` a `decisions.md` píšeš pro cizí oči.** Do konce září 2026 ležely v soukromém `~/Dev/context/` právě proto; od 20. 9. 2026 jsou tady a tu ochranu musí nahradit pravidlo. **Hranice vede mezi strukturou a obsahem.** Struktura veřejná je a `README.md` ji sám píše – že analytické know-how leží v `context/analytics/`, autorovy články v `context/archive/` a jeho styl psaní v `context/compose/`, se smí napsat a odkazovat se na to. **Konkrétní obsah veřejný není:** jméno klienta nebo organizace (tedy i to, že `context/organizations/` drží zrovna tenhle profil), sazba a obchodní údaj, osobní údaj, detail přístupu ke klientskému systému, jméno klientského projektu nebo domény a know-how, které se prodává.
**Platí to na každý zápis, ne na ten první.** Úkol vzniklý při práci pro klienta se zapisuje tak, aby popsal *co* se má v konfigurační vrstvě udělat, ne *u koho* se to ukázalo: „u jednoho projektu chyběl kontrakt příkazů“, ne jméno toho projektu. Potřebuje-li položka konkrétní klientský kontext, aby dávala smysl, patří celá do `~/Dev/context/todo.md` – tam se nic nezveřejňuje.
## Instrukce pro tenhle repozitář
Projektové instrukce pro práci **v tomhle repozitáři**. Načítají se jen tady, na rozdíl od `~/.claude/CLAUDE.md`, který je uživatelský a jde do každé session v každém projektu.
@~/.claude/skills/SKILLS.md
- **Norma výš platí pro každou cestu ke změně skillu:** ruční úpravu, `/skill` i cizí nástroj typu `skill-creator` nebo `superpowers:writing-skills`. Ty mají vlastní představu o tvaru a prosadí ji, když jim nic neřekneš; tenhle řádek je to, co jim ji přebíjí (`~/.claude/RULES.md`, *Přednost pravidel*). Postup zakládání, revize a rušení drží `/skill`, ne norma.
- **Každý skill má dvě README.** Vlastní `skills/<name>/README.md` psané pro člověka zvenčí, na které se posílá odkaz, a k tomu jeden odstavec v kořenovém `README.md`, jehož nadpis na ně odkazuje. Tvar obojího drží `skills/SKILLS.md`, *README skillu*, a vynucují ho testy. Když skill přidáš nebo zásadně změníš jeho chování, aktualizuj obojí rovnou jako součást té změny – nečekej na vyžádání.
## Kontrakt příkazů
Kontrakt příkazů (`~/Dev/context/coding/quality.md`). Průběžná kontrola ho tady najde v `.claude/CLAUDE.md` a příkazy spouští v kořeni repozitáře.
- typecheck: swiftc -typecheck -warnings-as-errors skills/*/*.swift
- lint: shellcheck -x --severity=info ./*.sh skills/*/*.sh githooks/* && ruff check --isolated --select F,E9,C901 --config 'lint.mccabe.max-complexity = 10' ./*.py skills/*.py skills/*/*.py skills/*/*/*.py tests/*.py
- test: python3 -m unittest discover -s tests
- build: -
- e2e: -
- audit: -
- coverage: -
- mutation: -
- dev: -
**Pomlčky jsou rozhodnutí, ne díra.** `dev` mezi nimi stojí schválně: `/attack` se tu nikdy nepouští (viz *Výjimky z obecných pravidel* výš), takže není co zvedat – a chybějící klíč se od vědomé pomlčky nepozná. Repozitář nemá manifest závislostí, nic se z něj nebuildí ani nenasazuje a není tu aplikace, kterou by šlo projít; `coverage` a `mutation` nad sadou, která z devadesáti procent testuje Markdown, měří délku textu, ne sílu testů. Bez pomlčky by je `/review` i CI hlásily jako nezkontrolované kroky – tedy jako trvalý šum místo informace (`~/Dev/context/coding/quality.md`, *Kontrakt příkazů*).
`shellcheck` běží se `--severity=info`, ne se `--severity=style`: stylové nálezy jsou preference a kontrola, která padá na preferenci, se obchází. **Ze stejného důvodu má `ruff` jen `--select F,E9,C901`** – nedefinovaná jména, nepoužité importy, syntaktické chyby a cyklomatická složitost, tedy vady a jeden měřitelný práh, ne názory. `C901` je tu od 8. 9. 2026 s prahem 10 podle `~/Dev/context/coding/quality.md`. Do té doby se neměřila vůbec a 5 funkcí ho překračovalo, nejvíc `progress.py` s 19. Práh se **nesnižuje kvůli tomu, že překáží** – to smí jen člověk a se zápisem do rozhodnutí.
Výchozí sada by tu hlásila pořadí importů a závorky navíc. `--isolated` k tomu zajistí, že se nechytí cizí konfigurace odněkud z domovského adresáře. Python přibyl do repozitáře 6. 9. 2026 se skripty `/compose`.
**Od 20. 9. 2026 kryje i kořen repozitáře (`./*.py`).** Chyběl tam od začátku a nikomu to nevadilo, dokud v kořeni žádný Python neležel; jakmile tam přibyl `git-guard.py`, nečetla ho **žádná statická kontrola** – chování mu testuje `tests/test_hooks.py`, ale lint ani typecheck na kořen nesahaly a `test_contract_patterns_cover_repo` hlásí nepokrytí, ne vady uvnitř. Po doplnění vzoru vyšla najevo první z nich: funkce `verdict` měla cyklomatickou složitost 14 proti prahu 10, takže se rozdělila na `table_verdict` a `combo_verdict`. **Práh se kvůli tomu nesnižoval** a snižovat se nesmí; je to přesně ten případ, na který ta věta níž míří.
**Od 27. 9. 2026 kryje i kořen `skills/` (`skills/*.py`).** Vzory tam mířily jen na `skills/*/*.py` a `skills/*/scripts/*.py`, takže sdílený skript přímo ve `skills/` by nečetla žádná kontrola. Doloženo měřidlem `skills/cost.py` hned v den jeho vzniku: nedefinované jméno vložené do něj prošlo lintem zeleně. **Nejhorší na tom bylo, že mlčel i test pokrytí** – `test_contract_patterns_cover_repo` porovnával cesty `fnmatch`em, jehož `*` přechází přes lomítko, takže vzor `./*.py` vypadal jako pokrytí celého repozitáře. Shoda se proto počítá po segmentech cesty, jak ji dělá shell, a test tím rovnou odhalil tři nepokryté skripty `/replace`, o kterých nikdo nevěděl.
**Od 28. 9. 2026 je `KNOWN_GAPS` v `tests/test_skills.py` prázdný a lint kryje repozitář beze zbytku.** Do té doby v něm stál celý `skills/replace/deklinace/`, protože funkce `resolve` měla cyklomatickou složitost **64 proti prahu 10** – tabulku pravidel české deklinace. **Práh se nesnižoval**; funkce se rozdělila na dispečera a devět pojmenovaných stupňů, každý pod prahem. Vzor `skills/*/scripts/*.py` se přitom nahradil obecnějším `skills/*/*/*.py`: mířil jen na jméno `scripts`, takže skript dvě úrovně pod skillem v jiném adresáři nečetla žádná kontrola. Seznam se **porovnává se skutečností v obou směrech**, takže nepokrytý soubor mimo seznam shodí testy stejně jako položka, která přežila svůj důvod – a prázdný seznam znamená, že žádná výjimka neexistuje, ne že se na ni zapomnělo.
**Od 7. 9. 2026 lint kryje i `tests/`** – je to největší Python v repozitáři a nekontroloval ho nikdo, přestože je to zároveň jediná vrstva, která tu něco doopravdy vynucuje. Běh testů sám chytí syntaktickou chybu, ale ne nepoužitý import ani překlep ve jménu uvnitř větve, která se zrovna nevykonala.
`test` pokrývá tyhle vrstvy. **Počty se tu schválně neuvádějí** – ani vrstev, ani testů: rozejdou se s prvním doplněným scénářem a nikdo je nepřepočítává. Kolik jich je, řekne `python3 -m unittest discover -s tests`:
- **`tests/test_skills.py` – meta-testy nad konfigurací.** Hlídají tyhle skupiny:
- **Hlavičku a odkazy:** že hlavička parsuje a sedí s adresářem, že `description` říká, kdy se skill použije, a vejde se do 1024 znaků, že `allowed-tools` je vyplněné (chybějící pole není „neurčeno“, ale nejširší možná sada včetně připojených MCP serverů) a že vyjmenované MCP nástroje plugin na disku opravdu nabízí, že režim popsaný v těle je i v `argument-hint`, že odkazy na soubory vedou někam a odkaz na sekci míří na skutečný nadpis – včetně vnitroskillových odkazů na vlastní fáze i kroky, písmenných podkroků a celých výčtů čísel –, a že se skilly odkazují na kroky životního cyklu, ne na jejich vnitřky – **včetně vedlejších souborů skillu**, kde se to dřív nekontrolovalo vůbec a `skills/ptydepe/terms.md` jich nasbíral osm.
- **Soulad s normou `skills/SKILLS.md`:** povinné sekce a jejich pořadí včetně toho, kde smí stát `Časté chyby`, odkaz na `skills/PREFLIGHT.md`, závěrečný verdikt, mez délky, zákaz odkazu dovnitř fáze jiného skillu a povinnou sekci `Jak je to postavené uvnitř` u skillu s vlastními skripty. Vynucuje se to tím, že **seznam `MIGRATION` musí přesně sedět se skutečností**, takže opravený a nevyškrtnutý skill shodí testy stejně jako regrese.
- **Nosné části:** že `/review`, `/consistency` i `/oponent` vykazují **měřené pokrytí** svého vstupu a nesou pravidlo, že prázdný výstup agenta není čisto (hlídá se u všech tří naráz, protože je to princip, ne vlastnost jednoho z nich), že `/review` má fázi ověřování nálezů, `/cleanup` fázi na nevypořádaná témata, že `/evaluate` má fázi, ve které padá rozhodnutí u každého poznatku, a žebříček zdrojů s oběma krajními stupni, že zadání specialistů `/review` nesou `severity` a `basis`, že datovaný záznam jmenuje `date +%F`, že žádný skill neopisuje pořadí kroků cyklu šipkami místo odkazu do `RULES.md` a že `.claude/run/` je v `.gitignore`.
- **Hranici paušálního kontextu:** že se `STRUCTURE.md` ani `skills/LIFECYCLE.md` nevrátí mezi `@import`y v globálním `CLAUDE.md`, že příprava skillu říká, kdy si je načíst, a že odrážky v `LIFECYCLE.md` jmenují tytéž kroky jako rámeček v `RULES.md`. **Číslovaná řada to od 20. 9. 2026 být nemůže** – cyklus má dvě vrstvy a kontrolní kroky v žádném pořadí nestojí. K tomu **krok cyklu, ke kterému skill ještě nevznikl**: `LIFECYCLE.md` ho musí přiznat jmenovitě a test to porovnává se skutečností v obou směrech, takže nepřiznaný chybějící krok i přiznání, které přežilo svůj důvod, shodí testy. Takový krok stojí v rámečcích README bez odkazu a do hromadné instalace nepatří vůbec – pokyn, který si vyžádá neexistující adresář, se nepovede celý.
- **Šablony výstupu:** že každý blok kódu, jehož obsah jde do konverzace, nese pokyn vypsat ho jako Markdown – s jmenovitým seznamem bloků, které se místo toho zapisují do souboru, a ten **musí přesně sedět**.
- **README skillů:** že každý skill má `README.md` s povinnými sekcemi ve správném pořadí, instalací psanou jako pokyn pro Clauda, rámečkem cyklu a hromadnou instalací u kroků cyklu (a bez nich mimo něj), pojmenovanými všemi režimy z `argument-hint` včetně výchozího, dodrženou mezí délky a relativními odkazy, které vedou na existující soubor. Kořenové README zná každý skill a nezná žádný zmizelý.
- **Kanonický tvar autocommitu:** že projektový `CLAUDE.md` tohohle repozitáře nese přepínač pod `## Autocommit` i s importem pravidel ze skillu, že nestojí pod zastřešující sekcí a že se definice mechanismu nevrátila do globálního `CLAUDE.md`.
- **Skripty ve skillech** (`skills/*/*.py` i `skills/*/scripts/*.py`).
Nad tím vším stojí **mutační testy**, které poškodí vzor a ověří, že kontrola nález opravdu nahlásí. Kroky životního cyklu se čtou z `RULES.md`, povinné sekce README z normy a kontrakt příkazů se parsuje týmž výrazem jako `verify.sh` – žádný z těch seznamů nestojí v testu natvrdo.
**Proč která kontrola existuje, drží docstring toho testu, ne tenhle výčet.** Odůvodnění opsané na dvě místa se rozejde a ta trvanlivější kopie bývá ta horší: jedna oprava změní chování skillu a druhé místo dál vysvětluje kontrolu selháním, které už nastat nemůže. Zdejší popis proto říká jen *co* se hlídá; *proč* si přečti u testu.
- **`tests/test_verify.py` – regresní testy průběžné kontroly.** Scénáře nad dočasným repozitářem s přesměrovaným `HOME`:
souhlas a jeho platnost pro celý repozitář včetně worktree i přes symlink v cestě, rozdíl mezi `exit 1` a `exit 2`, druhý pokus po zablokování, shoda otisku, chybějící nástroj, díra v kontraktu, pomlčka, vypnutá kontrola, otisk nad neverzovaným adresářem i nad rozpracovaným souborem, `## Kontrakt příkazů` v bloku kódu i v HTML komentáři, dvě sekce téhož jména, neuzavřené ohraničení, upovídaný úspěšný krok s uříznutým výstupem, kontrakt v `.claude/`, klíč `cwd` včetně cesty mimo projekt, zámek proti souběhu, `--revoke` s tečkou i lomítkem. Od 14. 9. 2026 k tomu vazba souhlasu na kontrakt (podadresář s vlastním `CLAUDE.md`, změněný kontrakt, starý formát bez otisku, worktree jiné větve), vydání souhlasu jen z terminálu, čtení Markdownu podle CommonMarku a pojistka, že stav běhu nejde přesměrovat přes `XDG_STATE_HOME`.
Je to spolu s git hookem a CI jedno z míst konfigurace, která něco doopravdy vynucují, takže jeho tichá regrese je nejdražší, jaká tu může nastat. Používají jen stdlib – kontrola, která si žádá instalaci balíčku, se v cizím prostředí neprojeví jako nález, ale jako rozbitý nástroj, a ten se obchází.
- **`tests/test_hooks.py` – regresní testy vrstev, které něco vynucují mimo model.** Nad nimi stojí kontrola **registru obcházení** `BYPASS.md`: každá vrstva, která běží automaticky – hook ze `settings.json`, soubor v `githooks/`, workflow v `.github/` –, v něm musí mít řádek, a seznam se čte z disku, ne z výčtu v testu. Registr, který zestárne, je horší než žádný: tváří se jako úplná mapa, ale nová vrstva v něm chybí. Hlídá se i to, že u každého `accepted` stojí důvod, ne jen slovo. Scénáře nad dočasným repozitářem hlídají **git hook na zprávu merge commitu**: odmítnutí defaultní zprávy v české i anglické podobě, komentáře v `COMMIT_EDITMSG`, skutečný `git merge --no-ff`, a proti tomu všechno, co projít musí – vlastní zpráva, aktualizace rozdělané větve z `main`, merge po `git pull`, zpráva jen zmiňující slovo merge. K tomu delegace na lokální `.git/hooks/commit-msg`, kterou globální `core.hooksPath` jinak vypne. `githooks/commit-msg` běží nad **každým** repozitářem na stroji, takže falešný poplach tu neblokuje jeden skill, ale běžnou práci v cizím projektu – a člověk si ho pak vypne, čímž přestane hlídat cokoliv. Proto se testují oba nebezpečné směry, ne jen ten zjevný.
K tomu testy nad **nasazením a CI**: že `core.hooksPath` na hook doopravdy míří (hook, který nikdo nevolá, funguje při přímém zavolání úplně stejně jako ten nasazený) a že workflow v `.github/` pouští klíče z *Kontraktu příkazů* a neopisuje si jejich příkazy. **Ne všechny** – `dev` je watch server, který nikdy neskončí, a `cwd` není příkaz; množinová rovnost s kontraktem by v prvním projektu s `dev` vyrobila falešný poplach, takže se kontroluje podmnožina. Od 14. 9. 2026 k tomu kontrola, že je `verify.sh` vůbec zaregistrovaný jako `Stop` hook v `settings.json` a že jeho timeout pokryje tři kroky – ztráta té registrace je nejtišší selhání celé vrstvy, protože nic nespadne, jen se přestane kontrolovat.
Od 27. 9. 2026 k tomu **hooky z pluginů**, tedy vrstva, o které registr do té doby nevěděl vůbec: plugin nese vlastní `hooks/hooks.json` a zapíná se jedním řádkem v `enabledPlugins`, kde ho kontrola hooků nehledala. Hlídá se dvojicí – **jmenovitě** že `gitkraken-hooks` není zapnutý (vrátil se potřetí, viz `decisions.md`), a **obecně** že zapnutý plugin s hooky má řádek v `BYPASS.md`. Ta obecná schválně **mlčí, když plugin na disku není**, protože `plugins/` je v `.gitignore` a tvrdit cokoliv v CI by byl falešný poplach; proto vedle ní ta jmenovitá stojí. Hned prvním během našla pravý nález – `superpowers` vkládá do každé session vlastní instrukce a v registru o něm řádek nebyl.
Od 20. 9. 2026 k tomu **zastavení destruktivního git příkazu** (`git-guard.py` jako `PreToolUse` hook): přerovnané argumenty, na které deny seznam prefixem nedosáhne (`git push origin main --force`), rozbalený alias i neprůhledný shellový alias, refspec s `+`, slepené krátké přepínače u `clean` (`-fx`, `-xdf`) i holé `git clean`, které maže při `clean.requireForce=false`, a proti tomu druhý směr – obyčejný `git push`, `git log --grep=force`, commit se slovem `--force` ve zprávě, suchý běh `clean -n`, cizí nástroj a rozbitý vstup. K tomu registrace hooku v `settings.json`, bez které nehlídá nic. **Popis sem přibyl až úklidem po revizi skillů**, pár hodin po hooku samotném – vrstva tedy chvíli běžela nepopsaná, přestože testy k ní vznikly s ní.
- **`tests/test_statusline.py` – regresní testy status line, tedy kódu, který běží nad cizím repozitářem bez souhlasu.** Status line se překresluje po každé odpovědi, permission systém na ni nesahá a stojí v adresáři, který si uživatel nevybral. Git přitom umí spustit program podle konfigurace toho repozitáře: `filter.<name>.clean` se volá pokaždé, když potřebuje obsah pracovního souboru – tedy i při `git diff --name-only`, kterým se počítaly změny –, a jméno filtru si volí ten, kdo config napsal, takže ho nejde přebít `-c` přepínačem. Doloženo 14. 9. 2026 spuštěním nad návnadou: status line vypsala normální řádek a zároveň spustila cizí příkaz.
Testují se oba směry: že se program nespustí a že se nad běžným repozitářem nespustí falešný poplach, protože zmizelý počet změn je přesně ta funkce, kvůli které status line existuje. Nad tím stojí mutační test, který vyřízne filtry z blacklistu a ověří, že se pak cizí příkaz opravdu spustí – bez něj by hlavní test zůstal zelený i tehdy, kdyby marker nevznikal z docela jiného důvodu. K tomu od 14. 9. 2026 šířka pruhu využití v obou krajních hodnotách: BSD `seq 1 0` počítá dolů, takže se nula bloků kreslila jako dva.
- **`tests/test_compose.py` – regresní testy generátorů archivu.** Skripty `/compose` přepisují archiv textů v `~/Dev/context/archive/`, ze kterého se pak destiluje znalostní báze autorova psaní. Chyba v nich se neprojeví jako pád, ale jako archiv, který vypadá v pořádku a není: chybí příspěvky, je rozbitá diakritika, nebo tweet nese text jiného tweetu. Testují se proto tichá místa: oprava mojibake ve facebookovém exportu (oba směry, protože oprava, která zkazí správný text, je horší než neopravená), vynechávání čistých retweetů, párování plných textů dlouhých tweetů a od 14. 9. 2026 i chybějící `note-tweet.js` a bluesky post bez `createdAt` – dva vstupy, na kterých generátor spadl dřív, než zapsal první soubor, takže jeden vadný záznam zlikvidoval celý archiv.
**Párování odhalilo skutečnou vadu** (14. 9. 2026): párování hledalo plný text v okně ±2 s a nekontrolovalo, komu patří, takže tweet bez vlastního dlouhého textu mohl sebrat sousedův. Ve stávajícím exportu se to neprojevilo – regenerace opraveným generátorem dala tělo shodné se všemi devatenácti ročníky archivu –, ale scénář je reálný a testem doložený. `parse_bluesky.py` se netestuje: potřebuje `cbor2`, a kontrola, která si žádá instalaci balíčku, se v cizím prostředí neprojeví jako nález, ale jako rozbitý nástroj.
- **`tests/test_transcript.py` – regresní testy skriptů `/transcript`, zatím jen tam, kde hrozí ztráta dat.** Vstupem téhle vrstvy jsou nahrávky, které většinou nejde pořídit znovu, takže chyba v ní neznamená vadu nástroje, ale ztracený podklad – a `ffmpeg -y` to udělá mlčky s návratovým kódem 0. Oba scénáře nastaly doopravdy 14. 9. 2026: pojistka proti přepsání vlastního vstupu porovnávala **řetězce cest**, takže `./rec.wav` a `rec.wav` prošly jako různé soubory a z třicetisekundové nahrávky zbylo 4,6 s; a kontrola „výstupy už existují“ neznala příponu `wav`, takže existující nahrávku přepsala i ve výchozím režimu `stop`. **Od 14. 9. 2026 se pojistka ověřuje na spuštěném skriptu, ne na opisu jeho podmínky.**
Do té doby tu běžela kopie té podmínky plus kontrola, že ve skriptu zbyl řetězec `-ef` – a ta dvojice nechytila nic: vyřazení podmínky ve skriptu nechalo všech 6 testů zelených, protože hlídaný řetězec v souboru zůstal. Skutečný běh chce whisper a model o velikosti gigabajtu, takže se obojí podstrčí, a nad tím stojí mutační test, který pojistku vyřadí a ověří, že se zdroj opravdu zničí. `ffmpeg` se nevyžaduje – kde není, testy se přeskočí nahlas.
**Od 14. 9. 2026 k tomu přibylo přiřazování mluvčích v `merge.py`** – nejtišší vada celého skillu, protože špatně přiřazená replika vypadá v přepisu stejně věrohodně jako správná. Skript ji řeší dvěma prahy a raději nechá mluvčího prázdného, než aby hádal; testy hlídají oba **zvlášť**. To rozlišení tam nebylo od začátku: první verze shazovala oba prahy naráz a scénář „55:45“ přičítala `MIN_MARGIN`, jenže ten padá na `MIN_RATIO` – druhý práh tak nehlídal nikdo a jeho vypnutí neshodilo jediný test.
- **`tests/test_cleanup.py` – regresní testy dvou deterministických skriptů, které `/cleanup` volá.** Oba existují kvůli pravidlu nula: mechanické vady a počty nemá hledat model. **`skills/links.py`** (od 27. 9. 2026 sdílený, protože ho volá i `/consistency`) ověřuje odkazy a kotvy; testují se oba směry – že nález vznikne, i že nevznikne falešný poplach nad blokem kódu, protože šablony výstupu ve skillech začínají řádkem `## Úklid dokončen` a ukázky nesou zástupné symboly. Hlídají se i **návratové kódy** `0`/`1`/`2`, protože právě o ně se opírá jediné rozhodnutí, které skill podle skriptu dělá – že `2` je chyba volání, ne čistý výsledek. **`extract.py`** očišťuje transcript a měří pokrytí; jeho tiché selhání je dražší, protože **vynechá-li kategorii, která obsah nese, nikdo to nepozná** – výsledek vypadá úplně. Testuje se proto, že se vynechává jen to, co se vynechávat má (výstupy čtecích nástrojů, prázdné bloky myšlení), že čísla řádků odpovídají zdroji – bez toho padá doložitelnost grepem, na které stojí celý úklid –, že zpráva poslaná uprostřed odpovědi nezmizí, a že **kotvy evidence nesou jen to, co uživatel skutečně napsal**: vsuvka harnessu a notifikace o dokončení úlohy mají roli uživatele, ale odškrtávat se nemají. K tomu se hlídá **rozhraní kontextu proti transcriptu**, podle kterého skill rozhoduje, jestli má transcript vůbec čist: počet kompaktací v obou směrech (falešná by poslala skill čist zbytečně, chybějící by zatajila část konverzace) a cesty k odloženým výstupům, které jsou uříznuté v transcriptu i v kontextu. A **deduplikace kotev**, odhalená prvním ostrým během 26. 9. 2026: zpráva z fronty je uložená dvakrát a rozepsaný prompt se uloží i před doplněním, pod jiným `promptId`. Nad tím vším stojí mutační testy – u odkazů se vyřadí vynechávání bloků kódu a ověří, že falešný poplach vznikne; u předfiltru se vyřadí rozlišení podle `isMeta` a ověří, že se vsuvka opravdu stane kotvou.
- **`tests/test_next.py` – regresní testy skriptů `/next`, které zjišťují živé session a stav větví.** `/next` podle jejich výstupu rozhoduje, jestli úkol v rozdělané větvi nabídne, nebo skryje, a obě chyby jsou tiché: mrtvá session vydávaná za živou schová zapomenutou práci navždy, živá vydávaná za mrtvou nabídne druhé session úkol, na kterém už dělá první. Hlídá se proto záznam po spadlém procesu, větev z posledního záznamu transcriptu, chybějící registr jako chyba, ne prázdný seznam, stavy větve (`occupied`, `abandoned`, `uncertain`, `empty`) a čtení plánu a kol z `todo.md`. **Od 20. 9. 2026 k tomu `lifecycle()`**, která rámeček z `RULES.md` rozebírá na vrstvy `osa` a `kontroly`: vracela syrové řádky bloku, takže po rozdělení rámečku na dvě vrstvy dělala z odsazeného pokračování osy „krok“ jménem `/breakdown` a z komentáře pod rámečkem další položku. Je to táž třída tiché vady jako u živých session – nic nespadne, jen fronta práce nabídne krok, který neexistuje. **Od 22. 9. 2026 k tomu parkování položky k datu** – zápis data hned za názvem položky –, na kterém stojí jediný spouštěč `/evaluate`: zapíše ho `/release` a `/next` položku do fronty nezařadí, dokud ten den nenastane. Hlídají se oba směry, protože **falešné parkování je ta horší polovina** – položka nezmizí hlučně, jen se nikdy nenabídne. Vzor proto hledá datum jen hned za názvem: první verze prohledávala začátek textu a věta o sazbě platné od nějakého data jí položku skryla.
- **`tests/test_cost.py` – regresní testy měřidla `skills/cost.py`.** Měřidlo je vrstva, na které stojí rozhodnutí o stavbě skillů, takže **měřidlo, které lže, je horší než žádné**: čísla vypadají stejně věrohodně, ať jsou z celého vzorku, nebo z jeho poloviny. Testují se oba směry – že se do běhu **nezapočítá cizí práce** (to je ta zrádnější polovina, protože číslo vyjde vyšší a podle něj se zamítne změna, která ušetřila) a že se běh **tiše nezahodí**, proto se počet zahozených hlásí. Odvození markeru konce ze `SKILL.md` má vlastní skupinu, protože je to jediné místo, kde měřidlo závisí na tvaru cizího souboru. **Od 28. 9. 2026 k tomu druhá osa pásem** – velikost rozsahu čtená z řádku `Pokrytí:` –, a tam je zrádnější směr obrácený: přečíst rozsah **omylem** z kotev `N/N`, z kilobajtů transcriptu nebo ze zástupného `N z M` v šabloně dá běhu pásmo, které si nezaslouží, a medián se spočítá z pomíchaného vzorku. Nad hlavním scénářem stojí mutační testy na ohraničení běhu a na deduplikaci po `/resume`.
+ - **`tests/test_order.py` – regresní testy kontroly pořadí datovaných záznamů (`skills/order.py`).** Norma *Nejstarší nahoře* se sama nedodržuje: ruční srovnání `done.md` se od 6. 9. 2026 dělalo **třikrát** a pokaždé se to vrátilo, protože do těch sekcí zapisují skilly samy a každý se řídí tím, co v souboru zrovna vidí – jeden obrácený zápis stačí, aby ho další napodobily. Naměřeno 28. 9. 2026 při čtvrtém rozpadu: *Průchody životním cyklem* 2 zlomy, *Odvedená práce* 9 a `decisions.md` 2. Testují se oba směry a **falešný poplach je ta horší polovina**, protože se neprojeví jako díra, ale jako překážka v běžné práci, kterou si člověk vypne – proto se hlídá, že nálezem není datum ve vnořené poznámce, v bloku kódu ani pokles na hranici dvou sekcí, a že se na jednu sekci vypíše **jeden** nález, ne padesát. Nad tím stojí vynucení nad skutečnými soubory repozitáře, bez kterého by kontrola existovala, ale nepouštěl by ji nikdo, a mutační test, který vyřadí vynechávání bloků kódu a ověří, že falešný poplach opravdu vznikne. `~/Dev/context` to nepokrývá – tam `tests/` ani CI nejsou, a proto skript volá `/cleanup`.
+
- **`tests/test_deklinace.py` – kontrolní sada 44 vět pro převod pádů v `/replace`.** Hlídá, že se úpravy pravidel české deklinace navzájem neruší. **Leží tady, a ne u skriptů ve skillu, a je to ta podstatná část:** do 28. 9. 2026 stála uvnitř `/replace`, kde ji nespouštěl nikdo, protože kontrakt `test` pouští jen `tests/` – a **padala na 2 ze 44 vět, aniž by se to kdokoliv dozvěděl**. Dvě věty rozhodnout nejde (*partie* má shodný tvar v 1. pádě jednotného i množného čísla, *partii* ve 4. i 6.), takže stojí ve vyjmenovaném seznamu `LIMITS` porovnávaném **v obou směrech**: věta, která mez přežila a dnes prochází, shodí testy stejně jako nová vada. Dřív tu mez vyjadřoval pád testu, což je tvar, který nemůže nikdy zezelenat, a tedy nechrání nic. Že žádný test mimo `tests/` znovu nevznikne, hlídá `test_skills.py`.
`typecheck` tu **dlouho stála pomlčka** s odůvodněním, že repozitář je konfigurace, ne program. To přestalo platit ve chvíli, kdy k `/invoicing` přibyl `calendar.swift` – od té chvíle tu ležel program, který nečetla žádná kontrola, a překlep v něm by se poznal až uprostřed ostré fakturace. Dnes proto `typecheck` pouští `swiftc -typecheck -warnings-as-errors` nad všemi swiftovými skripty ve skillech – **`-warnings-as-errors` schválně**, protože `~/Dev/context/coding/quality.md` má u přísnosti překladače práh „žádné varování“ a bez toho přepínače ho nevynucoval nikdo: varování by prošlo a kontrola by zůstala zelená (na Python ve `skills/` je test v `tests/`, ne tahle kontrola); běží kolem dvou vteřin a nic neinstaluje, protože Swift je na macOS součástí vývojářských nástrojů.
**Kdyby Swift z repozitáře jednou zmizel, vrať pomlčku**, ne prázdný řádek: chybějící klíč hook po každé odpovědi hlásí jako nezkontrolovaný krok, a to je trvalý šum místo informace.
## Review
Nálezy vyhodnocené jako „neopravovat“. Při dalším běhu se neuvádějí, dokud se
nezmění kód, kterého se týkají.
- **2026-09-14** · `2ca14c5` · *Pyannote načítá diarizační modely jako pickle a ty nejdou připnout ani ověřit* (zdroj: review, podklad: OWASP – integrita dat, zranitelné závislosti): Riziko je skutečné – pickle znamená spuštění kódu při načtení –, ale zavřít ho nejde bez toho, aby kontrola přestala být ověřitelná. Modely stahuje `huggingface_hub` uvnitř pyannote, ne zdejší kód, a `Pipeline.from_pretrained` revizi jako parametr nenabízí; „oprava“ by tedy byla neověřená domněnka o cizím API. Čím se to nahrazuje: whisper modely **jsou** připnuté na revizi (`common.sh`) a jsou to data pro whisper.cpp, ne spustitelný kód, takže jejich podvržení dá špatný přepis, ne cizí kód. Diarizační repozitáře jsou gated – vyžadují ruční souhlas s licencí u známého autora – a celý diarizační průchod je volitelný, běží jen na výslovné přání. Zruší se, jakmile pyannote začne umět revizi předat, nebo jakmile modely přejdou na safetensors.
- Lokace: skills/transcript/diarize.py (`Pipeline.from_pretrained`), skills/transcript/common.sh (`DIARIZE_MODEL`)
- **2026-09-14** · `2ca14c5` · *CI spouští kontrakt příkazů z cizího pull requestu* (zdroj: review, podklad: OWASP – integrita dat): Přesně to CI dělá a jinak by nekontrolovalo nic. Dopad je přitom omezený týmiž fakty jako u nálezu o nepřipnutých akcích: repozitář nemá jediný secret, `default_workflow_permissions` je `read`, `can_approve_pull_request_reviews` je `false` a runner je efemérní – cizí kód nemá co ukrást a může leda podvrhnout výsledek vlastní kontroly. Čím se to nahrazuje: `fork-pr-contributor-approval` je nastavené na `first_time_contributors`, takže první PR od cizího člověka nespustí nic bez ručního schválení. Zbývá vědomě přijaté riziko, že přispěvatel, který už jednou prošel, pustí workflow bez schválení. Zruší se zpřísněním na `all_external_contributors`, jakmile do repozitáře přijde první cizí PR – do té doby je to nastavení proti nikomu.
- Lokace: .github/workflows/verify.yml (`on: pull_request`, krok „Spusť kontrakt příkazů“)
- **2026-09-14** · `fd75365` · *Parsery cizích exportů nemají limit na délku ani hloubku* (zdroj: review, podklad: OWASP – neošetřený vstup): Vstupem nejsou cizí data, ale **vlastní** exporty z účtů autora, které si sám stáhl. Nejhorší dopad je `RecursionError` nebo vyčerpaná paměť, tedy pád skriptu nad souborem, který si člověk právě vyexportoval – ne spuštění kódu ani únik dat. Čím se to nahrazuje: skripty se pouštějí ručně a jejich výstup se porovnává s archivem, takže se pád pozná okamžitě. Zruší se, jakmile by parser měl číst export od někoho jiného.
- Lokace: skills/compose/scripts/gen_twitter_md.py, skills/compose/scripts/gen_bluesky_md.py (`json.loads`)
## Autocommit
Autocommit je zapnutý.
@~/.claude/skills/autocommit/autocommit.md