diff --git a/docs/superpowers/specs/2026-08-10-findings-store-design.md b/docs/superpowers/specs/2026-08-10-findings-store-design.md new file mode 100644 index 00000000..57e0aef6 --- /dev/null +++ b/docs/superpowers/specs/2026-08-10-findings-store-design.md @@ -0,0 +1,262 @@ +# Findings-Store — Design + +Stand: 2026-08-10. + +Persistenter, projektgebundener Speicher für **Fallen und Invarianten**, den Agents im +Moment des Schmerzes selbst befüllen und in späteren Sessions vor dem Erkunden lesen. +Ziel ist Token-Ersparnis durch verhinderte Fehlläufe. + +--- + +## 1. Ausgangslage + +Auslöser war die Frage, ob ein Code-Knowledge-Graph (Graphify-Bauart) das +Wiedererkunden in jeder Session einspart. Die Messung sagt: nein, nicht in dieser Form. + +### Die Zahlen (aus `docs/usage-optimization.md`, 2026-08-05) + +- Kontext-Resend = **99,3 %** des Rohverbrauchs. Jeder Kontext-Token wird im Schnitt + **~425× nachberechnet**. Das gilt symmetrisch: für gespartes *und* für geladenes Wissen. +- **Explore-note ersetzt Exploration:** Note 4,6k tok in Turn 5 eines 60-Turn-Runs + → 4,6k × 55 ≈ **250k**. Ersparte Exploration (~8k tok Findings ab Turn 10) ≈ **400k**. + Netto ~150k, Faktor 1,6. Wird die Note geladen, ohne gebraucht zu werden: **−250k**. +- **Ein verhinderter Retry:** ~11 Mio Token. **27 % der Tasks brauchten einen Retry.** + +Faktor 27 zwischen den beiden Klassen. Deshalb ist der Inhalt dieses Stores **nicht** +„Architektur-Map, damit ich nicht neu erkunden muss" (400k-Klasse), sondern „Agent läuft +in die bekannte Falle und der Run ist Müll" (11-Mio-Klasse). + +### Was heute schon da ist + +| Ebene | Größe | Ladeverhalten | +|---|---:|---| +| `CLAUDE.md` (root) | 6,4 KB ≈ 1,6k tok | immer | +| `src/*/CLAUDE.md` (6×) | 1,5–14 KB | beim Arbeiten im Verzeichnis | +| `docs/explore-notes/` (6 Notes) | 11–18 KB, 92 KB gesamt | on demand, **ganze Datei** | +| Auto-Memory (`~/.claude/projects/.../memory/`) | 55 Dateien, 397 KB | Index immer (nur interaktiv) | + +Die Sektionen der Notes sind bereits 12–36 Zeilen (~150–450 tok) — also schon +knotengroß. Und `conpty-sessions.md` markiert Fallen bereits konventionell als +`## ⚠️ Gotcha: …` (5 Stück). Die anderen Notes tragen dieselbe Art Wissen unmarkiert im +Fließtext. **Das Wissen ist zu großen Teilen schon geschrieben, nur nicht einsammelbar.** + +--- + +## 2. Verworfene Alternativen + +- **Code-Knowledge-Graph (Graphify o. ä.).** Ein tree-sitter-Graph liefert Call-Kanten — + genau die Klasse, die Grep exakt und billig liefert (Grep = 5–8 % des Kontexts; Read = + 60–68 %, das Problem ist zu viel *lesen*, nicht Finden). Der wertvolle Inhalt ist + semantisch und nicht ableitbar („Approve = merge the whole unit", „no directory arg may + end in a separator"). Dazu: Build-/Staleness-Pipeline über N parallele Worktrees und + Merge-Konflikte auf einem Graph-Artefakt im Repo. AXAML wird ohnehin nicht geparst. +- **DB-Tabelle + UI-Editor.** Kuration passiert in VSCode, damit entfällt der einzige + Vorteil. Agent-Zugriff bräuchte ein Lese-Tool statt `Read`; Agents könnten veraltete + Einträge nicht selbst korrigieren. +- **Hook-Injektion der Findings in den System-Prompt.** Früh und groß in den Prefix ist + das teuerste Muster überhaupt (Befund 5), auch wenn nichts davon gebraucht wird. +- **Post-Merge-Trigger.** Ursprünglicher Entwurf. Hinfällig, weil der Aufrufer des Tools + den Kontext bereits hat — siehe §4. +- **Retro-Distill aus alten Transkripten.** Zurückgestellt, siehe §8. + +--- + +## 3. Speicher + +``` +/.claudedo/ + INDEX.md # eine Zeile je Finding — die einzige Datei, die immer gelesen wird + traps/.md # ein Finding je Datei, ~150–300 tok +``` + +`maps/` ist reserviert, aber **nicht Teil von v1**. + +Ordner liegt im Repo-Root, das er beschreibt — maximale Erkennbarkeit, reist bei +Bedarf mit. `.claudedo` ist als Name frei; die Worktrees liegen unter dem *Geschwister*-Pfad +`.claudedo-worktrees`, und `TranscriptUsageReader` prüft auf das exakte Segment, also kein +Fehlalarm bei der Scope-Erkennung. + +### Finding-Format + +```markdown +--- +slug: conpty-arg-quoting +scope: src/ClaudeDo.Worker/Planning +source-task: 5d627df8 +verified-against: e10634b +--- + +# Ein Verzeichnis-Argument, das auf `\` endet, frisst alle folgenden Argumente + +<2–6 Sätze: was passiert, warum es nicht danach aussieht, was man stattdessen tut.> +``` + +### INDEX.md + +```markdown +# Findings + +- [conpty-arg-quoting](traps/conpty-arg-quoting.md) — Dir-Arg auf `\` escapet sein eigenes Quote +- [shared-worktree-partial-commit](traps/shared-worktree-partial-commit.md) — nie blank committen +``` + +Reine Routing-Ebene: Titel + einzeiliger Aufhänger + Pfad. Ablauf im Agenten: +Index lesen (~700 tok) → ein bis zwei Findings lesen (~400 tok), statt 4 600 für eine +ganze Note. + +### Einchecken oder nicht — Toggle je Liste + +Beim Anlegen einer Liste: **„`.claudedo` einchecken"**, Default **aus**. + +- **Aus** → `/.claudedo/` wird nach `.git/info/exclude` geschrieben (per-Clone, + unsichtbar fürs Repo, **keine getrackte Datei angefasst**). Mechanik existiert bereits in + `SessionSkillSeeder.AppendExcludeLineAsync` (auflösen über + `git rev-parse --git-path info/exclude`) — wiederverwenden, nicht neu bauen. +- **An** → nichts tun, der Nutzer commitet den Ordner selbst. + +### Worktree-Regeln (hart) + +- **Geschrieben wird ausschließlich im Haupt-Checkout, nie in einem Worktree.** Sonst + entstehen `INDEX.md`-Merge-Konflikte über parallele Tasks — exakt das Problem, das oben + gegen den Graph-Ansatz spricht. +- **Gelesen wird über den absoluten Pfad auf den Haupt-Checkout.** Eine Regel für beide + Toggle-Stellungen; in „aus" existiert der Ordner im Worktree ohnehin nicht. +- Falls der Store getrackt ist und der Distiller committet: pfad-skopiert + (`git commit -- .claudedo`), nie blank — der Haupt-Checkout wird von parallelen + Sessions geteilt. +- Ein Finding je Datei heißt: parallele Schreiber kollidieren nicht. Umkämpft ist nur + `INDEX.md`; das Tool serialisiert dessen Rewrite prozessintern. + +--- + +## 4. Schreibweg — ein MCP-Tool + +`save_finding(slug, title, body, scope = "", list = "")` am bestehenden `claudedo`-MCP-Server. + +Signatur folgt den zwei test-erzwungenen Konventionen des Servers: jeder optionale +Parameter hat einen C#-Defaultwert, und das Tool gibt kein blankes `Task` und keine +nullable Payload zurück (→ `docs/explore-notes/external-mcp.md`). Beschreibungsstil nach +`External/McpToolDocs.cs`. + +### Welcher Store wird beschrieben? + +Der MCP-Server ist global, der Store projektgebunden — die Zuordnung muss explizit sein: + +1. **Autonomer Run:** aus dem Aufrufkontext (`TaskRunMcpContext`) → Task → Liste → + `working_dir`. Der `list`-Parameter wird ignoriert. +2. **Interaktive Session ohne Task-Kontext:** `list` (Name oder Id) entscheidet. Fehlt er + und es existiert **genau eine** Liste, wird diese genommen; bei mehreren gibt das Tool + einen Fehler zurück, der die verfügbaren Listen nennt. Nie raten. + +Geschrieben wird immer in `/.claudedo/` — also den Haupt-Checkout, +auch wenn der Aufrufer in einem Worktree läuft. + +Bewusst ein **dummes Schreib-Tool**, kein „generiere mir Findings"-Tool: der Aufrufer hat +den Kontext bereits. Ein zweiter Claude-Lauf (RefineRunner-Bauart) würde ihn aus +Transkripten rekonstruieren — teurer und schlechter. + +**Genau ein Tool.** Gelesen wird mit `Read` auf die Dateien; das kostet nichts und braucht +kein Tool. Der Server exponiert bereits ~60 Tools, eines mehr ist im Prefix Rauschen — +fünf wären es nicht. + +**Verhalten:** +- Gleicher `slug` → **überschreiben**, nicht anlegen. Dedupe gehört ins Tool, nicht in die + Prompt-Disziplin. +- Schreibt `traps/.md` **und** aktualisiert die Zeile in `INDEX.md` atomar. +- `verified-against` wird vom Tool aus dem aktuellen HEAD des Haupt-Checkouts gesetzt, + nicht vom Aufrufer. +- `source-task` wird aus dem Aufrufkontext gesetzt, wenn vorhanden (autonome Runs), + sonst leer (interaktive Sessions). + +### Aufrufwege + +1. **Im Run.** Ein Task-Agent tritt in eine Falle und hält sie fest. Höchste Qualität — + er hat gerade Turns dafür verbrannt. Billigster Weg, kein zusätzlicher Prozess. +2. **Merge-Helper-Abschlussphase.** Der List-Handler sweept am Ende nach, was ihm über den + Batch hinweg auffiel. Er sieht Diffs, nicht das Straucheln — daher Ergänzung zu (1), + kein Ersatz. +3. **Interaktive Sessions.** Der `claudedo`-MCP ist global registriert, das Tool steht + dort ohne Weiteres zur Verfügung. +4. **VSCode.** Nutzer korrigiert und löscht direkt in den Dateien. + +### Aufnahmehürde + +Ein Finding ist nur, was **dauerhaft**, **nicht offensichtlich** und **verhaltensändernd** +ist: „X sieht aus wie Y, ist aber Z — mach stattdessen W". Ein gefixter Bug ist **kein** +Finding, das ist Git-Historie. + +Diese Hürde steht in der Tool-Beschreibung. Sie ist kein Stilhinweis, sondern die +Betriebsbedingung: `INDEX.md` wird immer gelesen; bei ~15 tok je Zeile sind 50 Findings +≈ 750 tok, 300 Findings ≈ 4 500 tok — ab da kostet der Index so viel wie früher eine ganze +Note und die Ersparnis ist weg. Deshalb zusätzlich eine **Warnschwelle bei 80 Einträgen**, +die das Tool im Ergebnis zurückmeldet. + +--- + +## 5. Leseweg + +ClaudeDo hängt einen Satz an den System-Prompt seiner eigenen Runs +(`--append-system-prompt`, wie heute schon für andere Zwecke genutzt): + +> Findings-Index für dieses Projekt: `/.claudedo/INDEX.md`. Lies ihn, bevor du den +> Code erkundest, und öffne nur die Findings, die zu deiner Aufgabe passen. + +~30 Token, komplett in ClaudeDo gekapselt, **keine CLAUDE.md wird angefasst** — weder die +des Projekts noch die globale. + +Interaktive Sessions bekommen diesen Zeiger nicht (ClaudeDo startet sie nicht). Sie können +schreiben, aber nicht automatisch lesen. Wer das will, setzt selbst eine Zeile in seine +globale CLAUDE.md — außerhalb des Scopes dieses Features. + +--- + +## 6. UI + +Ein Button an der Liste: **„Findings öffnen"** → öffnet `/.claudedo/` im +Datei-Explorer bzw. der Standardanwendung. **Kein In-App-Editor** — Kuration passiert in +VSCode. + +Dazu der Toggle aus §3 im Dialog „Liste anlegen" und in den Listen-Einstellungen. + +--- + +## 7. Scope-Grenze (bewusst akzeptiert) + +Die Kapselung in ClaudeDo deckelt den Lese-Nutzen auf den Agent-Anteil: laut Messung +**18,4 %** des Verbrauchs (interaktiv = 81,6 %). Das ist kein Argument gegen das Feature — +Retries passieren ausschließlich auf der Agent-Seite, und dort sitzt der 11-Mio-Hebel. +Aber „Token-Ersparnis" heißt hier: Ersparnis in den 18 %, nicht auf dem Konto insgesamt. + +--- + +## 8. Nicht in v1 + +- **`maps/`** — Subsystem-Maps. Ordner reserviert, Inhalt später. Die bestehenden + `docs/explore-notes/` bleiben unangetastet. +- **Retro-Distill aus Transkripten.** Erst interessant, wenn belegt ist, dass Findings + überhaupt wirken. Rohmaterial bleibt verfügbar: vollständiges NDJSON je Run unter + `~/.todo-app/logs/{taskId}_run{N}.ndjson`, `ResultMarkdown` in `task_runs`, + `WorktreeEntity.BaseCommit/HeadCommit/MergeCommit` für den nachträglichen Diff. +- **Ernte der bestehenden `⚠️ Gotcha:`-Sektionen** aus `docs/explore-notes/` in den Store. + Naheliegender erster Füllstand, aber eine eigene, manuelle Aktion. +- **Wirkungsmessung** (welches Finding wurde je gelesen, hat es einen Retry verhindert). + Braucht ein eigenes Konzept; Dateien tragen keinen Zähler. +- **Automatischer Staleness-Check** gegen `verified-against`. Frontmatter trägt den Commit, + ausgewertet wird er in v1 nur vom Menschen. + +--- + +## 9. Testbarkeit + +- `save_finding`: Neuanlage, Überschreiben bei gleichem Slug, Index-Update, Slug-Validierung + (kein Pfad-Escape), Warnschwelle bei 80 Einträgen, paralleler Index-Rewrite. +- Store-Auflösung: Task-Kontext schlägt `list`; genau eine Liste ohne `list` → Treffer; + mehrere Listen ohne `list` → Fehler statt Raten; Schreibziel ist der Haupt-Checkout, + auch wenn der Aufrufer in einem Worktree sitzt. +- Die beiden `external-mcp`-Konventionen (Default je optionalem Parameter, kein blankes + `Task`/nullable Payload) sind test-erzwungen — die bestehenden Tests greifen automatisch. +- Toggle: `.git/info/exclude`-Zeile wird genau einmal geschrieben (Idempotenz) — die + bestehenden Tests zu `SessionSkillSeeder` sind die Vorlage. +- Kein Test darf die echte `claude`-CLI starten. +- `IWorkerClient` / `WorkerHub` / ViewModel-Konstruktoren ändern → handgeschriebene Fakes in + **beiden** Testprojekten mitziehen.