Remake skillu project

This commit is contained in:
lachtan
2026-07-22 12:31:40 +02:00
parent a8c9770d7d
commit 19014ed3d9
9 changed files with 234 additions and 256 deletions

View File

@@ -0,0 +1,11 @@
# Překážky a prohřešky — life
Sem zapisujeme prohřešky a věci, které reálně brání dosáhnout cílů (změna váhy
+ kondice) — ať vidíme vzorce a můžeme je adresovat.
- 2026-07-22: Plánovaný salát nebyl k dispozici (zavřeno) → fallback byl
kaloričtější sushi (nasi goreng). Problém: zdravá volba není vždy dostupná,
fallback není ekvivalentní.
- 2026-07-22: Odpolední chuť na sladké / „plnící“ jídlo po jídle. Jablko
pomohlo částečně, ale chuť zůstává. Vzorec: po teplejším / tučnějším jídle
přichází chuť na něco sladkého / zasytivého.

18
projects/life/memory.md Normal file
View File

@@ -0,0 +1,18 @@
# Deník rozhodnutí — life
- 2026-07-22: Projekt založen. Cíl: změna váhy + dobrá kondice, udržitelně,
kolem rodiny a malého dítěte. Jídlo řešíme jako návyky (večerní jedení,
sladké), NE počítáním kalorií ani zápisem jídla. Oblasti: cvičení (vlastní
váha / kettlebell, nepravidelné), běh (nepravidelné), protahování/mobilita
(po běhu), rodina, alkohol. Pokrok sleduju denním vážením + vnímáním kondice
a bolestí. Prohřešky a překážky jdou do artifacts/prekazky-a-prohresky.md.
- 2026-07-22: Výchozí stav zapsán. Aktuálně 76 kg (poslední dny 75), meta 72
kg (ideálně pod 70), horizont do konce roku 2026. Dříve byl pod 70, dlouho
držel 72, poslední 12 roky pozvolna nahoru — pravděpodobně souvisí s Ema
(2,5 roku): narušený spánek, méně času, večerní jedení jako kompenzace.
Alkohol nejen relaxační, občas větší dávky (ne pravidelně) — silnější páka na
váhu i udržitelnost.
- 2026-07-22: Běh. Snídaně protein + banán + mléko, pak ovoce. Oběd chtěl
salát, měli zavřeno → sushi (nasi goreng). Odpoledne chuť na sladké / něco
„plnícího“; pravidlo „jen ovoce neomezeně“ → dal si jablko. Poznámka: chuť
zůstává i po jablku.

56
projects/life/prompt.md Normal file
View File

@@ -0,0 +1,56 @@
# Projekt: life
Osobní průvodce a deníček k hledání udržitelného životního stylu. Cílem není
dokonalý plán, ale postupně najít, co reálně funguje v mém životě s rodinou a
malým dítětem — a hlavně to udržet.
Projekt se bude v čase vyvíjet: zkouším různé přístupy, píšu si úvahy, a podle
toho, co funguje a co ne, se plán zpřesňuje. Ber to jako živou věc, ne pevný režim.
## Primární cíl
**Změna váhy a dobrá kondice.** Vše ostatní (návyky, běh, jídlo) je cesta k
tomuhle. Když se rozhodujeme mezi variantami, měř je proti tomuto cíli a proti
udržitelnosti.
## Oblasti, které řešíme
- **Cvičení** — síla (vlastní váha nebo kettlebell), zatím taky nepravidelně.
- **Běh** — běhám, ale nepravidelně; cíl je spíš dostat to do pravidelnosti.
- **Jídlo — návyky, ne počítání.** NEpočítáme kalorie a NEzapisujeme, co jsem
snědl. Řešíme obecnější úvahy: jak večer už nejíst, jak se necpat sladkým.
(Kalorické tabulky mám k dispozici, kdybych je někdy chtěl na kalibraci —
nejsou ale součástí běžného režimu.)
- **Protahování / mobilita** — po běhu ho dělám; chci ho integrovat i mimo běh,
pravidelněji.
- **Rodina a dítě** — jak to všechno poskládat kolem rodiny, ať to jde utáhnout.
- **Alkohol** — páka, která ovlivňuje jídlo i celkovou udržitelnost.
- **Jak poznávám pokrok** — vážím se každý den, vnímám kondici a jestli mě něco
bolí. Kvalitativní a denní, ne měřitelné tabulky.
## Jak se mnou pracuj
Jsi můj průvodce a deníček. Ukládej moje úvahy, pomáhej hledat udržitelnou
cestu, raď a ptej se doplňujícími otázkami. Když se ozvu, shrň, kde jsme, a
navrhni další malý krok — ale netlač na pilu a respektuj, že se přístup bude
měnit. Odpovídej stručně.
## Kam co zapisovat
- `memory.md` — chronologický, append-only log: rozhodnutí, co jsme zkusili, co
fungovalo / nefungovalo, milníky. Jedna položka na věc, ne přepis konverzace.
- `state.md` — živý aktuální stav: výchozí čísla, aktuální cíle, co zrovna
zkouším za přístup. Edituj na místě, ať to nikdy nemate.
- `artifacts/prekazky-a-prohresky.md` — sem zapisuj prohřešky a věci, které mi
reálně brání dosáhnout cílů. Slouží k tomu, abychom viděli vzorce.
## První session (co doplnit)
Až se poprvé pustíme do práce, pomoz mi zjistit a zapsat do `state.md`:
- aktuální váha jako výchozí bod (vážím se denně),
- kolik času reálně mám a kdy (kolem dítěte, práce, víkendy),
- cílová váha / kondiční meta a hrubý horizont,
- co jsem s tímhle zkoušel dřív a proč to (ne)vyšlo.
Nevymýšlej si za mě čísla ani fakta — vyplní se tím, co reálně řeknu.

40
projects/life/state.md Normal file
View File

@@ -0,0 +1,40 @@
# Aktuální stav — life
## Výchozí stav
- Váha: aktuálně 76 kg, poslední dny 75 kg (mírný pokles nahoru)
- Cvičení: vlastní váha / kettlebell, nepravidelně
- Běh: nepravidelně
- Časový rozpočet: čas je, ale nepravidelný a opportunistický — někdy ráno
před rodinou, někdy dojížďka na kole (už nebaví). Fixní rozvrh nepasuje.
- Historie: dříve byl pod 70, dlouho držel 72, poslední 12 roky pozvolna nahoru
## Cíle
Primární: **změna váhy + dobrá kondice, udržitelně.**
- Cílová váha: realisticky 72 kg, ideálně pod 70 — klíčové je **udržet**, ne jen dosáhnout
- Horizont: do konce roku 2026 (5 měsíců, bez tlaku)
## Kontext
- Ema je 2,5 roku — souvisí s nárůstem váhy: narušený spánek → večerní jedení,
méně času na cvičení, menší disciplína u sladkého
- Alkohol: nejen relaxační, občas větší dávky (i když ne pravidelně) — silnější
páka na váhu i udržitelnost
## Jak poznávám pokrok
Denní vážení, vnímaná kondice, jestli mě něco bolí.
## Časování pohybu
- Večer cvičit nechce — nechce se a špatné načasování
- Ráno ano, případně přes den (v práci nebo home office) — už se stalo
- Cvičení = pot, nutno převléct a sprcha → není to 20-min záležitost
- Protahování ano i večer (bez potu)
- Dojížďka na kole už nebaví → nestavět na ní
## Aktuální přístup
*(co zrovna zkouším — bude se v čase měnit)*
- Pravidlo „jen ovoce neomezeně“ jako nouzová náhrada sladkého / svačiny.
- Pozorování: po teplejším jídle (sushi) přichází chuť na sladké / zasytivé.
- Otázka k řešení: jak tu chuť zastavit nebo nahradit něčím, co nezvedne váhu.

View File

@@ -1,14 +1 @@
---
status: active
priority: medium
created: '2026-06-09'
slug: radio-1
---
# Radio 1
## Poznámky
- 2026-06-09: Cílem je rozřezat vstupní audio stream z Radia 1 na jednotlivé písničky, přičemž se vyhází všechny reklamy. Mám na to plán jak to udělat (připravil Claude) a hodlám to realizovat za vydatné pomoci Claude Code. Nahrávat se může na wood.hell a dekódování pak poběží na nvidia.hell protože ma k dispozici RTX 4060. Sypat se to bude asi po dávkách.
## Další krok

View File

10
projects/radio1/state.md Normal file
View File

@@ -0,0 +1,10 @@
# Radio 1
Cíl: rozřezat vstupní audio stream z Radia 1 na jednotlivé písničky a
odstranit z něj reklamy. Nahrávání poběží na wood.hell, dekódování na
nvidia.hell (má k dispozici RTX 4060). Zpracování po dávkách.
Stav: active (priorita: medium — migrováno ze starého project skillu,
založeno 2026-06-09).
Další krok: (zatím nezadáno)

View File

@@ -1,88 +1,128 @@
---
name: project
aliases: [proj]
description: >
Project management — long-running things with notes, next-steps, and status.
Use when user mentions "project".
Switch between named ongoing projects, each with its own persistent
context, carried forward across turns until the user switches or ends it.
Triggers on: "project X", "switch to project X", "we're working on X",
"end project" / "no project". For a long-lived, named work context — not a
single task to plan and execute, and not a short or global fact to
remember.
---
# Project
File-backed project store in `projects/`. Each project is one markdown file
with YAML frontmatter (`status`, `priority`, `created`, `slug`) and a free-form
body for notes and next-steps.
Named, long-lived work contexts. Each project keeps its own instructions, its
own history, its own current-state summary, and its own generated files —
separate from every other project and from the agent's general memory.
## Backend
## Layout
`skills/project/scripts/project.py` — deterministic CRUD for frontmatter and
basic operations. Agent handles all body edits via `edit_file` / `apply_patch`.
`workspace/projects/<slug>/`:
## Commands
- `prompt.md` — project-specific context/instructions, read in full whenever
the project becomes active
- `memory.md` — append-only chronological log of decisions and history
- `state.md` — living document: the current state/synthesis of the project,
edited in place, not appended to
- `artifacts/` — generated files (documents, code, data exports, reports)
### `project add <název>` — create
**Which project is active is tracked purely through conversation memory —
nothing is persisted to disk for that.** Once a project is chosen, keep
treating it as active for the rest of this conversation; don't re-read its
files on every subsequent turn once they're already in context.
1. Run: `uv run skills/project/scripts/project.py add "<název>" [--priority high|medium|low]`
2. Default priority is `medium`.
3. Echo: `Created project '<slug>' (priority: <priority>)`
## Activation
### `project list` — list active
Triggered by "project X" / "switch to project X" / "we're working on X":
1. Turn `X` into a kebab-case directory name (like picking a slug for a plan
file — no need to spell out an algorithm, just pick something sensible).
2. **`workspace/projects/<slug>/` already exists** → read `prompt.md`,
`memory.md`, and `state.md` in full, then briefly confirm what's active
and, if `prompt.md` or `state.md` has content, what context you loaded.
3. **It doesn't exist, but the name is a close match to one or more existing
projects** (case-insensitive) → ask which one was meant. Don't guess, and
don't create a near-duplicate of an existing project.
4. **It doesn't exist and there's no close match** → this skill only works
with projects that already exist without asking. Ask the user whether to
start a new project with that name. Only on explicit yes, create
`prompt.md`, `memory.md`, `state.md` (all empty) and `artifacts/`. Never
create a project just because a trigger phrase was said.
**Already active:** if the same project is already active in this
conversation, don't re-create or re-read anything — just continue.
## Staying active
Once a project is active, keep applying its `prompt.md` instructions and
`state.md` context for the rest of the conversation, until the user switches
or ends it.
If a long gap or an ambiguous reference makes it unclear whether the project
is still the right context (e.g. the conversation has clearly moved to an
unrelated topic), ask rather than silently carrying it forward or silently
dropping it.
## Switching and ending
- **"switch to Y"** → run the Activation flow for Y; Y becomes active
instead.
- **"end project" / "no project" / "stop working on X"** → stop treating any
project as active. Say so. Don't delete anything.
- With no active project, behave normally — never force a project onto an
unrelated request.
## Writing to memory.md
Append only — never rewrite or reorder existing entries. One entry per
decision, dead end, or noteworthy piece of history:
1. Run: `uv run skills/project/scripts/project.py list`
2. Echo JSON output. Format as:
```
Active projects:
- <slug> (priority: high) — <first line of body / project name>
- YYYY-MM-DD: <terse entry, reformulated, not verbatim>
```
3. If empty → "No active projects."
### `project show <slug>` — display
Write an entry when a decision is made, a dead end is found, or a fact
central to the project's ongoing context emerges — not for routine
back-and-forth. When in doubt whether something is memory-worthy, prefer not
writing it; `memory.md` is for what a future conversation needs to pick up
the thread, not a transcript.
1. Run: `uv run skills/project/scripts/project.py show <slug>`
2. Echo the full markdown file.
## Maintaining state.md
### `project status <slug> <active|paused|done>` — change status
Unlike `memory.md`, `state.md` is edited in place: surgically update the
relevant section when the project's current state or understanding has
moved on enough that the old text would mislead a reader. It answers "where
is this now", not "what happened" — old content gets replaced, not appended
to. If it's still empty and the project has accumulated enough context to
synthesize, offer to draft it.
1. Run: `uv run skills/project/scripts/project.py status <slug> <status>`
2. Echo: `Project '<slug>' is now <status>.`
## Artifacts
### `project next <slug> <text>` — set next step
Files generated while working on the project (documents, code, data exports,
reports) go in `workspace/projects/<slug>/artifacts/` instead of scattered
elsewhere. Name them descriptively; no numbering or index file needed at
this scale.
1. Read the project file.
2. Use `edit_file` to replace the content under `## Další krok` with the new text.
3. If the section does not exist, add it before the end of the file.
4. Echo: `Next step for '<slug>' updated.`
## Listing
### `project note <slug> <text>` — add a note
1. Read the project file.
2. Use `edit_file` to append a bullet under `## Poznámky`:
`- <today>: <text>`
3. If `## Poznámky` does not exist, add it after the first heading.
4. Echo: `Note added to '<slug>'.`
### `project switch <slug>` — session context
1. Run `my(action="set", key="project_context", value="<slug>")`.
2. Echo: `Switched to project '<slug>'. Next project commands without slug will use this context.`
3. If a command is missing a slug and `project_context` is set, use it automatically.
## Rules
- **Slug** = kebab-case from first 4 words of the name. Used as filename (`<slug>.md`).
- **Frontmatter** is read-only for the agent — never edit it directly in the file.
Use `project.py status` to change status.
- **Body edits** (notes, next-step, structure changes) are always done by the agent
via `edit_file` / `apply_patch`.
- **No database** — pure markdown files. Git-friendly, one commit per change.
- **Session context** (`project switch`) lives only in `my` scratchpad and is lost
on restart. Re-run `project switch` after restart if needed.
- **Priority** = `high` | `medium` | `low`. `list` sorts by priority (high first).
- **Status** = `active` | `paused` | `done`. `list` shows only `active`.
**"list projects" / "which projects exist":** list the directory names under
`workspace/projects/`. If none exist, say so.
## Edge cases
- `project add` with existing slug → error, do not overwrite.
- `project show` / `project status` / `project next` / `project note` with
missing slug → "Project '<slug>' not found."
- Missing `## Poznámky` or `## Další krok` → agent creates the section.
- Empty `projects/` → `list` returns empty array.
- "project" with no name → ask which project.
- The project directory exists but one or more of `prompt.md` / `memory.md`
/ `state.md` is missing (e.g. created by hand) → create the missing
file(s) empty, don't error.
- Deleting or renaming a project is out of scope for this skill — point the
user at `workspace/projects/<slug>/` to do it by hand.
## Rules
- This skill's body is English; reply to the user in their own language.
- Never fabricate project content — `prompt.md`, `memory.md`, and `state.md`
only grow from what the user actually said or what actually happened.
- Never create a new project without the user's explicit confirmation.
- Don't force a project context onto an unrelated request.
- The Dream memory processor must not touch `workspace/projects/` — it is
outside the memory and skills Dream curates.

View File

@@ -1,184 +0,0 @@
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["pyyaml"]
# ///
"""
project.py — backend for /project skill.
Deterministic CRUD for project markdown files with YAML frontmatter.
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from datetime import date
from pathlib import Path
import yaml
WORKSPACE = Path(__file__).resolve().parent.parent.parent.parent
PROJECTS_DIR = WORKSPACE / "projects"
# Valid statuses and priorities
STATUSES = {"active", "paused", "done"}
PRIORITIES = {"high", "medium", "low"}
def _slugify(name: str) -> str:
"""Kebab-case slug from first few words of name. Max 4 words."""
words = re.sub(r"[^a-zA-Z0-9\s]", "", name).lower().split()
words = words[:4]
return "-".join(words) if words else "project"
def _list_projects() -> list[dict]:
"""Parse frontmatter from all .md files in projects/."""
if not PROJECTS_DIR.exists():
return []
projects = []
for path in sorted(PROJECTS_DIR.glob("*.md")):
text = path.read_text(encoding="utf-8")
frontmatter, _ = _split_frontmatter(text)
if frontmatter:
meta = yaml.safe_load(frontmatter) or {}
meta["_file"] = path.name
projects.append(meta)
return projects
def _split_frontmatter(text: str) -> tuple[str | None, str]:
"""Split YAML frontmatter from body. Returns (frontmatter_yaml, body)."""
if not text.startswith("---\n"):
return None, text
end = text.find("\n---\n", 4)
if end == -1:
return None, text
return text[4:end], text[end + 5 :]
def _load_file(slug: str) -> tuple[Path, str, str | None, str]:
"""Load project file. Returns (path, full_text, frontmatter_yaml, body)."""
path = PROJECTS_DIR / f"{slug}.md"
if not path.exists():
raise FileNotFoundError(f"Project '{slug}' not found ({path.name})")
text = path.read_text(encoding="utf-8")
fm, body = _split_frontmatter(text)
return path, text, fm, body
def _write_file(path: Path, frontmatter: dict, body: str) -> None:
"""Write project file with YAML frontmatter."""
fm_yaml = yaml.safe_dump(frontmatter, allow_unicode=True, sort_keys=False, default_flow_style=False)
path.write_text(f"---\n{fm_yaml}---\n{body}", encoding="utf-8")
# ---------------------------------------------------------------------------
# Commands
# ---------------------------------------------------------------------------
def cmd_add(args: argparse.Namespace) -> int:
name = (args.name or "").strip()
if not name:
print(json.dumps({"error": "name must not be empty"}), file=sys.stderr)
return 1
slug = _slugify(name)
PROJECTS_DIR.mkdir(parents=True, exist_ok=True)
path = PROJECTS_DIR / f"{slug}.md"
if path.exists():
print(json.dumps({"error": f"project '{slug}' already exists"}), file=sys.stderr)
return 1
priority = (args.priority or "medium").lower()
if priority not in PRIORITIES:
print(json.dumps({"error": f"invalid priority '{priority}' — use high/medium/low"}), file=sys.stderr)
return 1
frontmatter = {
"status": "active",
"priority": priority,
"created": date.today().isoformat(),
"slug": slug,
}
body = f"# {name}\n\n## Poznámky\n\n## Další krok\n\n"
_write_file(path, frontmatter, body)
print(json.dumps({"added": {"slug": slug, "name": name, "path": str(path.relative_to(WORKSPACE))}}, ensure_ascii=False))
return 0
def cmd_list(_args: argparse.Namespace) -> int:
projects = _list_projects()
active = [p for p in projects if p.get("status") == "active"]
# Sort by priority: high > medium > low
priority_order = {"high": 0, "medium": 1, "low": 2}
active.sort(key=lambda p: priority_order.get(p.get("priority", "medium"), 1))
print(json.dumps({"projects": active}, ensure_ascii=False))
return 0
def cmd_show(args: argparse.Namespace) -> int:
slug = (args.slug or "").strip()
try:
_path, text, _fm, _body = _load_file(slug)
except FileNotFoundError as exc:
print(json.dumps({"error": str(exc)}), file=sys.stderr)
return 1
print(text)
return 0
def cmd_status(args: argparse.Namespace) -> int:
slug = (args.slug or "").strip()
new_status = (args.status or "").strip().lower()
if new_status not in STATUSES:
print(json.dumps({"error": f"invalid status '{new_status}' — use active/paused/done"}), file=sys.stderr)
return 1
try:
path, text, fm_yaml, body = _load_file(slug)
except FileNotFoundError as exc:
print(json.dumps({"error": str(exc)}), file=sys.stderr)
return 1
if not fm_yaml:
print(json.dumps({"error": "no frontmatter found"}), file=sys.stderr)
return 1
frontmatter = yaml.safe_load(fm_yaml) or {}
frontmatter["status"] = new_status
_write_file(path, frontmatter, body)
print(json.dumps({"updated": {"slug": slug, "status": new_status}}, ensure_ascii=False))
return 0
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main() -> int:
parser = argparse.ArgumentParser(description="Project file backend")
sub = parser.add_subparsers(dest="command", required=True)
p_add = sub.add_parser("add", help="Create a new project")
p_add.add_argument("name", help="Project name")
p_add.add_argument("--priority", default="medium", help="Priority: high/medium/low")
sub.add_parser("list", help="List active projects")
p_show = sub.add_parser("show", help="Show full project file")
p_show.add_argument("slug", help="Project slug")
p_status = sub.add_parser("status", help="Change project status")
p_status.add_argument("slug", help="Project slug")
p_status.add_argument("status", help="New status: active/paused/done")
args = parser.parse_args()
dispatch = {"add": cmd_add, "list": cmd_list, "show": cmd_show, "status": cmd_status}
return dispatch[args.command](args)
if __name__ == "__main__":
sys.exit(main())