Compare commits
10 Commits
82c17422a3
...
a3f007bea3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3f007bea3 | ||
|
|
af0ffc522a | ||
|
|
ecd390ff03 | ||
|
|
33e14376b8 | ||
|
|
c26d927580 | ||
|
|
060315441f | ||
|
|
2e69c6fbe5 | ||
|
|
fe2d417c5f | ||
|
|
e58a50a97a | ||
|
|
b978f8994b |
@@ -46,6 +46,10 @@ Never use `/tmp/`, hardcoded absolute paths, or in-memory databases for persiste
|
|||||||
|
|
||||||
Explicit user facts are stored in `keep.md`. Read at every turn.
|
Explicit user facts are stored in `keep.md`. Read at every turn.
|
||||||
|
|
||||||
|
## Projects (deep details)
|
||||||
|
|
||||||
|
More details about the user, projects, hardware etc. live in `projects/<name>/` (memory.md, state.md) — search those too.
|
||||||
|
|
||||||
## Knowledge base
|
## Knowledge base
|
||||||
|
|
||||||
`knowledge/` holds verified facts and measured values you can draw on when answering (e.g. notes on the models available to you). See `knowledge/README.md` for what's there; read on demand.
|
`knowledge/` holds verified facts and measured values you can draw on when answering (e.g. notes on the models available to you). See `knowledge/README.md` for what's there; read on demand.
|
||||||
|
|||||||
35
cook/caj/karak.md
Normal file
35
cook/caj/karak.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
type: caj
|
||||||
|
category: tea-milk
|
||||||
|
added: 2026-09-08
|
||||||
|
origin: blizky-vychod
|
||||||
|
tags: [caj,kozicka]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Karak
|
||||||
|
|
||||||
|
Porce: 2 hrnky
|
||||||
|
|
||||||
|
## Ingredience
|
||||||
|
|
||||||
|
- 2 plné hrnky vody (~500 ml)
|
||||||
|
- zázvor nakrájený najemno — dávka upřesnit
|
||||||
|
- kardamom, rozdrcený — dávka upřesnit
|
||||||
|
- několik lžic černého čaje (správný druh — upřesnit)
|
||||||
|
- mléko + mléko do kávy (kávová smetana), dohromady ~půl hrnku
|
||||||
|
- několik lžic třtinového cukru — dávka upřesnit
|
||||||
|
|
||||||
|
## Postup
|
||||||
|
|
||||||
|
1. Vodu přelij do rendlíku, přiveď k varu.
|
||||||
|
2. Vhod nakrájený zázvor a rozdrcený kardamom, vař nějakou dobu.
|
||||||
|
3. Přidej čaj, znovu vař.
|
||||||
|
4. Mezitím si v hrnku smíchej mléko a mléko do kávy (~půl hrnku směsi).
|
||||||
|
5. Směs přilej do rendlíku, přidej třtinový cukr, důkladně rozmíchej.
|
||||||
|
6. Přiveď k varu — těsně před přetečením rendlíku ztlum.
|
||||||
|
7. Opakuj var celkem třikrát (přetékání = známka správné síly/konzistence).
|
||||||
|
8. Hotovo — přeceď přes sítko zpět do hrnku.
|
||||||
|
|
||||||
|
## Poznámky
|
||||||
|
|
||||||
|
- Neupřesněné dávky (zázvor, kardamom, čaj, cukr) dohledat/vyzkoušet.
|
||||||
17
cook/caj/teh-tarik.md
Normal file
17
cook/caj/teh-tarik.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
type: caj
|
||||||
|
category: tea-milk
|
||||||
|
added: 2026-09-08
|
||||||
|
origin: malajsie
|
||||||
|
tags: [caj]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Teh Tarik
|
||||||
|
|
||||||
|
Porce pro dvě osoby:
|
||||||
|
|
||||||
|
- 700 až 800 ml vody
|
||||||
|
- dvě polévkové lžíce čaje (zarovnáno nebo mírný kopeček)
|
||||||
|
- louhovat 6 minut
|
||||||
|
- 2 polévkové lžíce salka
|
||||||
|
- 2 polévkové lžíce kondenzovaného mléka nebo dvě mini mlíčka do kávy
|
||||||
1
keep.md
1
keep.md
@@ -1 +1,2 @@
|
|||||||
- Chce mít filmy uložené lokálně na disku — streaming služby mění podmínky a dostupnost, chce mít obsah trvale k dispozici po zaplacení
|
- Chce mít filmy uložené lokálně na disku — streaming služby mění podmínky a dostupnost, chce mít obsah trvale k dispozici po zaplacení
|
||||||
|
- Má předplatné: Netflix, HBO Max, SkyShowtime, Disney+
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
captured_at: 2026-09-04T21:13:39+02:00
|
||||||
|
---
|
||||||
|
|
||||||
|
Chci si stáhnout film Wrath of Man (2021, Guy Ritchie, Jason Statham) — ostraha obrněného vozu, pomsta za zavražděného syna.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
captured_at: 2026-09-08T08:54:08+02:00
|
||||||
|
channel: webui
|
||||||
|
---
|
||||||
|
|
||||||
|
Článek 'Don't Chat With Your AI. Mob With It.' (David Rodenas, Medium): instrukce pro AI agenta psát jako tagy přímo do souborů místo chatu ([[CLAUDE: ...]] instrukce, [[DAVID: ...]] odpověď agenta). Autor tvrdí, že tím řeší latenci agent loopu přes paralelizaci (mob programming analogie). Diagnóza s Claude: latenční argument je špatný (nejde o GPU sloty, ale round-trip a sériovost kroků; je to fronta, ne zrychlení), mob analogie obrácená, a mazání tagů po provedení ničí kontrolní bod. Použitelné jádro: anotace patří tam, kde je předmět anotace — pozice v souboru je kontext (jako TODO komentář), přežije session, vidět v diffu. S tím, že se agent nechá běžet ve smyčce autonomně a tag se nemaže, jen připíše odpověď, a max 1-2 tagy na kolo. Detailní rozbor: projects/ai/artifacts/file-based-agent-communication.md
|
||||||
@@ -52,6 +52,7 @@
|
|||||||
- Komunikace v češtině
|
- Komunikace v češtině
|
||||||
- Informace hledej nejenom ve zdrojovém kódu, ale i v dokumentaci online
|
- Informace hledej nejenom ve zdrojovém kódu, ale i v dokumentaci online
|
||||||
- Pokud si nejsi jistý, ptej se
|
- Pokud si nejsi jistý, ptej se
|
||||||
|
- Článek „Don't Chat With Your AI. Mob With It." (David Rodenas, Medium): instrukce pro AI agenta psát jako tagy přímo do souborů místo chatu (`[[CLAUDE: …]]` instrukce, `[[DAVID: …]]` odpověď agenta), pravidlo v CLAUDE.md. Článek argumentuje špatně (latence nejsou GPU sloty, ale round-trip a sériovost kroků; „zrychlení" je ve skutečnosti fronta), ale jádro drží: anotace patří tam, kde je předmět anotace — pozice v souboru je kontext (jako TODO komentář), přežije session, je vidět v diffu. Korekce oproti článku: agent tag nemaže, jen připíše odpověď (mazání jen člověk před commitem), agent neběží autonomně ve smyčce (explicitní „projdi tagy"), max 1–2 tagy na kolo. Detailní rozbor: projects/ai/artifacts/file-based-agent-communication.md
|
||||||
|
|
||||||
## Work
|
## Work
|
||||||
|
|
||||||
@@ -76,6 +77,7 @@
|
|||||||
## Filmy ke stažení / potřebuju vlastnit
|
## Filmy ke stažení / potřebuju vlastnit
|
||||||
|
|
||||||
- *The Beekeeper* (2024) — akční thriller, Jason Statham v hlavní roli; vítězství pomsty bývalého agenta tajné organizace "Beekeepers"
|
- *The Beekeeper* (2024) — akční thriller, Jason Statham v hlavní roli; vítězství pomsty bývalého agenta tajné organizace "Beekeepers"
|
||||||
|
- *Wrath of Man* (2021) — Guy Ritchie, Jason Statham jako ostraha obrněného vozu na převážení peněz; ve skutečnosti sleduje bandu, která při přepadení zabila jeho syna, a mstí se
|
||||||
|
|
||||||
## Viděl jsem
|
## Viděl jsem
|
||||||
|
|
||||||
|
|||||||
133
plans/2026-09-02_reflect-review-findings.md
Normal file
133
plans/2026-09-02_reflect-review-findings.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# Reflect skill — review 2026-09-02
|
||||||
|
|
||||||
|
Stav: nálezy review, opravy navrženy, čekají na schválení implementace
|
||||||
|
Datum: 2026-09-02
|
||||||
|
|
||||||
|
Review prošlo celý skill: SKILL.md, README.md, `reflect_apply.py`, `reflect_auto.py`,
|
||||||
|
`reflect_distill.py` a všech 168 testů (prošly, 1.1 s). Celkový verdikt: nadprůměrně
|
||||||
|
dobře napsaný — „záruky v kódu, ne v promptu" je proveden důsledně, testy kódují
|
||||||
|
racionalu u každého assertu. Níže jsou problémy v pořadí závažnosti + návrh opravy
|
||||||
|
u každého.
|
||||||
|
|
||||||
|
## 1. Fold přeloženého open nálezu zahodí drafted patch a skip count (bug)
|
||||||
|
|
||||||
|
`merge_findings` (reflect_auto.py ~522) dědí `regression_of` a `history`, ale ne
|
||||||
|
`patch`, `patch_drafted_at` ani `skipped`. Jakmile noční běh znovu spatří stejný
|
||||||
|
`open` pattern, `supersede` starý záznam zahodí a nový vzniká bez těchto polí.
|
||||||
|
|
||||||
|
Následky:
|
||||||
|
|
||||||
|
- patch složený při review přes `--set-patch` (agent ho pracně ověřil proti
|
||||||
|
souboru) zmizí; SKILL.md krok 2 tvrdí „`patch_drafted_at` says an earlier
|
||||||
|
review drafted it" — po jedné noci to neplatí
|
||||||
|
- „deferred 2× already" z kroku 3 se vynuluje — nález se předkládá donebezedne
|
||||||
|
bez viditelné historie odkladů, i když ho uživatel už několikrát odložil
|
||||||
|
- audit log (`DRAFTED`, `SKIPPED`) ukazuje práci, na kterou store už neodkazuje
|
||||||
|
|
||||||
|
**Fix:** v `merge_findings` při foldu open/watch předchozího záznamu přenést:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if previous and previous["status"] == STATUS_OPEN:
|
||||||
|
if previous.get("patch") and not item.get("patch"):
|
||||||
|
# drafted during a review, verified against the file — do not throw it away
|
||||||
|
patch = previous["patch"] # do Finding(...)
|
||||||
|
patch_drafted_at = previous.get("patch_drafted_at")
|
||||||
|
skipped = previous.get("skipped") # deferral history survives the fold
|
||||||
|
```
|
||||||
|
|
||||||
|
Pole `patch_drafted_at` a `skipped` je potřeba přidat do `Finding` dataclass a
|
||||||
|
`to_json()` (podmíněně jako ostatní volitelná pole). Nový patch z analýzy má
|
||||||
|
přednost před starým draftem; jinak se drží draft z review. Testy: fold open
|
||||||
|
nálezu s `skipped={count:2}` a drafted patchem → nový záznam obojí nese;
|
||||||
|
nový patch z modelu draft nepřepisuje, ale nahrazuje.
|
||||||
|
|
||||||
|
## 2. `reflect_apply.py` nechrání vlastní store jako cíl patche (designová mezera)
|
||||||
|
|
||||||
|
`_resolve_target` odmítne cestu mimo workspace, ale klidně aplikuje patch na
|
||||||
|
`reflect/findings.jsonl`, `reflect/state.json` nebo `log/reflect.log`. Celá
|
||||||
|
filozofie skillu je „do audit trail píše jen reflect_apply" — ale reflect_apply
|
||||||
|
sám může patchem přepsat audit trail (typicky změnit `rejected` záznam zpět na
|
||||||
|
`open`, což oživí zamítnutý vzor). Stane se to „se schválením uživatele", které
|
||||||
|
v diffu snadno přehlédne, že jde o store.
|
||||||
|
|
||||||
|
**Fix:** explicitní blocklist v `check_patch` / `_resolve_target`
|
||||||
|
(reflect_apply.py):
|
||||||
|
|
||||||
|
```python
|
||||||
|
PROTECTED = ("reflect/", "log/reflect.log")
|
||||||
|
|
||||||
|
def _resolve_target(workspace, relative):
|
||||||
|
target = (workspace / relative).resolve()
|
||||||
|
if not target.is_relative_to(workspace.resolve()):
|
||||||
|
raise ApplyError(f"{relative} resolves outside the workspace")
|
||||||
|
if target == (workspace / FINDINGS_REL).resolve() or \
|
||||||
|
any(target.is_relative_to(workspace / prefix) for prefix in PROTECTED):
|
||||||
|
raise ApplyError(f"{relative} is part of the reflect store — not a patch target")
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Důvod zdůvodnit v chybové hlášce („the audit trail is never a patch target").
|
||||||
|
Pozn.: `set_patch` tím kryje i draft, nejen apply. Test: patch s
|
||||||
|
`file: reflect/findings.jsonl` → exit 2, store netčen.
|
||||||
|
|
||||||
|
## 3. Vakuózní assert v testu (test_reflect_auto.py)
|
||||||
|
|
||||||
|
`test_first_run_has_no_trend_to_show` tvrdí `"(minule" not in report`, ale
|
||||||
|
`_rate_line` generuje anglické „(previous run …)". Assert nikdy nemůže selhat,
|
||||||
|
test tedy nic nehlídá.
|
||||||
|
|
||||||
|
**Fix:** `assert "(previous" not in report`. Jednořádková změna.
|
||||||
|
|
||||||
|
## 4. Noise prefix `"cli"` chytá i `client*` (případné falešné vyřazení)
|
||||||
|
|
||||||
|
`key.startswith(NOISE_PREFIXES)` — session `client_xyz` (nebo cokoli začínající
|
||||||
|
„cli") se tiše vyřadí z analýzy. Prefix-match na krátkých prefixech je
|
||||||
|
přístřelen. Podobně base64 session jména obsahující `-`/`_` (jsou v urlsafe
|
||||||
|
abecedě) se nedekódují kvůli heuristice `"_" in stem or "-" in stem` —
|
||||||
|
legitimní stará session se pak chytne prefixem, nebo naopak neprojde.
|
||||||
|
|
||||||
|
**Fix:** noise match na hranici klíče: session klíče mají tvar
|
||||||
|
`<prefix>_<rest>` resp. base64 bez `_`, takže matchovat
|
||||||
|
`key == prefix or key.startswith(prefix + "_")`. Případně (jednodušeji)
|
||||||
|
přejmenovat prefix na `cli_` v NOISE_PREFIXES, protože reálné machinery session
|
||||||
|
jsou `cli_<…>`. Test: `client_abc` prochází, `cli_kimi-ollama-test` ne.
|
||||||
|
|
||||||
|
## Menší
|
||||||
|
|
||||||
|
### 5. Git fingerprint se nekontroluje při LLM error cestě
|
||||||
|
|
||||||
|
`_resolve_findings`: fingerprint se porovná jen po úspěšném tahu. Když tah
|
||||||
|
skončí LLM errorem a agent v tu chvíli něco zapsal, guard se neprojeví a
|
||||||
|
run pokračuje. Riziko je teoretické (error odpověď znamená, že k zápisu
|
||||||
|
s nejvyšší pravděpodobností nedošlo), ale guard je zadarmadlo dokončit.
|
||||||
|
|
||||||
|
**Fix:** porovnat fingerprint i na začátku error větve
|
||||||
|
(`if result.stop_reason == "error" or result.error:`) — stejná kontrola,
|
||||||
|
stejná hláška.
|
||||||
|
|
||||||
|
### 6. `MIN_MESSAGES=5` počítá i tool výsledky
|
||||||
|
|
||||||
|
`distill_session` inkrementuje `message_count` pro user, assistant i tool
|
||||||
|
role. Session s 1 user zprávou a 2 tool cally (celkem 5 záznamů) projde
|
||||||
|
prahem, přestože je to jeden dotaz — nižší signál, ne rovnou chyba.
|
||||||
|
|
||||||
|
**Fix (volitelné):** počítat jen user + assistant zprávy
|
||||||
|
(`message_count += 1` jen v těch dvou větvích). Tool results počítat jako
|
||||||
|
součást tahu, ne jako zprávu. Existující testy `message_count == 6` adaptovat.
|
||||||
|
|
||||||
|
### 7. Neatomičnost mezi `git commit` patche a `_save(findings)`
|
||||||
|
|
||||||
|
Crash mezi commitem patche a zápisem store zanechá soubor patched, ale store
|
||||||
|
`open` (bez audit linky). Re-apply se správně odmítne na chybějícím `old_text`,
|
||||||
|
ale audit stopa pro ten commit chybí. U single-user workspace akceptovatelné;
|
||||||
|
zmiňuju pro úplnost. Plná atomicita (např. save-first-then-commit s rollbackem
|
||||||
|
store) nedoporučuju — přidá složitost pro okrajový scénář. Spíš uvážit
|
||||||
|
pořadí: nejdřív `_save` + audit, pak commit; pak crash zanechá `applied`
|
||||||
|
záznam bez commitu, což reflektuje hlášku `git revert` — ale zase commit
|
||||||
|
refusu zanechá store applied bez commitu. Trade-off, nechává rozhodnutí
|
||||||
|
na implementaci.
|
||||||
|
|
||||||
|
## Pořadí implementace
|
||||||
|
|
||||||
|
1 → 2 → 3 → 5 → 4 → (6, 7 volitelné). Položky 1–3 jsou přímé rozpory se
|
||||||
|
zárukami deklarovanými v README („Záruky"), 5 a 4 jsou levné tvrdnutí guardů.
|
||||||
70
plans/cook-skill-design.md
Normal file
70
plans/cook-skill-design.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# Cook skill — final design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
User wants a personal store for recipes and tea notes (origins, brewing
|
||||||
|
parameters, tasting). Wiki/note rejected as too heavy. Agreed design:
|
||||||
|
`cook/` data dir + thin `skills/cook/` skill manifest + a small safety
|
||||||
|
script. This plan finalizes the design agreed question-by-question in chat
|
||||||
|
(2026-09-08) and supersedes the quick-draft SKILL.md already written.
|
||||||
|
|
||||||
|
## Decisions (locked with user)
|
||||||
|
|
||||||
|
1. **Script, not raw file tools** — `cook.py` guards against accidental
|
||||||
|
overwrite/delete; frontmatter always machine-generated (no drift).
|
||||||
|
Keep it **minimal — shortest working code, no speculative features**.
|
||||||
|
2. **Frontmatter** (free-form values, only `type` is fixed):
|
||||||
|
`type: recept|caj`, `category`, `cuisine` (recepts only),
|
||||||
|
`origin` (caj only), `tags` (user hashtags, no `#`), `added` (auto date).
|
||||||
|
Can be lightened later.
|
||||||
|
3. **Hashtags live in frontmatter `tags`**, not in body.
|
||||||
|
4. **Search via script**: frontmatter filtering + fulltext body grep.
|
||||||
|
Start simple; enrich later.
|
||||||
|
5. **Slug collision**: `add` on existing slug → error; agent shows the
|
||||||
|
existing item, user decides edit vs. new slug. No auto `-2` suffixes.
|
||||||
|
6. **Assets**: `cook/assets/<slug>/` when documents/photos ever arrive;
|
||||||
|
not created upfront.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Rewrite `skills/cook/SKILL.md` to the final design:
|
||||||
|
- layout (`cook/recepty/`, `cook/caj/`, `cook/assets/` — created on demand)
|
||||||
|
- all file mutations via `cook.py` subcommands; agent never `write_file`s
|
||||||
|
into `cook/` directly except body edits via `edit_file`/`apply_patch`
|
||||||
|
after `add` creates the skeleton
|
||||||
|
- capture inline (same turn), commit after each change
|
||||||
|
(`git add cook/ && git commit -m "cook: ..."`; never `git add -A`)
|
||||||
|
- search/answer flow: `list`/`search` to narrow, then read files, answer
|
||||||
|
from files only, no confabulation
|
||||||
|
- edit/delete: two-turn confirm flow (show exact text → user confirms →
|
||||||
|
apply → commit)
|
||||||
|
- slug conventions: kebab-case; collision handling per decision 5
|
||||||
|
2. Write `skills/cook/scripts/cook.py` — minimal (~150 lines), stdlib only:
|
||||||
|
- `add <slug> --type recept|caj [--category C] [--cuisine C] [--origin O]
|
||||||
|
[--tags a,b] [--body-file F]` → creates file with frontmatter;
|
||||||
|
**exit 1 if slug exists**; reads body from stdin or `--body-file`
|
||||||
|
- `edit <slug>` → prints file path; **exit 1 if missing** (no silent
|
||||||
|
create); actual text edits done by agent with `edit_file` on the path
|
||||||
|
- `list [--type T] [--category C] [--tag X]` → one line per item:
|
||||||
|
`slug type category added title`
|
||||||
|
- `show <slug>` → full file content
|
||||||
|
- `search <text>` → fulltext grep across bodies, prints matching lines
|
||||||
|
with slug context
|
||||||
|
- `rename <old> <new>` → moves file (+ its assets dir if present);
|
||||||
|
exit 1 if target exists
|
||||||
|
- `delete <slug>` → removes file (and empty assets dir); only run
|
||||||
|
after user confirmation per SKILL.md flow
|
||||||
|
- `validate` → checks every file has parseable frontmatter and `type`
|
||||||
|
- Frontmatter: minimal hand-rolled parse (no external deps).
|
||||||
|
3. Test drive: `add` a sample recipe + sample tea, verify frontmatter,
|
||||||
|
`list`, `search`, collision error, `validate`, then delete samples and
|
||||||
|
commit the real state.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `cook.py add` twice on same slug → second exits 1
|
||||||
|
- `edit`/`show`/`rename`/`delete` on missing slug → exit 1
|
||||||
|
- `list --tag chata` filters on frontmatter tags
|
||||||
|
- `search <word>` finds body text
|
||||||
|
- `validate` passes on script-created files, fails on a hand-mangled one
|
||||||
|
- SKILL.md contains no leftover pipeline cruft (inbox/compile/lock)
|
||||||
43
projects/ai/artifacts/agent-to-human-tools.md
Normal file
43
projects/ai/artifacts/agent-to-human-tools.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Převod agent-nástrojů na nástroje pro člověka
|
||||||
|
|
||||||
|
Ověřený postup. Když AI agent napsal nástroj (runbook, skripty) původně
|
||||||
|
**pro sebe**, nejde ho přímočaře zjednodušit pro lidské použití.
|
||||||
|
|
||||||
|
## Proč opačný postup nefunguje
|
||||||
|
|
||||||
|
- Model nevidí hranici, kde končí pro člověka použitelná složitost —
|
||||||
|
„zjednoduš to" produkuje kosmetické úpravy, ne redesign.
|
||||||
|
- Existující podoba psaná pro agenta (hromada parametrů, dlouhé runbooky)
|
||||||
|
model **ukotvuje** — upravuje, co vidí, místo aby navrhl jiné rozhraní.
|
||||||
|
- Plan mode u designu rozhraní nestačí: plánuje implementaci, ne podobu
|
||||||
|
rozhraní. Tu musí určit člověk.
|
||||||
|
|
||||||
|
## Princip
|
||||||
|
|
||||||
|
**Rozhraní navrhuje člověk, model implementuje.** LLM je nejsilnější v
|
||||||
|
dokódování těla funkce se zadaným podpisem — přebírání designu je jeho slabá
|
||||||
|
stránka.
|
||||||
|
|
||||||
|
## Postup
|
||||||
|
|
||||||
|
1. **Existující agent-runbook ani skripty nepoužívat jako základ** — jen jako
|
||||||
|
zdroj doménových faktů (co nástroj umí, jaké scénáře existují, jak se čeká
|
||||||
|
na konec testu). Strukturu ne. Začít v čistém adresáři.
|
||||||
|
2. **Tabulka scénář → příkaz** — člověk ručně napíše, co kdy spouští.
|
||||||
|
To je celý human-runbook; žádný postup kroků.
|
||||||
|
3. **Kostry skriptů píše člověk** — param block, komentáře po sekcích,
|
||||||
|
protokol čekání na dokončení (např. čekat na exit procesu, žádný polling).
|
||||||
|
Složitost je tím rozhodnutá předem.
|
||||||
|
4. **Model jen dokódovává těla** — zadání: „dokóduj těla, parametry
|
||||||
|
a strukturu neměň, nic nepřidávej.“ Review pak kontroluje jen implementaci,
|
||||||
|
ne design.
|
||||||
|
|
||||||
|
## Pravidla pro zadání
|
||||||
|
|
||||||
|
- Složitost zadávat **čísly, ne adjektivy**: max počet parametrů na skript,
|
||||||
|
max počet kroků, jeden skript = jeden scénář. Model na numerické limity
|
||||||
|
reaguje spolehlivě; na „simple" ne.
|
||||||
|
- Negativní omezení („žádné extra parametry") prosadit **v kódu kostry,
|
||||||
|
ne v promptu** — když param block má 3 parametry, model 15 nedoplní.
|
||||||
|
- Agent-runbook pro sebe si nechat vedle — převod „agent-runbook →
|
||||||
|
human-runbook" je cesta, kde se to pořád láme. Nejsou to stejný dokument.
|
||||||
54
projects/ai/artifacts/file-based-agent-communication.md
Normal file
54
projects/ai/artifacts/file-based-agent-communication.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Anotace v souborech místo chatu („mob with AI")
|
||||||
|
|
||||||
|
Zdroj: článek „Don't Chat With Your AI. Mob With It." (David Rodenas, Medium,
|
||||||
|
2026). Článek sám je slabý — argument o latenci (GPU sloty, uvolňování/
|
||||||
|
načítání inference) je nedoložený a nesedí; reálná latence je round-trip
|
||||||
|
klient↔server, sériovost kroků a opakovaný prefill. Analogie s mob
|
||||||
|
programmingem taky ne — v mobu píše jeden driver a víc lidí řídí, tady je to
|
||||||
|
obráceně. Jádro je ale použitelné.
|
||||||
|
|
||||||
|
## Co drží (jádro oddělené od balastu)
|
||||||
|
|
||||||
|
- **Instrukce patří tam, kde je předmět instrukce.** Tag na konkrétním řádku
|
||||||
|
dá kontext pozicí — agent nepotřebuje popis „v tom souboru, u té funkce".
|
||||||
|
Stejný princip jako TODO komentář / poznámka v code review.
|
||||||
|
- **Trvalost:** anotace nesedí v chatu, který se zavře — je v souboru, vidět
|
||||||
|
v diffu, přežije session.
|
||||||
|
- Není to o latenci ani paralelizaci. „Zrychlení" je ve skutečnosti **fronta**:
|
||||||
|
zadám a nejdu čekat. Round-trip se nezkrátil, jen u něj nestojím.
|
||||||
|
- Článek si protiřečí vlastním východiskem (DORA/TDD = rychlý feedback):
|
||||||
|
hromadné zadání a společný review na konci **prodlouží feedback loop pro
|
||||||
|
člověka**, ne pro agenta. „Hotovo dřív, než si všimnu" je z tohoto pohledu
|
||||||
|
špatná zpráva, ne dobrá.
|
||||||
|
|
||||||
|
## Mechanismus
|
||||||
|
|
||||||
|
Komunikace přes tagy přímo v souborech (autor: `[[CLAUDE: …]]` instrukce,
|
||||||
|
`[[DAVID: …]]` dotaz agenta). Pravidlo v CLAUDE.md: agent tagy hledá, provádí,
|
||||||
|
odpovídá. Autor tag po provedení maže — to je chyba.
|
||||||
|
|
||||||
|
## Úpravy oproti článku
|
||||||
|
|
||||||
|
- **Tag nemaže, jen připisuje odpověď pod něj** — vlákno zůstane v souboru
|
||||||
|
a v diffu; mazání je jen člověk, před commitem (grep na prefix).
|
||||||
|
- **Nespouštět agenta ve smyčce nad soubory** (autonomní reakce = ztráta
|
||||||
|
kontrolního bodu). Místo toho explicitní příkaz „projdi tagy a vyjádři se".
|
||||||
|
- **Max 1–2 tagy na kolo.** Deset tagů = deset změn ke společnému review =
|
||||||
|
nejasné, co odkud přišlo.
|
||||||
|
|
||||||
|
## Varianty kam dát vlákno
|
||||||
|
|
||||||
|
1. **Anotace jako komentáře v kódu** — nulová infrastruktura, funguje všude,
|
||||||
|
diff ukáže celou historii výměny. Nevýhoda: zaneřádí kód, hlídání ručně.
|
||||||
|
Dobré na krátké výměny u konkrétního místa.
|
||||||
|
2. **Vedlejší soubor** (`file.notes.md` vedle zdrojáku, odkaz na symbol,
|
||||||
|
ne na číslo řádku) — kód čistý, vlákna libovolně dlouhá, .gitignore.
|
||||||
|
Dobré na dlouho se vlečoucí věci.
|
||||||
|
|
||||||
|
## Otevřené otázky
|
||||||
|
|
||||||
|
- Korekce agentových odpovědí („odpověď chci přepsat") — chat to řeší tím,
|
||||||
|
že je mimo kód; anotace v souboru to nemají kde. Varianta 1 to řeší
|
||||||
|
nejlíp (přepis = nová verze vlákna na místě), ale je potřeba vyzkoušet.
|
||||||
|
- Vazba na symboly místo řádků u varianty 2 — jinak se odkazy rozbijí
|
||||||
|
při každém editu.
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
- 2026-09-02: Založen projekt: deník + hledání řešení pro AI témata (agenti, kódování s AI, LLM).
|
- 2026-09-02: Založen projekt: deník + hledání řešení pro AI témata (agenti, kódování s AI, LLM).
|
||||||
- 2026-09-02: Incident „gpt 5.3 se zasekl na PH/PZ/TX z mobilu": rekonstruováno — finální odpověď byla kompletní a verzovaná; problém byl agent loop (guard bloky, retry smyčky, ~6-8 min). Model = glm-5.3:cloud (žádný gpt 5.3). Kanál na kvalitu vliv nemá.
|
- 2026-09-02: Incident „gpt 5.3 se zasekl na PH/PZ/TX z mobilu": rekonstruováno — finální odpověď byla kompletní a verzovaná; problém byl agent loop (guard bloky, retry smyčky, ~6-8 min). Model = glm-5.3:cloud (žádný gpt 5.3). Kanál na kvalitu vliv nemá.
|
||||||
- 2026-09-02: Compaction: uživatel si vyžádal drastické zkrácení incident zápisu; detaily zahozdeny, zachovány jen závěry.
|
- 2026-09-02: Compaction: uživatel si vyžádal drastické zkrácení incident zápisu; detaily zahozdeny, zachovány jen závěry.
|
||||||
|
- 2026-09-08: Série měření na Solarflare X2/X4 (TCPDirect, C# knihovna). Claude Code navrhl měřicí setup, aplikace sockbench na měření, Claude spouštěl scénáře, ukládal a vyhodnocoval výsledky. Fungovalo.
|
||||||
|
Pak cíl: převést proces na ruční spouštění skriptů (PowerShell) pod vlastní kontrolou. Claude zde selhává opakovaně: skripty zbytečně složité, moc parametrů, špatně čekal na dokončení test setu, testoval zbytečné věci. Zadání v plan modu se nedaří, výsledek stále špatný — frustrace, ztracený čas místo ušetřeného.
|
||||||
|
Uživatelova diagnóza: nedokáže správně popsat zadání; buď málo informací, nebo špatný přístup. Zvažované cesty: napsat kostry skriptů sám s komentáři a nechat Claude dokodovat; lepší projektový dokument; úplně odpoutat se od současné verze a začít znovu. Hledá efektivní způsob práce.
|
||||||
|
- 2026-09-08: Upřesnění diagnózy (Solarflare/sockbench): Claude si původně napsal runbook pro sebe — pro agenta byl ok, pro člověka nepoužitelný (moc kroků, moc složitosti). Skripty nebyly úplně špatné, ale měly tunu parametrů. Jádro problému: dvě různé cílové skupiny — agent snese libovolnou složitost, člověk potřebuje mnohem menší míru komplexity. Uživatel se o tom v plan modu snažil mluvit, přiblížil se, ale komunikace nestačila.
|
||||||
|
- 2026-09-08: correction: „úplně znovu od nuly — ne" z minula bylo nedorozuměním. Uživatel tím nemyslel „znovu bez specifikace", ale nepoužít existující runbook a existující skripty jako základ — ty ho limitují a anchornují ho špatným směrem. Restart s jeho vlastní specifikací (tabulka scénářů + kostry) je v pořádku, jediné co ne: recyklace agent-runbooku.
|
||||||
|
- 2026-09-08: Destilace postupu „převod agent-nástrojů na nástroje pro člověka" uložena jako artefakt: projects/ai/artifacts/agent-to-human-tools.md. Strukturovaný markdown: proč zjednodušení agent-nástrojů nefunguje (neviditelná hranice složitosti, ukotvení existující podobou, plan mode neřeší design), princip „rozhraní navrhuje člověk, model implementuje", 4krokový postup (čistý adresář → tabulka scénář→příkaz → kostry → dokódování těl) a pravidla pro zadání (čísla místo adjektiv, omezení v kódu ne v promptu, agent-runbook nepřevádět). Po vzájemné korektní diskusi: knowledge/ odmítnut, artefakt patří do projektu ai.
|
||||||
|
- 2026-09-08: Rozbor článku „Don't Chat With Your AI. Mob With It." (Rodenas) + diskuse s Claude uložen jako artefakt: projects/ai/artifacts/file-based-agent-communication.md. Technikou je komunikace s agentem přes tagy v souborech místo chatu. Článková argumentace odmítnuta (latence ≠ GPU sloty; mob analogie obrácená; autonomní smyčka + mazání tagů = ztráta kontrolního bodu a prodloužení feedback loopu pro člověka). Použitelné jádro: anotace patří tam, kde je předmět anotace (pozice = kontext, jako TODO komentář), trvalost v diffu, tag se nemaže jen se připíše odpověď, max 1–2 tagy na kolo, explicitní spouštění místo autonomní smyčky. Otevřená otázka: jak korigovat agentovy odpovědi — kandidát varianta „vlákno jako komentáře v kódu" oproti vedlejšímu .notes.md souboru. Zájem uživatele o vyzkoušení.
|
||||||
|
|||||||
@@ -44,3 +44,4 @@ Denní vážení, vnímaná kondice, jestli mě něco bolí.
|
|||||||
- **Fyzio terapie k zadání**: omezená mobilita pravého kyčle; proč se pravá noha
|
- **Fyzio terapie k zadání**: omezená mobilita pravého kyčle; proč se pravá noha
|
||||||
na rozdíl od levé tak snadno a často přetěžuje.
|
na rozdíl od levé tak snadno a často přetěžuje.
|
||||||
- **Cíl pravidelnosti**: cvičit aspoň 2× týdně; protahování častěji než doteď.
|
- **Cíl pravidelnosti**: cvičit aspoň 2× týdně; protahování častěji než doteď.
|
||||||
|
- **Klinika**: https://fyzioklinika.cz
|
||||||
|
|||||||
96
skills/cook/SKILL.md
Normal file
96
skills/cook/SKILL.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
name: cook
|
||||||
|
description: >
|
||||||
|
Capture and search recipes and tea notes (brewing parameters, origins, tasting
|
||||||
|
notes) in the cook/ directory — one markdown file per item. Triggers on:
|
||||||
|
"cook: X", "zapiš recept X", "save recipe X", "co mám na X" (tea/food lookup),
|
||||||
|
"čaj X", tea temperature/time questions against stored notes. For growing
|
||||||
|
personal food & tea knowledge — not a one-off task or a durable user fact.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Cook
|
||||||
|
|
||||||
|
Recipes and tea notes in `cook/`, one markdown file per item. No inbox, no
|
||||||
|
pipeline, no background compile — capture is inline in the same turn.
|
||||||
|
|
||||||
|
**All file mutations go through the safety script.** Frontmatter is generated
|
||||||
|
only by the script, never by hand — it cannot drift.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run skills/cook/scripts/cook.py <cmd> ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
cook/
|
||||||
|
├── recepty/ ← recepty/<slug>.md (type: recept)
|
||||||
|
├── caj/ ← caj/<slug>.md (type: caj)
|
||||||
|
└── assets/ ← cook/assets/<slug>/ (only if a document/photo ever arrives)
|
||||||
|
```
|
||||||
|
|
||||||
|
Slugs: kebab-case, descriptive (`gulas-classic.md`, `sencha-japonsko.md`).
|
||||||
|
Subdirs are created by the script on first `add`.
|
||||||
|
|
||||||
|
## Frontmatter (script-generated)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
type: recept | caj # the only fixed value
|
||||||
|
category: volná hodnota # polévka, hlavní-chod / zelený, černý...
|
||||||
|
cuisine: volná # recepts only
|
||||||
|
origin: volná # caj only
|
||||||
|
tags: [chata, zima] # user hashtags, no '#'
|
||||||
|
added: 2026-09-08 # auto
|
||||||
|
```
|
||||||
|
|
||||||
|
Taxonomy is deliberately free-form — it settles over time, don't force enums.
|
||||||
|
|
||||||
|
## Capture (inline, same turn)
|
||||||
|
|
||||||
|
1. `add <slug> --type recept|caj [--category C] [--cuisine C] [--origin O] [--tags a,b]`
|
||||||
|
with the body on **stdin** (heredoc). The script creates the file with
|
||||||
|
frontmatter + body and prints the path. Exit 1 if the slug exists.
|
||||||
|
2. **Slug collision** → `show` the existing item, ask the user: update it via
|
||||||
|
`edit`, or file as a new slug (`gulas-hrachova`). Never auto-suffix `-2`.
|
||||||
|
3. Commit: `git add cook/ && git commit -m "cook: add <slug>"`. Stage only
|
||||||
|
`cook/` — never `git add -A`.
|
||||||
|
4. Confirm the file path and quote what was filed.
|
||||||
|
|
||||||
|
Language: user's (Czech), verbatim where sensible. New content appends **at
|
||||||
|
the end** of a file; never insert into the middle.
|
||||||
|
|
||||||
|
### Body formats — conventions, not rigid schemas
|
||||||
|
|
||||||
|
Recipe: `# <název>`, **Ingredience**, **Postup**, **Zdroj**, **Poznámky**.
|
||||||
|
Tea: `# <název čaje>`, **Země/Typ/Teplota/Čas**, **Poznámky** (chuť, vůně,
|
||||||
|
odkud koupeno). Adapt to what the user sends; don't force empty fields.
|
||||||
|
|
||||||
|
## Search / answer
|
||||||
|
|
||||||
|
1. Narrow via script: `list [--type T] [--category C] [--tag X]` (frontmatter
|
||||||
|
filters) and/or `search <text>` (fulltext across bodies).
|
||||||
|
2. Read the shortlisted files (`show <slug>` or `read_file`), answer **from the
|
||||||
|
files only** — no confabulation. If not covered, say so.
|
||||||
|
3. Read-only: never modify files in a search turn.
|
||||||
|
|
||||||
|
## Edit / delete — hard-gated TWO-TURN flow
|
||||||
|
|
||||||
|
- **Turn 1 (no mutation):** `edit <slug>` prints the path + full text. Show the
|
||||||
|
user the exact verbatim text (or before → after), ask to confirm. STOP.
|
||||||
|
- **Turn 2 (explicit confirmation only):** apply body changes with a surgical
|
||||||
|
`edit_file` on the script-printed path (frontmatter stays untouched), or
|
||||||
|
`delete <slug>` / `rename <old> <new>`. Then commit:
|
||||||
|
`git add cook/ && git commit -m "cook: edit|delete|rename <slug>"`.
|
||||||
|
|
||||||
|
`rename` also moves `cook/assets/<old>/` if present. `delete` removes the
|
||||||
|
assets dir only when empty — non-empty assets block silent data loss.
|
||||||
|
|
||||||
|
## House rules
|
||||||
|
|
||||||
|
- Script guards: `add` on existing slug, `edit`/`delete`/`rename` on missing
|
||||||
|
slug → exit 1. Never bypass the script by `write_file`-ing into `cook/`
|
||||||
|
(except surgical body edits via `edit_file`/`apply_patch` after `add`).
|
||||||
|
- `validate` checks frontmatter integrity — run it if files were touched
|
||||||
|
by anything other than the script.
|
||||||
|
- Surgical edits only — never reformat or rewrite stored recipes/notes
|
||||||
|
unless asked.
|
||||||
240
skills/cook/scripts/cook.py
Executable file
240
skills/cook/scripts/cook.py
Executable file
@@ -0,0 +1,240 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""cook — minimal store for recipes and tea notes, one markdown file per item.
|
||||||
|
|
||||||
|
Frontmatter is generated only here (never hand-written) so it never drifts.
|
||||||
|
All operations exit 1 on missing/already-existing targets instead of guessing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from datetime import date
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
WORKSPACE = Path(__file__).resolve().parents[3]
|
||||||
|
COOK = WORKSPACE / "cook"
|
||||||
|
RECEPTY = COOK / "recepty"
|
||||||
|
CAJ = COOK / "caj"
|
||||||
|
ASSETS = COOK / "assets"
|
||||||
|
FRONTMATTER_RE = re.compile(r"\A---\n(.*?)\n---\n", re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_fm(text: str) -> dict[str, str]:
|
||||||
|
"""Parse the frontmatter block into a flat dict (list values joined by ',')."""
|
||||||
|
m = FRONTMATTER_RE.match(text)
|
||||||
|
if not m:
|
||||||
|
return {}
|
||||||
|
out: dict[str, str] = {}
|
||||||
|
for line in m.group(1).splitlines():
|
||||||
|
if ":" in line:
|
||||||
|
k, v = line.split(":", 1)
|
||||||
|
out[k.strip()] = v.strip().strip("[]")
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def build_fm(fields: dict[str, str]) -> str:
|
||||||
|
"""Emit frontmatter; 'tags' is a list, everything else a plain scalar."""
|
||||||
|
lines = ["---"]
|
||||||
|
for k, v in fields.items():
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
if k == "tags":
|
||||||
|
lines.append(f"tags: [{v}]")
|
||||||
|
else:
|
||||||
|
lines.append(f"{k}: {v}")
|
||||||
|
lines.append("---")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def die(msg: str) -> None:
|
||||||
|
print(f"ERROR: {msg}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def find_file(slug: str) -> Path | None:
|
||||||
|
for d in (RECEPTY, CAJ):
|
||||||
|
p = d / f"{slug}.md"
|
||||||
|
if p.is_file():
|
||||||
|
return p
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def require_file(slug: str) -> Path:
|
||||||
|
p = find_file(slug)
|
||||||
|
if not p:
|
||||||
|
die(f"not found: {slug}")
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def type_dir(item_type: str) -> Path:
|
||||||
|
if item_type == "recept":
|
||||||
|
return RECEPTY
|
||||||
|
if item_type == "caj":
|
||||||
|
return CAJ
|
||||||
|
die(f"invalid type: {item_type} (recept|caj)")
|
||||||
|
raise AssertionError
|
||||||
|
|
||||||
|
|
||||||
|
def load_items() -> list[tuple[Path, dict[str, str]]]:
|
||||||
|
items = []
|
||||||
|
for d in (RECEPTY, CAJ):
|
||||||
|
if not d.is_dir():
|
||||||
|
continue
|
||||||
|
for p in sorted(d.glob("*.md")):
|
||||||
|
items.append((p, parse_fm(p.read_text(encoding="utf-8"))))
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def get_body(text: str) -> str:
|
||||||
|
m = FRONTMATTER_RE.match(text)
|
||||||
|
return text[m.end() :] if m else text
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_add(args: argparse.Namespace) -> None:
|
||||||
|
d = type_dir(args.type)
|
||||||
|
d.mkdir(parents=True, exist_ok=True)
|
||||||
|
target = d / f"{args.slug}.md"
|
||||||
|
if target.exists() or find_file(args.slug):
|
||||||
|
die(f"already exists: {args.slug} (use edit, or pick a new slug)")
|
||||||
|
|
||||||
|
fm: dict[str, str] = {
|
||||||
|
"type": args.type,
|
||||||
|
"category": args.category or "",
|
||||||
|
"added": date.today().isoformat(),
|
||||||
|
}
|
||||||
|
if args.type == "recept":
|
||||||
|
fm["cuisine"] = args.cuisine or ""
|
||||||
|
else:
|
||||||
|
fm["origin"] = args.origin or ""
|
||||||
|
if args.tags:
|
||||||
|
fm["tags"] = ", ".join(args.tags)
|
||||||
|
|
||||||
|
body = sys.stdin.read() if not sys.stdin.isatty() else ""
|
||||||
|
if args.body_file:
|
||||||
|
body = Path(args.body_file).read_text(encoding="utf-8")
|
||||||
|
if body and not body.startswith("\n"):
|
||||||
|
body = "\n" + body
|
||||||
|
target.write_text(build_fm(fm) + "\n" + body, encoding="utf-8")
|
||||||
|
print(target)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_edit(args: argparse.Namespace) -> None:
|
||||||
|
p = require_file(args.slug)
|
||||||
|
print(p)
|
||||||
|
print("---8<---")
|
||||||
|
print(p.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_list(args: argparse.Namespace) -> None:
|
||||||
|
for p, fm in load_items():
|
||||||
|
if args.type and fm.get("type") != args.type:
|
||||||
|
continue
|
||||||
|
if args.category and fm.get("category") != args.category:
|
||||||
|
continue
|
||||||
|
if args.tag and args.tag not in [
|
||||||
|
t.strip() for t in fm.get("tags", "").split(",")
|
||||||
|
]:
|
||||||
|
continue
|
||||||
|
title = get_body(p.read_text(encoding="utf-8")).strip().splitlines()
|
||||||
|
first = title[0].lstrip("# ").strip() if title else ""
|
||||||
|
print(
|
||||||
|
f"{p.stem}\t{fm.get('type', '?')}\t{fm.get('category', '?')}\t{fm.get('added', '?')}\t{first}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_show(args: argparse.Namespace) -> None:
|
||||||
|
print(require_file(args.slug).read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_search(args: argparse.Namespace) -> None:
|
||||||
|
needle = args.text.lower()
|
||||||
|
for p, _ in load_items():
|
||||||
|
body = get_body(p.read_text(encoding="utf-8"))
|
||||||
|
for i, line in enumerate(body.splitlines(), 1):
|
||||||
|
if needle in line.lower():
|
||||||
|
print(f"{p.stem}:{i}: {line.strip()}")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_rename(args: argparse.Namespace) -> None:
|
||||||
|
src = require_file(args.old)
|
||||||
|
if find_file(args.new) or args.old == args.new:
|
||||||
|
die(f"target exists or same: {args.new}")
|
||||||
|
dest = src.parent / f"{args.new}.md"
|
||||||
|
src.rename(dest)
|
||||||
|
a = ASSETS / args.old
|
||||||
|
if a.is_dir():
|
||||||
|
a.rename(ASSETS / args.new)
|
||||||
|
print(dest)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_delete(args: argparse.Namespace) -> None:
|
||||||
|
p = require_file(args.slug)
|
||||||
|
p.unlink()
|
||||||
|
a = ASSETS / args.slug
|
||||||
|
if a.is_dir() and not any(a.iterdir()):
|
||||||
|
a.rmdir()
|
||||||
|
print(f"deleted: {p}")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_validate(_: argparse.Namespace) -> None:
|
||||||
|
ok = True
|
||||||
|
for p, fm in load_items():
|
||||||
|
if not fm:
|
||||||
|
print(f"FAIL {p}: no frontmatter")
|
||||||
|
ok = False
|
||||||
|
elif fm.get("type") not in ("recept", "caj"):
|
||||||
|
print(f"FAIL {p}: invalid type: {fm.get('type')!r}")
|
||||||
|
ok = False
|
||||||
|
print("ok" if ok else "invalid")
|
||||||
|
sys.exit(0 if ok else 1)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
ap = argparse.ArgumentParser(prog="cook")
|
||||||
|
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||||
|
|
||||||
|
sp = sub.add_parser("add")
|
||||||
|
sp.add_argument("slug")
|
||||||
|
sp.add_argument("--type", required=True, choices=["recept", "caj"])
|
||||||
|
sp.add_argument("--category")
|
||||||
|
sp.add_argument("--cuisine")
|
||||||
|
sp.add_argument("--origin")
|
||||||
|
sp.add_argument("--tags", nargs="+")
|
||||||
|
sp.add_argument("--body-file")
|
||||||
|
sp.set_defaults(fn=cmd_add)
|
||||||
|
|
||||||
|
sp = sub.add_parser("edit")
|
||||||
|
sp.add_argument("slug")
|
||||||
|
sp.set_defaults(fn=cmd_edit)
|
||||||
|
|
||||||
|
sp = sub.add_parser("list")
|
||||||
|
sp.add_argument("--type", choices=["recept", "caj"])
|
||||||
|
sp.add_argument("--category")
|
||||||
|
sp.add_argument("--tag")
|
||||||
|
sp.set_defaults(fn=cmd_list)
|
||||||
|
|
||||||
|
sp = sub.add_parser("show")
|
||||||
|
sp.add_argument("slug")
|
||||||
|
sp.set_defaults(fn=cmd_show)
|
||||||
|
|
||||||
|
sub.add_parser("validate").set_defaults(fn=cmd_validate)
|
||||||
|
|
||||||
|
sp = sub.add_parser("search")
|
||||||
|
sp.add_argument("text")
|
||||||
|
sp.set_defaults(fn=cmd_search)
|
||||||
|
|
||||||
|
sp = sub.add_parser("rename")
|
||||||
|
sp.add_argument("old")
|
||||||
|
sp.add_argument("new")
|
||||||
|
sp.set_defaults(fn=cmd_rename)
|
||||||
|
|
||||||
|
sp = sub.add_parser("delete")
|
||||||
|
sp.add_argument("slug")
|
||||||
|
sp.set_defaults(fn=cmd_delete)
|
||||||
|
|
||||||
|
args = ap.parse_args()
|
||||||
|
args.fn(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user