Files
km/docs/ingest-gitea.md
T
eltonandClaude Fable 5.1 4986ef0575 Baue den Index als SQLite mit FTS5 und sqlite-vec, dazu die Suche
Entscheidung Andreas 2026-09-04 (spine/core#8): kein Datenbankdienst. Der
Index ist eine abgeleitete Sicht auf die Markdown-Ablage in einer Datei im
Volume, jederzeit neu baubar. image/km/index.py zerlegt die Dateien in
Chunks (Chunking aus info/ai/scripts/index_docs.py übernommen), hält
Volltext in FTS5 und Vektoren aus bge-m3 in sqlite-vec, inkrementell über
SHA-256 je Datei. image/km/search.py sucht Volltext führend, Vektor
ergänzend, mit Filtern vor dem Ranking (0 Treffer statt gesperrt).

Ingest: Kommentare bei der Erstbefüllung repo-weit statt je Vorgang —
die erste Fassung hing bei live/live nach 371 Vorgängen; jetzt 2175 in zwei
Minuten. Gemessen: Volltext über 61.000 Chunks in 3 s, Vektoren 0,31 s je
Chunk auf der CPU-Box. Kein Bytecode mehr im Repo.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 18:04:35 +02:00

105 lines
5.1 KiB
Markdown

# Gitea-Ingest — Tickets und Kommentare in die Ablage
Umsetzung von [spine/core#13](https://git.42i.org/spine/core/issues/13).
Code: `image/km/ingest_gitea.py`. Stand: Prototyp, läuft am Baum, noch nicht
als Dienst.
## Was er tut
Er liest Issues, Pull Requests und deren Kommentare aus einer Gitea-Instanz
und legt **je Vorgang eine Markdown-Datei** in der km-Ablage ab:
```
<ablage>/<namespace>/vorgaenge/<owner>/<repo>/<nummer>.md
```
Die Datei ist das, was km über den Vorgang weiß — lesbar ohne den Dienst, in
SilverBullet blätterbar, für die Suche indexierbar. Sie besteht aus:
| Teil | Inhalt |
|---|---|
| Frontmatter | Forge, Namespace, Klassifikation, Repo, Nummer, Art (issue/pull), Titel, Zustand, Autor, Beteiligte, Labels, Milestone, Zuweisung, erstellt/aktualisiert/geschlossen, URL, **Verweise** |
| Stand-Block | der `tldr`-Block aus dem Ticketkopf, wenn vorhanden, vor dem Body |
| Body | der Ticket-Text |
| Kommentare | alle, in Reihenfolge, mit Autor und Zeit; „(bearbeitet)" wenn nachträglich geändert |
**Verweise** sind das, was über eine Volltextsuche hinausgeht: jedes
`owner/repo#N`, jedes nackte `#N` (im eigenen Repo) und jede Ticket-URL in Body
oder Kommentaren wird zu einem Eintrag `verweise:` im Frontmatter — also zu
einer Kante, nicht einer Zeichenkette. Aufzählungen wie `42i/intern#193 / #198`
werden dem qualifizierten Repo zugeordnet, nicht dem eigenen.
## Wie er läuft
```bash
# Erstbefüllung oder Abgleich einzelner Repos
AGENT_PERSONA=elton image/km/ingest_gitea.py --ablage /srv/km/ablage \
--repo spine/core --repo 42i/intern --repo live/live
# alle Repos einer Organisation
image/km/ingest_gitea.py --ablage /srv/km/ablage --org spine --org hive
# zweite Forge, eigener Namespace
KM_GITEA_URL=https://git.home KM_NAMESPACE=familie KM_CLASSIFICATION=home \
image/km/ingest_gitea.py --ablage /srv/km/ablage --org ahmann
```
- **Inkrementell.** Je Repo merkt er sich in einer Standdatei
(`<ablage>/../km-ingest-state.json`, außerhalb der Ablage) den Zeitpunkt des
letzten vollständigen Durchlaufs und fragt Gitea nur nach seither geänderten
Vorgängen (`since`, Gitea filtert nach `updated`). Ein geänderter Vorgang
wird **komplett neu geschrieben** — ein umgeschriebener Body ist eine
Änderung des Stands, kein Anhang.
- **Idempotent.** Dieselbe Eingabe ergibt dieselbe Datei; unveränderte Dateien
werden nicht angefasst. Damit ist `--full` zugleich der Abgleich aus
spine/core#17: wiederholbar, mit belegbarem „zuletzt geprüft um".
- **Abbruch ohne Lücke.** Der Stand wird erst nach vollständigem Durchlauf
eines Repos gesetzt. Bricht ein Lauf ab, holt der nächste dieselben Vorgänge
noch einmal — wiederholen ist billig, Lücken sind es nicht.
- **Zugang** kommt aus OpenBao (`secret get <persona> git username|password`),
wie beim Gitea-Helfer der Agenten. Nichts davon erscheint in Ausgabe,
Kommandozeile oder Datei. Der Ingest **schreibt nichts nach Gitea** (die
Festlegung aus #13: Vorgänge bleiben in der Forge, km liest).
## Gemessen am 2026-09-04
| Repo | Vorgänge | Dauer | Anmerkung |
|---|---|---|---|
| spine/core | 20 | 6 s | Erstbefüllung |
| spine/core | 0 gesehen | 0,3 s | inkrementell, nichts geändert |
| 42i/intern | 1165 | ~3 min | Erstbefüllung, noch ein API-Aufruf je Vorgang für die Kommentare |
| live/live | 2175 | ~2 min | Erstbefüllung mit repo-weitem Kommentar-Endpunkt (ein Aufruf je 50 Kommentare) |
| elf Repos (live, intern, spine/*, hive/*) | 2241 | 94 s | ein Lauf |
Die erste Fassung holte die Kommentare je Vorgang und blieb bei live/live
nach 371 Vorgängen an einer offenen Verbindung hängen. Seitdem gilt: bei der
Erstbefüllung kommen die Kommentare **repo-weit** (`/issues/comments`, älteste
zuerst), inkrementell je geändertem Vorgang. Dazu ein Verbindungs-Timeout und
Fortschritt je 100 Vorgänge auf stderr.
## Was noch fehlt (Reihenfolge)
1. **Bus-Anschluss.** Heute ist der Ingest ein Aufruf; er soll von
`wake.km` geweckt werden (spine/core#12: Gitea-Webhook → HTTP→Bus-Adapter
→ Bus → km) und dann den inkrementellen Lauf machen. Der periodische
Abgleich bleibt daneben Pflicht.
2. ~~Suche über die Ablage.~~ Gebaut: SQLite mit FTS5 und sqlite-vec, siehe
[index-suche.md](index-suche.md).
3. **Verknüpfung mit Doku und Memory.** Die `verweise:` sind erst Kanten
zwischen Vorgängen. Kanten zu Doku-Seiten (ein Ticket, das eine Seite
betrifft) und die Konsistenzprüfung gegen abgelegte Festlegungen kommen
mit der prüfenden Schreibfunktion.
4. **Commit und Push** der Ablage nach dem Lauf, wie `wissens_mcp/repo.py`
(Serialisierung, Rebase bei abgelehntem Push). Beim Prototyp am Baum läuft
das noch von Hand.
## Offen aus #13, hier vorgeschlagen
- **Historientiefe:** Erstbefüllung vollständig, weil sie ohnehin nur einmal
anfällt und drei Minuten je tausend Vorgänge kostet. Ein Stichtag spart
nichts Nennenswertes.
- **Zwei Forges:** getrennt über `--namespace` und `--classification`;
`git.home``familie`/`home`, `git.42i.org``42i`/`lan`. Mit einem km je
Instanz (hive/core#61) liegt die Familien-Forge ohnehin in einem anderen
Dienst.