Prečo som si postavil vlastný 'druhý mozog' namiesto poznámkového bloku (a čo ma to naučilo o dátach)
Appka, ktorá vznikla čisto na to, aby si vlastná práca naprieč desiatkami projektov a dvoma strojmi nezabúdala, čo už bolo urobené a prečo — a ktorá si za pár mesiacov každodenného používania odžila dosť na to, aby dávalo zmysel ju aj zverejniť.
Problém, ktorý appka rieši
Keď spravuješ desiatky projektov naraz — homelab služby, vlastné appky, klientsku prácu, na viacerých strojoch — vzniká špecifický druh problému: kontext sa stráca. K projektu sa vrátiš o týždeň a nevieš, čo presne sa vtedy robilo, prečo bolo niečo urobené presne tak a nie inak, a čo ešte čaká na dokončenie. Bežné riešenie — "zapisuj si priebežne poznámky do .md súborov" — znie rozumne, ale v praxi má jednu slabinu: je to krok navyše, ktorý nemá žiadnu mechanickú väzbu na samotnú prácu. Pri kratšej úlohe sa dodrží. Pri dlhšom, rozbehnutom kole práce sa postupne vytráca.
Presne to sa aj potvrdilo — audit ukázal, že pri type záznamov, ktoré mali ísť zapisovať priebežne, chýbalo až 10 z 16 očakávaných záznamov za jeden deň. Pravidlo existovalo, bolo aj zrozumiteľné, no nebolo ničím vynútené — a tak sa jednoducho vynechávalo.
Prvý pokus: zapisuj na dve miesta naraz
Prvá reakcia bola logická — ak sa .md zápis vynecháva, pridaj druhé, redundantné miesto zápisu (dual-write): appka s API, ktorá si zároveň drží aj vlastnú databázu. Zápis by mal ísť na oba ciele naraz.
Problém je, že "zapisuj na dve miesta" má presne tú istú slabinu ako pôvodné pravidlo — je to znova krok navyše bez mechanickej väzby. Chvíľu to fungovalo lepšie, no nakoniec šlo o rovnaký typ krehkosti, len s jedným miestom zápisu naviac.
Rozhodnutie: jeden smer zápisu, jeden zdroj pravdy
Riešenie napokon nebolo "zapisuj dôslednejšie", ale zmeniť, čo je vôbec zdroj pravdy. Namiesto dvoch rovnocenných miest zápisu vznikol jeden jediný — appka s API — a pôvodné .md súbory sa zmenili na čisto generovaný, čitateľný backup, obnovovaný automaticky raz za hodinu skriptom bežiacim opačným smerom, než pôvodný importér.
Dôsledok je jednoduchý, ale dôležitý: teraz existuje presne jedno miesto, kde môže zápis chýbať — a keď chýba, dá sa to jednoducho overiť (posledný timestamp zápisu cez vlastný sync-status endpoint), nie dohadovať sa, či to náhodou nie je len v tom druhom súbore.
Cesta k tomu nebola priamočiara — dátové bugy, ktoré sa objavili cestou
Prechod na jediný zdroj pravdy odhalil sériu reálnych chýb, ktoré predtým jednoducho neboli vidieť, lebo dáta boli rozdelené na viacero miest naraz:
- Case-sensitive duplicita projektov. Vlastný zápisový kód si pri zakladaní nového projektu robil
lookuppodľa mena bez ohľadu na veľkosť písmen inak, než pri neskoršom vyhľadávaní — výsledkom boli dva samostatné projektyMimiramimir, ktoré mali obsahovať to isté. Presne ten istý typ bugu sa neskôr našiel ešte raz, na inom mieste kódu (živé zapisovanie z paralelnej relácie na inom stroji), čo potvrdilo, že nešlo o jednorazový preklep, ale o vzorec — všade, kde sa robí lookup-alebo-vytvor podľa textového mena, treba explicitne riešiť veľkosť písmen. - SQLite schema poradie. Pridanie nového stĺpca do existujúcej tabuľky zhavarovalo backend hneď pri štarte s "no such column" — hoci príkaz na jeho pridanie bol v kóde prítomný. Príčina: SQLite vykoná celý blok príkazov v poradí, v akom sú napísané, a
CREATE INDEXna novom stĺpci sa pokúsil spustiť skôr, než samostatná funkcia stihla dobehnúťALTER TABLE ADD COLUMN. V systéme bez poriadneho migračného frameworku musí byť poradie explicitné: najprv nemenená schéma, potom idempotentný backfill nových stĺpcov, až potom čokoľvek, čo na tých stĺpcoch závisí. - Príliš veľké API odpovede. Zoznam projektov vracal pri každom volaní aj plné poznámkové texty ku každému — pri niektorých projektoch až 70+ KB. Pri bežnom čítaní appky to nevadilo, ale pri čítaní cez API to prekračovalo rozumný limit na jedno načítanie a bolo to nakoniec nepohodlnejšie, než len otvoriť jeden
.mdsúbor. Riešenie: odľahčený zoznamový endpoint (len príznak "má poznámky", nie ich obsah), plný text len na vyžiadanie pre konkrétny záznam. Rovnaký princíp neskôr aplikovaný aj na zoznam záznamov (voliteľné orezanie textu na pár stoviek znakov).
Veľké upratovanie: keď duplicita vôbec nevyzerá ako duplicita
Najzaujímavejšia časť prišla pri hľadaní duplicitných záznamov, ktoré vznikli tak, že tá istá udalosť sa počas prechodového obdobia zapísala dvakrát — raz živým zápisom, raz spätným importom z .md zálohy toho istého obdobia.
Prvý, najjednoduchší prístup — presná zhoda dátumu a názvu — našiel len časť z nich, pretože formulácie sa medzi dvomi zápismi tej istej udalosti mierne líšili (interpunkcia, diakritika). Rozšírenie na "fuzzy" porovnanie normalizovaných názvov našlo ďalšie páry, no stále nie všetky.
Skutočné riešenie prišlo až s porovnaním obsahu, nie len názvu — jaccard podobnosť (prekryv slov) medzi telami všetkých záznamov v rámci toho istého dňa, naprieč celým súborom dát, nie len v tej časti, kde sa duplicity nahlásili. Tento prístup našiel výrazne viac skutočných duplicít, no priniesol aj dôležitú pascu, ktorej sa bolo treba vyhnúť: pri jednej obzvlášť hustej sage (viacero pokusov o opravu tej istej chyby za sebou) mali susedné, ale skutočne odlišné záznamy ("test úspešný" vs. "test mylne označený za vyriešený — oprava") natoľko podobný text, že hrubší prístup by ich nesprávne spojil ako duplicitu. Až kontrola pomocou dostatočne vysokého prahu podobnosti (a chápania obsahu, nie len povrchnej zhody) dokázala spoľahlivo odlíšiť "toto je to isté zapísané dvakrát" od "toto je tvrdenie a jeho neskoršia oprava".
Celkovo sa takto postupne našlo a vyčistilo 46 duplicitných záznamov naprieč viacerými kolami — presný názov, fuzzy názov, obsahová podobnosť, a napokon jeden úplne identický pár, ktorý bol artefaktom pôvodného spôsobu parsovania starých súborov.
Čo z toho zostáva ako poučenie
- Pravidlo bez mechanickej väzby na prácu sa v dlhšej relácii vytráca — bez ohľadu na to, aké je rozumné. Dual-write nie je výnimka, má presne tú istú slabinu ako pôvodné manuálne pravidlo.
- Jeden zdroj pravdy je jednoduchšie overiť než dva synchronizované zdroje. Namiesto "dúfajme, že oba súhlasia" stačí jedna otázka: zapísalo sa to tam, kde sa má?
- Rozdelenie dát na viacero miest skrýva chyby. Case-duplicita, schema poradie aj duplicitné záznamy boli prítomné už predtým — vyplavili sa na povrch až vtedy, keď dáta museli žiť na jednom mieste a byť interne konzistentné.
- Pri hľadaní duplicít nestačí porovnávať povrch (názov). Skutočný obsah je spoľahlivejší signál, ale treba nastaviť dostatočne opatrný prah, aby sa "to isté dvakrát" nezamieňalo s "podobné, ale zámerne odlišné".
Appka, ktorá vznikla len na to, aby som si nemusel pamätať kontext naprieč desiatkami rozbehnutých projektov, sa touto fázou stala aj malým cvičením v dátovej integrite. Tým to ale neskončilo — po mesiacoch bežnej dennej prevádzky sa ukázalo, že rozsah toho, čo appka drží, siaha ďalej než len ručne písané záznamy.
Od súkromného nástroja k niečomu, čo môže existovať aj mimo mňa
Appka bola od začiatku stavaná výlučne pre vlastnú potrebu, žiadny plán na zdieľanie. Po dostatočne dlhom používaní — keď sa denník, build-notes a checklist stali skutočným, spoľahlivým zdrojom pravdy, nie len experimentom — dávalo zmysel položiť si inú otázku: má zmysel to držať len pre seba, alebo je to už dosť poriadny nástroj na to, aby existoval aj verejne, ako ukážka toho, ako sa dá takýto problém riešiť.
Rozhodnutie zverejniť appku (Apache-2.0, verejný repozitár) prinieslo vlastný, menší kus roboty — nie kozmetický, ale reálny. README predtým opisovalo appku len sebe samému, s odkazmi na vlastnú konkrétnu prevádzku; muselo sa prepísať tak, aby dávalo zmysel niekomu, kto appku nikdy nevidel — čo appka robí, prečo, a ako by si ju vedel nasadiť niekto iný so svojím vlastným AI nástrojom, nie len s tým, čo používam ja. Súčasťou predpublikačného auditu bolo aj prehľadanie repozitára na osobné cesty, staré zabudnuté súbory a čokoľvek, čo by pri zverejnení nemalo uniknúť von — vrátane dvoch nepoužívaných záložných súborov (.bak), ktoré tam ostali z dávnejšej úpravy a nemali dôvod existovať.
Väčší, funkčný kus tejto fázy bolo pridanie druhej vrstvy oprávnení. Appka mala doteraz jedno heslo na všetko — prihlásenie aj mazanie. To je v poriadku pre nástroj, ktorý používa jeden človek na jednom stroji, ale akonáhle je appka niečo, čo by mohol nasadiť niekto iný (alebo čo by mohlo bežať dlhodobo bez dozoru), nezmyselné mazanie záznamu jedným zbytočným klikom si zaslúži vyššiu prekážku než bežné prihlásenie. Riešenie: samostatné, voliteľné administrátorské heslo, ktoré appka vyžaduje navyše len pri deštruktívnych operáciách (mazanie záznamu, projektu, celej terminálovej session) — nie pri bežnej práci. Nie je to skutočná bezpečnostná hranica (kto má prístup k stroju alebo priamo k databáze, appku aj tak obíde), ale je to zámerná, druhá prekážka proti nešťastnej náhode, nie proti útočníkovi.
Čo appka odvtedy pribrala
Popri publikovaní pribudlo aj niekoľko funkčných vrstiev, ktoré appku posunuli od "denníka, do ktorého niečo píšem" k niečomu, čo si samo všíma väčšinu toho, čo sa v skutočnosti deje:
- Pasívne logovanie git commitov. Namiesto ručného zápisu "urobil som X" appka teraz sama, na pozadí, prehľadáva lokálne repozitáre a commity bulk-importuje ako záznamy — cez ten istý import endpoint, čo appka mala už predtým pre iný účel, len s dedupom podľa hashu commitu. Čo sa reálne zmenilo v kóde, je odteraz vidno bez toho, aby si to niekto musel pamätať zapísať.
- Cross-source timeline pre projekt. Ručné záznamy, automaticky zalogované commity aj (nižšie opísaná) terminálová história sa dajú teraz pre jeden konkrétny projekt prehľadávať ako jeden zlúčený, chronologický prúd. Scenár, pre ktorý to vzniklo: vrátiš sa k projektu po roku, v niektorom starom module sa objaví bug, zadáš názov toho modulu a appka ukáže úplne všetko, čo sa ho kedy dotklo — nezávisle od toho, či to bol ručný zápis, commit, alebo diagnostika v termináli.
- AI handoff briefing. Appka vie na požiadanie z poznámok projektu, jeho timeline a otvoreného checklistu vygenerovať štruktúrovaný odovzdávací dokument — aktuálny stav, čo sa skúšalo (vrátane slepých uličiek, nie len toho, čo vyšlo), čo zostáva otvorené, a rozhodnutia, ktoré netreba omylom zrušiť. Presne pre scenár, keď treba niekomu odovzdať rozbehnutý projekt narýchlo — alebo si ho po dlhšej pauze sám poriadne pripomenúť.
Terminálová história — najväčšie rozšírenie
Väčšina reálnej diagnostickej práce sa neodohráva v appke, ale v termináli — ladenie, sledovanie logov, opravovanie vecí naživo cez SSH. Dovtedy sa to dalo spätne nájsť len cez pamäť "kedy som to asi robil", nikdy cez skutočný obsah toho, čo sa v teréne dialo. Appka teraz vie zachytávať celé tmux session — nie len scrollback, celý výstup — a robiť ich neskôr kontextovo prehľadateľné.
Táto časť mala od začiatku dve tvrdé podmienky, nie odporúčania: súkromné veci sa nesmú dostať do appky v čitateľnej podobe, a vyhľadávanie vo veľkej session nesmie byť trápenie.
Zachytávanie funguje cez tmux hooky — hneď ako vznikne nový panel, appka naň napojí pipe-pane a jeho výstup priebežne ukladá do vlastného raw logu, nezávisle od appky samotnej. Spracovanie do appky ale nerobí hook na "zatvorenie session" — to je krehký moment (proces môže spadnúť, SSH môže vypadnúť skôr, než hook stihne doexistovať) — ale periodický časovač, ktorý si sám nájde logy patriace panelom, čo už neexistujú, a spracuje ich. Odolnosť pred okamžitosťou.
Kľúčové rozhodnutie je v tom, čo sa deje pred zápisom do appky, nie po ňom. Redakcia citlivých údajov (heslá, privátne kľúče, bearer tokeny, bežné prefixy API kľúčov, riadky s VPN kľúčmi) beží ako prvý krok, skôr než čokoľvek skončí v appke — nikdy naopak. A appka sa zámerne netvári, že vlastnému regexu na sto percent verí: čokoľvek redakcia zasiahne, sa v uloženom texte nahradí, ale celý kus okolo (nie len zasiahnutý riadok) sa zároveň označí ako karanténny a je fyzicky vylúčený z vyhľadávacieho indexu na úrovni databázy — nie len odfiltrovaný pri dopyte — kým ho človek ručne neschváli. Rozdiel medzi "automaticky vyriešené" a "automaticky, ale nikto to neoveril" je práve tento krok.
Delenie na menšie, prehľadateľné kusy (chunky) beží podľa pauzy v čase, nie podľa pevného počtu riadkov — dlhší príkaz aj s celým jeho výstupom tak ostáva pokope ako jedna logická jednotka namiesto toho, aby sa roztrhal naprieč viacerými kusmi.
Prvý reálny test tejto časti hneď odhalil skutočnú hranicu návrhu: veľmi krátko žijúci panel (príkaz, čo dobehne pod sekundu) sa vie zavrieť skôr, než sa naň hook stihne pripojiť — v takom prípade sa nezachytí nič. Pre bežnú prácu to nevadí (interaktívna session žije dosť dlho, aby sa hook stihol napojiť), ale je to presne ten typ hraničného prípadu, čo sa neodhalí návrhom na papieri, len tým, že sa appka naozaj použije.
Nový záver
Appka, ktorá vznikla len na to, aby som si nemusel pamätať kontext naprieč desiatkami rozbehnutých projektov, si dnes pamätá aj to, čo sa reálne stalo v termináli a v gite — nie len to, čo si do nej niekto ručne napísal. Dôveryhodnosť dát, o ktorú išlo v prvej fáze, teraz platí aj pre vrstvu, ktorá predtým jednoducho miznula bez stopy — s tým rozdielom, že táto vrstva má zabudovanú vlastnú, tvrdú poistku proti tomu, aby sa v nej ocitlo niečo, čo tam nepatrí.