munin/REPOMAP.md

38 KiB

Repomap — Munin

Übersicht aller Dateien und Ordner mit ihrer Aufgabe.

munin/
├── main.go                     ← Einstiegspunkt. Liest die Konfiguration, prüft die
│                                 Pflichtangaben (SESSION_SECRET, ADMIN_TOKEN inkl.
│                                 Mindestlänge), öffnet Kontroll-Datenbank und
│                                 Workspace-Verwaltung (legt beim ersten Start den
│                                 Workspace "Intern" an), setzt die
│                                 Bestandteile zusammen (baueHandler, auch vom
│                                 Lasttest genutzt) und startet den Server.
│                                 Enthält außerdem die Server-Timeouts (gegen
│                                 Slowloris), die Body-Größenlimits und die
│                                 täglichen Aufgaben (Protokoll aufräumen,
│                                 Export), die über fuerJedenWorkspace alle
│                                 Workspaces erreichen. Tests: main_test.go,
│                                 Lasttest: last_test.go (nur mit -tags last).
│
├── go.mod / go.sum             ← Moduldefinition und Prüfsummen der Abhängigkeiten.
│
├── Dockerfile                  ← Zweistufiger Bau: Stufe 1 kompiliert mit dem
│                                 Go-Compiler, Stufe 2 packt nur das fertige Binary
│                                 in ein minimales Alpine-Abbild. Läuft als
│                                 gewöhnlicher Benutzer, mit Healthcheck und Volume
│                                 für die Datenbank.
│
├── .env.example                ← Vorlage für .env, mit Erklärung jeder Variable.
├── .gitignore / .dockerignore  ← Schließen Datenbank, Binary und Geheimnisse aus.
│
├── README.md                   ← Anleitung: Schnellstart, MCP einrichten, Werkzeuge,
│                                 Konfiguration, Sicherheit, Betrieb.
├── AGENTEN-GUIDE.md            ← Wie man KI-Werkzeuge anbindet und anspricht,
│                                 damit sie den Speicher tatsächlich nutzen:
│                                 Projektprompt zum Übernehmen und wohin er gehört
│                                 (AGENTS.md für OpenCode/Cline, CLAUDE.md mit
│                                 @AGENTS.md für Claude Code), slug-Regeln,
│                                 Muster für einzelne Aufgaben.
├── BETRIEB.md                  ← Betriebshandbuch für den Ernstfall: Server startet
│                                 nicht, Platte voll, Key oder Geheimnis geleakt,
│                                 Wiederherstellen aus der Sicherung, Updates.
├── AUSBAUSTUFEN.md             ← Was bewusst nicht gebaut wurde: Messwerte,
│                                 Begründung und die Auslöser, an denen man
│                                 merkt, dass es Zeit dafür wird.
├── LASTTEST.md                 ← Lasttest: Ausführung, Schalter, Ergebnisse mit
│                                 Datum und ihre Grenzen.
├── REPOMAP.md                  ← Diese Datei.
│
├── internal/
│   │
│   ├── store/                  ── Die einzige Stelle, die SQL kennt ──────────────
│   │   ├── store.go            ← Öffnet eine Workspace-Datei (Open), setzt die
│   │   │                         nötigen PRAGMAs (WAL, busy_timeout, foreign_keys)
│   │   │                         und wendet die Migrationen an. Erklärt auch, warum
│   │   │                         der Treiber modernc.org/sqlite ist: Er ist reines
│   │   │                         Go und erlaubt damit ein statisches Binary.
│   │   │
│   │   ├── control.go          ← Die zentrale Kontroll-Datenbank (OpenControl):
│   │   │                         Workspaces anlegen, umbenennen, löschen, dazu die
│   │   │                         Zuordnung Key-Prüfsumme → Workspace
│   │   │                         (ResolveKeyWorkspace). Das ist der erste Schritt
│   │   │                         jeder Anmeldung: Erst danach ist klar, welche
│   │   │                         Workspace-Datei den Key kennt.
│   │   │
│   │   ├── models.go           ← Die Datentypen (Project, APIKey, Entry, …), die
│   │   │                         Fehlerwerte (ErrNotFound, ErrConflict,
│   │   │                         ErrDuplicate) sowie ID- und Zeitbehandlung.
│   │   │                         NewID erzeugt sortierbare IDs in Go statt per
│   │   │                         AUTOINCREMENT — das hält den Umstieg auf eine
│   │   │                         andere Datenbank offen.
│   │   │
│   │   ├── projects.go         ← Projekte: anlegen, lesen, ändern, löschen.
│   │   │                         Enthält ValidSlug und Slugify sowie
│   │   │                         SetProjectWriteLimit für die eigene Obergrenze
│   │   │                         eines Projekts.
│   │   │
│   │   ├── keys.go             ← API-Keys. ResolveKeyHash ist der heißeste Pfad im
│   │   │                         Programm (jeder MCP-Request) und deshalb ein
│   │   │                         indizierter Lookup über die Prüfsumme, keine
│   │   │                         Schleife. Zurückgezogene Keys gelten als nicht
│   │   │                         vorhanden, der Datensatz bleibt fürs Protokoll.
│   │   │
│   │   ├── entries.go          ← Einträge. PutEntry legt an ODER aktualisiert —
│   │   │                         der Bezeichner entscheidet, und genau das ist der
│   │   │                         Mechanismus gegen Dubletten. Enthält das
│   │   │                         optimistische Sperren (ExpectedVersion) und das
│   │   │                         Nachladen der Schlagworte in einer Abfrage statt
│   │   │                         einer pro Eintrag. Außerdem:
│   │   │                         · snapshotEntryVersion sichert vor jeder Änderung
│   │   │                           die bisherige Fassung, in derselben Transaktion;
│   │   │                           EntryVersions/EntryVersionByNumber lesen sie
│   │   │                         · BulkUpdateTags ändert Schlagworte vieler
│   │   │                           Einträge auf einmal und überspringt IDs aus
│   │   │                           fremden Projekten
│   │   │                         · LinkGraph liefert Knoten und Kanten für die
│   │   │                           Graph-Ansicht
│   │   │                         Jede lesende Abfrage trägt nichtImPapierkorb
│   │   │                         (deleted_at IS NULL) — außer PutEntry, das
│   │   │                         einen Eintrag im Papierkorb bewusst sieht und
│   │   │                         ihn beim Überschreiben zurückholt.
│   │   │
│   │   ├── nutzung.go          ← Kennzahlen der Nutzungsübersicht: UsageSummaries
│   │   │                         (Schreibvorgänge der letzten 30 Tage aus dem
│   │   │                         Protokoll, Bestand aus entries — für ein oder
│   │   │                         alle Projekte in zwei Abfragen), dazu
│   │   │                         MissingLinks, UntaggedEntries, RecentlyUsedKeys.
│   │   │                         Keine eigene Zähltabelle.
│   │   │
│   │   ├── suchprotokoll.go    ← Was Agenten mit recall suchen (search_log):
│   │   │                         LogSearch, SearchStatsFor, EmptySearches,
│   │   │                         PruneSearchLog. Eigene Tabelle statt Protokoll,
│   │   │                         30 Tage, verschwindet mit dem Projekt.
│   │   │
│   │   ├── papierkorb.go       ← Papierkorb: MoveEntryToTrash (statt DELETE),
│   │   │                         ListTrash, RestoreFromTrash, PurgeFromTrash
│   │   │                         (nur was schon im Papierkorb liegt) und
│   │   │                         PurgeTrash für die tägliche Aufgabe. Die Frist
│   │   │                         TrashRetention (30 Tage) steht hier, damit
│   │   │                         Oberfläche und Aufräumen dieselbe Zahl nutzen.
│   │   │
│   │   ├── related.go          ← RelatedEntries: Nachbarn im Verweisgraphen, in
│   │   │                         beide Richtungen, bis zu zwei Schritte weit
│   │   │                         (Breitensuche, eine Abfrage je Richtung und Ebene).
│   │   │
│   │   ├── links.go            ← Erkennt [[bezeichner]] im Inhalt und ersetzt sie
│   │   │                         beim Rendern. Das ersetzt die frühere
│   │   │                         Ordnerhierarchie als Struktur zwischen Einträgen.
│   │   │
│   │   ├── audit.go            ← Protokoll: schreiben, lesen, aufräumen. Die
│   │   │                         Aufbewahrungsfrist ist nicht nur eine Frage des
│   │   │                         Plattenplatzes — das Protokoll enthält Adressen
│   │   │                         und Zugangsnamen. EntryWriteDetail vermerkt bei
│   │   │                         Schreibvorgängen "angelegt" oder "aktualisiert".
│   │   │
│   │   ├── migrations_control/
│   │   │   └── 001_init.sql    ← Schema der Kontroll-Datenbank: workspaces und
│   │   │                         key_index (Key-Prüfsumme → Workspace). Bewusst
│   │   │                         dünn — Inhalte liegen nie hier.
│   │   │
│   │   └── migrations/         ← Schema jeder Workspace-Datei
│   │       ├── 001_init.sql    ← Das Schema. Der Kopfkommentar nennt die Regeln
│   │       │                     für portables SQL, an die sich alle weiteren
│   │       │                     Migrationen halten sollen.
│   │       ├── 002_projekt_in_nebentabellen.sql
│   │                           ← Führt project_id auch in entry_tags und
│   │                             entry_links. Eine bewusste Redundanz gegen zwei
│   │                             gemessene Bremsen: das Schlagwortmenü lief über
│   │                             alle Einträge (6,7 ms bei jedem Seitenaufruf),
│   │                             und bei den Rückverweisen kippte durch ein
│   │                             ORDER BY die Verknüpfungsreihenfolge
│   │                             (3,1 ms statt 9 µs).
│   │       ├── 003_schreibbudget_je_projekt.sql
│   │                           ← Spalte write_limit_hourly an projects. Vorgabe 0
│   │                             heißt "keine eigene Angabe" — dann gilt der
│   │                             Standard aus WRITE_LIMIT_HOURLY. So muss die
│   │                             Migration keine Daten anfassen.
│   │       ├── 004_aenderungshistorie.sql
│   │                           ← Tabelle entry_versions für abgelöste Fassungen.
│   │                             Wird mit dem Eintrag gelöscht (ON DELETE CASCADE).
│   │       └── 005_papierkorb.sql
│   │                           ← deleted_at/deleted_by an entries und ein
│   │                             Teilindex nur über den Papierkorb. Löschen setzt
│   │                             nur noch die Markierung; CASCADE greift erst
│   │                             beim endgültigen Löschen.
│   │
│   ├── workspace/              ── Welche Datei gehört zu welcher Abteilung? ──────
│   │   └── workspace.go        ← Manager öffnet Workspace-Dateien bei Bedarf und
│   │                             hält sie offen, jeweils mit eigenem Suchindex.
│   │                             Datenbank und Index gehören zusammen, damit kein
│   │                             Index versehentlich an der falschen Datei hängt.
│   │                             Delete schließt eine Datei und entfernt sie.
│   │
│   ├── auth/                   ── Wer ist das? ────────────────────────────────────
│   │   ├── doc.go              ← Erklärt die beiden Wege hinein (API-Key im Header,
│   │   │                         Sitzungs-Cookie im Browser) und was hier bewusst
│   │   │                         NICHT liegt: die Frage, was jemand darf.
│   │   │
│   │   ├── keys.go             ← Erzeugen, Prüfsumme bilden, Form prüfen. Enthält
│   │   │                         die Rollen (read/write/admin) und die Begründung,
│   │   │                         warum SHA-256 statt bcrypt richtig ist.
│   │   │
│   │   ├── session.go          ← Signierte Sitzungs-Cookies (HMAC-SHA256). Der
│   │   │                         Cookie trägt nur eine Behauptung ("ich bin Key X"),
│   │   │                         nie den Key selbst. Die Sitzung des Plattform-
│   │   │                         Admins trägt einen Fingerabdruck des ADMIN_TOKEN,
│   │   │                         damit ein neues Token sie sofort beendet.
│   │   │
│   │   ├── csrf.go             ← CSRF-Tokens aus Sitzung und Geheimnis, ohne Zustand
│   │   │                         auf dem Server. Der Kopfkommentar erklärt den
│   │   │                         Angriff, gegen den das schützt.
│   │   │
│   │   ├── cookies.go          ← Setzt HttpOnly, SameSite und Secure an einer
│   │   │                         Stelle. Secure ist ohne Konfiguration an, außer
│   │   │                         bei localhost — damit die lokale Entwicklung über
│   │   │                         http weiter funktioniert.
│   │   │
│   │   ├── ratelimit.go        ← Zwei Begrenzungen auf einem gemeinsamen
│   │   │                         Fixed-Window-Zähler: RateLimiter sperrt eine
│   │   │                         Adresse nach zu vielen Fehlversuchen,
│   │   │                         WriteBudget deckelt die schreibenden
│   │   │                         MCP-Aufrufe je Projekt und Stunde. Letzteres
│   │   │                         hängt am Projekt und nicht am Key — ein
│   │   │                         Key-Limit ließe sich mit einem zweiten Key
│   │   │                         umgehen.
│   │   │
│   │   └── trustedproxy.go     ← Entscheidet, wann X-Forwarded-For geglaubt wird:
│   │                             nur bei einer über TRUSTED_PROXY hinterlegten
│   │                             Absenderadresse. Ohne Konfiguration wird der
│   │                             Header ignoriert — er ist frei fälschbar.
│   │
│   ├── entry/                  ── Wie sieht ein Eintrag aus? ─────────────────────
│   │   ├── entry.go            ← Markdown nach HTML (über internal/markdown),
│   │   │                         Auflösen der [[…]]-Verweise, Textausschnitte für
│   │   │                         Trefferlisten samt sicherer Hervorhebung (am
│   │   │                         längsten Wort der Anfrage, das im Text steht),
│   │   │                         die Zeile „Auch gesucht als: …" auslesen.
│   │   │
│   │   └── diff.go             ← Zeilenvergleich zweier Fassungen (DiffLines) als
│   │                             LCS ohne externe Abhängigkeit. Bei sehr langen
│   │                             Einträgen weicht er auf einen groben Vergleich
│   │                             aus (grobDiff), damit Speicher und Rechenzeit
│   │                             begrenzt bleiben.
│   │
│   ├── markdown/               ── Markdown nach HTML, eine Regel für alles ───────
│   │   └── markdown.go         ← Der gemeinsame goldmark-Renderer für Einträge und
│   │                             Hilfe: GFM (Tabellen, Checklisten,
│   │                             Durchstreichen, automatische Links), Roh-HTML
│   │                             gesperrt. Nur die Hilfe bekommt Überschrift-
│   │                             Anker (RenderMitAnkern) — mit gefalteten
│   │                             Umlauten über store.Slugify, damit die
│   │                             "?"-Links der Oberfläche darauf zeigen können.
│   │
│   ├── export/                 ── Sicherung als Markdown ─────────────────────────
│   │   ├── export.go           ← Schreibt jeden Workspace als Ordner
│   │   │                         (<workspace-id>/, mit _workspace.md) und darin
│   │   │                         jedes Projekt als Ordner voller
│   │   │                         Markdown-Dateien, mit allen Feldern im Dateikopf.
│   │   │                         Kennt selbst keine Datenbank: Der Aufrufer gibt
│   │   │                         die Workspace-Datei je Lauf mit.
│   │   │                         Gedacht als Git-fähige Sicherung: Ein Lauf ohne
│   │   │                         inhaltliche Änderungen fasst keine Datei an und
│   │   │                         erzeugt deshalb auch keinen Diff. Verwaiste
│   │   │                         Dateien gelöschter Einträge werden entfernt,
│   │   │                         fremde Dateien im Zielordner bleiben unberührt.
│   │   └── zip.go              ← Dieselben Dateien als ZIP für den Download.
│   │                             Erst alles aus der Datenbank sammeln, dann
│   │                             packen — sonst bekäme der Browser bei einem
│   │                             Fehler ein halbes Archiv statt einer Fehlerseite.
│   │
│   ├── importer/               ── Gegenstück zum Export ──────────────────────────
│   │   ├── archiv.go           ← Liest Markdown-Dateien aus einem ZIP-Archiv, mit
│   │   │                         Grenzen für Anzahl und ausgepackte Größe.
│   │   │                         Übergeht _index.md, versteckte Ordner, __MACOSX.
│   │   ├── parse.go            ← Kleiner Frontmatter-Leser (kein YAML-Paket):
│   │   │                         Munin-Export und fremde Köpfe (Hugo, Obsidian).
│   │   │                         Ohne Kopf: Bezeichner aus dem Dateinamen, Titel
│   │   │                         aus der ersten Überschrift.
│   │   └── importer.go         ← Importiere: Vorschau oder Schreiben, bei
│   │                             belegtem Bezeichner überspringen oder
│   │                             überschreiben (alter Stand in den Verlauf).
│   │                             Erkennt Unverändertes und den Papierkorb.
│   │
│   ├── search/                 ── Volltextsuche ──────────────────────────────────
│   │   ├── index.go            ← Invertierter Index mit BM25-Bewertung. Titel zählen
│   │   │                         dreifach, Schlagworte zweifach. Search für
│   │   │                         Agenten, SearchFuzzy (mit Tippfehlern) für die
│   │   │                         Weboberfläche.
│   │   │
│   │   ├── varianten.go        ← Wortvarianten je Suchwort, schwächer gewichtet
│   │   │                         als ein exakter Treffer: Wortanfang ("deploy" →
│   │   │                         Deployment), Endung ("deployen"), Teilwort
│   │   │                         ("prozess" → Releaseprozess), Tippfehler.
│   │   │                         Dazu Stoppwörter, nur auf der Anfrageseite.
│   │   │
│   │   └── registry.go         ← Ein Index je Projekt, verzögert aufgebaut. Getrennte
│   │                             Indizes statt eines gemeinsamen mit Filter: So wird
│   │                             die Abschottung strukturell, und die BM25-Werte
│   │                             werden nicht über Projektgrenzen hinweg verzerrt.
│   │                             Refresh liest nach jedem Schreiben aus der
│   │                             Datenbank nach, damit sich überholende Schreiber
│   │                             keinen alten Stand im Index hinterlassen. Jedes
│   │                             Projekt hat eine eigene Sperre, damit ein langer
│   │                             Aufbau nicht den ganzen Workspace aufhält.
│   │
│   ├── hilfe/                  ── Eingebaute Onboarding-Hilfe ───────────────────
│   │   ├── hilfe.go            ← Liste der Hilfeseiten (Reihenfolge, Titel) und
│   │   │                         das Rendern mit Überschrift-Ankern. Erklärt,
│   │   │                         warum die Hilfe im Binary steckt statt als
│   │   │                         Einträge in der Datenbank: Sie muss zur
│   │   │                         laufenden Fassung des Programms passen.
│   │   └── seiten/*.md         ← Die Inhalte, je Seite eine Datei. {{SERVER}} und
│   │                             {{PROJEKT}} werden beim Anzeigen ersetzt.
│   │
│   ├── web/                    ── HTTP: Browser und API ──────────────────────────
│   │   ├── context.go          ← Session-Typ (wer greift zu, was darf er) und die
│   │   │                         Ablage im Request-Context. CanAccessProject ist
│   │   │                         die Mandantentrennung der Weboberfläche. Die
│   │   │                         Can…-Methoden sind die einzige Stelle, an der
│   │   │                         der Workspace-Admin Rechte bekommt.
│   │   │
│   │   ├── middleware.go       ← Sitzungsprüfung (inkl. "gilt der Key noch?"),
│   │   │                         CSRF, Projektauflösung aus der URL, Rollenprüfung.
│   │   │                         Die Sitzung trägt die Workspace-ID signiert mit
│   │   │                         (sessionFromSubject); requireWorkspace hält einen
│   │   │                         Plattform-Admin ohne gewählten Workspace von den
│   │   │                         Projektseiten fern.
│   │   │                         Dazu die allgemeinen Schutzschichten
│   │   │                         SecurityHeaders und LimitBody sowie APIAuth für
│   │   │                         den MCP-Endpunkt.
│   │   │
│   │   ├── server.go           ← Der Server-Typ, alle Routen und das Rendern der
│   │   │                         Templates (erst in einen Puffer, dann senden —
│   │   │                         sonst gäbe es bei einem Fehler eine halbe Seite
│   │   │                         mit Status 200).
│   │   │
│   │   ├── handlers_auth.go    ← Anmelden, Abmelden, Startseite. Der
│   │   │                         Plattform-Admin ohne gewählten Workspace landet
│   │   │                         auf /admin/workspaces.
│   │   ├── handlers_entries.go ← Einträge auflisten, suchen, ansehen, bearbeiten.
│   │   │                         Dazu Vorschau (preview), Vorschläge für [[…]]
│   │   │                         (suggest), Fassungen und Vergleich, Graph-Daten
│   │   │                         Schlagworte für viele Einträge (bulk-tags) und
│   │   │                         viele Einträge in den Papierkorb (bulk-delete).
│   │   ├── handlers_tags.go    ← Seite "Alle Schlagworte" (/p/{projekt}/tags),
│   │   │                         nach Häufigkeit oder alphabetisch gruppiert. Die
│   │   │                         Seitenleiste zeigt nur die häufigsten (navTagLimit
│   │   │                         in server.go).
│   │   ├── handlers_nutzung.go ← Nutzungsseite je Projekt (/p/{projekt}/usage),
│   │   │                         für jede Rolle. Die Admin-Projektliste nutzt
│   │   │                         dieselben Zahlen (projectListItems).
│   │   ├── handlers_download.go
│   │   │                       ← Download als ZIP: ein Projekt (/p/{projekt}/
│   │   │                         export.zip, wer darf: Session.CanDownloadProject)
│   │   │                         oder der ganze Workspace (/admin/export.zip).
│   │   │                         Unabhängig von EXPORT_DIR, nichts bleibt liegen.
│   │   ├── handlers_import.go  ← ZIP-Upload für den Importer (/p/{projekt}/import),
│   │   │                         nur mit Rolle admin. Läuft im Server, damit der
│   │   │                         Suchindex im selben Zug nachgezogen wird.
│   │   ├── handlers_papierkorb.go
│   │   │                       ← Papierkorb je Projekt: ansehen, wiederherstellen,
│   │   │                         endgültig löschen, Filter nach löschendem Zugang.
│   │   │                         Alles nur mit Schreibrecht, und bewusst nur hier
│   │   │                         — über MCP gibt es kein Wiederherstellen.
│   │   ├── handlers_hilfe.go   ← Zeigt die eingebauten Hilfeseiten unter /hilfe
│   │   │                         für jede Sitzung. Setzt vorher Server-Adresse
│   │   │                         und Projekt-Bezeichner in die Beispiele ein
│   │   │                         (serverURL, mit geprüftem Host-Header).
│   │   ├── handlers_workspaces.go
│   │   │                       ← Workspaces auflisten, anlegen, wählen, umbenennen,
│   │   │                         löschen (nur Plattform-Admin). Löscht jemand den
│   │   │                         Workspace der eigenen Sitzung, wird die Auswahl
│   │   │                         zurückgesetzt, statt ihn auszusperren.
│   │   ├── handlers_workspace_admins.go
│   │   │                       ← Workspace-Admins (wa_…, AUSBAUSTUFEN 4.8)
│   │   │                         anlegen und zurückziehen, nur durch den
│   │   │                         Plattform-Admin, auf der Bearbeitungsseite des
│   │   │                         Workspace.
│   │   └── handlers_admin.go   ← Projekte, Zugänge, Protokollansicht, Export. Die
│   │                             Bearbeitungsseite je Projekt (Name,
│   │                             Beschreibung, Schreibbudget, Löschen) liegt
│   │                             hier — bewusst eine Seite je Projekt statt
│   │                             eines Auswahlfelds über alle: Nur so stehen die
│   │                             tatsächlichen Werte in den Feldern, und ein
│   │                             Speichern ohne Änderung überschreibt nichts.
│   │
│   └── mcpserver/              ── Die Hauptschnittstelle ────────────────────────
│       ├── server.go           ← Handler und Transport (Streamable HTTP), ein
│       │                         MCP-Server je Projekt. Enthält den Text der
│       │                         `instructions` — das wirksamste Stellrad für die
│       │                         Qualität der Antworten, weil er bei jedem Client
│       │                         im Kontext steht.
│       │
│       ├── kuratiert.go        ← Kuratierte Einträge ändert remember nur mit
│       │                         override_curated und Begründung; die Änderung
│       │                         landet auf der Nutzungsseite zur Prüfung.
│       │
│       ├── dubletten.go        ← Vor dem Anlegen: Gibt es einen Eintrag mit
│       │                         ähnlichem Titel? Dann nichts schreiben, sondern
│       │                         candidates zurückgeben (force legt doch an).
│       │
│       └── tools.go            ← Die sechs Werkzeuge: recall, get, list, related,
│                                 remember, forget. Alte Einträge tragen in den
│                                 Antworten "stale": true. Jedes ist eine Closure
│                                 über genau ein Projekt und kann strukturell
│                                 nicht an ein anderes.
│
├── templates/                  ← HTML der Weboberfläche. partials.html enthält die
│                                 gemeinsamen Blöcke "kopf" und "fuss", die jede
│                                 Seite einbindet — kein verschachteltes Layout.
│
├── static/
│   ├── style.css               ← Das gesamte Aussehen, ohne Framework. Farben als
│   │                             Variablen ganz oben, helle und dunkle Darstellung.
│   ├── confirm.js              ← Bestätigungsabfrage vor dem Löschen. Liest das
│   │                             Attribut data-confirm, statt onclick="" im HTML zu
│   │                             verwenden — Inline-JavaScript verbietet die
│   │                             Content-Security-Policy.
│   ├── preview.js              ← Reiter "Schreiben"/"Vorschau" im Editor. Holt das
│   │                             HTML vom Server, statt Markdown im Browser zu
│   │                             rendern. Ohne JavaScript bleibt das Formular
│   │                             unverändert nutzbar.
│   ├── autocomplete.js         ← Vorschläge nach "[[" im Textfeld, per Tastatur
│   │                             und Maus bedienbar.
│   ├── graph.js                ← Selbst geschriebene Kräftesimulation und
│   │                             SVG-Darstellung für die Graph-Ansicht. Zoom zieht
│   │                             Knoten auseinander, Namen nur, wo Platz ist.
│   │                             Texte nur über textContent, nie innerHTML.
│   ├── tagfilter.js            ← Filterfeld auf der Seite "Alle Schlagworte".
│   └── img/                    ← Logo in mehreren Fassungen (hell, dunkel, ohne
│                                 Hintergrund). Die Anmeldeseite wählt je nach
│                                 Farbschema die passende.
│
├── scripts/
│   └── backup.sh               ← Sichert control.db und jede Workspace-Datei
│                                 einzeln über sqlite3 .backup in einen
│                                 zeitgestempelten Ordner; RETENTION_DAYS räumt
│                                 alte Sicherungen auf.
│
└── data/                       ← Daten (nicht im Repository, als Volume gemountet)
    ├── control.db              ← Kontroll-Datenbank: Workspaces, Key-Zuordnung
    └── workspaces/
        └── <workspace-id>.db   ← Eine Datei je Workspace

Tests

Jedes Package hat seine Tests direkt daneben. Sie laufen mit go test ./... ohne weitere Vorbereitung — die Datenbanktests legen jeweils eine echte SQLite-Datei in einem Testverzeichnis an, damit auch Migrationen und Fremdschlüssel geprüft werden.

Ausnahme ist der Lasttest last_test.go: Er braucht Zeit und lastet die Maschine aus und läuft deshalb nur auf Wunsch, mit go test -tags last -run TestLast -v . (Schalter für Agentenzahl, Dauer usw. oben in der Datei). Er misst über echtes HTTP und MCP drei Aufstellungen (ein Projekt, mehrere Projekte in einem Workspace, mehrere Workspaces) und prüft am Ende, dass kein Schreibvorgang verloren ging und der Suchindex aktuell ist. Ausführung, Schalter und Ergebnisse: LASTTEST.md.

Datei Schwerpunkt
internal/auth/auth_test.go Signatur, Manipulation, Ablauf, CSRF, Key-Form, Rollen, Cookie-Flags, Rate-Limiting, X-Forwarded-For
internal/store/nutzung_test.go Zählung nach Art und Weg samt Zeitfenster, Bestand ohne Papierkorb, Verweise ins Leere, aktive Zugänge
internal/store/papierkorb_test.go Jeder lesende Weg blendet den Papierkorb aus (Einzelabruf, Liste mit Filtern, Suchindex/Export, Zählen, Schlagworte, Rückverweise, Graph, Sammelbearbeitung); Wiederherstellen, Zurückholen durch Überschreiben, Konflikt, endgültiges Löschen, Frist
internal/store/store_test.go Migrationen, Slug-Prüfung, Abschottung, Dublettenvermeidung, optimistisches Sperren, Verweise, Protokoll, Änderungshistorie
internal/store/control_test.go Kontroll-Datenbank: Standard-Workspace bleibt stabil, Auflistung, Zuordnung Key → Workspace
internal/workspace/workspace_test.go Manager hält Dateien offen, trennt Workspaces, schließt sauber
internal/entry/entry_test.go HTML-Escaping, Verweisauflösung, Textausschnitte, sichere Hervorhebung
internal/markdown/markdown_test.go GFM-Erweiterungen, kein Roh-HTML und keine javascript:-Links, Anker nur in der Hilfe
internal/entry/diff_test.go Zeilenvergleich, Grenzfälle, Ausweichen bei sehr langen Einträgen
internal/search/search_test.go Bewertung, Wortanfang, Aktualisieren und Entfernen
internal/search/varianten_test.go Wortanfang neben exaktem Treffer, Komposita, Endungen, Stoppwörter, Tippfehler nur in SearchFuzzy
internal/search/bench_test.go Laufzeit einer Suche bei 10.000 Einträgen und 20.000 Wörtern
internal/search/registry_test.go Index folgt der Datenbank, auch wenn sich Schreiber überholen, ein Eintrag gleich wieder gelöscht wird oder während des Aufbaus geschrieben wird; ein langsamer Aufbau bremst andere Projekte nicht
internal/mcpserver/gleichzeitig_test.go Viele Agenten schreiben über MCP gleichzeitig denselben Eintrag: keine verlorene Fassung, Index aktuell
internal/store/related_test.go Verwandte Einträge über zwei Ebenen, Papierkorb als Sackgasse, Grenze für veraltete Einträge, übernommene Zeitstempel beim Anlegen
internal/importer/importer_test.go Rundlauf Export → ZIP → Import ohne Verlust, auch mit dem ZIP aus dem Download, Vorschau, Überspringen/Überschreiben, Papierkorb, doppelte Bezeichner, fremde Köpfe, Grenzen des Archivs
internal/export/export_test.go Dateikopf und YAML-Maskierung, Aufräumen verwaister Dateien, Stabilität über mehrere Läufe
internal/web/download_test.go ZIP-Download: Inhalt und Protokolleintrag, nur Admins, kein fremdes Projekt, Workspace-ZIP für Plattform- und Workspace-Admin, sichere Dateinamen
internal/web/import_test.go Import mit Vorschau und Übernehmen, Suchindex nachgezogen, ein Protokolleintrag, nur für admin, Body-Grenze je Route
internal/web/web_test.go Anmeldung, zurückgezogene Keys, CSRF, Projektabschottung, Workspace-Trennung, Rollen, Header, Hilfeseiten samt eingesetzter Adresse
internal/mcpserver/mcpserver_test.go Werkzeugbestand, instructions, Dublettenvermeidung (gleicher Bezeichner und ähnlicher Titel), Synonyme, Suchprotokoll, Konflikte, Rollen, Projektabschottung
internal/store/suchprotokoll_test.go Kennzahlen und Anfragen ohne Treffer, Zeitfenster, Aufräumen, verschwindet mit dem Projekt
internal/hilfe/hilfe_test.go Seitenliste und Dateien passen zusammen, interne Links führen zu vorhandenen Seiten, kein Roh-HTML

Datenfluss: Agent ruft Wissen ab

KI-Werkzeug  POST /mcp  { "method": "tools/call", "params": { "name": "recall", … } }
    → web/middleware.go (SecurityHeaders, LimitBody)
    → web/middleware.go (APIAuth)          Key → Prüfsumme
        → store/control.go                  (ResolveKeyWorkspace) → Workspace
        → workspace/workspace.go            (Get) → dessen Datei und Suchindex
        → store/keys.go                     (ResolveKeyHash) → Projekt + Rolle
                                            in den Context; Fehlversuche in die Sperre
    → mcpserver/server.go (ServeHTTP)      Projekt aus dem Context → dessen MCP-Server
    → mcpserver/tools.go (recall)
        → search/registry.go                Index dieses Projekts (beim ersten Mal
                                            aus der Datenbank aufgebaut)
        → search/index.go (Search)          BM25 über Wortvarianten (varianten.go)
        → store/entries.go (EntryBySlug)    Treffer nachladen
        → entry/entry.go (Snippet)          Textausschnitt je Treffer
        → store/suchprotokoll.go (LogSearch) Anfrage und Trefferzahl
    → JSON-Antwort mit Trefferliste und Hinweis zum weiteren Vorgehen

Datenfluss: Agent legt Wissen ab

KI-Werkzeug  POST /mcp  { … "name": "remember", "arguments": { "slug": …, "content": … } }
    → web/middleware.go (APIAuth)          wie oben
    → mcpserver/tools.go (remember)
        → requireWrite()                    Rolle read wird hier abgewiesen
        → dubletten.go (pruefeDubletten)    neuer Bezeichner, ähnlicher Titel?
                                            → candidates zurück, nichts schreiben
        → kuratiert.go (istKuratiert)       kuratiert und kein override_curated?
                                            → judgment_required, nichts schreiben
        → store/entries.go (PutEntry)       eine Transaktion:
                                              · Bezeichner vorhanden? → aktualisieren
                                              · ExpectedVersion prüfen → ggf. Konflikt
                                              · Schlagworte und Verweise ersetzen
                                              · Herkunft (agent) und Zugangsname setzen
        → search/registry.go (Refresh)      Index nachziehen
        → store/audit.go (LogAudit)         wer, was, wann
    → Antwort: Bezeichner, neue Fassung, angelegt oder aktualisiert

Datenfluss: Mensch bearbeitet im Browser

Browser  POST /p/plattform/e/deployment/save
    → web/middleware.go (SecurityHeaders, LimitBody)
    → web/middleware.go (requireSession)   Cookie prüfen → Key aus der Datenbank
                                            laden → gilt er noch? → CSRF-Token prüfen
    → web/middleware.go (requireProject)   Projekt aus der URL; fremdes Projekt → 404
    → web/middleware.go (requireWrite)     Rollenprüfung
    → web/handlers_entries.go (handleEntrySave)
        → store/entries.go (PutEntry)       Herkunft: human, Autor: Key-Name
        → search/registry.go (Refresh)
        → store/audit.go (LogAudit)
    → Weiterleitung zur Detailansicht