<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Mirek Hovorka — Blog</title>
    <link>https://mirekhovorka.cz/blog/</link>
    <atom:link href="https://mirekhovorka.cz/blog/feed.xml" rel="self" type="application/rss+xml"/>
    <description>Knowledge base IT architekta — architektura, AI, metodologie.</description>
    <language>cs-CZ</language>
    <lastBuildDate>Sun, 12 Jul 2026 16:17:21 GMT</lastBuildDate>
    
    <item>
      <title>Jedno repo, dvě klávesnice: synchronizace práce mezi Windows a Macem</title>
      <link>https://mirekhovorka.cz/blog/sync-repa-mezi-stanicemi/</link>
      <guid isPermaLink="true">https://mirekhovorka.cz/blog/sync-repa-mezi-stanicemi/</guid>
      <pubDate>Sun, 12 Jul 2026 00:00:00 GMT</pubDate>
      <description>Jedno privátní repo jako zdroj pravdy pro kód i kontext Claude Code. Automatický pull při startu session, připomínka pushe na konci — a pasti, které si Mac s Windows navzájem chystají.</description>
      <category>claude-code</category><category>git</category><category>workflow</category>
      <content:encoded><![CDATA[<p>Přes den sedím u Windows desktopu, večer u MacBooku. Stejný projekt, dvě klávesnice. Přenést mezi nimi kód není problém — to git umí od svého prvního dne. Problém je přenést <strong>rozdělanou hlavu</strong>: co bylo včera rozhodnuté, co si pamatuje agent, se kterým na projektu pracuji, jaké hooky a skripty má session k dispozici. Tenhle článek popisuje setup, který na to používám: jedno privátní repo jako jediný zdroj pravdy — pro kód i pro kontext — a automatizace, díky které na synchronizaci nemusím myslet.</p>
<p>(Každodenní práci s gitem přes Claude Code — commity, PR, záchranné situace — pokrývá <a href="/blog/tahak-git-github-claude-code/">tahák Git a GitHub s Claude Code</a>. Tady jde o vrstvu nad tím: víc strojů, jeden projekt.)</p>
<h2 id="zaklad-remote-jako-jediny-zdroj-pravdy">Základ: remote jako jediný zdroj pravdy</h2>
<p>Nejdřív to, co <strong>nedělat</strong>: nekopírovat složky přes USB a nedávat repo do složky synchronizované iCloudem, Dropboxem ani OneDrivem. Sync klient nevidí do <code>.git</code> atomicky — přenáší soubory po jednom, uprostřed zápisu, a lock soubory a poloviční objekty umí repozitář rozbít způsobem, který se projeví až za týden. O synchronizaci se má starat git — sync klient mu do toho jen hrabe.</p>
<p>Zbytek je disciplína o dvou příkazech:</p>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> pull</span><span style="color:#79B8FF"> --ff-only</span><span style="color:#6A737D">   # než začnu pracovat</span></span>
<span class="line"><span style="color:#6A737D"># ... práce, commity ...</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> push</span><span style="color:#6A737D">             # než vstanu od stroje</span></span></code></pre><p><code>--ff-only</code> znamená, že pull nikdy potichu nevyrobí merge commit. Když se stroje skutečně rozjedou — na obou vznikly commity — příkaz selže a já to řeším vědomě, místo abych to zjistil z divné historie.</p>
<p>Z toho plyne jediné tvrdé pravidlo: <strong>nevstávat od stroje s nepushnutými commity.</strong> Commit klidně může být rozpracovaný — na to jsou větve a <code>wip:</code> prefixy. Nepushnutý commit je ale commit, který na druhé stanici neexistuje.</p>
<h2 id="druha-vrstva-synchronizovat-kontext-ne-jen-kod">Druhá vrstva: synchronizovat kontext, ne jen kód</h2>
<p>Kdyby šlo jen o kód, tenhle článek by skončil o odstavec výš. Jenže s Claude Code žije v repu víc než kód:</p>
<ul>
<li><strong><code>CLAUDE.md</code></strong> — instrukce pro agenta: konvence, strukturu projektu, pravidla workflow</li>
<li><strong><code>memory/</code></strong> — persistentní paměť agenta, obyčejné markdown soubory: rozhodnutí, specifikace sekcí, zpětná vazba, rozdělaná práce</li>
<li><strong><code>.claude/settings.json</code></strong> — sdílené hooky (o těch za chvíli)</li>
<li><strong><code>tools/</code></strong> — skripty, které hooky spouštějí</li>
</ul>
<p>Když se tohle všechno verzuje spolu s kódem, druhá stanice není čistá instalace. Sednu k MacBooku a agent ví, co jsme ve středu rozhodli u Windows desktopu — protože si to přečte ze stejných souborů, které tam napsal.</p>
<p>Jedna podmínka: <strong>privátní repo.</strong> Paměť agenta obsahuje interní poznámky o projektu, rozhodnutí a jejich zdůvodnění — nic z toho nepatří do veřejného repozitáře.</p>
<p>A jedna výjimka: <strong>cesty se synchronizovat nedají.</strong> <code>C:\Users\...</code> na Windows není <code>/Users/...</code> na Macu. Per-machine nastavení proto žije v gitignorovaném lokálním configu (<code>.claude/settings.local.json</code>), který generuje init skript — spouští se jednou po clone:</p>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">./claude-init.sh</span><span style="color:#6A737D">   # zapíše lokální cesty, doinstaluje per-machine drobnosti</span></span></code></pre><p>Dělení je pak jednoduché: <strong>verzuje se všechno, co je pro stanice společné</strong> (instrukce, paměť, hooky, skripty), <strong>gitignoruje se všechno, co je per stroj</strong> (lokální cesty, buildy, <code>node_modules</code>).</p>
<h2 id="automatizace-ritualu">Automatizace rituálu</h2>
<p>Rituál „pull před prací, push po ní&quot; má jednu slabinu: spoléhá na mě. A já si po víkendu nevzpomenu. Claude Code umí spouštět příkazy při událostech session — a dvě události přesně odpovídají oběma koncům rituálu:</p>
<pre data-lang="json" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#E1E4E8">{</span></span>
<span class="line"><span style="color:#79B8FF">  "hooks"</span><span style="color:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#79B8FF">    "SessionStart"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#E1E4E8">      {</span></span>
<span class="line"><span style="color:#79B8FF">        "matcher"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"startup|resume"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF">        "hooks"</span><span style="color:#E1E4E8">: [{ </span><span style="color:#79B8FF">"type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"command"</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">"command"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"bash tools/sync-pull.sh"</span><span style="color:#E1E4E8"> }]</span></span>
<span class="line"><span style="color:#E1E4E8">      }</span></span>
<span class="line"><span style="color:#E1E4E8">    ],</span></span>
<span class="line"><span style="color:#79B8FF">    "Stop"</span><span style="color:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#E1E4E8">      { </span><span style="color:#79B8FF">"hooks"</span><span style="color:#E1E4E8">: [{ </span><span style="color:#79B8FF">"type"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"command"</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">"command"</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"bash tools/unpushed-check.sh"</span><span style="color:#E1E4E8"> }] }</span></span>
<span class="line"><span style="color:#E1E4E8">    ]</span></span>
<span class="line"><span style="color:#E1E4E8">  }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre><p><strong>SessionStart</strong> pullne za mě. Jádro skriptu:</p>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> fetch</span><span style="color:#79B8FF"> --prune</span><span style="color:#9ECBFF"> origin</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> merge</span><span style="color:#79B8FF"> --ff-only</span><span style="color:#9ECBFF"> '@{u}'</span><span style="color:#6A737D">   # tiché, když není co; křičí jen při reálném rozjetí</span></span></code></pre><p><strong>Stop</strong> — konec odpovědi agenta — spočítá nepushnuté commity a připomene se:</p>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#E1E4E8">n</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">$(</span><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> log</span><span style="color:#79B8FF"> --oneline</span><span style="color:#9ECBFF"> @{u}..</span><span style="color:#F97583"> 2></span><span style="color:#9ECBFF">/dev/null</span><span style="color:#F97583"> |</span><span style="color:#B392F0"> wc</span><span style="color:#79B8FF"> -l</span><span style="color:#F97583"> |</span><span style="color:#B392F0"> tr</span><span style="color:#79B8FF"> -d</span><span style="color:#9ECBFF"> ' '</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">[ </span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$n</span><span style="color:#9ECBFF">"</span><span style="color:#F97583"> !=</span><span style="color:#9ECBFF"> "0"</span><span style="color:#E1E4E8"> ] &#x26;&#x26; </span><span style="color:#79B8FF">echo</span><span style="color:#9ECBFF"> "📌 Máš </span><span style="color:#E1E4E8">$n</span><span style="color:#9ECBFF"> nepushnutých commitů — git push, ať je vidí druhý stroj."</span></span></code></pre><p>Efekt v praxi: otevřu session na MacBooku a repo je aktuální dřív, než napíšu první prompt. A protože <code>settings.json</code> s hooky je sám součástí repa, chová se tak každá stanice automaticky — automatizace se synchronizuje stejnou cestou jako všechno ostatní.</p>
<h2 id="pasti-mac-windows">Pasti Mac ↔ Windows</h2>
<p>Věci, na které se nepřijde čtením dokumentace, ale až když kousnou.</p>
<p><strong>Konce řádků.</strong> Windows git s <code>core.autocrlf=true</code> checkoutuje textové soubory s CRLF — včetně bash skriptů. Jednoduchý skript to přežije; skript s podmínkami a funkcemi spadne na neviditelném <code>\r</code> na konci řádku. A protože skript commitnutý z Windows poběží přes hook i na Macu, je to past s časovaným spuštěním. Oprava jednou provždy, v repu:</p>
<pre data-lang="text" tabindex="0"><code># .gitattributes
*.sh text eol=lf</code></pre>
<p><strong>Git identity na novém stroji.</strong> První commit na čerstvém MacBooku mi skončil hláškou „Author identity unknown&quot;. Nic dramatického, ale patří to do checklistu nové stanice, ne do debugování v půlce práce:</p>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> config</span><span style="color:#79B8FF"> --global</span><span style="color:#9ECBFF"> user.name</span><span style="color:#9ECBFF"> "Jmeno Prijmeni"</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> config</span><span style="color:#79B8FF"> --global</span><span style="color:#9ECBFF"> user.email</span><span style="color:#9ECBFF"> "mail@example.com"</span></span></code></pre><p><strong>Smazaná větev na remotu.</strong> Feature větev mergnutá a smazaná z druhého stroje: na prvním stroji na ní pořád stojím a <code>git pull</code> skončí chybou „no such ref was fetched&quot;. Vypadá to jako konflikt, ale je to jen mrtvá reference:</p>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> fetch</span><span style="color:#79B8FF"> --prune</span><span style="color:#6A737D">            # ukliď reference na smazané remote větve</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> checkout</span><span style="color:#9ECBFF"> main</span><span style="color:#E1E4E8"> &#x26;&#x26; </span><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> pull</span><span style="color:#79B8FF"> --ff-only</span></span></code></pre><p>Sync skript výše proto rozlišuje tři stavy: živý upstream (pullni), větev celá obsažená v <code>origin/main</code> (řekni „přepni na main&quot;), a skutečné rozjetí (jediný případ, kdy má křičet).</p>
<p><strong>Nástroje, které na druhé platformě nejsou.</strong> <code>lsof</code> v git-bash na Windows chybí, <code>netstat</code> se zase jinak chová na Macu. Skripty sdílené mezi platformami potřebují guard — <code>command -v lsof &gt;/dev/null || exit 0</code> — aby chybějící nástroj znamenal tichou degradaci, ne rozbitý hook.</p>
<h2 id="checklist-nova-stanice-za-deset-minut">Checklist: nová stanice za deset minut</h2>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> repo</span><span style="color:#9ECBFF"> clone</span><span style="color:#F97583"> &#x3C;</span><span style="color:#9ECBFF">use</span><span style="color:#E1E4E8">r</span><span style="color:#F97583">></span><span style="color:#9ECBFF">/</span><span style="color:#F97583">&#x3C;</span><span style="color:#9ECBFF">rep</span><span style="color:#E1E4E8">o</span><span style="color:#F97583">></span><span style="color:#6A737D">          # 1. mimo iCloud/OneDrive/Dropbox složky!</span></span>
<span class="line"><span style="color:#79B8FF">cd</span><span style="color:#F97583"> &#x3C;</span><span style="color:#9ECBFF">rep</span><span style="color:#E1E4E8">o</span><span style="color:#F97583">></span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> config</span><span style="color:#79B8FF"> --global</span><span style="color:#9ECBFF"> user.name</span><span style="color:#9ECBFF"> "..."</span><span style="color:#6A737D">  # 2. identita, pokud je stroj čerstvý</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> config</span><span style="color:#79B8FF"> --global</span><span style="color:#9ECBFF"> user.email</span><span style="color:#9ECBFF"> "..."</span></span>
<span class="line"><span style="color:#B392F0">./claude-init.sh</span><span style="color:#6A737D">                     # 3. lokální config, per-machine instalace</span></span>
<span class="line"><span style="color:#B392F0">npm</span><span style="color:#9ECBFF"> install</span><span style="color:#6A737D">                          # 4. závislosti projektu</span></span></code></pre><p>A pátý krok, který se vyplatí nepřeskočit: <strong>ověřovací round-trip.</strong> Commit z nové stanice, push, pull na staré — a totéž obráceně. Až projdou oba směry včetně hooků, je stanice plnohodnotná. Já si při něm odchytil jak chybějící identitu, tak CRLF ve skriptech.</p>
<h2 id="zaver">Závěr</h2>
<p>Nesynchronizuji soubory, synchronizuji <strong>pracoviště</strong>. Měřítko úspěchu je jednoduché — sednu k druhému stroji, otevřu session a první prompt může znít „pokračujeme&quot;. Bez vysvětlování, kde jsme skončili. Git nese kód, repo nese kontext a hooky nesou disciplínu, na kterou bych se sám spolehnout nemohl.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Tahák: Git a GitHub s Claude Code</title>
      <link>https://mirekhovorka.cz/blog/tahak-git-github-claude-code/</link>
      <guid isPermaLink="true">https://mirekhovorka.cz/blog/tahak-git-github-claude-code/</guid>
      <pubDate>Sun, 05 Jul 2026 00:00:00 GMT</pubDate>
      <description>Konkrétní prompty pro git a GitHub s Claude Code — a příkazy, které u toho reálně běží pod kapotou. Commity, PR, issues, CI i záchrana rozbitého repa.</description>
      <category>claude-code</category><category>git</category><category>github</category><category>cheatsheet</category>
      <content:encoded><![CDATA[<p>Claude Code umí obsloužit prakticky celý git a GitHub workflow — commity, větve, pull requesty, review i záchranu rozbitého repa. Tenhle tahák shrnuje, co mu po měsících denního používání svěřuji bez obav, a u každé situace ukazuje obojí: prompt, který píšu, i příkazy, které se pod kapotou reálně spustí. Protože delegovat neznamená přestat rozumět tomu, co se děje.</p>
<h2 id="commity-a-zpravy">Commity a zprávy</h2>
<p>Nejčastější věc, kterou po Claudovi chci. Stačí napsat „commitni to“ a pod kapotou proběhne:</p>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> status</span><span style="color:#6A737D">                 # co je změněné a co untracked</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> diff</span><span style="color:#6A737D">                   # co se skutečně změnilo</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> add</span><span style="color:#9ECBFF"> build.mjs</span><span style="color:#9ECBFF"> serve.mjs</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> commit</span><span style="color:#79B8FF"> -m</span><span style="color:#9ECBFF"> "fix: dev server nepodával webp z podadresářů"</span></span></code></pre><p>Zprávu píše podle toho, co se v diffu skutečně změnilo — ne podle mojí představy o tom, co jsem udělal. Co se mi osvědčilo:</p>
<ul>
<li><strong>Konvence patří do CLAUDE.md.</strong> Jazyk commit zpráv, prefixy typu <code>feat:</code>/<code>fix:</code>, formát — jednou zapsat do instrukcí repozitáře a Claude je dodržuje bez připomínání.</li>
<li><strong>Selektivní commit funguje spolehlivě.</strong> Když mám rozdělaných víc věcí najednou:</li>
</ul>
<pre data-lang="text" tabindex="0"><code>commitni jen změny v build.mjs, zbytek nech být</code></pre>
<p>Pod kapotou je to <code>git add build.mjs</code> a commit — žádné <code>git add -A</code>, které by s sebou vzalo i rozpracovaný zbytek.</p>
<ul>
<li><strong>Nesouvisející změny nechám rozdělit.</strong> „Rozděl to do logických commitů“ — Claude si projde diff, seskupí soubory podle toho, co k čemu patří, a stageuje po skupinách (v krajním případě po hunkách přes <code>git add -p</code>).</li>
</ul>
<p>Co si hlídám: <strong>push je vždycky explicitní krok.</strong> Claude mám instruovaný, ať nikdy nepushuje sám — commit je lokální a vratný, push už je publikace.</p>
<h2 id="zpatecka-amend-restore-stash">Zpátečka: amend, restore, stash</h2>
<p>Drobné opravy, na které jsem dřív hledal přesný příkaz na Stack Overflow:</p>
<ul>
<li><strong>„Přidej tohle do posledního commitu“:</strong></li>
</ul>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> add</span><span style="color:#9ECBFF"> build.mjs</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> commit</span><span style="color:#79B8FF"> --amend</span><span style="color:#79B8FF"> --no-edit</span></span></code></pre><p>Amend přepisuje historii — bezpečný je jen do chvíle, než commit pushnu. Claude na to upozorní sám, ale je dobré to vědět.</p>
<ul>
<li><strong>„Zahoď změny v serve.mjs“:</strong></li>
</ul>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> restore</span><span style="color:#9ECBFF"> serve.mjs</span><span style="color:#6A737D">            # zahodí neuložené změny v souboru</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> restore</span><span style="color:#79B8FF"> --staged</span><span style="color:#9ECBFF"> serve.mjs</span><span style="color:#6A737D">   # jen vyndá ze stage, změny nechá</span></span></code></pre><ul>
<li><strong>„Ulož rozdělanou práci, potřebuju hotfix“:</strong></li>
</ul>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> stash</span><span style="color:#9ECBFF"> push</span><span style="color:#79B8FF"> -m</span><span style="color:#9ECBFF"> "wip: nova paticka"</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> stash</span><span style="color:#9ECBFF"> list</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> stash</span><span style="color:#9ECBFF"> pop</span></span></code></pre><p>Jen pozor: když <code>git stash pop</code> narazí na konflikt, změny aplikuje, ale stash nesmaže — schválně, ať o nic nepřijdete. Po vyřešení konfliktu ho ukliďte ručně: <code>git stash drop</code>.</p>
<p>Od té doby, co používám worktrees, stashuju míň — viz další sekce.</p>
<h2 id="vetve-a-worktrees">Větve a worktrees</h2>
<p>Založení větve je jednoduché — „založ větev pro úpravu patičky“:</p>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> switch</span><span style="color:#79B8FF"> -c</span><span style="color:#9ECBFF"> feat/paticka</span></span></code></pre><p>Zajímavější jsou <strong>worktrees</strong>: druhá pracovní kopie repozitáře na jiné větvi, ve vedlejším adresáři.</p>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> worktree</span><span style="color:#9ECBFF"> add</span><span style="color:#9ECBFF"> ../projekt-oprava</span><span style="color:#9ECBFF"> fix/prepocet-provizi</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> worktree</span><span style="color:#9ECBFF"> list</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> worktree</span><span style="color:#9ECBFF"> remove</span><span style="color:#9ECBFF"> ../projekt-oprava</span><span style="color:#6A737D">   # až je hotovo</span></span></code></pre><p>K čemu je to dobré s AI nástrojem: můžu mít dvě session Claude Code vedle sebe — jedna píše novou feature, druhá opravuje bug — a nešlapou si po souborech, protože každá pracuje ve vlastním adresáři. Bez přepínání větví a bez <code>git stash</code> akrobacie.</p>
<p>Dvě věci, které worktree nováčkovi neřekne: stejnou větev nejde mít checkoutnutou ve dvou worktrees najednou (git to odmítne — a dobře dělá). A <code>node_modules</code> se neklonují: každá kopie si žije vlastním životem, takže po <code>git worktree add</code> přijde ještě <code>npm install</code>.</p>
<p>(Worktrees řeší paralelní práci na jednom stroji. Jak udržet jeden projekt — včetně kontextu Claude Code — synchronizovaný mezi dvěma počítači, popisuje <a href="/blog/sync-repa-mezi-stanicemi/">Jedno repo, dvě klávesnice</a>.)</p>
<h2 id="pull-requesty-pres-gh-cli">Pull requesty přes gh CLI</h2>
<p>Claude Code používá GitHub přes oficiální <code>gh</code> CLI. Jednorázová příprava:</p>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">brew</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> gh</span><span style="color:#6A737D">            # macOS</span></span>
<span class="line"><span style="color:#B392F0">winget</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> GitHub.cli</span><span style="color:#6A737D">  # Windows</span></span>
<span class="line"><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> apt</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> gh</span><span style="color:#6A737D">        # Debian/Ubuntu</span></span>
<span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> auth</span><span style="color:#9ECBFF"> login</span><span style="color:#6A737D">              # jednorázové přihlášení</span></span></code></pre><p>Pak stačí:</p>
<pre data-lang="text" tabindex="0"><code>vytvoř PR proti main, popis stručně</code></pre>
<p>Pod kapotou:</p>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> log</span><span style="color:#9ECBFF"> main..HEAD</span><span style="color:#79B8FF"> --oneline</span><span style="color:#6A737D">   # jaké commity větev přináší</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> diff</span><span style="color:#9ECBFF"> main...HEAD</span><span style="color:#6A737D">           # celý diff větve proti main</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> push</span><span style="color:#79B8FF"> -u</span><span style="color:#9ECBFF"> origin</span><span style="color:#9ECBFF"> feat/paticka</span></span>
<span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> pr</span><span style="color:#9ECBFF"> create</span><span style="color:#79B8FF"> --base</span><span style="color:#9ECBFF"> main</span><span style="color:#79B8FF"> --title</span><span style="color:#9ECBFF"> "..."</span><span style="color:#79B8FF"> --body</span><span style="color:#9ECBFF"> "..."</span></span></code></pre><p>Popis PR vzniká ze <strong>skutečného diffu celé větve proti main</strong>, ne jen z posledního commitu — takže sedí i po deseti commitech s opravami. Druhý směr funguje taky:</p>
<pre data-lang="text" tabindex="0"><code>načti review komentáře z PR #42 a zapracuj je</code></pre>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> pr</span><span style="color:#9ECBFF"> view</span><span style="color:#79B8FF"> 42</span><span style="color:#79B8FF"> --comments</span><span style="color:#6A737D">                        # diskuze pod PR</span></span>
<span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> api</span><span style="color:#9ECBFF"> repos/{owner}/{repo}/pulls/42/comments</span><span style="color:#6A737D">   # inline komentáře v kódu</span></span></code></pre><p>Claude si komentáře stáhne, projde je jeden po druhém a ke každému řekne, co s ním udělal. U komentářů, se kterými nesouhlasí, chci, aby to řekl — ne aby slepě vyhověl.</p>
<p>Review cizího PR — „stáhni PR #42 a projdi ho“:</p>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> pr</span><span style="color:#9ECBFF"> checkout</span><span style="color:#79B8FF"> 42</span><span style="color:#6A737D">   # větev PR do lokální kopie</span></span>
<span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> pr</span><span style="color:#9ECBFF"> diff</span><span style="color:#79B8FF"> 42</span><span style="color:#6A737D">       # rychlý pohled bez checkoutu</span></span></code></pre><p>A merge, když je hotovo — „mergni PR #42 squashem“:</p>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> pr</span><span style="color:#9ECBFF"> merge</span><span style="color:#79B8FF"> 42</span><span style="color:#79B8FF"> --squash</span><span style="color:#79B8FF"> --delete-branch</span></span></code></pre><p>Merge beru stejně jako push: je to publikace, tak ho odklepávám explicitně.</p>
<h2 id="issues-a-ci">Issues a CI</h2>
<p>Sekce, kterou jsem v první verzi tohohle taháku vynechal — a přitom je to půlka toho, co <code>gh</code> denně dělá.</p>
<ul>
<li><strong>„Vypiš otevřené issues s labelem bug“:</strong></li>
</ul>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> issue</span><span style="color:#9ECBFF"> list</span><span style="color:#79B8FF"> --label</span><span style="color:#9ECBFF"> bug</span><span style="color:#79B8FF"> --state</span><span style="color:#9ECBFF"> open</span></span>
<span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> issue</span><span style="color:#9ECBFF"> view</span><span style="color:#79B8FF"> 12</span></span></code></pre><ul>
<li><strong>„Založ z tohohle issue“</strong> — uprostřed session, když narazím na bug, který teď nechci řešit:</li>
</ul>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> issue</span><span style="color:#9ECBFF"> create</span><span style="color:#79B8FF"> --title</span><span style="color:#9ECBFF"> "..."</span><span style="color:#79B8FF"> --body</span><span style="color:#9ECBFF"> "..."</span><span style="color:#79B8FF"> --label</span><span style="color:#9ECBFF"> bug</span></span></code></pre><p>Popis píše Claude z kontextu session, dokud je čerstvý — takže je konkrétní: co se stalo, kde, jak to reprodukovat. Issue založené takhle je použitelnější než to, které bych psal z hlavy o tři dny později.</p>
<ul>
<li><strong>„Proč spadla CI na téhle větvi?“</strong> — možná největší časová úspora z celého taháku:</li>
</ul>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> pr</span><span style="color:#9ECBFF"> checks</span><span style="color:#6A737D">                        # stav checků na PR</span></span>
<span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> run</span><span style="color:#9ECBFF"> list</span><span style="color:#79B8FF"> --branch</span><span style="color:#9ECBFF"> feat/paticka</span><span style="color:#6A737D">   # poslední běhy workflow</span></span>
<span class="line"><span style="color:#B392F0">gh</span><span style="color:#9ECBFF"> run</span><span style="color:#9ECBFF"> view</span><span style="color:#79B8FF"> 1234567890</span><span style="color:#79B8FF"> --log-failed</span><span style="color:#6A737D"> # log jen toho, co selhalo</span></span></code></pre><p>Claude si vytáhne log selhání, najde příčinu a rovnou navrhne opravu. Bez klikání do webového UI a scrollování kilometrovým logem.</p>
<h2 id="review-vlastniho-diffu-pred-pr">Review vlastního diffu před PR</h2>
<p>Než PR otevřu, nechám si diff projít ještě lokálně — je to levnější než pálit čas kolegy:</p>
<pre data-lang="text" tabindex="0"><code>zreviduj diff téhle větve proti main — hledej chyby a rizika, ne styl</code></pre>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> diff</span><span style="color:#9ECBFF"> main...HEAD</span></span></code></pre><p>Tři tečky nejsou překlep: diff proti společnému předku (merge-base) ukáže jen to, co přinesla moje větev — ne změny, které mezitím přistály v main.</p>
<p>Funguje tím líp, čím konkrétnější zadání dostane: „zaměř se na ošetření chyb“, „hledej race conditions“, „zkontroluj, že se nikde neloguje osobní údaj“. Obecné „mrkni na to“ vrací obecné odpovědi.</p>
<h2 id="zachranne-situace">Záchranné situace</h2>
<p>Tady se pozná, jestli vám AI pomáhá, nebo škodí.</p>
<ul>
<li><strong>Konflikty při merge/rebase</strong> — Claude je vyřeší, ale chci vidět zdůvodnění:</li>
</ul>
<pre data-lang="text" tabindex="0"><code>vyřeš konflikty; u každého souboru mi řekni, kterou stranu jsi vzal a proč</code></pre>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> status</span><span style="color:#6A737D">                        # které soubory jsou v konfliktu</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> checkout</span><span style="color:#79B8FF"> --ours</span><span style="color:#9ECBFF"> soubor.js</span><span style="color:#6A737D">     # vzít celou naši stranu</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> checkout</span><span style="color:#79B8FF"> --theirs</span><span style="color:#9ECBFF"> soubor.js</span><span style="color:#6A737D">   # vzít celou jejich stranu</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> rebase</span><span style="color:#79B8FF"> --abort</span><span style="color:#6A737D">                # nouzová brzda: vrátit vše před rebase</span></span></code></pre><p>Většinou ale Claude konfliktní bloky edituje ručně — je to přesnější než brát celé soubory jednou stranou. (A pozor na klasickou past: při rebase se význam <code>--ours</code>/<code>--theirs</code> otáčí.)</p>
<ul>
<li><strong>Revert</strong> — bezpečná operace, vytváří nový commit, nic nepřepisuje:</li>
</ul>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> revert</span><span style="color:#9ECBFF"> abc123</span></span></code></pre><ul>
<li><strong>Bisect</strong> — hledání commitu, který něco rozbil, je s AI konečně bezbolestné:</li>
</ul>
<pre data-lang="text" tabindex="0"><code>najdi bisectem commit, který rozbil build; testuj příkazem node build.mjs</code></pre>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> bisect</span><span style="color:#9ECBFF"> start</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> bisect</span><span style="color:#9ECBFF"> bad</span><span style="color:#6A737D">                  # HEAD je rozbitý</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> bisect</span><span style="color:#9ECBFF"> good</span><span style="color:#9ECBFF"> v1.4</span><span style="color:#6A737D">            # tady to ještě fungovalo</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> bisect</span><span style="color:#9ECBFF"> run</span><span style="color:#9ECBFF"> node</span><span style="color:#9ECBFF"> build.mjs</span><span style="color:#6A737D">   # automatický test každého kroku</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> bisect</span><span style="color:#9ECBFF"> reset</span></span></code></pre><p>S <code>git bisect run</code> proběhne celé půlení bez jediného ručního kroku — Claude jen odečte výsledek a ukáže viníka.</p>
<ul>
<li><strong>Reflog</strong> — poslední záchrana, když „tu včera byl commit a teď je pryč“:</li>
</ul>
<pre data-lang="bash" class="shiki github-dark" style="background-color:rgba(255,255,255,0.04);color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> reflog</span><span style="color:#6A737D">                    # deník všeho, kde HEAD kdy byl</span></span>
<span class="line"><span style="color:#B392F0">git</span><span style="color:#9ECBFF"> reset</span><span style="color:#79B8FF"> --hard</span><span style="color:#9ECBFF"> HEAD@{</span><span style="color:#79B8FF">2</span><span style="color:#9ECBFF">}</span><span style="color:#6A737D">     # návrat na konkrétní stav</span></span></code></pre><p>Čtení reflogu je bezpečné. Ten <code>reset --hard</code> na konci už ne — a tím jsme u pointy: <strong><code>git reset --hard</code>, force push a mazání větví si dělám ručně</strong>, nebo minimálně chci, aby se Claude před provedením explicitně zeptal. Nastavení oprávnění v Claude Code tomu jde naproti: destruktivní příkazy nechávám na ručním schválení.</p>
<h2 id="rychla-reference">Rychlá reference</h2>
<div class="table-scroll" tabindex="0"><table>
<thead>
<tr>
<th>Situace</th>
<th>Prompt</th>
<th>Klíčový příkaz</th>
</tr>
</thead>
<tbody><tr>
<td>Commit všeho</td>
<td>„commitni to“</td>
<td><code>git commit -m</code></td>
</tr>
<tr>
<td>Selektivní commit</td>
<td>„commitni jen změny v X“</td>
<td><code>git add X &amp;&amp; git commit</code></td>
</tr>
<tr>
<td>Oprava posledního commitu</td>
<td>„přidej to do posledního commitu“</td>
<td><code>git commit --amend --no-edit</code></td>
</tr>
<tr>
<td>Zahození změn</td>
<td>„zahoď změny v X“</td>
<td><code>git restore X</code></td>
</tr>
<tr>
<td>Odložení práce</td>
<td>„ulož rozdělanou práci“</td>
<td><code>git stash push -m &quot;...&quot;</code></td>
</tr>
<tr>
<td>Nová větev</td>
<td>„založ větev pro …“</td>
<td><code>git switch -c</code></td>
</tr>
<tr>
<td>Paralelní práce</td>
<td>dvě session vedle sebe</td>
<td><code>git worktree add</code></td>
</tr>
<tr>
<td>Pull request</td>
<td>„vytvoř PR proti main“</td>
<td><code>gh pr create</code></td>
</tr>
<tr>
<td>Zapracování review</td>
<td>„načti komentáře z PR #N“</td>
<td><code>gh api …/pulls/N/comments</code></td>
</tr>
<tr>
<td>Cizí PR</td>
<td>„stáhni PR #N a projdi ho“</td>
<td><code>gh pr checkout N</code></td>
</tr>
<tr>
<td>Issues</td>
<td>„vypiš otevřené bugy“</td>
<td><code>gh issue list --label bug</code></td>
</tr>
<tr>
<td>CI selhání</td>
<td>„proč spadla CI?“</td>
<td><code>gh run view --log-failed</code></td>
</tr>
<tr>
<td>Review před PR</td>
<td>„zreviduj diff proti main“</td>
<td><code>git diff main...HEAD</code></td>
</tr>
<tr>
<td>Konflikty</td>
<td>„vyřeš konflikty a zdůvodni“</td>
<td><code>git checkout --ours/--theirs</code></td>
</tr>
<tr>
<td>Vrácení commitu</td>
<td>„revertni commit X“</td>
<td><code>git revert X</code></td>
</tr>
<tr>
<td>Regrese</td>
<td>„najdi bisectem, co rozbilo …“</td>
<td><code>git bisect run</code></td>
</tr>
<tr>
<td>Ztracený commit</td>
<td>„najdi ztracený commit“</td>
<td><code>git reflog</code></td>
</tr>
</tbody></table></div>
<h2 id="zaver">Závěr</h2>
<p>Gitu kvůli Claudovi nepřestávám rozumět — právě naopak, bez toho bych nepoznal, kdy sáhne vedle. Jen už nemusím ručně psát příkazy, které umím nadiktovat, a ušetřenou energii dávám do kontroly výsledků. Git zůstává stejný, jen na klávesnici píše někdo jiný.</p>
]]></content:encoded>
    </item>
    <item>
      <title>AI agenti v enterprise: z PoC do produkce</title>
      <link>https://mirekhovorka.cz/blog/ai-agenti-v-enterprise/</link>
      <guid isPermaLink="true">https://mirekhovorka.cz/blog/ai-agenti-v-enterprise/</guid>
      <pubDate>Sun, 28 Jun 2026 00:00:00 GMT</pubDate>
      <description>Demo agenta postavíte za týden. Mezi demem a produkcí v regulovaném prostředí ale stojí věci, které s technologií souvisí jen okrajově.</description>
      <category>ai-agents</category><category>enterprise</category><category>governance</category>
      <content:encoded><![CDATA[<p>AI agenta, který si čte dokumenty, volá systémy a vyřizuje úkoly, dnes naklikáte za pár dní. Vím to, protože je stavím — a zároveň dělám architekta v pojišťovně, takže vidím obě strany: jak rychle vznikne působivé demo a co všechno ještě stojí mezi ním a produkcí. A většina z toho s technologií souvisí jen málo.</p>
<h2 id="proc-poc-vypada-hotove-za-tyden">Proč PoC vypadá hotově za týden</h2>
<p>U proof of conceptu se snadno zapomíná na jednu věc: předvádí se na scénářích, které si vybral jeho autor. Deset ukázkových případů projde hladce, publikum je nadšené a všem se zdá, že zbývá „jen to nasadit“.</p>
<p>Jenže PoC většinou běží na vybraných datech, bez reálných oprávnění, bez auditní stopy a s člověkem, který u toho sedí a drobné přešlapy tiše opravuje. Nic z toho v produkci mít nebudete. PoC odpovídá na otázku „umí to fungovat?“ — produkce se ptá, co se stane, když to fungovat přestane.</p>
<p>Jak ten rozdíl vypadá v praxi, ukázala Klarna. V únoru 2024 <a href="https://www.klarna.com/international/press/klarna-ai-assistant-handles-two-thirds-of-customer-service-chats-in-its-first-month/" class="blog-extlink" target="_blank" rel="noopener noreferrer">oznámila</a>, že její AI asistent zvládl za první měsíc dvě třetiny zákaznické podpory — „práci 700 lidí“. O rok později CEO <a href="https://www.customerexperiencedive.com/news/klarna-reinvests-human-talent-customer-service-AI-chatbot/747586/" class="blog-extlink" target="_blank" rel="noopener noreferrer">přiznal</a>, že škrty přes AI zašly moc daleko, kvalita šla dolů a firma zase nabírá lidi. Tisková zpráva měřila, kolik konverzací agent vyřídil. Jak dobře — to se řešilo až o rok později.</p>
<h2 id="co-pridava-enterprise">Co přidává enterprise</h2>
<p>V regulovaném prostředí přibývají požadavky, které demo nikdy řešit nemuselo:</p>
<ul>
<li><strong>Auditní stopa.</strong> Každé rozhodnutí agenta musí být dohledatelné: co viděl na vstupu, proč a jak rozhodl, co provedl. „Model to tak vyhodnotil“ auditorovi ani regulátorovi stačit nebude.</li>
<li><strong>Odpovědnost.</strong> Když agent udělá chybu, musí být předem jasné, kdo za ni odpovídá a jak se opraví. Je to procesní otázka — a odpověď na ni chcete mít dřív, než agenta pustíte do produkce, ne až po prvním průšvihu. Soudy už na to názor mají: kanadský tribunál v únoru 2024 <a href="https://www.canlii.org/en/bc/bccrt/doc/2024/2024bccrt149/2024bccrt149.html" class="blog-extlink" target="_blank" rel="noopener noreferrer">nechal Air Canada zaplatit</a> za slevu, kterou si její chatbot vymyslel. Aerolinka se hájila tím, že chatbot je „samostatná právní entita odpovědná za své vlastní jednání“ — tribunál to označil za pozoruhodné tvrzení a nárok uznal. Šlo o 812 kanadských dolarů, ale ten případ se od té doby cituje jako precedens: za výstupy chatbota odpovídá firma.</li>
<li><strong>Data governance.</strong> V PoC je pohodlné pustit agenta ke všemu. V produkci musí přístup k datům odpovídat účelu — agent na likvidaci pojistných událostí nemá co číst mzdové údaje, i kdyby mu to zlepšilo odpovědi.</li>
<li><strong>Bezpečnost.</strong> Agent je nová útočná plocha: vstupy, kterým věří, se dají podvrhnout (prompt injection je jen ten nejznámější případ), a čím víc akcí smí provést, tím víc škody podvržený vstup napáchá. Dlouho se to ilustrovalo hlavně na kuriozitách, jako byl chatbot dealera <a href="https://incidentdatabase.ai/cite/622/" class="blog-extlink" target="_blank" rel="noopener noreferrer">slibující Chevrolet Tahoe za dolar</a>. Pak ale v červnu 2025 vyšel <a href="https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-32711" class="blog-extlink" target="_blank" rel="noopener noreferrer">EchoLeak</a>: první zero-click prompt injection na produkčního enterprise agenta (Microsoft 365 Copilot, CVSS 9.3). Stačil jeden obyčejně vypadající e-mail v poště — agent si ho sám zatáhl do kontextu a citlivá data odtekla, aniž by uživatel na cokoli klikl. Naštěstí to našli bezpečnostní výzkumníci.</li>
</ul>
<p>A nejde jen o interní opatrnost. Podle <a href="https://artificialintelligenceact.eu/annex/3/" class="blog-extlink" target="_blank" rel="noopener noreferrer">EU AI Act</a> spadá AI pro risk assessment a pricing v životním a zdravotním pojištění rovnou mezi high-risk systémy, s povinnostmi od srpna 2026 (v Bruselu se řeší odklad, ale směr platí). Takže velká část těch odrážek nahoře bude dřív nebo později prostě povinnost ze zákona.</p>
<h2 id="kde-smi-agent-jednat-sam">Kde smí agent jednat sám</h2>
<p>Nejdůležitější návrhové rozhodnutí celého řešení: <strong>které akce smí agent udělat sám a kde musí být člověk ve smyčce.</strong> Používám na to jednoduchou mřížku — dvě osy, vratnost a dopad:</p>
<ul>
<li><strong>Vratné + malý dopad</strong> → agent jedná sám (návrh odpovědi, kategorizace, příprava podkladů).</li>
<li><strong>Vratné + velký dopad</strong> → agent jedná, člověk kontroluje vzorky a metriky.</li>
<li><strong>Nevratné + malý dopad</strong> → agent připraví, člověk potvrdí jedním klikem.</li>
<li><strong>Nevratné + velký dopad</strong> → agent pouze doporučuje; rozhoduje člověk (výplata pojistného plnění, výpověď smlouvy).</li>
</ul>
<p>Ta hranice navíc není daná jednou provždy. Vyplatí se začít konzervativně a pravomoci agentovi přidávat podle toho, co ukážou čísla z provozu — vyjde to levněji než začít zostra a po prvním incidentu všechno zase zamknout.</p>
<h2 id="agent-jako-novy-ucastnik-procesu">Agent jako nový účastník procesu</h2>
<p>K agentovi se často přistupuje jako k dalšímu systému, který se prostě „zintegruje“. Mně v praxi víc pomáhá představa nového kolegy: rychlého, co se nikdy nenudí, ale občas si zadání vyloží po svém.</p>
<p>Z té představy plynou praktické otázky, které byste u nového kolegy položili taky: kdo mu zadává práci a v jakém formátu? Komu předává výsledek? Jak pozná, že si neví rady, a komu to řekne? A co se stane s procesem, když zrovna „nepřijde do práce“? Proces, který na tyhle otázky nemá odpověď, na agenta připravený není — bez ohledu na to, jak dobrý ten agent je.</p>
<h2 id="co-merit-aby-slo-rict-funguje-to">Co měřit, aby šlo říct „funguje to“</h2>
<p>Bez metrik se debata o agentovi vede na anekdotách: jeden si pamatuje skvělou odpověď, druhý průšvih. Tohle je minimální sada, kterou chci mít od prvního dne:</p>
<ol>
<li><strong>Úspěšnost na konci procesu</strong> — ne „agent odpověděl“, ale „případ byl správně vyřízen“, měřeno stejným metrem jako u lidí. Přesně na tomhle skončila <a href="https://www.cnbc.com/2024/06/17/mcdonalds-to-end-ibm-ai-drive-thru-test.html" class="blog-extlink" target="_blank" rel="noopener noreferrer">AI v drive-thru McDonald’s</a>: dva roky testů ve víc než stovce restaurací, jenže přesnost objednávek se podle franšízantů držela kolem 80 % a cíl byl 95. To virální video s 260 nugety v jedné objednávce byl jen ten nejviditelnější projev.</li>
<li><strong>Míra eskalací</strong> — jak často agent předává práci člověku. Nízké číslo nemusí být dobrá zpráva: agent, který nikdy neeskaluje, spíš nepozná, kdy si neví rady.</li>
<li><strong>Cena za vyřízený případ</strong> — včetně nákladů na kontrolu a opravy po agentovi. Bez nich vám vyjde hezčí číslo, než jaká je realita.</li>
<li><strong>Trend oprav</strong> — kolik agentových výstupů museli lidé předělat a jak se to vyvíjí. Podle tohohle čísla se posouvá hranice samostatnosti z předchozí kapitoly.</li>
</ol>
<h2 id="zaver">Závěr</h2>
<p>Nasadit AI agenta do enterprise znamená hlavně rozhodnout, kolik důvěry mu dáte: co smí udělat sám, kdo za něj odpovídá a čím se měří, že funguje. Technologie je dnes připravenější, než většina organizací tuší. Co obvykle chybí, jsou procesy, odpovědnosti a metriky.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Kdy API, kdy eventy</title>
      <link>https://mirekhovorka.cz/blog/kdy-api-kdy-eventy/</link>
      <guid isPermaLink="true">https://mirekhovorka.cz/blog/kdy-api-kdy-eventy/</guid>
      <pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate>
      <description>Synchronní volání, nebo event? Rozhodovací rámec, který používám místo debat o tom, co je modernější.</description>
      <category>integration</category><category>api</category><category>event-driven</category>
      <content:encoded><![CDATA[<p>Debatu „API versus eventy“ jsem zažil snad v každém týmu — a málokdy se vedla dobře. Argumentuje se tím, co je moderní, co už máme nakoupené, nebo jak se to dělalo vždycky. Místo toho používám pár otázek o samotném procesu. Většinou rozhodnou samy; tady je shrnuju i s příklady.</p>
<h2 id="kdo-potrebuje-odpoved-a-kdy">Kdo potřebuje odpověď a kdy</h2>
<p>Otázka, která architektura je „lepší“, se rozhodnout nedá. Užitečnější je ptát se, kdo potřebuje odpověď a kdy: když volající bez výsledku nemůže pokračovat, potřebujete synchronní volání; když ho výsledek zajímá „až to bude“, nebo zajímá víc příjemců, jste na území eventů. Většina sporů skončí ve chvíli, kdy se přestane mluvit o technologii a začne o procesu.</p>
<h2 id="kdy-synchronni-api">Kdy synchronní API</h2>
<p>Synchronní volání je správná volba, když:</p>
<ul>
<li><strong>Volající bez odpovědi nemůže pokračovat.</strong> Ověření limitu před potvrzením obchodu, výpočet ceny v kalkulaci, kontrola oprávnění. Čekání tady k operaci prostě patří.</li>
<li><strong>Chybu chcete řešit hned a v kontextu.</strong> Synchronní volání vrátí chybu tomu, kdo se ptal, ve chvíli, kdy se ptal — a ten může hned reagovat: opravit vstup, zkusit to znovu, říct to uživateli.</li>
<li><strong>Tok má být čitelný.</strong> Řetěz synchronních volání se čte shora dolů a pořadí kroků z něj vidíte na první pohled. U procesů, kde na pořadí a srozumitelnosti záleží, to má velkou cenu.</li>
</ul>
<p>Cena, kterou za to platíte: <strong>provázaná dostupnost.</strong> Každý článek synchronního řetězu dědí výpadky všech ostatních. Deset služeb s dostupností 99,9 % za sebou dá dohromady 0,999¹⁰ ≈ 99,0 % — z devíti hodin výpadku ročně je najednou osmdesát sedm. A tempo všem určuje ten nejpomalejší.</p>
<p>A pak je tu <strong>retry storm</strong>: malý výpadek se vlnou opakovaných volání nafoukne do velkého. Přesně tohle se stalo při <a href="https://aws.amazon.com/message/5467D2/" class="blog-extlink" target="_blank" rel="noopener noreferrer">výpadku DynamoDB v září 2015</a> — síť se vzpamatovala za okamžik, jenže vlna synchronních retry pak držela metadata službu přetíženou skoro pět hodin a kaskáda s sebou vzala SQS, CloudWatch i autoscaling. O délce výpadku tedy nerozhodla síť, ale architektura okolo ní. A v <a href="https://aws.amazon.com/message/101925/" class="blog-extlink" target="_blank" rel="noopener noreferrer">říjnu 2025</a> se to samé zopakovalo ve stejném regionu, tentokrát na čtrnáct hodin.</p>
<h2 id="kdy-eventy">Kdy eventy</h2>
<p>Event je správná volba, když:</p>
<ul>
<li><strong>Výsledek zajímá víc odběratelů.</strong> Uzavřená smlouva zajímá provize, reporting, dokumenty i marketing. Publikovat jeden event je lepší než volat čtyři API — a pátý odběratel se příště přidá, aniž byste sahali do zdroje.</li>
<li><strong>Producent nemá znát své konzumenty.</strong> Event říká „stalo se“, ne „udělej“. Zdrojový systém se nemusí měnit pokaždé, když si výsledek chce odebírat někdo nový. To je na event-driven přístupu to hlavní: životní cykly systémů se od sebe odpojí.</li>
<li><strong>Špičky zátěže nemají procházet dál.</strong> Fronta mezi producentem a konzumentem nápor rozloží v čase, místo aby se valil kaskádou do dalších systémů.</li>
<li><strong>Proces snese asynchronnost.</strong> „Do několika sekund“ většině navazujících kroků stačí — jen to byznys musí vědět a odsouhlasit.</li>
</ul>
<p>Cena je tentokrát provozní a mentální: <strong>eventy se hůř ladí.</strong> Tok procesu nevidíte v jednom trace, konzistence je eventuální, objeví se duplicitní doručení a mrtvé zprávy (DLQ). Bez pořádného monitoringu a idempotentních konzumentů se z „moderní architektury“ stane systém, kterému nikdo nerozumí.</p>
<p>K duplicitám jedna věc, kterou je lepší vědět předem: <strong>at-least-once doručení je součást kontraktu.</strong> SQS i Stripe webhooky to píšou přímo v dokumentaci — občas prostě dorazí ta samá zpráva dvakrát. Konzument, který na druhé doručení odpoví druhou výplatou provize, má chybu v návrhu; s duplicitami měl počítat. Řešení je staré a osvědčené: <a href="https://stripe.com/blog/idempotency" class="blog-extlink" target="_blank" rel="noopener noreferrer">idempotency key</a> — stejný požadavek se stejným klíčem se provede jen jednou, ať dorazí kolikrát chce.</p>
<h2 id="seda-zona-a-hybridy">Šedá zóna a hybridy</h2>
<p>Reálný svět málokdy zapadne čistě do jedné škatulky:</p>
<ul>
<li><strong>Request-reply přes eventy</strong> — volající pošle požadavek do fronty a čeká na odpovědní event. Špičky se vám rozloží v čase, ale zaplatíte za to složitější korelací. Dává smysl u dlouhých operací, ne jako náhrada obyčejného volání.</li>
<li><strong>Outbox pattern</strong> — když potřebujete zapsat do vlastní databáze <em>a zároveň</em> spolehlivě publikovat event, zapište obojí v jedné transakci (event do outbox tabulky) a publikování nechte na samostatném procesu. Řeší klasický problém „zapsáno, ale neodesláno“.</li>
<li><strong>Synchronní čtení, asynchronní zápis</strong> — častý a zdravý kompromis: na aktuální stav se ptáte přes API, změny se rozhlašují eventy.</li>
</ul>
<h2 id="cena-kazde-volby">Cena každé volby</h2>
<p>Žádná z možností není zadarmo — liší se jen tím, kde a kdy zaplatíte:</p>
<div class="table-scroll" tabindex="0"><table>
<thead>
<tr>
<th>Hledisko</th>
<th>Synchronní API</th>
<th>Eventy</th>
</tr>
</thead>
<tbody><tr>
<td>Odpověď pro volajícího</td>
<td>okamžitá</td>
<td>žádná / později</td>
</tr>
<tr>
<td>Vazba mezi systémy</td>
<td>těsná (dostupnost, verze)</td>
<td>volná (jen kontrakt eventu)</td>
</tr>
<tr>
<td>Chování při špičce</td>
<td>přenáší zátěž dál</td>
<td>rozloží ji frontou</td>
</tr>
<tr>
<td>Řešení chyb</td>
<td>hned, v kontextu volání</td>
<td>opožděně, mimo kontext</td>
</tr>
<tr>
<td>Čitelnost toku</td>
<td>vysoká</td>
<td>vyžaduje nástroje</td>
</tr>
<tr>
<td>Provozní nároky</td>
<td>nižší</td>
<td>vyšší (monitoring, DLQ, idempotence)</td>
</tr>
</tbody></table></div>
<h2 id="zaver">Závěr</h2>
<p>Celý rámec se vejde do tří otázek: kdo potřebuje odpověď a kdy, kolik je odběratelů a co se má stát při výpadku. Odpověď na „API, nebo event?“ z nich většinou vypadne sama. A když si dvě části procesu řeknou každá o něco jiného, nic se neděje — každá prostě potřebuje něco jiného.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>