diff --git a/projects/life/artifacts/prekazky-a-prohresky.md b/projects/life/artifacts/prekazky-a-prohresky.md new file mode 100644 index 0000000..860550f --- /dev/null +++ b/projects/life/artifacts/prekazky-a-prohresky.md @@ -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. diff --git a/projects/life/memory.md b/projects/life/memory.md new file mode 100644 index 0000000..70c55b5 --- /dev/null +++ b/projects/life/memory.md @@ -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í 1–2 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. diff --git a/projects/life/prompt.md b/projects/life/prompt.md new file mode 100644 index 0000000..6d42d39 --- /dev/null +++ b/projects/life/prompt.md @@ -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. diff --git a/projects/life/state.md b/projects/life/state.md new file mode 100644 index 0000000..29d294f --- /dev/null +++ b/projects/life/state.md @@ -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í 1–2 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. diff --git a/projects/radio-1.md b/projects/radio1/memory.md similarity index 76% rename from projects/radio-1.md rename to projects/radio1/memory.md index 240fd09..e73af28 100644 --- a/projects/radio-1.md +++ b/projects/radio1/memory.md @@ -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 - diff --git a/projects/radio1/prompt.md b/projects/radio1/prompt.md new file mode 100644 index 0000000..e69de29 diff --git a/projects/radio1/state.md b/projects/radio1/state.md new file mode 100644 index 0000000..d1cc7b7 --- /dev/null +++ b/projects/radio1/state.md @@ -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) diff --git a/skills/project/SKILL.md b/skills/project/SKILL.md index 186c52f..8e0c182 100644 --- a/skills/project/SKILL.md +++ b/skills/project/SKILL.md @@ -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//`: -## 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 ` — 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 "" [--priority high|medium|low]` -2. Default priority is `medium`. -3. Echo: `Created project '' (priority: )` +## Activation -### `project list` — list active +Triggered by "project X" / "switch to project X" / "we're working on X": -1. Run: `uv run skills/project/scripts/project.py list` -2. Echo JSON output. Format as: - ``` - Active projects: - - (priority: high) — - ``` -3. If empty → "No active projects." +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//` 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. -### `project show ` — display +**Already active:** if the same project is already active in this +conversation, don't re-create or re-read anything — just continue. -1. Run: `uv run skills/project/scripts/project.py show ` -2. Echo the full markdown file. +## Staying active -### `project status ` — change status +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. -1. Run: `uv run skills/project/scripts/project.py status ` -2. Echo: `Project '' is now .` +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. -### `project next ` — set next step +## Switching and ending -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 '' updated.` +- **"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. -### `project note ` — add a note +## Writing to memory.md -1. Read the project file. -2. Use `edit_file` to append a bullet under `## Poznámky`: - `- : ` -3. If `## Poznámky` does not exist, add it after the first heading. -4. Echo: `Note added to ''.` +Append only — never rewrite or reorder existing entries. One entry per +decision, dead end, or noteworthy piece of history: -### `project switch ` — session context +``` +- YYYY-MM-DD: +``` -1. Run `my(action="set", key="project_context", value="")`. -2. Echo: `Switched to project ''. 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. +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. -## Rules +## Maintaining state.md -- **Slug** = kebab-case from first 4 words of the name. Used as filename (`.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`. +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. + +## Artifacts + +Files generated while working on the project (documents, code, data exports, +reports) go in `workspace/projects//artifacts/` instead of scattered +elsewhere. Name them descriptively; no numbering or index file needed at +this scale. + +## Listing + +**"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 '' 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//` 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. diff --git a/skills/project/scripts/project.py b/skills/project/scripts/project.py deleted file mode 100644 index 6d9d7a1..0000000 --- a/skills/project/scripts/project.py +++ /dev/null @@ -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())