Files
ClaudeDo/docs/superpowers/specs/2026-08-10-findings-store-design.md
T

263 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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,514 KB | beim Arbeiten im Verzeichnis |
| `docs/explore-notes/` (6 Notes) | 1118 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 1236 Zeilen (~150450 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 = 58 % des Kontexts; Read =
6068 %, 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
```
<working-dir>/.claudedo/
INDEX.md # eine Zeile je Finding — die einzige Datei, die immer gelesen wird
traps/<slug>.md # ein Finding je Datei, ~150300 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
<26 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 `<list.working_dir>/.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/<slug>.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: `<abs>/.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 `<working-dir>/.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.