Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 31662dc3ff | |||
| 7fbb9a99df | |||
| 778764de4d | |||
| f7597b209c | |||
| 83018fcc7d |
+79
@@ -20,6 +20,85 @@ their date-only headings.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2.5.0 - 2026-09-02 - Versionsstelle: Kompatibilitaet statt Inhaltsmigration, Breaking-Change-Vermerk erzwungen
|
||||||
|
|
||||||
|
**Author:** Torben Nehmer
|
||||||
|
|
||||||
|
Die Doku des Stacks führte für die Wahl der Versionsstelle zwei Fragen zusammen, die nicht
|
||||||
|
dieselbe sind: *muss der Korpus migriert werden?* und *ist die neue Version ein
|
||||||
|
Drop-in-Ersatz?* An allen drei Stellen, die einer Session vor dem Bump begegnen - die Tabelle
|
||||||
|
in `stack-dev`, der Modul-Docstring in `version.py`, `INSTALL.md` § Version und Updates -
|
||||||
|
stand nur die erste. Nur `version bump --help` formulierte es richtig, also ausgerechnet die
|
||||||
|
Stelle, die man erst liest, wenn die Entscheidung gefallen ist. Bei `2.0.0` hat genau diese
|
||||||
|
Lücke zu `1.9.0` geführt (Gitea #26; der Eintrag zu `2.0.0` trägt den Fall im Volltext).
|
||||||
|
|
||||||
|
**Die Regel, jetzt an einer Stelle.** Neu ist
|
||||||
|
`instructions/dev/version-parts.md`: der Drop-in-Test in zwei Hälften - vorwärts (kopieren,
|
||||||
|
fertig, keine Handarbeit durch Nutzer oder Migrationsskript) und rückwärts (die alte Version
|
||||||
|
lässt sich noch zurückinstallieren) -, der Katalog der Brüche, die die Grenze bei völlig
|
||||||
|
unangetastetem `kb/` überschreiten (Update-Pfad, Artefaktname, Import-Name, Flags, Envvars,
|
||||||
|
Shape maschinengelesener Dateien), und der Fall `2.0.0` als Fallbeispiel. `instructions/dev/`,
|
||||||
|
also nicht in einer ausgelieferten Instanz - die Datei entscheidet, wer den Stack entwickelt.
|
||||||
|
Die ausgelieferten Artefakte tragen deshalb die Kurzform der Regel, aber keinen Verweis auf
|
||||||
|
sie: `instructions verify` meldet einen solchen Verweis, weil er nach `dist export` ins Leere
|
||||||
|
zeigen würde.
|
||||||
|
|
||||||
|
**Breaking Changes sind teuer, also genehmigungspflichtig.** `stack-dev` hat einen
|
||||||
|
Entscheidungspunkt bekommen: stellt sich eine Änderung als nicht drop-in heraus, wird nicht
|
||||||
|
aus eigener Initiative über die Grenze gebumpt. Vorgelegt werden der konkrete Bruch, was jede
|
||||||
|
bestehende Instanz von Hand tun muss, und die Alternativen - Bruch per Shim vermeiden,
|
||||||
|
aufschieben und mit dem nächsten bündeln, oder in einen kompatiblen und einen brechenden Teil
|
||||||
|
mit Deprecation-Fenster spalten -, dazu eine Empfehlung. Dann Freigabe abwarten.
|
||||||
|
|
||||||
|
**`--breaking` ist neu und bei jedem Grenzübertritt Pflicht.** Es schreibt eine
|
||||||
|
`**Breaking Change:**`-Zeile in den Eintrag, vor die Migrationszeile: das ist, was der Operator
|
||||||
|
einer bestehenden Instanz beim Lesen der Release-Notes zuerst braucht. `docs verify` prüft den
|
||||||
|
neuesten grenzüberschreitenden Eintrag darauf, analog zu `**Migration:** none required` und aus
|
||||||
|
demselben Grund - eine Prosa-Regel driftet, ein Marker nicht. Auf einem kompatiblen Bump wird
|
||||||
|
`--breaking` verweigert, damit es keine Gewohnheit wird. Beide Prüfungen bleiben getrennt:
|
||||||
|
`check_migration_for_boundary` fragt, ob der Inhalt sich bewegt, `check_breaking_change_for_boundary`,
|
||||||
|
ob dem Operator gesagt wurde, dass der Tausch keiner zum Drüberkopieren ist. Ein Rebranding
|
||||||
|
besteht die erste und fällt durch die zweite.
|
||||||
|
|
||||||
|
**Was ausdrücklich nicht passiert:** `docs verify` prüft weiterhin nicht, *ob die Stelle
|
||||||
|
richtig gewählt* wurde. Kein Validator unterscheidet ein umbenanntes Flag von einem neuen; das
|
||||||
|
bleibt Urteil, und dieser Eintrag existiert, weil das Urteil eine Anleitung braucht, keinen
|
||||||
|
Automaten.
|
||||||
|
|
||||||
|
Berührt: `instructions/dev/version-parts.md` (neu), `instructions/dev/stack-dev/SKILL.md`,
|
||||||
|
`tools/chemenu/version.py`, `tools/chemenu/commands/version_cmd.py`,
|
||||||
|
`tools/chemenu/commands/docs_verify.py`, `tools/CONTRACT.md`, `INSTALL.md`. Sechs neue Tests.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.4.1 - 2026-09-02 - INSTALL-MCP.md: Menschendoku fuer den Leseserver, und sie wird mit ausgeliefert
|
||||||
|
|
||||||
|
**Author:** Torben Nehmer
|
||||||
|
|
||||||
|
Nachtrag zu 2.4.0. Der Server war ausgeliefert und für Menschen nur über `tools/CONTRACT.md` und
|
||||||
|
eine Instruktion beschrieben — beide für Agenten geschrieben. `INSTALL-MCP.md` schließt das,
|
||||||
|
neben `INSTALL.md` und in derselben Form: Voraussetzungen, sechs Schritte, Verifikation,
|
||||||
|
Troubleshooting.
|
||||||
|
|
||||||
|
**Was drinsteht, das sonst nirgends zusammen stand:** die Client-Konfiguration als JSON-Block;
|
||||||
|
dass `WIKI_TRACE=0` nicht optional ist und wie die Startverweigerung aussieht; dass
|
||||||
|
`--host 0.0.0.0` bewusst nicht der Default ist; und der Verweis auf die
|
||||||
|
Traefik-ForwardAuth-Middleware unter <https://gitea.nehmer.net/torben/gitea-mcp-forward-auth>
|
||||||
|
samt dem, was sie erwartet (Bearer-Token gegen SHA-256-Hashes, `AUTH_PROXY_TOKEN_HASHES_DIR`).
|
||||||
|
Der Verifikationsabschnitt ist ein lauffähiges Skript — wörtlich so ausgeführt, bevor es hier
|
||||||
|
landete.
|
||||||
|
|
||||||
|
**`dist export` liefert die Datei mit.** `ROOT_FILES` ist eine Allowlist, also hätte sie sonst
|
||||||
|
keine Instanz erreicht: ein ausgelieferter Server, dessen Bedienungsanleitung im Ursprungs-Repo
|
||||||
|
zurückbleibt, ist einer, den der Empfänger durch Quelltextlesen findet. `INSTALL.md` bekommt
|
||||||
|
zusätzlich `CHEMENU_ROOT` und `WIKI_TRACE`/`WIKI_TRACE_DIR` in seine Konfigurationstabelle —
|
||||||
|
beide waren seit 2.3.0 bzw. seit jeher wirksam und dort nicht aufgeführt.
|
||||||
|
|
||||||
|
**Dateien:** `INSTALL-MCP.md` (neu), `INSTALL.md`, `README.md`,
|
||||||
|
`tools/chemenu/commands/dist_cmd.py`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2.4.0 - 2026-09-02 - MCP-Leseserver: zweiter Konsument auf demselben Kern
|
## 2.4.0 - 2026-09-02 - MCP-Leseserver: zweiter Konsument auf demselben Kern
|
||||||
|
|
||||||
**Author:** Torben Nehmer
|
**Author:** Torben Nehmer
|
||||||
|
|||||||
+239
@@ -0,0 +1,239 @@
|
|||||||
|
# MCP-Leseserver installieren
|
||||||
|
|
||||||
|
Dieses Dokument richtet sich an Menschen. Es beschreibt, wie der MCP-Leseserver eines Chemenu-
|
||||||
|
Wikis lokal läuft, wie ein Client ihn einbindet, und wie er hinter einer Authentifizierung
|
||||||
|
erreichbar wird. Der agent-seitige Betriebsablauf steht in
|
||||||
|
[instructions/mcp-read-server.md](instructions/mcp-read-server.md); die vollständige
|
||||||
|
Kommandoreferenz in [tools/CONTRACT.md](tools/CONTRACT.md).
|
||||||
|
|
||||||
|
**Was der Server ist.** Ein zweiter Konsument desselben Kerns, nicht ein zweites Programm. CLI
|
||||||
|
und Server rufen dieselben Funktionen auf; ein Golden-Test hält ihre Ausgaben gegeneinander.
|
||||||
|
Was `tools/wikitool search --json` liefert, liefert das MCP-Tool `search` auch — plus den
|
||||||
|
Commit, aus dem die Antwort berechnet wurde.
|
||||||
|
|
||||||
|
**Was er nicht ist.** Kein Schreibpfad. Es gibt kein Tool, das eine Seite anlegt, ändert oder
|
||||||
|
publiziert — nicht weil eine Liste gefiltert wird, sondern weil der Server nichts unter
|
||||||
|
`tools/chemenu/commands/` importiert. Die Funktionen sind aus diesem Prozess heraus nicht
|
||||||
|
erreichbar.
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
- Eine funktionierende Instanz nach [INSTALL.md](INSTALL.md) — inklusive `tools/.venv` und
|
||||||
|
`ripgrep`
|
||||||
|
- Python 3.11 oder neuer (wie die CLI)
|
||||||
|
|
||||||
|
## Schritt 1: Abhängigkeit installieren
|
||||||
|
|
||||||
|
Sie liegt bewusst nicht in `tools/requirements.txt`. Eine Instanz, die nur die CLI benutzt, soll
|
||||||
|
dafür nicht pydantic, starlette, uvicorn und cryptography mitinstallieren müssen.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tools/.venv/bin/pip install -r tools/requirements-mcp.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schritt 2: Lokal starten (stdio)
|
||||||
|
|
||||||
|
`stdio` ist der Weg zum Ausprobieren und für einen Client auf derselben Maschine: ein Prozess
|
||||||
|
pro Konsument, lokal gestartet, kein Netzwerk.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
WIKI_TRACE=0 tools/.venv/bin/python -m chemenu.mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Prozess spricht MCP über stdin/stdout und gibt für sich genommen nichts aus — das ist
|
||||||
|
richtig so. Gestartet wird er normalerweise nicht von Hand, sondern vom Client (Schritt 3).
|
||||||
|
|
||||||
|
**`WIKI_TRACE=0` ist nicht optional.** Telemetrie ist per Default an und schreibt nach
|
||||||
|
`reports/telemetry/` im Repo. Der Server **verweigert den Start**, solange das so ist, statt
|
||||||
|
still umzuleiten:
|
||||||
|
|
||||||
|
```
|
||||||
|
ERROR Telemetry is on and would write into the served checkout (...). Set WIKI_TRACE=0,
|
||||||
|
or point WIKI_TRACE_DIR outside the corpus.
|
||||||
|
```
|
||||||
|
|
||||||
|
Beide Auswege sind gleichwertig: `WIKI_TRACE=0` schaltet ab, `WIKI_TRACE_DIR=/var/log/chemenu`
|
||||||
|
lenkt um. Der Grund steht in Schritt 6 — der Sync darf `reports/` wegräumen.
|
||||||
|
|
||||||
|
## Schritt 3: Einen Client einbinden
|
||||||
|
|
||||||
|
Die Konfiguration folgt der üblichen MCP-Client-Form. Absolute Pfade, weil der Client kein
|
||||||
|
Arbeitsverzeichnis erbt:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"chemenu": {
|
||||||
|
"command": "/pfad/zur/instanz/tools/.venv/bin/python",
|
||||||
|
"args": ["-m", "chemenu.mcp"],
|
||||||
|
"cwd": "/pfad/zur/instanz/tools",
|
||||||
|
"env": {
|
||||||
|
"WIKI_TRACE": "0",
|
||||||
|
"CHEMENU_ROOT": "/pfad/zur/instanz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`CHEMENU_ROOT` sagt, **welches** Wiki bedient wird. Ohne die Variable nimmt der Server den
|
||||||
|
Checkout, in dem das Paket selbst liegt — für eine einzelne Instanz reicht das, aber wer mehrere
|
||||||
|
Korpora hat, setzt sie besser immer.
|
||||||
|
|
||||||
|
Danach kennt der Client fünf Werkzeuge:
|
||||||
|
|
||||||
|
| Tool | Was es beantwortet |
|
||||||
|
|---|---|
|
||||||
|
| `search` | Seiten in `kb/` nach Text, nach Frontmatter (`confidence<0.6`, `tags~k8s`) oder beidem |
|
||||||
|
| `types` | Welche Seitentypen dieses Wiki kennt |
|
||||||
|
| `describe_type` | Der vollständige Vertrag eines Typs: Felder, Pflichtangaben, Enums |
|
||||||
|
| `lint` | Strukturelle Befunde: kaputte Wikilinks, Waisen, Index-Drift, Schema-Lücken |
|
||||||
|
| `status` | Momentaufnahme: Seitenzahl, Verteilung auf Collections, Befundzahlen |
|
||||||
|
|
||||||
|
## Schritt 4: Ausgeliefert starten (streamable HTTP)
|
||||||
|
|
||||||
|
Der Transport für den Betrieb, und der einzige, vor den sich ein Reverse Proxy setzen kann.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
WIKI_TRACE=0 CHEMENU_ROOT=/srv/chemenu \
|
||||||
|
tools/.venv/bin/python -m chemenu.mcp \
|
||||||
|
--transport streamable-http --host 0.0.0.0 --port 8000
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Endpunkt ist dann `http://<host>:8000/mcp`.
|
||||||
|
|
||||||
|
`--host 0.0.0.0` ist bewusst nicht der Default. Ohne die Angabe bindet der Server auf Loopback,
|
||||||
|
was lokal richtig und im Container falsch ist — dort muss der Proxy ihn erreichen können. Wer
|
||||||
|
`0.0.0.0` setzt, muss also auch dafür sorgen, dass davor etwas steht (Schritt 5).
|
||||||
|
|
||||||
|
`sse` wird nicht angeboten. Es ist der abgelöste Remote-Transport; jetzt darauf zu bauen
|
||||||
|
verschiebt den Wechsel nur.
|
||||||
|
|
||||||
|
## Schritt 5: Authentifizierung davor
|
||||||
|
|
||||||
|
**Der Server authentifiziert nicht selbst, und das ist Absicht.** Nicht sauber
|
||||||
|
authentifizierte Zugriffe sollen den Python-Prozess gar nicht erst erreichen. Die
|
||||||
|
Authentifizierung ist eine Traefik-ForwardAuth-Middleware:
|
||||||
|
|
||||||
|
> **<https://gitea.nehmer.net/torben/gitea-mcp-forward-auth>**
|
||||||
|
|
||||||
|
Kurz, was sie tut: sie prüft `Authorization: Bearer <token>` gegen SHA-256-Hashes erlaubter
|
||||||
|
Tokens, antwortet `200` bei gültigem und `401` bei fehlendem oder falschem Token, und hält
|
||||||
|
`GET /healthz` immer offen. Klartext-Tokens liegen weder in der Konfiguration noch im Log — nur
|
||||||
|
Hashes und ein kurzer Fingerprint. Konfiguriert wird sie über
|
||||||
|
`AUTH_PROXY_TOKEN_HASHES_DIR` (ein Verzeichnis, eine Datei je Token-Hash — passend für ein
|
||||||
|
Kubernetes-Secret-Volume) oder `AUTH_PROXY_TOKEN_HASHES` (kommagetrennte Liste). Einzelheiten,
|
||||||
|
Referenzmanifeste und ein Testskript stehen im README dort.
|
||||||
|
|
||||||
|
Einen Token-Hash erzeugen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo -n "mein-token" | sha256sum | awk '{print $1}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rate Limiting gehört an dieselbe Stelle** — vor den Prozess, neben die Authentifizierung.
|
||||||
|
Nicht in den Iteration Budget Gate: der begrenzt eine *Agenten-Session* am unbemerkten Iterieren
|
||||||
|
über den Wiki-Zustand, weshalb Retrieval von ihm ausgenommen ist. Ihn als Rate Limiter zu
|
||||||
|
benutzen würde ihn dazu verwässern.
|
||||||
|
|
||||||
|
## Schritt 6: Den Korpus aktuell halten
|
||||||
|
|
||||||
|
Der Server liest den Arbeitsbaum. Ein veralteter Checkout antwortet selbstbewusst falsch —
|
||||||
|
deshalb trägt **jede Antwort den Commit**, aus dem sie berechnet wurde:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "commit": "<40-stelliger SHA>", "as_of": "<ISO-8601, UTC>", "count": 3, "...": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
Aktuell gehalten wird der Baum durch Polling, aus einem Timer neben dem Server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git -C "$CHEMENU_ROOT" fetch --quiet origin && \
|
||||||
|
git -C "$CHEMENU_ROOT" reset --hard --quiet origin/main
|
||||||
|
```
|
||||||
|
|
||||||
|
Polling statt Webhook, weil es keinen eingehenden Endpunkt und keine Signaturprüfung braucht —
|
||||||
|
eine kleinere Angriffsfläche als das, was es optimieren würde.
|
||||||
|
|
||||||
|
`reset --hard` ist dabei tragend und keine Bequemlichkeit: der Korpus-Cache verwendet einen
|
||||||
|
Parse wieder, solange der Commit gleich bleibt, und cacht einen **schmutzigen Baum überhaupt
|
||||||
|
nicht**. Ein abgedrifteter Checkout antwortet also zwar richtig, parst aber bei jeder Anfrage
|
||||||
|
neu — und stempelt jede Antwort mit `"commit": null`, weil sie keiner Revision entspricht.
|
||||||
|
|
||||||
|
## Verifikation
|
||||||
|
|
||||||
|
Läuft es? Der schnellste Test ohne Client — startet den Server über stdio, listet die Tools und
|
||||||
|
stellt eine Frage:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd tools && WIKI_TRACE=0 .venv/bin/python - <<'EOF'
|
||||||
|
import asyncio, os
|
||||||
|
from mcp import ClientSession, StdioServerParameters
|
||||||
|
from mcp.client.stdio import stdio_client
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
params = StdioServerParameters(
|
||||||
|
command=".venv/bin/python", args=["-m", "chemenu.mcp"],
|
||||||
|
env={"WIKI_TRACE": "0", "PATH": os.environ["PATH"]},
|
||||||
|
)
|
||||||
|
async with stdio_client(params) as (r, w):
|
||||||
|
async with ClientSession(r, w) as s:
|
||||||
|
await s.initialize()
|
||||||
|
print("Tools:", [t.name for t in (await s.list_tools()).tools])
|
||||||
|
out = await s.call_tool("status", {})
|
||||||
|
d = getattr(out, "structuredContent", None) or out.structured_content
|
||||||
|
print("Seiten:", d["pages"], "| Commit:", d["commit"])
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Erwartete Ausgabe, sinngemäß:
|
||||||
|
|
||||||
|
```
|
||||||
|
Tools: ['search', 'types', 'describe_type', 'lint', 'status']
|
||||||
|
Seiten: 176 | Commit: 576df2cdddc96614a7e6641e562022d52112d411
|
||||||
|
```
|
||||||
|
|
||||||
|
Gegen die CLI gegenprüfen — beide müssen dieselbe Antwort geben:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tools/wikitool search "<begriff>" --json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**`ERROR Telemetry is on and would write into the served checkout`** — erwartetes Verhalten,
|
||||||
|
kein Fehler in der Installation. `WIKI_TRACE=0` setzen oder `WIKI_TRACE_DIR` aus dem Korpus
|
||||||
|
heraus zeigen lassen (Schritt 2).
|
||||||
|
|
||||||
|
**`ModuleNotFoundError: No module named 'mcp'`** — Schritt 1 fehlt, oder der Client startet ein
|
||||||
|
anderes Python als das der Instanz. Im Client den absoluten Pfad auf `tools/.venv/bin/python`
|
||||||
|
setzen.
|
||||||
|
|
||||||
|
**`"commit": null` in jeder Antwort** — der bediente Baum hat uncommittete Änderungen. Entweder
|
||||||
|
läuft der Sync nicht, oder etwas schreibt in den Korpus, das dort nichts zu suchen hat. Der
|
||||||
|
Server selbst schreibt nie; ein Test prüft das, indem er alle fünf Tools aufruft und Dateibaum,
|
||||||
|
`HEAD` und `git status --porcelain` vorher/nachher vergleicht.
|
||||||
|
|
||||||
|
**`commit` nennt eine alte Revision** — der Sync aus Schritt 6 läuft nicht.
|
||||||
|
|
||||||
|
**Der Server antwortet anders als `wikitool`** — das ist ein Defekt, keine
|
||||||
|
Konfigurationsdifferenz: beide gehen durch dieselben Funktionen, und ein Golden-Test hält sie
|
||||||
|
zusammen. Zuerst prüfen, ob beide auf denselben Root zeigen; `CHEMENU_ROOT` ist leicht für einen
|
||||||
|
von beiden gesetzt und für den anderen nicht.
|
||||||
|
|
||||||
|
**Von außen nicht erreichbar** — ohne `--host 0.0.0.0` bindet der Server auf Loopback
|
||||||
|
(Schritt 4). Wenn er dann erreichbar ist, aber jeder Aufruf `401` bekommt, arbeitet die
|
||||||
|
Middleware aus Schritt 5 korrekt und das Token stimmt nicht.
|
||||||
|
|
||||||
|
## Was hier bewusst nicht steht
|
||||||
|
|
||||||
|
Deployment — Cluster, Ingress-Hosts, Secret-Store, FluxCD-Quelle. Das ist private Infrastruktur
|
||||||
|
und dieses Repo ist öffentlich.
|
||||||
|
|
||||||
|
Ein **Container-Image** für den Betrieb gibt es noch nicht; es ist als eigenes Vorhaben erfasst,
|
||||||
|
mitsamt den Entscheidungen, die dafür noch offen sind (Korpus im Image oder als Volume, wer den
|
||||||
|
Sync ausführt, Basis-Image, Healthcheck):
|
||||||
|
<https://gitea.nehmer.net/torben/chemenu/issues/37>. Bis dahin ist der Weg oben — venv,
|
||||||
|
`python -m chemenu.mcp`, Proxy davor — der vollständige.
|
||||||
+21
-2
@@ -7,6 +7,10 @@ Repo als Upstream** aufsetzen. Der agent-seitige Ablauf steckt in `instructions/
|
|||||||
nur die menschlichen Teile - für die vollständige Kommandoreferenz siehe
|
nur die menschlichen Teile - für die vollständige Kommandoreferenz siehe
|
||||||
[tools/CONTRACT.md](tools/CONTRACT.md).
|
[tools/CONTRACT.md](tools/CONTRACT.md).
|
||||||
|
|
||||||
|
Den optionalen **MCP-Leseserver** installiert und betreibt
|
||||||
|
[INSTALL-MCP.md](INSTALL-MCP.md): derselbe Korpus, lesend, für einen Konsumenten, der kein
|
||||||
|
Terminal auf dieser Maschine ist.
|
||||||
|
|
||||||
## Voraussetzungen
|
## Voraussetzungen
|
||||||
|
|
||||||
- Python 3.11 oder neuer
|
- Python 3.11 oder neuer
|
||||||
@@ -142,8 +146,21 @@ nicht erreichbarer Feed wird als Fehler gemeldet - **nie** als „aktuell".
|
|||||||
|
|
||||||
**Was die Versionsnummer aussagt:** kompatibel ist, was in der *linkesten von Null
|
**Was die Versionsnummer aussagt:** kompatibel ist, was in der *linkesten von Null
|
||||||
verschiedenen Stelle* übereinstimmt. `0.1.3 → 0.1.4` ist ein sicheres Update, `0.1.3 → 0.2.0`
|
verschiedenen Stelle* übereinstimmt. `0.1.3 → 0.1.4` ist ein sicheres Update, `0.1.3 → 0.2.0`
|
||||||
verlangt eine Migration, und ab `1.0.0` liest sich dieselbe Regel als das gewohnte „MAJOR heißt
|
nicht, und ab `1.0.0` liest sich dieselbe Regel als das gewohnte „MAJOR bricht". `version check`
|
||||||
Migration". `version check` sagt das direkt (`state: update` vs. `state: migration`).
|
sagt das direkt (`state: update` vs. `state: migration`).
|
||||||
|
|
||||||
|
Was diese Stelle beantwortet, ist **ob die neue Version ein Drop-in-Ersatz ist** - ob sich die
|
||||||
|
Maschinerie einfach darüberkopieren lässt und ob die alte danach noch zurückkann. Ob *Inhalt*
|
||||||
|
migriert werden muss, ist eine **zweite, unabhängige Frage**. Ein MAJOR-Sprung kann eine leere
|
||||||
|
Migrationskette haben und trotzdem Handarbeit verlangen: umbenannter Release-Feed, umbenanntes
|
||||||
|
Artefakt, umbenannter Import- oder Kommandoname, geänderte Envvar - `kb/` bleibt dabei
|
||||||
|
unangetastet, das Update ist trotzdem keins zum Drüberkopieren. Der Abschnitt „Sonderfall:
|
||||||
|
Update von 1.x auf 2.0.0" unten ist genau dieser Fall.
|
||||||
|
|
||||||
|
Deshalb stehen in den Release-Notes eines MAJOR zwei getrennte Zeilen, und beide sind vor dem
|
||||||
|
Update zu lesen: **Breaking Change:** sagt, was aufhört zu funktionieren und was diese Instanz
|
||||||
|
dagegen tun muss; **Migration:** sagt, ob und wie der Korpus umgeschrieben wird (`none required`,
|
||||||
|
wenn nicht). `tools/wikitool version notes` druckt den Eintrag.
|
||||||
|
|
||||||
### Eine Instanz aktualisieren
|
### Eine Instanz aktualisieren
|
||||||
|
|
||||||
@@ -223,6 +240,8 @@ behält Schema und Shape.
|
|||||||
| `WIKITOOL_SESSION_ID` | Scopt das Iteration-Budget-Gate auf eine Aufgabe statt auf ein Terminal | Parent-Process-ID (siehe [instructions/session-setup.md](instructions/session-setup.md)) |
|
| `WIKITOOL_SESSION_ID` | Scopt das Iteration-Budget-Gate auf eine Aufgabe statt auf ein Terminal | Parent-Process-ID (siehe [instructions/session-setup.md](instructions/session-setup.md)) |
|
||||||
| `WIKITOOL_UPDATE_URL` | Release-Feed, den `version check` abfragt | Wert aus `.wikitool-release.json`, sonst der Feed der Ursprungs-Instanz |
|
| `WIKITOOL_UPDATE_URL` | Release-Feed, den `version check` abfragt | Wert aus `.wikitool-release.json`, sonst der Feed der Ursprungs-Instanz |
|
||||||
| `WIKITOOL_UPDATE_TOKEN` | Gitea-Token für den Release-Feed | keiner - gegen `torben/chemenu` nicht nötig, nur für einen privaten Fork (siehe unten) |
|
| `WIKITOOL_UPDATE_TOKEN` | Gitea-Token für den Release-Feed | keiner - gegen `torben/chemenu` nicht nötig, nur für einen privaten Fork (siehe unten) |
|
||||||
|
| `CHEMENU_ROOT` | Auf welchen Korpus das Paket zeigt - für einen Aufrufer, der nicht im Checkout selbst liegt | der Checkout, in dem das Paket liegt (`tools/wikitool` verhält sich ohne die Variable unverändert) |
|
||||||
|
| `WIKI_TRACE` / `WIKI_TRACE_DIR` | Telemetrie abschalten bzw. aus dem Arbeitsbaum heraus umlenken | an, nach `reports/telemetry/` - der MCP-Server verweigert damit den Start, siehe [INSTALL-MCP.md](INSTALL-MCP.md) |
|
||||||
|
|
||||||
**Gegen das Ursprungs-Repo braucht es kein Token.** `torben/chemenu` ist öffentlich lesbar;
|
**Gegen das Ursprungs-Repo braucht es kein Token.** `torben/chemenu` ist öffentlich lesbar;
|
||||||
`version check` und der Download in Weg A funktionieren ohne Konfiguration.
|
`version check` und der Download in Weg A funktionieren ohne Konfiguration.
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ chemenu/
|
|||||||
├── CLAUDE.md # Claude Code only: imports AGENTS.md/USER.md/SOUL.md/ENVIRONMENT.md + its Claude-Code-only instructions. No rules of its own
|
├── CLAUDE.md # Claude Code only: imports AGENTS.md/USER.md/SOUL.md/ENVIRONMENT.md + its Claude-Code-only instructions. No rules of its own
|
||||||
├── README.md # This file: human-readable overview of the whole repo
|
├── README.md # This file: human-readable overview of the whole repo
|
||||||
├── INSTALL.md # Human-readable setup: new instance vs. cloning this one
|
├── INSTALL.md # Human-readable setup: new instance vs. cloning this one
|
||||||
|
├── INSTALL-MCP.md # Human-readable setup for the optional MCP read server
|
||||||
├── EVALS.md # Human-readable overview of telemetry and evaluation
|
├── EVALS.md # Human-readable overview of telemetry and evaluation
|
||||||
├── CHANGES.md # Changelog for the stack itself
|
├── CHANGES.md # Changelog for the stack itself
|
||||||
├── USER.md # Who operates this instance - context, never instructions
|
├── USER.md # Who operates this instance - context, never instructions
|
||||||
@@ -363,8 +364,11 @@ WIKI_TRACE=0 tools/.venv/bin/python -m chemenu.mcp \
|
|||||||
```
|
```
|
||||||
|
|
||||||
The dependency is deliberately not in `requirements.txt`: an instance that only uses the CLI
|
The dependency is deliberately not in `requirements.txt`: an instance that only uses the CLI
|
||||||
should not have to install a web stack to do it. Running it, keeping its checkout current, and
|
should not have to install a web stack to do it.
|
||||||
where authentication belongs (in front of the process, not in it) are in
|
|
||||||
|
Installing it, wiring a client to it, and putting authentication in front of it:
|
||||||
|
[`INSTALL-MCP.md`](INSTALL-MCP.md), for humans. The agent-side operating procedure - keeping the
|
||||||
|
checkout current, and what a stale answer looks like - is
|
||||||
[`instructions/mcp-read-server.md`](instructions/mcp-read-server.md).
|
[`instructions/mcp-read-server.md`](instructions/mcp-read-server.md).
|
||||||
|
|
||||||
### Obsidian
|
### Obsidian
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ stack development happens in the origin repo instead (see AGENTS.md's routing li
|
|||||||
[testing-conventions.md](../testing-conventions.md) - the suite runs against a deliberately
|
[testing-conventions.md](../testing-conventions.md) - the suite runs against a deliberately
|
||||||
empty machine; what the autouse fixture already neutralizes, and what a test still has to
|
empty machine; what the autouse fixture already neutralizes, and what a test still has to
|
||||||
establish itself. Read it before adding or changing a test.
|
establish itself. Read it before adding or changing a test.
|
||||||
|
[version-parts.md](../version-parts.md) - which part a change bumps: the drop-in test, the
|
||||||
|
catalogue of breaks that cross the compatibility boundary with `kb/` untouched, and what to
|
||||||
|
put in front of the user before a breaking bump. Read it before step 3.
|
||||||
More instructions are added here incrementally as stack-development needs come up - this
|
More instructions are added here incrementally as stack-development needs come up - this
|
||||||
list grows without needing this skill file to change shape.
|
list grows without needing this skill file to change shape.
|
||||||
3. **Raise the version, if the change ships.** A change under `tools/`, `types/`,
|
3. **Raise the version, if the change ships.** A change under `tools/`, `types/`,
|
||||||
@@ -59,18 +62,27 @@ stack development happens in the origin repo instead (see AGENTS.md's routing li
|
|||||||
```
|
```
|
||||||
|
|
||||||
Never edit `VERSION` or the entry's heading by hand - `bump` writes both, and `docs verify`
|
Never edit `VERSION` or the entry's heading by hand - `bump` writes both, and `docs verify`
|
||||||
fails a tree where they disagree. Pick the part by what an existing instance would have to do:
|
fails a tree where they disagree. Pick the part by whether the new version is a **drop-in
|
||||||
|
replacement** for the old one - not by whether content has to be migrated:
|
||||||
|
|
||||||
| Change | Part |
|
| Change | Part |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
| Fix, no interface change | `--patch` |
|
| Fix, no interface change | `--patch` |
|
||||||
| New capability, backwards compatible | `--minor` |
|
| New capability, still drop-in in both directions | `--minor` |
|
||||||
| **Existing content must be migrated** | `--major` |
|
| **Not a drop-in replacement** - any hand-work by the user or a migration script, or a downgrade that no longer works | `--major` |
|
||||||
|
|
||||||
A `--major` bump additionally needs a migration document for the new version - written per
|
Content migration is one way to land in the last row, not the definition of it: a rename of
|
||||||
[migrate-corpus.md](../../migrate-corpus.md) - or `--no-migration "<reason>"` when no content
|
the update path, the artefact, an import name, a flag or an envvar breaks a swap with `kb/`
|
||||||
actually has to change. `bump` refuses otherwise, and so does `docs verify`: an instance
|
entirely untouched. The full test, the catalogue of such breaks, and what to put in front of
|
||||||
learning that it must migrate, with nothing telling it how, is a dead end.
|
the user first are in [version-parts.md](../version-parts.md) - **read it before choosing
|
||||||
|
`--major`.**
|
||||||
|
|
||||||
|
A `--major` bump therefore needs two things recorded. `--breaking "<what stops working>"`
|
||||||
|
is required on every boundary-crossing bump; on top of it, a migration document for the new
|
||||||
|
version - written per [migrate-corpus.md](../../migrate-corpus.md) - or
|
||||||
|
`--no-migration "<reason>"` when no content actually has to change. `bump` refuses without
|
||||||
|
either, and so does `docs verify`: an instance learning that it must migrate, with nothing
|
||||||
|
telling it how, is a dead end.
|
||||||
|
|
||||||
Then write the entry's body - `bump` deliberately leaves it empty, the same way `new` leaves
|
Then write the entry's body - `bump` deliberately leaves it empty, the same way `new` leaves
|
||||||
the prose.
|
the prose.
|
||||||
@@ -90,6 +102,12 @@ stack development happens in the origin repo instead (see AGENTS.md's routing li
|
|||||||
- **Touches both stack code and wiki content in one session?** Apply this skill's rules to the
|
- **Touches both stack code and wiki content in one session?** Apply this skill's rules to the
|
||||||
code changes and the normal content skills' rules to the content changes - they are not
|
code changes and the normal content skills' rules to the content changes - they are not
|
||||||
mutually exclusive within a session, only per change.
|
mutually exclusive within a session, only per change.
|
||||||
|
- **The change turns out not to be a drop-in replacement?** Do not bump across the boundary on
|
||||||
|
your own initiative. Every existing instance pays for a breaking change once, by hand, so the
|
||||||
|
user decides whether it is worth that: show them what breaks, what an instance has to do about
|
||||||
|
it, and the alternatives (avoid the break with a shim, defer and batch it with the next one,
|
||||||
|
or split it behind a deprecation window), then recommend one and wait for a go-ahead.
|
||||||
|
[version-parts.md](../version-parts.md) step 4 has the full shape.
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
---
|
||||||
|
type: types/instruction.md
|
||||||
|
name: version-parts
|
||||||
|
description: Which part of the stack version a change bumps - the compatibility question (is the new version a drop-in replacement?) separated from the migration question (must existing content change?), plus what to do before a breaking bump.
|
||||||
|
---
|
||||||
|
# Pick the version part for a stack change
|
||||||
|
|
||||||
|
Two questions decide a version bump, and they are **not the same question**:
|
||||||
|
|
||||||
|
1. **Is the new version a drop-in replacement for the old one?** This is what the version
|
||||||
|
number itself says. Compatibility is read off the **leftmost non-zero component** - on this
|
||||||
|
stack (`2.x`) that is MAJOR, on a `0.x` stack it is MINOR. A bump that changes it is called
|
||||||
|
*boundary-crossing* below, because that is the term `version bump` and `docs verify` use in
|
||||||
|
their own messages.
|
||||||
|
2. **Must existing content be migrated?** This is a *consequence* a boundary crossing may or
|
||||||
|
may not have. `kb/` staying untouched does not make a change compatible, and
|
||||||
|
`version bump --no-migration` exists precisely because boundary-crossing bumps with an
|
||||||
|
untouched corpus are a real case.
|
||||||
|
|
||||||
|
Getting these backwards is how a genuinely breaking change ships as a MINOR. It happened once
|
||||||
|
already (see the case study at the end), which is why this file exists.
|
||||||
|
|
||||||
|
## When to run
|
||||||
|
|
||||||
|
Before every `tools/wikitool version bump` - the `stack-dev` skill's step 3 sends you here.
|
||||||
|
Read it in full the first time a change looks like it might be boundary-crossing; afterwards
|
||||||
|
the three-line test below is usually enough.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. **Apply the drop-in test.** The bump is boundary-crossing if **either** half fails:
|
||||||
|
|
||||||
|
- **Forward:** an existing instance can install the new machinery by copying `tools/`,
|
||||||
|
`types/`, `instructions/`, `AGENTS.md`, `VERSION` and `.wikitool-release.json` over itself,
|
||||||
|
and everything that worked before still works - with **no** hand-work by the user and **no**
|
||||||
|
migration script. Any step beyond the copy, however small, fails this half.
|
||||||
|
- **Backward:** having installed the new version, the user can put the old one back and be
|
||||||
|
where they started. A state file the old version cannot read, a rewritten corpus, a
|
||||||
|
renamed stamp - anything that makes the downgrade fail or leave a broken instance - fails
|
||||||
|
this half.
|
||||||
|
|
||||||
|
Content migration is one way to fail the forward half, not the definition of it.
|
||||||
|
|
||||||
|
2. **Check the catalogue** when the answer still feels like a judgment call. Each of these
|
||||||
|
crosses the boundary with `kb/` entirely untouched:
|
||||||
|
|
||||||
|
| What changed | Why the swap is not drop-in |
|
||||||
|
|---|---|
|
||||||
|
| The update path - `update_url`, the release feed, the repo it points at | The instance cannot repair its own `.wikitool-release.json`: it is machine-written, and invariant 1 forbids the hand-edit. The channel that would have told it to update is the channel that broke |
|
||||||
|
| The release artefact's name | Every download script and every pin against it breaks |
|
||||||
|
| The Python package's import name | `from <old> import ...` outside the shipped tree breaks |
|
||||||
|
| A command, subcommand, or flag that was removed or renamed | Scripts, CI workflows and instruction files calling the old spelling break |
|
||||||
|
| An environment variable's name | An instance configured through it silently loses the configuration |
|
||||||
|
| The shape of a machine-read file - `.wikitool-kb.json`, `.wikitool-release.json`, a generated index | The old version cannot read what the new one wrote, so the downgrade half fails even if the upgrade half passed |
|
||||||
|
| A type-spec's required fields | Existing pages stop validating - this one crosses *and* needs a content migration |
|
||||||
|
|
||||||
|
The catalogue is illustrative, not exhaustive. When something is not on it, go back to step 1.
|
||||||
|
|
||||||
|
3. **Otherwise pick the compatible part:**
|
||||||
|
|
||||||
|
| Change | Part |
|
||||||
|
|---|---|
|
||||||
|
| Fix, no interface change | `--patch` |
|
||||||
|
| New capability, drop-in in both directions | `--minor` |
|
||||||
|
|
||||||
|
4. **Stop and talk to the user before a boundary-crossing bump.** It is expensive in a way the
|
||||||
|
other two parts are not: every existing instance pays for it, once, by hand. Put in front of
|
||||||
|
them, in this order:
|
||||||
|
|
||||||
|
- **What breaks**, concretely - which file, which name, which call site.
|
||||||
|
- **What each existing instance must do**, as the steps they would actually run.
|
||||||
|
- **The alternatives**, so the break is a choice and not a side effect:
|
||||||
|
- *Avoid it* - keep the old name as an alias, read both file shapes, accept both flag
|
||||||
|
spellings. A compatibility shim carried for one release is usually cheaper than a
|
||||||
|
migration everyone runs.
|
||||||
|
- *Defer and batch it* - hold the break until the next boundary crossing, so instances pay
|
||||||
|
once instead of twice.
|
||||||
|
- *Split it* - ship the compatible half now, the breaking half later behind a deprecation
|
||||||
|
window that the changelog announces in advance.
|
||||||
|
- **Your recommendation**, with the trade-off named.
|
||||||
|
|
||||||
|
Then wait for an explicit go-ahead. Do not bump across the boundary on your own initiative.
|
||||||
|
|
||||||
|
5. **Record the break in the bump itself.** A boundary-crossing bump requires
|
||||||
|
`--breaking "<what breaks>"`, which writes a `**Breaking Change:**` line into the entry:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tools/wikitool version bump --major \
|
||||||
|
--title "<what changed>" \
|
||||||
|
--breaking "<what stops working, and what an instance must do about it>" \
|
||||||
|
--no-migration "<why no page has to change>" # only if that is true
|
||||||
|
```
|
||||||
|
|
||||||
|
`--breaking` is refused on a bump that crosses nothing, and required on one that does;
|
||||||
|
`docs verify` checks the newest boundary-crossing entry still carries the line. Write it for
|
||||||
|
the operator of an instance that has not read this repository: what stops working, and what
|
||||||
|
they do about it.
|
||||||
|
|
||||||
|
6. **Then answer the migration question separately.** Boundary-crossing and
|
||||||
|
content-migrating are independent:
|
||||||
|
|
||||||
|
- Content must change → write the migration document under `instructions/migrations/` per
|
||||||
|
[migrate-corpus.md](../migrate-corpus.md). `bump` finds it by its `migrates_to:` field.
|
||||||
|
- Content need not change → `--no-migration "<reason>"`, which records that in the entry.
|
||||||
|
|
||||||
|
Both are also needed by `docs verify`, for the same reason: an instance that learns it must
|
||||||
|
migrate, with nothing telling it how, is a dead end.
|
||||||
|
|
||||||
|
7. **Write the entry's body.** `bump` leaves it empty on purpose. A boundary-crossing entry
|
||||||
|
earns a paragraph that says *why this is breaking* - it is the one thing a future reader
|
||||||
|
cannot reconstruct from the diff, and it is what the next session in this position will read
|
||||||
|
instead of guessing.
|
||||||
|
|
||||||
|
## Decision points
|
||||||
|
|
||||||
|
- **The change ships no code - only `README.md`, `INSTALL.md`, `EVALS.md`, or `.gitea/`?** No
|
||||||
|
bump at all; CI's version gate is scoped to what changes behaviour.
|
||||||
|
- **A break you can see coming but are not making yet?** File it as an issue and let it
|
||||||
|
accumulate. Boundary crossings are cheaper in batches, and step 4's "defer" alternative is
|
||||||
|
only real if someone wrote the break down.
|
||||||
|
- **Unsure between MINOR and boundary-crossing?** It is boundary-crossing. The cost of an
|
||||||
|
unnecessary MAJOR is one extra release note; the cost of a MINOR that actually breaks is an
|
||||||
|
instance whose update path fails while its version number promised it would not.
|
||||||
|
- **The break only affects this repository, not a distributed instance** - something under
|
||||||
|
`instructions/dev/`, say? Then it is not a stack break at all: `dist export` never shipped it.
|
||||||
|
Judge by what an *exported* instance sees.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Applies to the stack version in `VERSION` - `tools/`, `types/`, `instructions/`, `AGENTS.md`
|
||||||
|
and the contracts. It says nothing about the content shape in `.wikitool-kb.json`, which is
|
||||||
|
advanced by `wikitool migrate done` and described by [migrate-corpus.md](../migrate-corpus.md),
|
||||||
|
and nothing about wiki content operations, which are logged in `kb/log.md` and carry no version
|
||||||
|
at all.
|
||||||
|
|
||||||
|
Choosing the part remains a judgment call, deliberately: `docs verify` checks that a
|
||||||
|
boundary-crossing entry *documents* its break and its migration, never that the part was chosen
|
||||||
|
correctly. No validator can tell a renamed flag from a new one.
|
||||||
|
|
||||||
|
## Case study: 2.0.0
|
||||||
|
|
||||||
|
The Chemenu rebranding renamed the repo, the release artefact and the Python package. No page
|
||||||
|
in `kb/` changed, so the first attempt was `1.9.0` - the migration question, answered correctly,
|
||||||
|
substituted for the compatibility question, which was never asked. Three things broke: every
|
||||||
|
existing instance's `update_url` pointed at a repo path that no longer existed and could not be
|
||||||
|
hand-repaired; the artefact name changed; the import name changed. The correct bump was
|
||||||
|
`--major --no-migration`, and the `CHANGES.md` entry for `2.0.0` carries the reasoning in full
|
||||||
|
under "Warum das trotzdem MAJOR ist". The error was caught by the user, not by the
|
||||||
|
documentation - which is what step 4 is for.
|
||||||
@@ -6,7 +6,7 @@ created: 2026-09-01
|
|||||||
modified: 2026-09-01
|
modified: 2026-09-01
|
||||||
related: [Chemenu]
|
related: [Chemenu]
|
||||||
sources: ['Source - Public Release, Corpus Purge and History Squash Session 2026-09-01', Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]
|
sources: ['Source - Public Release, Corpus Purge and History Squash Session 2026-09-01', Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]
|
||||||
confidence: 0.90
|
confidence: 0.70
|
||||||
confidence_base: 0.70
|
confidence_base: 0.70
|
||||||
provenance: sourced
|
provenance: sourced
|
||||||
summary: 'Private Korpusinhalte per Loeschung entfernen statt zu anonymisieren: ein Seitentitel ist der einzige Identifier eines Wikis, Umbenennen ist die volle page-lifecycle-Prozedur je Seite, Loeschen ist ein unterstuetztes Kommando.'
|
summary: 'Private Korpusinhalte per Loeschung entfernen statt zu anonymisieren: ein Seitentitel ist der einzige Identifier eines Wikis, Umbenennen ist die volle page-lifecycle-Prozedur je Seite, Loeschen ist ein unterstuetztes Kommando.'
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ created: 2026-09-01
|
|||||||
modified: 2026-09-01
|
modified: 2026-09-01
|
||||||
related: [Chemenu]
|
related: [Chemenu]
|
||||||
sources: ['Source - Public Release, Corpus Purge and History Squash Session 2026-09-01']
|
sources: ['Source - Public Release, Corpus Purge and History Squash Session 2026-09-01']
|
||||||
confidence: 0.50
|
confidence: 0.70
|
||||||
confidence_base: 0.70
|
confidence_base: 0.70
|
||||||
provenance: sourced
|
provenance: sourced
|
||||||
summary: Ein Repo mit Code- und Inhaltsanteil erhaelt zwei Lizenzen; die Grenze zwischen ihnen ist kein zweiter, gepflegter Pfadkatalog, sondern der ohnehin vorhandene Dateiplan des Distributionswerkzeugs.
|
summary: Ein Repo mit Code- und Inhaltsanteil erhaelt zwei Lizenzen; die Grenze zwischen ihnen ist kein zweiter, gepflegter Pfadkatalog, sondern der ohnehin vorhandene Dateiplan des Distributionswerkzeugs.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# kb/concepts/ - Index
|
# kb/concepts/ - Index
|
||||||
|
|
||||||
79 page(s). Regenerated by `wikitool index rebuild`.
|
80 page(s). Regenerated by `wikitool index rebuild`.
|
||||||
|
|
||||||
## All
|
## All
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
| [[Implementation Spectrum]] | architecture | Modularer Einführungspfad für die Funktionen von LLM Wiki v2, vom minimal tragfähigen Wiki bis zur vollen Umsetzung mit Automatisierung und Governance. | 2026-08-29 |
|
| [[Implementation Spectrum]] | architecture | Modularer Einführungspfad für die Funktionen von LLM Wiki v2, vom minimal tragfähigen Wiki bis zur vollen Umsetzung mit Automatisierung und Governance. | 2026-08-29 |
|
||||||
| [[Index Scaling]] | workflow | Skalierungsregeln für Indexseiten: Tabellenabschnitte ab 50 Einträgen teilen, ab 200 Seiten _meta/topic-map.md anlegen | 2026-08-29 |
|
| [[Index Scaling]] | workflow | Skalierungsregeln für Indexseiten: Tabellenabschnitte ab 50 Einträgen teilen, ab 200 Seiten _meta/topic-map.md anlegen | 2026-08-29 |
|
||||||
| [[Issue Label Scheme]] | decision | Zweiachsiges Pflicht-Labelschema fuer das Gitea-Board: prio/1..3 und size/XS..L, bewusst keine dritte Achse; die Regel liegt in instructions/dev/, weil sie keine ausgelieferte Instanz erreichen darf | 2026-08-31 |
|
| [[Issue Label Scheme]] | decision | Zweiachsiges Pflicht-Labelschema fuer das Gitea-Board: prio/1..3 und size/XS..L, bewusst keine dritte Achse; die Regel liegt in instructions/dev/, weil sie keine ausgelieferte Instanz erreichen darf | 2026-08-31 |
|
||||||
| [[Iteration and Cost Limits]] | workflow | Im Code durchgesetzte Obergrenze von 60 wikitool-Aufrufen je Session\, Loop-Breaker bei 3 identischen Wiederholungen\, Slot-Erstattung bei abgelehntem Aufruf und ein seit 1.5.0 gemessenes Kalibrierungsband von 5-15 (einfach) bzw. 20-35 (komplex) Aufrufen | 2026-08-31 |
|
| [[Iteration and Cost Limits]] | workflow | Im Code durchgesetzte Obergrenze von 60 wikitool-Aufrufen je Session, Loop-Breaker bei 3 identischen Wiederholungen, Slot-Erstattung, ein gemessenes Kalibrierungsband, und Retrieval sowie der MCP-Leseserver bleiben ausgenommen | 2026-09-02 |
|
||||||
| [[KB Migration]] | workflow | Migration des KB-Inhalts entlang einer geordneten Versionskette; abgegrenzt gegen offene Instanz-Aktionen, die in den doctor-Check gehoeren statt in die Kette | 2026-08-31 |
|
| [[KB Migration]] | workflow | Migration des KB-Inhalts entlang einer geordneten Versionskette; abgegrenzt gegen offene Instanz-Aktionen, die in den doctor-Check gehoeren statt in die Kette | 2026-08-31 |
|
||||||
| [[KB Stack Versioning]] | decision | Semantische Versionierung des Wiki-Stacks: VERSION beschreibt die Maschinerie, Kompatibilitaet ist die linkeste Nicht-Null-Komponente, und drei getrennte Dateien trennen Maschinerie, Herkunft und Content-Form | 2026-08-30 |
|
| [[KB Stack Versioning]] | decision | Semantische Versionierung des Wiki-Stacks: VERSION beschreibt die Maschinerie, Kompatibilitaet ist die linkeste Nicht-Null-Komponente, und drei getrennte Dateien trennen Maschinerie, Herkunft und Content-Form | 2026-08-30 |
|
||||||
| [[Knowledge Compounding]] | workflow | Effekt, bei dem Wissen im Wiki an Wert gewinnt, weil jede neue Quelle an bestehende, untereinander verwiesene Seiten anknüpft und sie ergänzt. | 2026-08-29 |
|
| [[Knowledge Compounding]] | workflow | Effekt, bei dem Wissen im Wiki an Wert gewinnt, weil jede neue Quelle an bestehende, untereinander verwiesene Seiten anknüpft und sie ergänzt. | 2026-08-29 |
|
||||||
@@ -50,6 +50,7 @@
|
|||||||
| [[Lint Workflow]] | workflow | Deterministischer Health-Check rund um wikitool lint; seit 1.7.2 maskiert es Code vor dem Notation-Match und zaehlt Zitat-Bloecke statt Zeilen | 2026-09-01 |
|
| [[Lint Workflow]] | workflow | Deterministischer Health-Check rund um wikitool lint; seit 1.7.2 maskiert es Code vor dem Notation-Match und zaehlt Zitat-Bloecke statt Zeilen | 2026-09-01 |
|
||||||
| [[LLM Wiki Pattern]] | architecture | Methodik für persönliches Wissensmanagement, bei der ein LLM aus Rohquellen ein dauerhaftes Wiki aufbaut - Wissen wird kompiliert statt per RAG neu hergeleitet. | 2026-08-29 |
|
| [[LLM Wiki Pattern]] | architecture | Methodik für persönliches Wissensmanagement, bei der ein LLM aus Rohquellen ein dauerhaftes Wiki aufbaut - Wissen wird kompiliert statt per RAG neu hergeleitet. | 2026-08-29 |
|
||||||
| [[Mass-Update Gate]] | workflow | Mass-Update Gate: publish endet mit 42 (Freigabe durch den Menschen noetig) ab 10 gezaehlten Dateien; generierte Dateien und work/ werden committet\, aber seit 1.5.0 nicht gezaehlt; freigegeben per --confirm <token> | 2026-09-01 |
|
| [[Mass-Update Gate]] | workflow | Mass-Update Gate: publish endet mit 42 (Freigabe durch den Menschen noetig) ab 10 gezaehlten Dateien; generierte Dateien und work/ werden committet\, aber seit 1.5.0 nicht gezaehlt; freigegeben per --confirm <token> | 2026-09-01 |
|
||||||
|
| [[MCP-Leseserver]] | architecture | Zweiter Konsument von chemenu ueber MCP: search/types/describe_type/lint/status auf chemenu.api.Corpus, strukturell ohne Schreibpfad, jede Antwort trage einen Commit-Stempel. | 2026-09-02 |
|
||||||
| [[Memory Lifecycle]] | architecture | Architektur des Wissenslebenszyklus mit Confidence Scoring, Supersession, Forgetting und Consolidation Tiers zur Pflege von Fakten über die Zeit. | 2026-08-29 |
|
| [[Memory Lifecycle]] | architecture | Architektur des Wissenslebenszyklus mit Confidence Scoring, Supersession, Forgetting und Consolidation Tiers zur Pflege von Fakten über die Zeit. | 2026-08-29 |
|
||||||
| [[Mesh Sync]] | pattern | Abgleichsmechanismus, der Beobachtungen paralleler Agenten in ein gemeinsames Wiki überführt; Last-Write-Wins mit Konflikterkennung und manuellem Eingriff. | 2026-08-29 |
|
| [[Mesh Sync]] | pattern | Abgleichsmechanismus, der Beobachtungen paralleler Agenten in ein gemeinsames Wiki überführt; Last-Write-Wins mit Konflikterkennung und manuellem Eingriff. | 2026-08-29 |
|
||||||
| [[Modbus]] | protocol | Industrielles Kommunikationsprotokoll von 1979 zur Anbindung speicherprogrammierbarer Steuerungen und Geräte über serielle oder TCP-Netze. | 2026-08-29 |
|
| [[Modbus]] | protocol | Industrielles Kommunikationsprotokoll von 1979 zur Anbindung speicherprogrammierbarer Steuerungen und Geräte über serielle oder TCP-Netze. | 2026-08-29 |
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
| [[Personalization Plane]] | architecture | Schicht fuer Instanz-Identitaet: USER.md/SOUL.md werden als Template ausgeliefert, im Setup-Interview woertlich befuellt und vom doctor-Check auf fehlend wie unbefuellt geprueft | 2026-08-31 |
|
| [[Personalization Plane]] | architecture | Schicht fuer Instanz-Identitaet: USER.md/SOUL.md werden als Template ausgeliefert, im Setup-Interview woertlich befuellt und vom doctor-Check auf fehlend wie unbefuellt geprueft | 2026-08-31 |
|
||||||
| [[Privacy and Governance]] | workflow | Rahmenwerk zur Absicherung von Wiki-Inhalten über Datenfilterung beim Ingest, Audit-Trail-Protokollierung und umkehrbare Massenoperationen. | 2026-08-29 |
|
| [[Privacy and Governance]] | workflow | Rahmenwerk zur Absicherung von Wiki-Inhalten über Datenfilterung beim Ingest, Audit-Trail-Protokollierung und umkehrbare Massenoperationen. | 2026-08-29 |
|
||||||
| [[Procedural Memory]] | architecture | Langlebigste Speicherschicht für Abläufe, Muster, bewährte Vorgehensweisen und Rezepte, gewonnen aus wiederholten semantischen Beobachtungen. | 2026-08-29 |
|
| [[Procedural Memory]] | architecture | Langlebigste Speicherschicht für Abläufe, Muster, bewährte Vorgehensweisen und Rezepte, gewonnen aus wiederholten semantischen Beobachtungen. | 2026-08-29 |
|
||||||
| [[Publish-Remote Gate]] | workflow | Drittes, im Code durchgesetztes Gate: publish bricht mit Exit 42 ab, wenn die aufgeloeste Push-URL nicht in einer optionalen, gitignoreten Allowlist steht; anders als die anderen Gates gibt es keinen Freigabe-Token. | 2026-09-01 |
|
| [[Publish-Remote Gate]] | workflow | Drittes, im Code durchgesetztes Gate: publish bricht mit Exit 42 ab, wenn die aufgeloeste Push-URL nicht in einer optionalen, gitignoreten Allowlist steht; doctor benennt seit 2026-09-02 den Gate-Zustand statt nur die Dateiexistenz | 2026-09-02 |
|
||||||
| [[Quality and Self-Correction]] | workflow | Automatische Qualitätssicherung für Wikis mit Inhaltsbewertung, Selbstheilung und Widerspruchserkennung. | 2026-08-29 |
|
| [[Quality and Self-Correction]] | workflow | Automatische Qualitätssicherung für Wikis mit Inhaltsbewertung, Selbstheilung und Widerspruchserkennung. | 2026-08-29 |
|
||||||
| [[Quality Scoring]] | pattern | Quantitative Bewertung aller vom LLM geschriebenen Inhalte nach struktureller Qualität, Vollständigkeit der Quellenangaben, Konsistenz mit dem Wiki und Themenabdeckung. | 2026-08-29 |
|
| [[Quality Scoring]] | pattern | Quantitative Bewertung aller vom LLM geschriebenen Inhalte nach struktureller Qualität, Vollständigkeit der Quellenangaben, Konsistenz mit dem Wiki und Themenabdeckung. | 2026-08-29 |
|
||||||
| [[RAG]] | architecture | Architekturmuster, bei dem LLMs die Generierung um Dokumente aus einer Wissensbasis anreichern. | 2026-08-29 |
|
| [[RAG]] | architecture | Architekturmuster, bei dem LLMs die Generierung um Dokumente aus einer Wissensbasis anreichern. | 2026-08-29 |
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ type: types/concept.md
|
|||||||
concept_type: workflow
|
concept_type: workflow
|
||||||
tags: [gate, safety, iteration-budget, loop-breaker]
|
tags: [gate, safety, iteration-budget, loop-breaker]
|
||||||
created: 2026-08-07
|
created: 2026-08-07
|
||||||
modified: 2026-08-31
|
modified: 2026-09-02
|
||||||
related: [Mass-Update Gate, Anti-Cramming Heuristic, Index Scaling, wikitool, Structural Enforcement over Documented Rule]
|
related: [Mass-Update Gate, Anti-Cramming Heuristic, Index Scaling, wikitool, Structural Enforcement over Documented Rule, MCP-Leseserver]
|
||||||
sources: [Source - LLM Improvements Production Agent Gaps 2026, Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31, Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31]
|
sources: [Source - LLM Improvements Production Agent Gaps 2026, Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31, Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31, Source - MCP Read Server Implementation Session 2026-09-02]
|
||||||
confidence: 0.88
|
confidence: 0.88
|
||||||
confidence_base: 0.88
|
confidence_base: 0.88
|
||||||
provenance: sourced
|
provenance: sourced
|
||||||
summary: Im Code durchgesetzte Obergrenze von 60 wikitool-Aufrufen je Session\, Loop-Breaker bei 3 identischen Wiederholungen\, Slot-Erstattung bei abgelehntem Aufruf und ein seit 1.5.0 gemessenes Kalibrierungsband von 5-15 (einfach) bzw. 20-35 (komplex) Aufrufen
|
summary: Im Code durchgesetzte Obergrenze von 60 wikitool-Aufrufen je Session, Loop-Breaker bei 3 identischen Wiederholungen, Slot-Erstattung, ein gemessenes Kalibrierungsband, und Retrieval sowie der MCP-Leseserver bleiben ausgenommen
|
||||||
---
|
---
|
||||||
# Iteration and Cost Limits
|
# Iteration and Cost Limits
|
||||||
|
|
||||||
@@ -48,6 +48,11 @@ Eine hart in Code durchgesetzte Obergrenze für die Anzahl der Tool-Aufrufe, die
|
|||||||
|
|
||||||
- Einzelne, begrenzte Einmalvorgänge, bei denen die Aufrufen-Anzahl inhärent festgelegt ist (z. B. ein einzelner `new entity`-Aufruf) - das Gate wird dort immer noch gleichmäßig angewendet, wird aber im Wesentlichen nie ausgelöst.
|
- Einzelne, begrenzte Einmalvorgänge, bei denen die Aufrufen-Anzahl inhärent festgelegt ist (z. B. ein einzelner `new entity`-Aufruf) - das Gate wird dort immer noch gleichmäßig angewendet, wird aber im Wesentlichen nie ausgelöst.
|
||||||
- Als Ersatz für das [[Mass-Update Gate]], das auf den *Schadensradius* eines `publish` (Dateien, die von einem einzelnen Push betroffen sind) begrenzt ist, nicht auf die *Iterationsmenge* über eine Sitzung - die beiden Gates beheben unterschiedliche Ausfallmodi und beide bleiben notwendig.
|
- Als Ersatz für das [[Mass-Update Gate]], das auf den *Schadensradius* eines `publish` (Dateien, die von einem einzelnen Push betroffen sind) begrenzt ist, nicht auf die *Iterationsmenge* über eine Sitzung - die beiden Gates beheben unterschiedliche Ausfallmodi und beide bleiben notwendig.
|
||||||
|
- Im [[MCP-Leseserver]]. Das Gate begrenzt eine Agenten-Session am unbemerkten Iterieren über den
|
||||||
|
Wiki-Zustand - deshalb ist Retrieval bereits generell ausgenommen (`SKIP_COMMANDS`) -, nicht
|
||||||
|
einen Nutzer, der oft sucht. Ein zu häufig suchender Nutzer ist ein Ressourcenproblem, das vor
|
||||||
|
den Serverprozess gehört (Rate Limiting), nicht in dieses Gate - beide zu vermischen würde es
|
||||||
|
zu einem Rate Limiter verwässern.[^s-mcp-read-server-implementation-session-2026-09-02]
|
||||||
|
|
||||||
## Verwandte Concepts
|
## Verwandte Concepts
|
||||||
|
|
||||||
@@ -63,6 +68,7 @@ Eine hart in Code durchgesetzte Obergrenze für die Anzahl der Tool-Aufrufe, die
|
|||||||
- **schützt:** [[Index Scaling]]
|
- **schützt:** [[Index Scaling]]
|
||||||
- **implementiert durch:** [[wikitool]]
|
- **implementiert durch:** [[wikitool]]
|
||||||
- **wendet an:** [[Structural Enforcement over Documented Rule]]
|
- **wendet an:** [[Structural Enforcement over Documented Rule]]
|
||||||
|
- **nimmt aus:** [[MCP-Leseserver]]
|
||||||
|
|
||||||
## Siehe auch
|
## Siehe auch
|
||||||
|
|
||||||
@@ -73,9 +79,11 @@ Eine hart in Code durchgesetzte Obergrenze für die Anzahl der Tool-Aufrufe, die
|
|||||||
- [[Source - LLM Improvements Production Agent Gaps 2026]]
|
- [[Source - LLM Improvements Production Agent Gaps 2026]]
|
||||||
- [[Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31]]
|
- [[Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31]]
|
||||||
- [[Structural Enforcement over Documented Rule]]
|
- [[Structural Enforcement over Documented Rule]]
|
||||||
|
- [[MCP-Leseserver]]
|
||||||
|
|
||||||
## Fußnoten
|
## Fußnoten
|
||||||
|
|
||||||
[^s-llm-improvements-production-agent-gaps-2026]: [[Source - LLM Improvements Production Agent Gaps 2026]]
|
[^s-llm-improvements-production-agent-gaps-2026]: [[Source - LLM Improvements Production Agent Gaps 2026]]
|
||||||
[^s-conversation-gate-counting-and-measured-calibration-session-2026-08-31]: [[Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31]]
|
[^s-conversation-gate-counting-and-measured-calibration-session-2026-08-31]: [[Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31]]
|
||||||
[^s-conversation-comma-bug-budget-refund-and-lint-report-path-session-2026-08-31]: [[Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31]]
|
[^s-conversation-comma-bug-budget-refund-and-lint-report-path-session-2026-08-31]: [[Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31]]
|
||||||
|
[^s-mcp-read-server-implementation-session-2026-09-02]: [[Source - MCP Read Server Implementation Session 2026-09-02]]
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
type: types/concept.md
|
||||||
|
concept_type: architecture
|
||||||
|
tags: [mcp, library-boundary, search, server]
|
||||||
|
created: 2026-09-02
|
||||||
|
modified: 2026-09-02
|
||||||
|
related: [wikitool, Publish-Remote Gate, Mass-Update Gate, Iteration and Cost Limits, Chemenu]
|
||||||
|
sources: [Source - MCP Read Server Implementation Session 2026-09-02]
|
||||||
|
confidence: 0.50
|
||||||
|
confidence_base: 0.50
|
||||||
|
provenance: sourced
|
||||||
|
summary: 'Zweiter Konsument von chemenu ueber MCP: search/types/describe_type/lint/status auf chemenu.api.Corpus, strukturell ohne Schreibpfad, jede Antwort trage einen Commit-Stempel.'
|
||||||
|
---
|
||||||
|
# MCP-Leseserver
|
||||||
|
|
||||||
|
**Typ:** Architecture
|
||||||
|
|
||||||
|
## Definition
|
||||||
|
|
||||||
|
Ein zweiter Konsument desselben Kerns, nicht ein zweites Programm: `tools/chemenu/mcp/` exponiert
|
||||||
|
`search`, `types`, `describe_type`, `lint` und `status` über MCP, indem es dieselben Funktionen
|
||||||
|
aufruft, die `wikitool` auch aufruft - vermittelt durch `chemenu.api.Corpus`, den In-Process-
|
||||||
|
Einstiegspunkt. Ein Golden-Test hält die Ausgaben beider Wege gegeneinander, statt darauf zu
|
||||||
|
vertrauen, dass sie übereinstimmen.
|
||||||
|
|
||||||
|
## Kernpunkte
|
||||||
|
|
||||||
|
- **Kein Schreibpfad, strukturell.** Weder der Server noch `chemenu.api` importiert etwas unter
|
||||||
|
`chemenu.commands` - `new`, `touch`, `xref`, `publish`, `migrate` sind aus diesem Prozess
|
||||||
|
heraus nicht erreichbar, statt aus einer Liste gefiltert zu werden. Ein Test importiert das
|
||||||
|
Servermodul in einem frischen Interpreter und prüft
|
||||||
|
`sys.modules`.[^s-mcp-read-server-implementation-session-2026-09-02]
|
||||||
|
- **Zwei Transports.** `stdio` zum Entwickeln und Testen ohne Netz; `streamable-http` für die
|
||||||
|
Auslieferung, der einzige, vor den sich ein HTTP-Reverse-Proxy setzen kann. `sse` ist über das
|
||||||
|
SDK erreichbar und wird bewusst nicht angeboten - der abgelöste Remote-Transport, jetzt darauf
|
||||||
|
zu bauen verschiebt den Wechsel nur.
|
||||||
|
- **Jede Antwort trägt den Commit, aus dem sie berechnet wurde** (`commit`, `as_of`). Ein
|
||||||
|
veralteter Checkout antwortet sonst selbstbewusst falsch. `null` heißt: der bediente Baum hat
|
||||||
|
uncommittete Änderungen, die Antwort entspricht keiner Revision. Der Stempel ist die Revision,
|
||||||
|
aus der die Seiten *tatsächlich* gelesen wurden, nicht die zum Zeitpunkt des Stempelns aktuelle
|
||||||
|
- ein Bug, der genau diesen Unterschied überging, wurde beim Schreiben des Golden-Tests selbst
|
||||||
|
gefunden und behoben.[^s-mcp-read-server-implementation-session-2026-09-02]
|
||||||
|
- **Telemetrie in den bedienten Baum wird beim Start verweigert**, nicht still umgeleitet. Der
|
||||||
|
Sync, der den Checkout aktuell hält (`git fetch && git reset --hard`), darf `reports/telemetry/`
|
||||||
|
wegräumen; ein Trace, der dort landet, wäre ein Verlust und eine stille Möglichkeit, den Baum
|
||||||
|
zu beschmutzen, dessen Sauberkeit der Korpus-Cache prüft.
|
||||||
|
- **Kein Iteration Budget Gate im Server.** Das Gate begrenzt eine Agenten-Session am unbemerkten
|
||||||
|
Iterieren über den Wiki-Zustand, nicht einen Nutzer, der oft sucht - Retrieval ist deshalb
|
||||||
|
bereits generell davon ausgenommen (siehe [[Iteration and Cost Limits]]). Rate Limiting gehört
|
||||||
|
stattdessen vor den Prozess, neben die Authentifizierung.
|
||||||
|
- **Authentifizierung ist Middleware, nicht Servercode.** Eine Traefik-ForwardAuth-Instanz
|
||||||
|
(Bearer-Token gegen SHA-256-Hashes) sitzt vor dem Prozess; nicht sauber authentifizierte
|
||||||
|
Zugriffe erreichen Python gar nicht erst.
|
||||||
|
- **Gemessen:** Korpus-Parse für 176 Seiten 265 ms → 54 ms (`CSafeLoader`),
|
||||||
|
`wikitool search` end-to-end 593 ms → 347 ms; die verbleibenden ~262 ms sind Modulimport und
|
||||||
|
entfallen im residenten Serverprozess, weil er ihn einmal pro Start statt pro Aufruf
|
||||||
|
zahlt.[^s-mcp-read-server-implementation-session-2026-09-02]
|
||||||
|
|
||||||
|
## Beispiele
|
||||||
|
|
||||||
|
- `search`/`types`/`describe_type`/`lint`/`status` als die fünf Tools - siehe
|
||||||
|
`tools/chemenu/mcp/server.py`.
|
||||||
|
- Der Korpus-Cache (`chemenu/corpus_cache.py`) hält einen Parse pro Commit und cacht nie einen
|
||||||
|
schmutzigen Arbeitsbaum - dieselbe Eigenschaft, die den Antwort-Stempel korrekt hält.
|
||||||
|
- `chemenu.api.Corpus`: nimmt einen Root, liefert exakt die `--json`-Formen der CLI, raised statt
|
||||||
|
zu exitieren.
|
||||||
|
|
||||||
|
## Wann zu verwenden
|
||||||
|
|
||||||
|
- Ein Konsument, der keine Shell auf der bedienenden Maschine ist, soll dieselben Fragen stellen
|
||||||
|
können wie ein Agent, der `wikitool` direkt aufruft.
|
||||||
|
- Mehrere gleichzeitige Leser eines Korpus, für die ein Prozess pro CLI-Aufruf (Modulimport,
|
||||||
|
Korpus-Parse) unnötigen Overhead bedeutet.
|
||||||
|
|
||||||
|
## Wann NICHT zu verwenden
|
||||||
|
|
||||||
|
- Als Ort für einen Schreibpfad - die Ingest-Queue (geplant, Issue #32) ist ein anderes Design
|
||||||
|
mit einer Quarantäne davor, nicht eine Erweiterung dieses Servers.
|
||||||
|
- Als Ersatz für den Iteration Budget Gate oder das Traefik-Rate-Limiting - beide bleiben
|
||||||
|
notwendig und leben an anderer Stelle.
|
||||||
|
|
||||||
|
## Verwandte Concepts
|
||||||
|
|
||||||
|
- [[Publish-Remote Gate]]
|
||||||
|
- [[Mass-Update Gate]]
|
||||||
|
- [[Iteration and Cost Limits]]
|
||||||
|
|
||||||
|
## Fußnoten
|
||||||
|
|
||||||
|
[^s-mcp-read-server-implementation-session-2026-09-02]: [[Source - MCP Read Server Implementation Session 2026-09-02]]
|
||||||
|
|
||||||
|
## Beziehungen
|
||||||
|
|
||||||
|
- **implementiert:** [[wikitool]]
|
||||||
|
- **verwandt mit:** [[Publish-Remote Gate]]
|
||||||
|
- **verwandt mit:** [[Mass-Update Gate]]
|
||||||
|
- **ausgenommen von:** [[Iteration and Cost Limits]]
|
||||||
|
- **Teil von:** [[Chemenu]]
|
||||||
|
|
||||||
|
## Siehe auch
|
||||||
|
|
||||||
|
- [[wikitool]]
|
||||||
|
- [[Publish-Remote Gate]]
|
||||||
|
- [[Mass-Update Gate]]
|
||||||
|
- [[Iteration and Cost Limits]]
|
||||||
|
- [[Chemenu]]
|
||||||
|
- [[Source - MCP Read Server Implementation Session 2026-09-02]]
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ concept_type: workflow
|
|||||||
tags: [gate, safety, mass-update, confirmation]
|
tags: [gate, safety, mass-update, confirmation]
|
||||||
created: 2026-08-03
|
created: 2026-08-03
|
||||||
modified: 2026-09-01
|
modified: 2026-09-01
|
||||||
related: [Content Quality Control, wikitool, Iteration and Cost Limits, Structural Enforcement over Documented Rule, Bulk Operations, Publish-Remote Gate]
|
related: [Content Quality Control, wikitool, Iteration and Cost Limits, Structural Enforcement over Documented Rule, Bulk Operations, Publish-Remote Gate, MCP-Leseserver]
|
||||||
sources: [Source - LLM Improvements Sonnet Analysis, Source - LLM Improvements Production Agent Gaps 2026, Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31, Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31, Source - LLM Improvements Codex Analysis]
|
sources: [Source - LLM Improvements Sonnet Analysis, Source - LLM Improvements Production Agent Gaps 2026, Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31, Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31, Source - LLM Improvements Codex Analysis]
|
||||||
confidence: 0.88
|
confidence: 0.88
|
||||||
confidence_base: 0.88
|
confidence_base: 0.88
|
||||||
@@ -78,6 +78,7 @@ Das Mass-Update Gate ist ein Sicherheitsmechanismus, der die Ausführung pausier
|
|||||||
- **wendet an:** [[Structural Enforcement over Documented Rule]]
|
- **wendet an:** [[Structural Enforcement over Documented Rule]]
|
||||||
- **grenzt ab gegen:** [[Bulk Operations]]
|
- **grenzt ab gegen:** [[Bulk Operations]]
|
||||||
- **verwandtes Gate:** [[Publish-Remote Gate]]
|
- **verwandtes Gate:** [[Publish-Remote Gate]]
|
||||||
|
- **verwandt mit:** [[MCP-Leseserver]]
|
||||||
|
|
||||||
## Siehe auch
|
## Siehe auch
|
||||||
|
|
||||||
@@ -88,6 +89,7 @@ Das Mass-Update Gate ist ein Sicherheitsmechanismus, der die Ausführung pausier
|
|||||||
- [[Structural Enforcement over Documented Rule]]
|
- [[Structural Enforcement over Documented Rule]]
|
||||||
- [[Bulk Operations]]
|
- [[Bulk Operations]]
|
||||||
- [[Publish-Remote Gate]]
|
- [[Publish-Remote Gate]]
|
||||||
|
- [[MCP-Leseserver]]
|
||||||
|
|
||||||
## Fußnoten
|
## Fußnoten
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ type: types/concept.md
|
|||||||
concept_type: workflow
|
concept_type: workflow
|
||||||
tags: []
|
tags: []
|
||||||
created: 2026-09-01
|
created: 2026-09-01
|
||||||
modified: 2026-09-01
|
modified: 2026-09-02
|
||||||
related: [Mass-Update Gate, Chemenu]
|
related: [Mass-Update Gate, Chemenu, MCP-Leseserver]
|
||||||
sources: [Source - Publish-Remote Gate and Issue Triage Session 2026-09-01, Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]
|
sources: [Source - Publish-Remote Gate and Issue Triage Session 2026-09-01, Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01, Source - MCP Read Server Implementation Session 2026-09-02]
|
||||||
confidence: 0.50
|
confidence: 0.70
|
||||||
confidence_base: 0.70
|
confidence_base: 0.70
|
||||||
provenance: sourced
|
provenance: sourced
|
||||||
summary: 'Drittes, im Code durchgesetztes Gate: publish bricht mit Exit 42 ab, wenn die aufgeloeste Push-URL nicht in einer optionalen, gitignoreten Allowlist steht; anders als die anderen Gates gibt es keinen Freigabe-Token.'
|
summary: 'Drittes, im Code durchgesetztes Gate: publish bricht mit Exit 42 ab, wenn die aufgeloeste Push-URL nicht in einer optionalen, gitignoreten Allowlist steht; doctor benennt seit 2026-09-02 den Gate-Zustand statt nur die Dateiexistenz'
|
||||||
---
|
---
|
||||||
# Publish-Remote Gate
|
# Publish-Remote Gate
|
||||||
|
|
||||||
@@ -40,6 +40,12 @@ Remote lokal konfiguriert ist.
|
|||||||
ist wichtig: Ein Checkout ohne Beschränkungsbedarf soll nicht gezwungen sein, eine leere
|
ist wichtig: Ein Checkout ohne Beschränkungsbedarf soll nicht gezwungen sein, eine leere
|
||||||
Konfigurationsdatei zu pflegen; eine beschädigte Datei darf aber nicht wie eine abwesende
|
Konfigurationsdatei zu pflegen; eine beschädigte Datei darf aber nicht wie eine abwesende
|
||||||
behandelt werden, sonst wird eine defekte Sicherung zu einer stillschweigend abgeschalteten.
|
behandelt werden, sonst wird eine defekte Sicherung zu einer stillschweigend abgeschalteten.
|
||||||
|
- **`doctor` benennt seit 2026-09-02 den Gate-*Zustand*, nicht nur, ob die Datei existiert.**
|
||||||
|
Vorher meldete der Check nur die Anwesenheit von `.wikitool-remotes.json`; ob das
|
||||||
|
gleichbedeutend mit "scharf" ist, musste der Leser selbst schließen. Alle drei Ausgaben
|
||||||
|
beginnen jetzt mit `Gate armed:` bzw. `Gate not armed:` - der Ein-Remote-Fall ohne Allowlist
|
||||||
|
bleibt `OK` (er hat nichts zu schützen), sagt aber ausdrücklich, dass jedes Push-Ziel
|
||||||
|
durchkommt.[^s-mcp-read-server-implementation-session-2026-09-02]
|
||||||
|
|
||||||
## Wann zu verwenden
|
## Wann zu verwenden
|
||||||
|
|
||||||
@@ -88,11 +94,13 @@ Maschinerie ziehen) stehen in Gitea-Issue #30.
|
|||||||
## Verwandte Concepts
|
## Verwandte Concepts
|
||||||
|
|
||||||
- [[Mass-Update Gate]]
|
- [[Mass-Update Gate]]
|
||||||
|
- [[MCP-Leseserver]]
|
||||||
|
|
||||||
## Beziehungen
|
## Beziehungen
|
||||||
|
|
||||||
- **gilt fuer:** [[Chemenu]]
|
- **gilt fuer:** [[Chemenu]]
|
||||||
- **verwandtes Gate:** [[Mass-Update Gate]]
|
- **verwandtes Gate:** [[Mass-Update Gate]]
|
||||||
|
- **verwandt mit:** [[MCP-Leseserver]]
|
||||||
|
|
||||||
## Siehe auch
|
## Siehe auch
|
||||||
|
|
||||||
@@ -100,7 +108,9 @@ Maschinerie ziehen) stehen in Gitea-Issue #30.
|
|||||||
- [[Chemenu]]
|
- [[Chemenu]]
|
||||||
- [[Mass-Update Gate]]
|
- [[Mass-Update Gate]]
|
||||||
- [[Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]]
|
- [[Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]]
|
||||||
|
- [[MCP-Leseserver]]
|
||||||
|
|
||||||
## Fußnoten
|
## Fußnoten
|
||||||
|
|
||||||
[^s-private-instance-merge-correction-and-issue-30-session-2026-09-01]: [[Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]]
|
[^s-private-instance-merge-correction-and-issue-30-session-2026-09-01]: [[Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]]
|
||||||
|
[^s-mcp-read-server-implementation-session-2026-09-02]: [[Source - MCP Read Server Implementation Session 2026-09-02]]
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|------|------|---------|----------------|
|
|------|------|---------|----------------|
|
||||||
| [[andybalholm-edl]] | project | Go-basierte EDL-Bibliothek für die Kommunikation mit eingebetteten Geräten. | 2026-08-29 |
|
| [[andybalholm-edl]] | project | Go-basierte EDL-Bibliothek für die Kommunikation mit eingebetteten Geräten. | 2026-08-29 |
|
||||||
| [[BCDModule]] | project | Go-Modul, das die Entscheidungslogik für die Batterieladung umsetzt. | 2026-08-29 |
|
| [[BCDModule]] | project | Go-Modul, das die Entscheidungslogik für die Batterieladung umsetzt. | 2026-08-29 |
|
||||||
| [[Chemenu]] | project | Deterministischer Wissenskompiler (raw/ -> kb/); seit 2.0.0 unter dem Namen Chemenu, seit 2026-09-01 oeffentlich als Testbett und Demo unter AGPL-3.0/CC-BY-4.0 | 2026-09-01 |
|
| [[Chemenu]] | project | Deterministischer Wissenskompiler (raw/ -> kb/); seit 2.0.0 unter dem Namen Chemenu, seit 2026-09-01 oeffentlich, seit 2.4.0 mit einem MCP-Leseserver als zweitem Konsumenten | 2026-09-02 |
|
||||||
| [[goresponsiveness]] | project | Go-Werkzeug zur Messung von Anwendungsleistung und Responsiveness. | 2026-08-29 |
|
| [[goresponsiveness]] | project | Go-Werkzeug zur Messung von Anwendungsleistung und Responsiveness. | 2026-08-29 |
|
||||||
| [[ha-core]] | project | Kern-Integrationsbibliothek für Home-Assistant-E3DC-Systeme; stellt die E3DC-Kommunikationsprotokolle und den Home-Assistant-Integrationscode bereit. | 2026-08-29 |
|
| [[ha-core]] | project | Kern-Integrationsbibliothek für Home-Assistant-E3DC-Systeme; stellt die E3DC-Kommunikationsprotokolle und den Home-Assistant-Integrationscode bereit. | 2026-08-29 |
|
||||||
| [[hacs-e3dc]] | project | Home Assistant Custom Component zur Überwachung von E3DC-Energiesystemen. | 2026-08-29 |
|
| [[hacs-e3dc]] | project | Home Assistant Custom Component zur Überwachung von E3DC-Energiesystemen. | 2026-08-29 |
|
||||||
@@ -96,8 +96,8 @@
|
|||||||
| [[pascalandy schema]] | tool | Von der Community beigesteuertes Wiki Schema (Global) aus pascalandys Kommentar in Farzas Gist, mit alternativer Tag-Taxonomie (area/kind/topic/status/pty) | 2026-08-29 |
|
| [[pascalandy schema]] | tool | Von der Community beigesteuertes Wiki Schema (Global) aus pascalandys Kommentar in Farzas Gist, mit alternativer Tag-Taxonomie (area/kind/topic/status/pty) | 2026-08-29 |
|
||||||
| [[Pi]] | tool | Assistenz-Agent von Inflection AI; kann das LLM-Wiki-Muster wie andere LLM-Agenten umsetzen. | 2026-08-29 |
|
| [[Pi]] | tool | Assistenz-Agent von Inflection AI; kann das LLM-Wiki-Muster wie andere LLM-Agenten umsetzen. | 2026-08-29 |
|
||||||
| [[Proton]] | tool | Wine-basierte Kompatibilitätsschicht von Valve; lässt Windows-Spiele über Steam unter Linux laufen, mit optimierter DirectX-Übersetzung. | 2026-08-29 |
|
| [[Proton]] | tool | Wine-basierte Kompatibilitätsschicht von Valve; lässt Windows-Spiele über Steam unter Linux laufen, mit optimierter DirectX-Übersetzung. | 2026-08-29 |
|
||||||
| [[qmd]] | tool | Lokale Suchmaschine für Markdown-Dateien mit hybrider BM25-Vektor-Suche und LLM-Reranking. | 2026-08-29 |
|
| [[qmd]] | tool | Lokale Suchmaschine fuer Markdown-Dateien: TypeScript/Node.js/Bun, SQLite-FTS5-BM25 plus sqlite-vec-Vektorsuche plus node-llama-cpp-LLM-Reranking. | 2026-09-02 |
|
||||||
| [[Steam]] | tool | Valves Plattform für digitalen Spielevertrieb und Spielebibliothek auf dem PC. | 2026-08-29 |
|
| [[Steam]] | tool | Valves Plattform für digitalen Spielevertrieb und Spielebibliothek auf dem PC. | 2026-08-29 |
|
||||||
| [[wikitool]] | tool | Deterministisches CLI fuer alle mechanischen Wiki-Operationen; seit 2.0.0 liegt es im Python-Paket chemenu, das Kommando heisst weiterhin wikitool | 2026-09-01 |
|
| [[wikitool]] | tool | Deterministisches CLI fuer alle mechanischen Wiki-Operationen; seit 2.0.0 im Paket chemenu, seit 2.4.0 zusaetzlich als MCP-Leseserver erreichbar | 2026-09-02 |
|
||||||
| [[Wine]] | tool | Kompatibilitätsschicht, die Windows-API-Aufrufe nach POSIX übersetzt und Windows-Anwendungen unter Linux, BSD und macOS ohne Virtualisierung oder Emulation ausführt. | 2026-08-29 |
|
| [[Wine]] | tool | Kompatibilitätsschicht, die Windows-API-Aufrufe nach POSIX übersetzt und Windows-Anwendungen unter Linux, BSD und macOS ohne Virtualisierung oder Emulation ausführt. | 2026-08-29 |
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ type: types/entity.md
|
|||||||
entity_type: project
|
entity_type: project
|
||||||
tags: [wiki, llm, knowledge-base]
|
tags: [wiki, llm, knowledge-base]
|
||||||
created: 2026-08-04
|
created: 2026-08-04
|
||||||
modified: 2026-09-01
|
modified: 2026-09-02
|
||||||
related: [Personalization Plane, Issue Label Scheme, Optional Instance Context File, Delete Rather Than Anonymize, Dual Licensing by File Plan, Publish-Remote Gate]
|
related: [Personalization Plane, Issue Label Scheme, Optional Instance Context File, Delete Rather Than Anonymize, Dual Licensing by File Plan, Publish-Remote Gate, MCP-Leseserver]
|
||||||
sources: [Source - Copilot Skill Restructure Instructions, Source - Conversation - AGENTS.md Skill Restructuring Session 2026-08-04, Source - Conversation - Versioning CI-CD and Content Migration Session 2026-08-30, Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31, Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31, Source - Conversation - Write-Once Frontmatter Fields and touch --set Session 2026-08-31, Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31, Source - Conversation - Two Round-Trip Defects Found by an Ingest Session 2026-08-31, Source - Conversation - Hardening the Test Suite Against Silent Environment Dependencies Session 2026-08-31, 'Source - Public Release, Corpus Purge and History Squash Session 2026-09-01', Source - Publish-Remote Gate and Issue Triage Session 2026-09-01, Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]
|
sources: [Source - Copilot Skill Restructure Instructions, Source - Conversation - AGENTS.md Skill Restructuring Session 2026-08-04, Source - Conversation - Versioning CI-CD and Content Migration Session 2026-08-30, Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31, Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31, Source - Conversation - Write-Once Frontmatter Fields and touch --set Session 2026-08-31, Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31, Source - Conversation - Two Round-Trip Defects Found by an Ingest Session 2026-08-31, Source - Conversation - Hardening the Test Suite Against Silent Environment Dependencies Session 2026-08-31, 'Source - Public Release, Corpus Purge and History Squash Session 2026-09-01', Source - Publish-Remote Gate and Issue Triage Session 2026-09-01, Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01, Source - MCP Read Server Implementation Session 2026-09-02]
|
||||||
confidence: 0.90
|
confidence: 0.90
|
||||||
confidence_base: 0.90
|
confidence_base: 0.90
|
||||||
provenance: mixed
|
provenance: mixed
|
||||||
summary: Deterministischer Wissenskompiler (raw/ -> kb/); seit 2.0.0 unter dem Namen Chemenu, seit 2026-09-01 oeffentlich als Testbett und Demo unter AGPL-3.0/CC-BY-4.0
|
summary: Deterministischer Wissenskompiler (raw/ -> kb/); seit 2.0.0 unter dem Namen Chemenu, seit 2026-09-01 oeffentlich, seit 2.4.0 mit einem MCP-Leseserver als zweitem Konsumenten
|
||||||
---
|
---
|
||||||
# Chemenu
|
# Chemenu
|
||||||
|
|
||||||
@@ -63,6 +63,7 @@ Das Repository hat bereits ein deterministisches CLI, `tools/wikitool` (Python,
|
|||||||
- **wendet an:** [[Delete Rather Than Anonymize]]
|
- **wendet an:** [[Delete Rather Than Anonymize]]
|
||||||
- **wendet an:** [[Dual Licensing by File Plan]]
|
- **wendet an:** [[Dual Licensing by File Plan]]
|
||||||
- **setzt um:** [[Publish-Remote Gate]]
|
- **setzt um:** [[Publish-Remote Gate]]
|
||||||
|
- **enthaelt:** [[MCP-Leseserver]]
|
||||||
|
|
||||||
## Details
|
## Details
|
||||||
|
|
||||||
@@ -142,6 +143,13 @@ Repositorys selbst und keine Aussagen aus einer Rohdatenquelle; der Änderungsda
|
|||||||
|
|
||||||
## Historie
|
## Historie
|
||||||
|
|
||||||
|
- 2026-09-02 - `2.2.3`-`2.4.1`: Issue #36s vierstufige Sequenz zum [[MCP-Leseserver]] umgesetzt
|
||||||
|
([[Publish-Remote Gate]] scharf gestellt, Lesepfad gehärtet, Root-Auflösung von der Importzeit
|
||||||
|
gelöst, der Server selbst), dazu `INSTALL-MCP.md` und Issue #37 für das noch fehlende
|
||||||
|
Container-Image. Alle vier Sequenz-Issues geschlossen; #36 blieb offen, weil sein eigenes
|
||||||
|
Abschlusskriterium - ein Konsument, der nachweislich über die
|
||||||
|
Authentifizierungs-Middleware antwortet - erst mit #37 erfüllbar
|
||||||
|
ist.[^s-mcp-read-server-implementation-session-2026-09-02]
|
||||||
- 2026-09-01 - `2.0.0` (Commit `9a7abe6`, 121 Dateien, 730 Tests grün): Rebranding von
|
- 2026-09-01 - `2.0.0` (Commit `9a7abe6`, 121 Dateien, 730 Tests grün): Rebranding von
|
||||||
`llm-wiki-test1` auf **Chemenu** nach Gitea-Issue #3 - Repo-Rename, Produktname,
|
`llm-wiki-test1` auf **Chemenu** nach Gitea-Issue #3 - Repo-Rename, Produktname,
|
||||||
Release-Artefakt (`chemenu-stack-<version>.tar.gz`), Release-Feed, und das Python-Paket
|
Release-Artefakt (`chemenu-stack-<version>.tar.gz`), Release-Feed, und das Python-Paket
|
||||||
@@ -219,6 +227,7 @@ Repositorys selbst und keine Aussagen aus einer Rohdatenquelle; der Änderungsda
|
|||||||
- [[Source - Publish-Remote Gate and Issue Triage Session 2026-09-01]]
|
- [[Source - Publish-Remote Gate and Issue Triage Session 2026-09-01]]
|
||||||
- [[Publish-Remote Gate]]
|
- [[Publish-Remote Gate]]
|
||||||
- [[Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]]
|
- [[Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]]
|
||||||
|
- [[MCP-Leseserver]]
|
||||||
|
|
||||||
## Fußnoten
|
## Fußnoten
|
||||||
|
|
||||||
@@ -230,3 +239,4 @@ Repositorys selbst und keine Aussagen aus einer Rohdatenquelle; der Änderungsda
|
|||||||
[^s-conversation-gate-counting-and-measured-calibration-session-2026-08-31]: [[Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31]]
|
[^s-conversation-gate-counting-and-measured-calibration-session-2026-08-31]: [[Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31]]
|
||||||
[^s-conversation-write-once-frontmatter-fields-and-touch-set-session-2026-08-31]: [[Source - Conversation - Write-Once Frontmatter Fields and touch --set Session 2026-08-31]]
|
[^s-conversation-write-once-frontmatter-fields-and-touch-set-session-2026-08-31]: [[Source - Conversation - Write-Once Frontmatter Fields and touch --set Session 2026-08-31]]
|
||||||
[^s-conversation-comma-bug-budget-refund-and-lint-report-path-session-2026-08-31]: [[Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31]]
|
[^s-conversation-comma-bug-budget-refund-and-lint-report-path-session-2026-08-31]: [[Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31]]
|
||||||
|
[^s-mcp-read-server-implementation-session-2026-09-02]: [[Source - MCP Read Server Implementation Session 2026-09-02]]
|
||||||
|
|||||||
+22
-11
@@ -3,13 +3,13 @@ type: types/entity.md
|
|||||||
entity_type: tool
|
entity_type: tool
|
||||||
tags: [search, markdown, cli, local]
|
tags: [search, markdown, cli, local]
|
||||||
created: 2026-07-26
|
created: 2026-07-26
|
||||||
modified: 2026-08-29
|
modified: 2026-09-02
|
||||||
related: [Obsidian, LLM Wiki Pattern]
|
related: [Obsidian, LLM Wiki Pattern]
|
||||||
sources: [Source - LLM Wiki Pattern]
|
sources: [Source - LLM Wiki Pattern, Source - qmd - GitHub Repository]
|
||||||
confidence: 0.85
|
confidence: 0.80
|
||||||
confidence_base: 0.85
|
confidence_base: 0.80
|
||||||
provenance: sourced
|
provenance: sourced
|
||||||
summary: Lokale Suchmaschine für Markdown-Dateien mit hybrider BM25-Vektor-Suche und LLM-Reranking.
|
summary: 'Lokale Suchmaschine fuer Markdown-Dateien: TypeScript/Node.js/Bun, SQLite-FTS5-BM25 plus sqlite-vec-Vektorsuche plus node-llama-cpp-LLM-Reranking.'
|
||||||
---
|
---
|
||||||
# qmd
|
# qmd
|
||||||
|
|
||||||
@@ -22,11 +22,12 @@ qmd ist eine lokale Suchmaschine, die speziell für Markdown-Dateien ausgelegt i
|
|||||||
## Kerndaten
|
## Kerndaten
|
||||||
|
|
||||||
- **Typ:** CLI-Tool
|
- **Typ:** CLI-Tool
|
||||||
- **Sprache:** Nicht angegeben (wahrscheinlich Go oder Rust)
|
- **Sprache:** TypeScript, Laufzeit Node.js oder Bun
|
||||||
- **Such-Typen:** Hybrid (BM25 + Vector)
|
- **Such-Typen:** Hybrid (SQLite-FTS5-BM25 + `sqlite-vec`-Vektorsuche)
|
||||||
- **Neu-Ranking:** LLM-basiert
|
- **Neu-Ranking:** LLM-basiert über `node-llama-cpp` (lokale GGUF-Modelle)
|
||||||
- **Bereitstellung:** On-device/lokal
|
- **Bereitstellung:** On-device/lokal
|
||||||
- **Repository:** https://github.com/tobi/qmd
|
- **Repository:** https://github.com/tobi/qmd
|
||||||
|
- **Installation:** `npm install -g @tobilu/qmd` oder `bun install -g @tobilu/qmd`
|
||||||
|
|
||||||
## Beziehungen
|
## Beziehungen
|
||||||
|
|
||||||
@@ -50,11 +51,13 @@ qmd ist eine lokale Suchmaschine, die speziell für Markdown-Dateien ausgelegt i
|
|||||||
## Installation und Verwendung
|
## Installation und Verwendung
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Installation (hypothetisch, siehe aktuelles Repo für Details)
|
# Installation (Node oder Bun)
|
||||||
go install github.com/tobi/qmd@latest
|
npm install -g @tobilu/qmd
|
||||||
|
# oder
|
||||||
|
bun install -g @tobilu/qmd
|
||||||
|
|
||||||
# Suche von CLI
|
# Suche von CLI
|
||||||
qmd search "knowledge management"
|
qmd query "knowledge management"
|
||||||
|
|
||||||
# Als MCP-Server für LLM-Integration verwenden
|
# Als MCP-Server für LLM-Integration verwenden
|
||||||
qmd server
|
qmd server
|
||||||
@@ -74,9 +77,17 @@ qmd server
|
|||||||
|
|
||||||
## Historie
|
## Historie
|
||||||
|
|
||||||
|
- [2026-09-02] - Sprachangabe korrigiert. Die Seite trug seit ihrer Erstellung "Nicht angegeben
|
||||||
|
(wahrscheinlich Go oder Rust)" bei `confidence: 0.85` - eine Vermutung mit einer Konfidenz, die
|
||||||
|
für eine Vermutung zu hoch war. Direkt gegen das Repository geprüft: TypeScript, Laufzeit
|
||||||
|
Node.js/Bun, SQLite-FTS5-BM25 + `sqlite-vec` + `node-llama-cpp`.[^s-qmd-github-repository]
|
||||||
- [2026-07-26] - Entity-Seite während der Aufnahme des LLM-Wiki-Pattern-Artikels erstellt
|
- [2026-07-26] - Entity-Seite während der Aufnahme des LLM-Wiki-Pattern-Artikels erstellt
|
||||||
|
|
||||||
## Siehe auch
|
## Siehe auch
|
||||||
|
|
||||||
- [[LLM Wiki Pattern]]
|
- [[LLM Wiki Pattern]]
|
||||||
- [[Obsidian]]
|
- [[Obsidian]]
|
||||||
|
|
||||||
|
## Fußnoten
|
||||||
|
|
||||||
|
[^s-qmd-github-repository]: [[Source - qmd - GitHub Repository]]
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ type: types/entity.md
|
|||||||
entity_type: tool
|
entity_type: tool
|
||||||
tags: [cli, automation, deterministic, wiki-management]
|
tags: [cli, automation, deterministic, wiki-management]
|
||||||
created: 2026-08-03
|
created: 2026-08-03
|
||||||
modified: 2026-09-01
|
modified: 2026-09-02
|
||||||
related: [Semantic Lint Automation, Session Orientation, Iteration and Cost Limits, KB Stack Versioning, KB Migration, Personalization Plane, Detect-Repair Asymmetry, Write-Once Frontmatter Fields, Denylist over Allowlist, Command Round-Trip Integrity, Green Suite Blind Spot, Ambient Environment Dependency, Structural Enforcement over Documented Rule, Optional Instance Context File]
|
related: [Semantic Lint Automation, Session Orientation, Iteration and Cost Limits, KB Stack Versioning, KB Migration, Personalization Plane, Detect-Repair Asymmetry, Write-Once Frontmatter Fields, Denylist over Allowlist, Command Round-Trip Integrity, Green Suite Blind Spot, Ambient Environment Dependency, Structural Enforcement over Documented Rule, Optional Instance Context File, MCP-Leseserver]
|
||||||
sources: [Source - LLM Improvements Codex Analysis, Source - LLM Improvements Sonnet Analysis, Source - Copilot Skill Restructure Instructions, Source - Conversation - AGENTS.md Skill Restructuring Session 2026-08-04, Source - LLM Improvements Production Agent Gaps 2026, Source - Conversation - Versioning CI-CD and Content Migration Session 2026-08-30, Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31, Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31, Source - Conversation - Auto Mode and Tool Choice Session 2026-08-31, Source - Conversation - Write-Once Frontmatter Fields and touch --set Session 2026-08-31, Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31, Source - Conversation - Two Round-Trip Defects Found by an Ingest Session 2026-08-31, Source - Conversation - Hardening the Test Suite Against Silent Environment Dependencies Session 2026-08-31, Source - Conversation - ENVIRONMENT.md as an Optional Third Session-Level File Session 2026-08-31, 'Source - Public Release, Corpus Purge and History Squash Session 2026-09-01', Source - Publish-Remote Gate and Issue Triage Session 2026-09-01, Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]
|
sources: [Source - LLM Improvements Codex Analysis, Source - LLM Improvements Sonnet Analysis, Source - Copilot Skill Restructure Instructions, Source - Conversation - AGENTS.md Skill Restructuring Session 2026-08-04, Source - LLM Improvements Production Agent Gaps 2026, Source - Conversation - Versioning CI-CD and Content Migration Session 2026-08-30, Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31, Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31, Source - Conversation - Auto Mode and Tool Choice Session 2026-08-31, Source - Conversation - Write-Once Frontmatter Fields and touch --set Session 2026-08-31, Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31, Source - Conversation - Two Round-Trip Defects Found by an Ingest Session 2026-08-31, Source - Conversation - Hardening the Test Suite Against Silent Environment Dependencies Session 2026-08-31, Source - Conversation - ENVIRONMENT.md as an Optional Third Session-Level File Session 2026-08-31, 'Source - Public Release, Corpus Purge and History Squash Session 2026-09-01', Source - Publish-Remote Gate and Issue Triage Session 2026-09-01, Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01, Source - MCP Read Server Implementation Session 2026-09-02]
|
||||||
confidence: 0.90
|
confidence: 0.90
|
||||||
confidence_base: 0.90
|
confidence_base: 0.90
|
||||||
provenance: sourced
|
provenance: sourced
|
||||||
summary: Deterministisches CLI fuer alle mechanischen Wiki-Operationen; seit 2.0.0 liegt es im Python-Paket chemenu, das Kommando heisst weiterhin wikitool
|
summary: Deterministisches CLI fuer alle mechanischen Wiki-Operationen; seit 2.0.0 im Paket chemenu, seit 2.4.0 zusaetzlich als MCP-Leseserver erreichbar
|
||||||
---
|
---
|
||||||
# wikitool
|
# wikitool
|
||||||
|
|
||||||
@@ -51,6 +51,7 @@ Wie in der Codex-Analyse vermerkt, bietet wikitool die deterministische Grundlag
|
|||||||
- **zeigte:** [[Ambient Environment Dependency]]
|
- **zeigte:** [[Ambient Environment Dependency]]
|
||||||
- **setzt um:** [[Structural Enforcement over Documented Rule]]
|
- **setzt um:** [[Structural Enforcement over Documented Rule]]
|
||||||
- **setzt um:** [[Optional Instance Context File]]
|
- **setzt um:** [[Optional Instance Context File]]
|
||||||
|
- **implementiert:** [[MCP-Leseserver]]
|
||||||
|
|
||||||
## Befehle
|
## Befehle
|
||||||
|
|
||||||
@@ -73,6 +74,9 @@ wikitool bietet die folgenden Befehlskategorien:
|
|||||||
Seitenkopf zurückgefaltet statt abgelehnt, und weil der Block immer zuletzt gerendert wird,
|
Seitenkopf zurückgefaltet statt abgelehnt, und weil der Block immer zuletzt gerendert wird,
|
||||||
richtet die erste Zitatoperation eine verrutschte Seite von selbst wieder ein[^s-conversation-two-round-trip-defects-found-by-an-ingest-session-2026-08-31]
|
richtet die erste Zitatoperation eine verrutschte Seite von selbst wieder ein[^s-conversation-two-round-trip-defects-found-by-an-ingest-session-2026-08-31]
|
||||||
- **Abfrage:** `search` - Textsuche über `kb/` durch ein austauschbares Backend (`rg` heute), plus `--field`-Prädikate, die auf Frontmatter evaluiert werden (`entity_type=system`, `confidence>=0.8`, `tags=k8s`, `!source_url`). Ohne Text ist es eine reine strukturierte Abfrage. Schreibgeschützt und ausgenommen von der Iteration-Budget-Gate, da Abfrage das Lesen statt das Iterieren ist
|
- **Abfrage:** `search` - Textsuche über `kb/` durch ein austauschbares Backend (`rg` heute), plus `--field`-Prädikate, die auf Frontmatter evaluiert werden (`entity_type=system`, `confidence>=0.8`, `tags=k8s`, `!source_url`). Ohne Text ist es eine reine strukturierte Abfrage. Schreibgeschützt und ausgenommen von der Iteration-Budget-Gate, da Abfrage das Lesen statt das Iterieren ist
|
||||||
|
- **MCP-Server:** `python -m chemenu.mcp` (`--transport stdio|streamable-http`) - kein
|
||||||
|
`wikitool`-Unterbefehl, sondern ein zweiter Einstiegspunkt über `chemenu.api.Corpus`, derselbe
|
||||||
|
Kern wie `search`/`types`/`lint`. Siehe [[MCP-Leseserver]]
|
||||||
- **Indizierung:** `index rebuild` - regeneriert die `kb/index.md`-Map plus eine pro-Sammlung `INDEX.md`, wobei ein Bereich bei 50 Zeilen in seine eigene Shard aufgeteilt wird
|
- **Indizierung:** `index rebuild` - regeneriert die `kb/index.md`-Map plus eine pro-Sammlung `INDEX.md`, wobei ein Bereich bei 50 Zeilen in seine eigene Shard aufgeteilt wird
|
||||||
- **Herkunft:** `sources coverage`, `sources trace`, `sources rebuild-index`
|
- **Herkunft:** `sources coverage`, `sources trace`, `sources rebuild-index`
|
||||||
- **Protokollierung:** `log append`, `log status`
|
- **Protokollierung:** `log append`, `log status`
|
||||||
@@ -147,6 +151,22 @@ ist[^s-conversation-versioning-ci-cd-and-content-migration-session-2026-08-30].
|
|||||||
|
|
||||||
## Historie
|
## Historie
|
||||||
|
|
||||||
|
- 2026-09-02 - `2.2.3`-`2.4.1`: die vierstufige Sequenz aus Issue #36 (Publish-Remote-Gate
|
||||||
|
scharf, Lesepfad gehärtet, Root-Auflösung und Bibliotheksgrenze, [[MCP-Leseserver]]) plus
|
||||||
|
Menschendoku. `.wikitool-remotes.json` war trotz gegenteiliger Dokumentation nicht gesetzt -
|
||||||
|
angelegt und gegen ein erfundenes Ziel gegengeprüft (Exit 42). Sechs Lesepfad-Härtungen:
|
||||||
|
gelöschter ReDoS-Zweig in der Ranking-Funktion, ein 30-Sekunden-`rg`-Timeout, ein
|
||||||
|
YAML-Alias-Refusal (267 B → 672.603 Knoten reproduziert), `CSafeLoader` (265 ms → 54 ms),
|
||||||
|
gemeldetes statt geschlucktes kaputtes Frontmatter, ein Korpus-Cache, der nie einen
|
||||||
|
schmutzigen Arbeitsbaum cacht. `config.ROOT` und alle abgeleiteten Pfade waren zur Importzeit
|
||||||
|
gebunden - jetzt lazy aufgelöst (`$CHEMENU_ROOT` → Walk-up), mit einem eigenen
|
||||||
|
`config.reset()` gegen eine Falle, in die `monkeypatch`s eigener Teardown sonst zurückläuft.
|
||||||
|
Neuer typerloser Lesekern (`search/service.py`, `lint_core.py`, `types_core.py`) hinter
|
||||||
|
`chemenu.api.Corpus`, darüber der MCP-Server: `search`/`types`/`describe_type`/`lint`/`status`,
|
||||||
|
strukturell ohne Schreibpfad, jede Antwort mit Commit-Stempel. `wikitool search` end-to-end
|
||||||
|
593 ms → 347 ms. Menschendoku `INSTALL-MCP.md`, ausgeliefert über `dist_cmd.ROOT_FILES`.
|
||||||
|
Siehe [[Publish-Remote Gate]], [[Mass-Update Gate]],
|
||||||
|
[[MCP-Leseserver]][^s-mcp-read-server-implementation-session-2026-09-02]
|
||||||
- 2026-09-01 - `2.1.0`-`2.2.2`: Vorbereitung und Durchführung der Veröffentlichung. `dist export`
|
- 2026-09-01 - `2.1.0`-`2.2.2`: Vorbereitung und Durchführung der Veröffentlichung. `dist export`
|
||||||
bekommt `REQUIRED_ROOT_FILES` (fehlende Lizenzdateien lassen den Export scheitern statt still
|
bekommt `REQUIRED_ROOT_FILES` (fehlende Lizenzdateien lassen den Export scheitern statt still
|
||||||
weiterzulaufen) und `find_leaks()` (strukturelle Prüfung des fertigen Export-Plans gegen
|
weiterzulaufen) und `find_leaks()` (strukturelle Prüfung des fertigen Export-Plans gegen
|
||||||
@@ -267,6 +287,7 @@ ist[^s-conversation-versioning-ci-cd-and-content-migration-session-2026-08-30].
|
|||||||
- [[Source - Public Release, Corpus Purge and History Squash Session 2026-09-01]]
|
- [[Source - Public Release, Corpus Purge and History Squash Session 2026-09-01]]
|
||||||
- [[Source - Publish-Remote Gate and Issue Triage Session 2026-09-01]]
|
- [[Source - Publish-Remote Gate and Issue Triage Session 2026-09-01]]
|
||||||
- [[Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]]
|
- [[Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]]
|
||||||
|
- [[MCP-Leseserver]]
|
||||||
|
|
||||||
## Fußnoten
|
## Fußnoten
|
||||||
|
|
||||||
@@ -281,3 +302,4 @@ ist[^s-conversation-versioning-ci-cd-and-content-migration-session-2026-08-30].
|
|||||||
[^s-llm-improvements-codex-analysis]: [[Source - LLM Improvements Codex Analysis]]
|
[^s-llm-improvements-codex-analysis]: [[Source - LLM Improvements Codex Analysis]]
|
||||||
[^s-public-release-corpus-purge-and-history-squash-session-2026-09-01]: [[Source - Public Release, Corpus Purge and History Squash Session 2026-09-01]]
|
[^s-public-release-corpus-purge-and-history-squash-session-2026-09-01]: [[Source - Public Release, Corpus Purge and History Squash Session 2026-09-01]]
|
||||||
[^s-publish-remote-gate-and-issue-triage-session-2026-09-01]: [[Source - Publish-Remote Gate and Issue Triage Session 2026-09-01]]
|
[^s-publish-remote-gate-and-issue-triage-session-2026-09-01]: [[Source - Publish-Remote Gate and Issue Triage Session 2026-09-01]]
|
||||||
|
[^s-mcp-read-server-implementation-session-2026-09-02]: [[Source - MCP Read Server Implementation Session 2026-09-02]]
|
||||||
|
|||||||
+6
-6
@@ -13,12 +13,12 @@ The page tables live in a generated `INDEX.md` inside each collection, linked be
|
|||||||
|
|
||||||
## Statistics
|
## Statistics
|
||||||
|
|
||||||
- **Total Pages:** 176
|
- **Total Pages:** 179
|
||||||
- **Comparisons:** 1
|
- **Comparisons:** 1
|
||||||
- **Concepts:** 79
|
- **Concepts:** 80
|
||||||
- **Entities:** 72
|
- **Entities:** 72
|
||||||
- **Sources:** 24
|
- **Sources:** 26
|
||||||
- **Last Updated:** 2026-09-01
|
- **Last Updated:** 2026-09-02
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -27,9 +27,9 @@ The page tables live in a generated `INDEX.md` inside each collection, linked be
|
|||||||
| Collection | Pages | Index |
|
| Collection | Pages | Index |
|
||||||
|------------|------:|-------|
|
|------------|------:|-------|
|
||||||
| `comparisons/` | 1 | [comparisons/INDEX.md](comparisons/INDEX.md) |
|
| `comparisons/` | 1 | [comparisons/INDEX.md](comparisons/INDEX.md) |
|
||||||
| `concepts/` | 79 | [concepts/INDEX.md](concepts/INDEX.md) |
|
| `concepts/` | 80 | [concepts/INDEX.md](concepts/INDEX.md) |
|
||||||
| `entities/` | 72 | [entities/INDEX.md](entities/INDEX.md) |
|
| `entities/` | 72 | [entities/INDEX.md](entities/INDEX.md) |
|
||||||
| `sources/` | 24 | [sources/INDEX.md](sources/INDEX.md) |
|
| `sources/` | 26 | [sources/INDEX.md](sources/INDEX.md) |
|
||||||
|
|
||||||
### entities/
|
### entities/
|
||||||
|
|
||||||
|
|||||||
@@ -73,3 +73,21 @@ Source-Seite angelegt, neue Concept-Seite Publish-Remote Gate, wikitool-Historie
|
|||||||
Source-Seite angelegt, Publish-Remote-Gate-Seite um die gemessene Merge-Semantik erweitert, quer verlinkt.
|
Source-Seite angelegt, Publish-Remote-Gate-Seite um die gemessene Merge-Semantik erweitert, quer verlinkt.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [2026-09-02] ingest | MCP Read Server Implementation Session 2026-09-02
|
||||||
|
|
||||||
|
Source-Seite und neues Concept MCP-Leseserver angelegt; wikitool, Chemenu, Publish-Remote Gate und Iteration and Cost Limits aktualisiert. Deckt Issue #36s Sequenz (#34/#33/#31/#19), INSTALL-MCP.md und Issue #37 ab.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-09-02] update | qmd - Sprachangabe korrigiert
|
||||||
|
|
||||||
|
Direkt gegen tobi/qmd auf GitHub geprueft: TypeScript statt der geratenen 'Go oder Rust'-Angabe. Neue Quelle Source - qmd - GitHub Repository (raw/documents/), confidence_base 0.85 -> 0.80 neu begruendet, Installationsbeispiel korrigiert.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-09-02] update | MCP Read Server Implementation Session 2026-09-02 - Korrektur
|
||||||
|
|
||||||
|
Aufgaben-Checkbox fuer die qmd.md-Korrektur nachgezogen (war [ ], ist erledigt) und ein Korrektur-Hinweis ergaenzt: der Fidelity-Block des Rohtranskripts kuendigte ein zweites Transkript fuer die qmd-Korrektur an, das nie geschrieben wurde - die Korrektur lief stattdessen als eigene Quellen-Verifikation. raw/ ist unveraenderlich, die Korrektur steht deshalb auf der Source-Seite.
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
+12
-2
@@ -8,8 +8,8 @@ inline `[^cite-id]` footnote).
|
|||||||
|
|
||||||
## Coverage Summary
|
## Coverage Summary
|
||||||
|
|
||||||
- **Total raw files:** 24
|
- **Total raw files:** 26
|
||||||
- **Covered:** 24
|
- **Covered:** 26
|
||||||
- **Uncovered:** 0
|
- **Uncovered:** 0
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -31,6 +31,11 @@ inline `[^cite-id]` footnote).
|
|||||||
- Covered by: [[Source - LLM Wiki Pattern]]
|
- Covered by: [[Source - LLM Wiki Pattern]]
|
||||||
- Cited by: [[Andrej Karpathy]], [[ChatGPT]], [[Claude Code]], [[Dataview]], [[Knowledge Compounding]], [[LLM Wiki Pattern]], [[Marp]], [[Memex]], [[NotebookLM]], [[Obsidian]], [[Obsidian Web Clipper]], [[OpenAI Codex]], [[OpenCode]], [[Pi]], [[RAG]], [[Three-Layer Architecture]], [[Tolkien Gateway]], [[Vannevar Bush]], [[qmd]]
|
- Cited by: [[Andrej Karpathy]], [[ChatGPT]], [[Claude Code]], [[Dataview]], [[Knowledge Compounding]], [[LLM Wiki Pattern]], [[Marp]], [[Memex]], [[NotebookLM]], [[Obsidian]], [[Obsidian Web Clipper]], [[OpenAI Codex]], [[OpenCode]], [[Pi]], [[RAG]], [[Three-Layer Architecture]], [[Tolkien Gateway]], [[Vannevar Bush]], [[qmd]]
|
||||||
|
|
||||||
|
### `raw/documents/qmd - GitHub Repository.md`
|
||||||
|
|
||||||
|
- Covered by: [[Source - qmd - GitHub Repository]]
|
||||||
|
- Cited by: [[qmd]]
|
||||||
|
|
||||||
### `raw/notes/Arch Linux Cheat Sheet.md`
|
### `raw/notes/Arch Linux Cheat Sheet.md`
|
||||||
|
|
||||||
- Covered by: [[Source - Arch Linux Cheat Sheet]]
|
- Covered by: [[Source - Arch Linux Cheat Sheet]]
|
||||||
@@ -71,6 +76,11 @@ inline `[^cite-id]` footnote).
|
|||||||
- Covered by: [[Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31]]
|
- Covered by: [[Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31]]
|
||||||
- Cited by: [[Chemenu]], [[Detect-Repair Asymmetry]], [[Gitea]], [[Gitea Actions]], [[Gitea MCP Server]], [[Issue Label Scheme]], [[KB Stack Versioning]], [[wikitool]]
|
- Cited by: [[Chemenu]], [[Detect-Repair Asymmetry]], [[Gitea]], [[Gitea Actions]], [[Gitea MCP Server]], [[Issue Label Scheme]], [[KB Stack Versioning]], [[wikitool]]
|
||||||
|
|
||||||
|
### `raw/notes/Conversation Transcript - MCP Read Server Implementation Session 2026-09-02.md`
|
||||||
|
|
||||||
|
- Covered by: [[Source - MCP Read Server Implementation Session 2026-09-02]]
|
||||||
|
- Cited by: [[Chemenu]], [[Iteration and Cost Limits]], [[MCP-Leseserver]], [[Publish-Remote Gate]], [[wikitool]]
|
||||||
|
|
||||||
### `raw/notes/Conversation Transcript - Nightly Drift-Check Workflow and doctor's Bootstrap Gap Session 2026-08-31.md`
|
### `raw/notes/Conversation Transcript - Nightly Drift-Check Workflow and doctor's Bootstrap Gap Session 2026-08-31.md`
|
||||||
|
|
||||||
- Covered by: [[Source - Conversation - Nightly Drift-Check Workflow and doctor's Bootstrap Gap Session 2026-08-31]]
|
- Covered by: [[Source - Conversation - Nightly Drift-Check Workflow and doctor's Bootstrap Gap Session 2026-08-31]]
|
||||||
|
|||||||
+3
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# kb/sources/ - Index
|
# kb/sources/ - Index
|
||||||
|
|
||||||
24 page(s). Regenerated by `wikitool index rebuild`.
|
26 page(s). Regenerated by `wikitool index rebuild`.
|
||||||
|
|
||||||
## All
|
## All
|
||||||
|
|
||||||
@@ -28,8 +28,10 @@
|
|||||||
| [[Source - LLM Improvements Sonnet Analysis]] | notes | Sonnet-Analyse, die AGENTS.md und wikitool mit Farzas Gist und awesome-llm-wiki vergleicht und die Codex-Analyse um konkrete Empfehlungen zu Qualitätsschwellen, Stilrichtlinie, Auditrhythmus und Skalierung ergänzt | 2026-08-03 |
|
| [[Source - LLM Improvements Sonnet Analysis]] | notes | Sonnet-Analyse, die AGENTS.md und wikitool mit Farzas Gist und awesome-llm-wiki vergleicht und die Codex-Analyse um konkrete Empfehlungen zu Qualitätsschwellen, Stilrichtlinie, Auditrhythmus und Skalierung ergänzt | 2026-08-03 |
|
||||||
| [[Source - LLM Wiki Pattern]] | article | Grundlegendes Muster für persönliche Wissensbasen mit LLMs: ein dauerhaftes Wiki schrittweise pflegen, statt es aus den Quellen neu herzuleiten. | 2026-07-26 |
|
| [[Source - LLM Wiki Pattern]] | article | Grundlegendes Muster für persönliche Wissensbasen mit LLMs: ein dauerhaftes Wiki schrittweise pflegen, statt es aus den Quellen neu herzuleiten. | 2026-07-26 |
|
||||||
| [[Source - LLM Wiki v2]] | article | Erweitertes LLM-Wiki-Muster mit Praxiserfahrungen aus agentmemory zu Memory Lifecycle, Confidence Scoring, Wissensgraph und Automatisierung. | 2026-07-26 |
|
| [[Source - LLM Wiki v2]] | article | Erweitertes LLM-Wiki-Muster mit Praxiserfahrungen aus agentmemory zu Memory Lifecycle, Confidence Scoring, Wissensgraph und Automatisierung. | 2026-07-26 |
|
||||||
|
| [[Source - MCP Read Server Implementation Session 2026-09-02]] | notes | Sitzung, die die Sequenz aus Issue #36 umsetzt: Publish-Remote Gate scharf, Lesepfad gehaertet, Root-Aufloesung und Bibliotheksgrenze gezogen, MCP-Leseserver gebaut - vier Versionsstufen 2.2.3 bis 2.4.0, dazu INSTALL-MCP.md und Issue #37. | 2026-09-02 |
|
||||||
| [[Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]] | notes | Sitzung, die eine ungeprueft niedergeschriebene Merge-Behauptung in private-instance.md durch einen empirischen Test widerlegt, die Prozedur korrigiert (2.2.1) und Issue #30 mit einem getesteten Skript sowie zwei Architekturvorschlaegen anlegt. | 2026-09-01 |
|
| [[Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]] | notes | Sitzung, die eine ungeprueft niedergeschriebene Merge-Behauptung in private-instance.md durch einen empirischen Test widerlegt, die Prozedur korrigiert (2.2.1) und Issue #30 mit einem getesteten Skript sowie zwei Architekturvorschlaegen anlegt. | 2026-09-01 |
|
||||||
| [[Source - Public Release, Corpus Purge and History Squash Session 2026-09-01]] | notes | Sitzung, die den Chemenu-Stack von einer privaten Testinstanz in ein oeffentliches Repo ueberfuehrt: Korpus geloescht statt anonymisiert, Git-History auf einen Commit gesquashed, AGPL-3.0/CC-BY-4.0-Dual-Lizenz gewaehlt, dist export um einen Leak-Canary gehaertet. | 2026-09-01 |
|
| [[Source - Public Release, Corpus Purge and History Squash Session 2026-09-01]] | notes | Sitzung, die den Chemenu-Stack von einer privaten Testinstanz in ein oeffentliches Repo ueberfuehrt: Korpus geloescht statt anonymisiert, Git-History auf einen Commit gesquashed, AGPL-3.0/CC-BY-4.0-Dual-Lizenz gewaehlt, dist export um einen Leak-Canary gehaertet. | 2026-09-01 |
|
||||||
| [[Source - Publish-Remote Gate and Issue Triage Session 2026-09-01]] | notes | Sitzung, die ein drittes, Token-loses Gate fuer publish baut, instructions/private-instance.md schreibt, sechs Gitea-Issues auf den Rename und die neue Architektur nachzieht und die Actions-Run-Historie entfernen laesst. | 2026-09-01 |
|
| [[Source - Publish-Remote Gate and Issue Triage Session 2026-09-01]] | notes | Sitzung, die ein drittes, Token-loses Gate fuer publish baut, instructions/private-instance.md schreibt, sechs Gitea-Issues auf den Rename und die neue Architektur nachzieht und die Actions-Run-Historie entfernen laesst. | 2026-09-01 |
|
||||||
|
| [[Source - qmd - GitHub Repository]] | document | GitHub-API-Metadaten, package.json und README-Auszuege von tobi/qmd: TypeScript/Node/Bun statt Go oder Rust, BM25 (SQLite FTS5) plus Vektor-Suche (sqlite-vec) plus LLM-Reranking ueber node-llama-cpp. | 2026-09-02 |
|
||||||
| [[Source - Wine]] | notes | Wine-Konfiguration für Arch Linux: pacman-NoExtract-Einstellungen und Bottles-Runtime-Optionen einschließlich Proton- und Lutris-Varianten. | 2026-08-01 |
|
| [[Source - Wine]] | notes | Wine-Konfiguration für Arch Linux: pacman-NoExtract-Einstellungen und Bottles-Runtime-Optionen einschließlich Proton- und Lutris-Varianten. | 2026-08-01 |
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
---
|
||||||
|
type: types/source.md
|
||||||
|
source_type: notes
|
||||||
|
author: Torben
|
||||||
|
raw_files: [raw/notes/Conversation Transcript - MCP Read Server Implementation Session 2026-09-02.md]
|
||||||
|
source_language: de
|
||||||
|
date: 2026-09-02
|
||||||
|
tags: []
|
||||||
|
entities: [wikitool, Chemenu]
|
||||||
|
concepts: [Publish-Remote Gate, Mass-Update Gate, Iteration and Cost Limits, MCP-Leseserver]
|
||||||
|
summary: 'Sitzung, die die Sequenz aus Issue #36 umsetzt: Publish-Remote Gate scharf, Lesepfad gehaertet, Root-Aufloesung und Bibliotheksgrenze gezogen, MCP-Leseserver gebaut - vier Versionsstufen 2.2.3 bis 2.4.0, dazu INSTALL-MCP.md und Issue #37.'
|
||||||
|
---
|
||||||
|
# Source: MCP Read Server Implementation Session 2026-09-02
|
||||||
|
|
||||||
|
**Autor:** Torben
|
||||||
|
**Datum:** 2026-09-02
|
||||||
|
**Raw-Dateien:** raw/notes/Conversation Transcript - MCP Read Server Implementation Session 2026-09-02.md
|
||||||
|
**Typ:** Notes
|
||||||
|
|
||||||
|
## Zusammenfassung
|
||||||
|
|
||||||
|
Diese Sitzung arbeitet die vierstufige Sequenz aus Issue #36 ab, dem Sammel-Issue für den Weg
|
||||||
|
zum MCP-Leseserver: #34 (Publish-Remote-Gate scharf stellen), #33 (Lesepfad vor der Exposition
|
||||||
|
härten), #31 (Root-Auflösung von der Importzeit lösen und eine Bibliotheksgrenze ziehen), #19
|
||||||
|
(der Leseserver selbst). Jeder Schritt endet mit einem Versions-Bump und einem Testlauf; die
|
||||||
|
Reihenfolge folgt dem Master-Issue, weil #33 dieselben Dateien anfasst, die #31 strukturell
|
||||||
|
umbaut, und #31 die Grenze liefert, auf der #19 aufsetzt.
|
||||||
|
|
||||||
|
`.wikitool-remotes.json` fehlte in diesem Checkout trotz Dokumentation, die das Gegenteil
|
||||||
|
behauptete - angelegt und gegen ein erfundenes Ziel gegengeprüft (Exit 42). Der Lesepfad bekam
|
||||||
|
sechs Fixes gegen einen 267-Byte-YAML-Alias, der zu 672.603 Knoten expandiert, gegen einen
|
||||||
|
ReDoS-Zweig in der Ranking-Funktion, einen fehlenden Subprozess-Timeout, und einen Korpus-Cache,
|
||||||
|
der nie einen schmutzigen Arbeitsbaum cacht. `config.ROOT` und alle abgeleiteten Pfade waren zur
|
||||||
|
Importzeit gebunden; die Auflösung ist jetzt lazy (`CHEMENU_ROOT` → Walk-up), und der reine
|
||||||
|
Lesekern (`search/service.py`, `lint_core.py`, `types_core.py`) importiert kein `typer` mehr.
|
||||||
|
Der MCP-Server (`tools/chemenu/mcp/`) exponiert `search`/`types`/`describe_type`/`lint`/`status`
|
||||||
|
über `chemenu.api.Corpus` - strukturell ohne Schreibpfad, mit Commit-Stempel auf jeder Antwort
|
||||||
|
und einer Startverweigerung, falls Telemetrie in den bedienten Baum schreiben würde.
|
||||||
|
|
||||||
|
Nach Freigabe des 37-Datei-Changesets (Mass-Update-Gate, Token `46442f4419c1`) folgten
|
||||||
|
`INSTALL-MCP.md` für Menschen, ein Verweis auf die separate Traefik-ForwardAuth-Middleware
|
||||||
|
(`gitea-mcp-forward-auth`), und Issue #37 für das noch fehlende Container-Image - mit den
|
||||||
|
konkreten CI-Vorlagen aus `gitea-mcp-forward-auth` (Registry-Push) und `gitea-mcp`
|
||||||
|
(Dockerfile-Form, aber DockerHub statt der eigenen Registry). Alle vier Sequenz-Issues wurden
|
||||||
|
geschlossen, #36 blieb offen, weil sein eigenes Abschlusskriterium - ein Konsument, der
|
||||||
|
nachweislich über die Middleware antwortet - erst mit #37 erfüllbar ist.
|
||||||
|
|
||||||
|
## Kernaussagen
|
||||||
|
|
||||||
|
- Gemessen: Korpus-Parse 265 ms → 54 ms (`CSafeLoader`), `wikitool search` end-to-end
|
||||||
|
593 ms → 347 ms; die verbleibenden ~262 ms sind Modulimport und entfallen erst im residenten
|
||||||
|
MCP-Prozess.
|
||||||
|
- Der ReDoS-Zweig (`_contains` mit `re.search` gegen nutzergesteuerten Regex) wurde gelöscht,
|
||||||
|
nicht begrenzt - `rg` wendet das Muster ohnehin mit einer linearen Engine an, bevor die
|
||||||
|
Funktion je läuft.
|
||||||
|
- `monkeypatch.setattr(config, "ROOT", ...)` baute nach der lazy-Auflösung die stale Bindung
|
||||||
|
beim Teardown wieder auf, weil es den *aufgelösten* alten Wert zurückschreibt - `config.reset()`
|
||||||
|
musste dazukommen, in derselben autouse-Fixture, die das Problem eine Ebene höher (Umgebungsvariablen)
|
||||||
|
bereits kannte.
|
||||||
|
- Der MCP-Server hat keinen Schreibpfad, weil `chemenu.api` nichts unter `chemenu.commands`
|
||||||
|
importiert - nicht, weil eine Liste gefiltert wird. Ein Test importiert das Servermodul in
|
||||||
|
einem frischen Interpreter und prüft `sys.modules`.
|
||||||
|
- Ein Stempel-Bug wurde beim Schreiben des Golden-Tests selbst gefunden: `_stamp()` fragte nach
|
||||||
|
der *aktuellen* statt der beim Laden tatsächlich gelesenen Revision und hätte bei einem
|
||||||
|
minimal verzögerten zweiten Zugriff `commit: null` auf einem sauberen Baum liefern können.
|
||||||
|
- `gitea-mcp` ist als Registry-Vorlage ungeeignet - sein Release-Workflow pusht nach DockerHub
|
||||||
|
(Fork des Upstream), nicht in die eigene Gitea-Registry.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
|
||||||
|
- [x] #34, #33, #31, #19 umgesetzt und mit Abschlusskommentar geschlossen
|
||||||
|
- [x] `INSTALL-MCP.md` geschrieben, in `INSTALL.md`/`README.md` verlinkt, in `dist export` aufgenommen
|
||||||
|
- [x] Issue #37 (Container-Image) angelegt, mit neun offenen Entscheidungen benannt
|
||||||
|
- [ ] #37 selbst umsetzen
|
||||||
|
- [ ] #36 schließen, sobald #37 den Middleware-Nachweis liefert
|
||||||
|
- [ ] #23 (Env-Var-Erzwingung) - `CHEMENU_ROOT` wurde von Hand in `_WIKITOOL_ENV` eingetragen
|
||||||
|
- [x] `kb/entities/tools/qmd.md` - falsche Sprachangabe korrigiert (siehe Korrektur unten)
|
||||||
|
|
||||||
|
## Korrektur zum Transkript-Kopf
|
||||||
|
|
||||||
|
Der Fidelity-Block des Rohtranskripts sagt: "One of two transcripts cut from this session; the
|
||||||
|
other covers fixing `kb/entities/tools/qmd.md`". Dieses zweite Transkript wurde nie geschrieben
|
||||||
|
- `raw/` ist unveränderlich, die Korrektur gehört hierher, nicht in die Datei selbst. Tatsächlich
|
||||||
|
lief die Korrektur ohne eigenes Transkript: direkt gegen `tobi/qmd` auf GitHub geprüft und als
|
||||||
|
eigene Quelle mit eigenem Raw-Beleg abgelegt (`Source - qmd - GitHub Repository`,
|
||||||
|
`raw/documents/qmd - GitHub Repository.md`) - eine Quellen-Verifikation statt eines
|
||||||
|
Gesprächsprotokolls, was für eine Sprachangaben-Korrektur die passendere Belegform ist.
|
||||||
|
|
||||||
|
## Nicht übernommen
|
||||||
|
|
||||||
|
- Der vollständige Wortlaut der geprüften Docstrings, Kommentare und Testfälle - das Transkript
|
||||||
|
benennt Dateien und die tragenden Eigenschaften, der Code selbst ist die Quelle.
|
||||||
|
- Die exakten neun offenen Entscheidungspunkte aus Issue #37 (Korpus im Image vs. Volume,
|
||||||
|
Basis-Image, Healthcheck etc.) - dort bereits vollständig dokumentiert, hier nicht dupliziert.
|
||||||
|
- Der Wortlaut der abgerufenen READMEs von `gitea-mcp-forward-auth` und `gitea-mcp` - nur die
|
||||||
|
für die Entscheidung relevanten Fakten (Config-Variablen, Workflow-Form, Registry-Ziel)
|
||||||
|
wurden übernommen.
|
||||||
|
|
||||||
|
## Verwandte Entities
|
||||||
|
|
||||||
|
- [[wikitool]]
|
||||||
|
- [[Chemenu]]
|
||||||
|
|
||||||
|
## Verwandte Concepts
|
||||||
|
|
||||||
|
- [[Publish-Remote Gate]]
|
||||||
|
- [[Mass-Update Gate]]
|
||||||
|
- [[Iteration and Cost Limits]]
|
||||||
|
- [[MCP-Leseserver]]
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
type: types/source.md
|
||||||
|
source_type: document
|
||||||
|
author: Torben
|
||||||
|
raw_files: [raw/documents/qmd - GitHub Repository.md]
|
||||||
|
source_url: https://github.com/tobi/qmd
|
||||||
|
source_language: en
|
||||||
|
date: 2026-09-02
|
||||||
|
tags: []
|
||||||
|
entities: [qmd]
|
||||||
|
concepts: []
|
||||||
|
summary: 'GitHub-API-Metadaten, package.json und README-Auszuege von tobi/qmd: TypeScript/Node/Bun statt Go oder Rust, BM25 (SQLite FTS5) plus Vektor-Suche (sqlite-vec) plus LLM-Reranking ueber node-llama-cpp.'
|
||||||
|
---
|
||||||
|
# Source: qmd - GitHub Repository
|
||||||
|
|
||||||
|
**Autor:** Torben
|
||||||
|
**Datum:** 2026-09-02
|
||||||
|
**Raw-Dateien:** raw/documents/qmd - GitHub Repository.md
|
||||||
|
**Typ:** Document
|
||||||
|
|
||||||
|
## Zusammenfassung
|
||||||
|
|
||||||
|
Direkter Abruf von `tobi/qmd` auf GitHub (Repo-Metadaten über die GitHub-API, `package.json`,
|
||||||
|
README-Auszüge), um eine bestehende, unsichere Behauptung auf der Entity-Seite [[qmd]] zu
|
||||||
|
prüfen. Die Seite trug seit ihrer Erstellung `Sprache: Nicht angegeben (wahrscheinlich Go oder
|
||||||
|
Rust)` bei `confidence: 0.85` - eine geratene Angabe mit einer Konfidenz, die für eine Vermutung
|
||||||
|
zu hoch war. Die GitHub-API meldet `"language": "TypeScript"`, mit einem byte-gewichteten
|
||||||
|
Sprach-Split von 1.597.168 B TypeScript gegenüber 200.880 B Python, 26.136 B Shell,
|
||||||
|
23.132 B JavaScript und weniger als 6 KB restlicher Sprachen - kein Go, kein Rust im Repository.
|
||||||
|
|
||||||
|
Das npm-Paket heißt `@tobilu/qmd`, installiert per `npm install -g` oder `bun install -g`,
|
||||||
|
ausgeführt per `npx`/`bunx` - keine Go-typische `go install`-Zeile, wie die alte Seite
|
||||||
|
behauptete. `package.json` listet `node-llama-cpp` (LLM-Inferenz), `sqlite-vec` (Vektorindex)
|
||||||
|
und `better-sqlite3` (SQLite-Bindung) als direkte Abhängigkeiten; das README bestätigt das
|
||||||
|
Zusammenspiel explizit: „QMD combines BM25 full-text search, vector semantic search, and LLM
|
||||||
|
re-ranking—all running locally via node-llama-cpp with GGUF models." Die Scoring-Tabelle des
|
||||||
|
Architektur-Abschnitts benennt zusätzlich `SQLite FTS5 BM25` als konkreten Volltext-Mechanismus,
|
||||||
|
nicht nur „BM25" allgemein.
|
||||||
|
|
||||||
|
## Kernaussagen
|
||||||
|
|
||||||
|
- Sprache: TypeScript (GitHub-API `language`-Feld, bestätigt durch die Byte-Gewichtung).
|
||||||
|
- Runtime: Node.js oder Bun, nicht Go/Rust - Installationsbefehle im README belegen das direkt.
|
||||||
|
- Such-Stack: SQLite FTS5 (BM25) + `sqlite-vec` (Vektor-Ähnlichkeit) + `node-llama-cpp`
|
||||||
|
(LLM-Reranking mit GGUF-Modellen) - deckungsgleich mit dem, was die alte Seite unter
|
||||||
|
„Such-Capabilities" bereits beschrieb, nur ohne konkrete Technologienamen.
|
||||||
|
- Enthält einen `@modelcontextprotocol/server`-Abhängigkeit, deckungsgleich mit der bereits auf
|
||||||
|
der Seite genannten MCP-Server-Funktion.
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
|
||||||
|
- [x] `kb/entities/tools/qmd.md`s Sprachangabe korrigiert und mit dieser Quelle belegt
|
||||||
|
|
||||||
|
## Nicht übernommen
|
||||||
|
|
||||||
|
- Der vollständige Funktionsumfang (Library-API, MCP-Server-Details, Tree-Sitter-basiertes
|
||||||
|
Code-Chunking, Konfigurationsdatei-Referenz, CLI-Unterbefehle) - nur das zur Korrektur der
|
||||||
|
Sprach-/Stack-Angabe Nötige wurde übernommen. Eine künftige Erweiterung der `qmd`-Seite um
|
||||||
|
diese Themen bräuchte einen neuen Abruf, nicht diese Quelle.
|
||||||
|
|
||||||
|
## Verwandte Entities
|
||||||
|
|
||||||
|
- [[qmd]]
|
||||||
|
|
||||||
|
## Verwandte Concepts
|
||||||
|
|
||||||
|
- None identified
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# qmd - GitHub Repository (tobi/qmd)
|
||||||
|
|
||||||
|
> Source: https://github.com/tobi/qmd
|
||||||
|
> Retrieved: 2026-09-02, via `https://api.github.com/repos/tobi/qmd`,
|
||||||
|
> `https://api.github.com/repos/tobi/qmd/languages`, and the raw README/`package.json` at the
|
||||||
|
> `main` branch HEAD (`pushed_at: 2026-08-18T14:26:31Z` at retrieval time).
|
||||||
|
> Fidelity: the API JSON fields are quoted verbatim; the README/package.json excerpts below are
|
||||||
|
> quoted verbatim from the fetched files, trimmed to what backs the corrected claims on
|
||||||
|
> `kb/entities/tools/qmd.md` - not a full copy of either file.
|
||||||
|
|
||||||
|
## Repository metadata (GitHub API)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"full_name": "tobi/qmd",
|
||||||
|
"description": "mini cli search engine for your docs, knowledge bases, meeting notes, whatever. Tracking current sota approaches while being all local",
|
||||||
|
"language": "TypeScript",
|
||||||
|
"html_url": "https://github.com/tobi/qmd",
|
||||||
|
"license": {"key": "mit", "name": "MIT License", "spdx_id": "MIT"}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Byte-weighted language breakdown (`/languages` endpoint):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"TypeScript": 1597168,
|
||||||
|
"Python": 200880,
|
||||||
|
"Shell": 26136,
|
||||||
|
"JavaScript": 23132,
|
||||||
|
"Nix": 5984,
|
||||||
|
"Just": 1081,
|
||||||
|
"Dockerfile": 1074
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `package.json` dependencies
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "@tobilu/qmd",
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/server": "2.0.0",
|
||||||
|
"better-sqlite3": "^13.0.3",
|
||||||
|
"fast-glob": "3.3.3",
|
||||||
|
"node-llama-cpp": "3.20.0",
|
||||||
|
"picomatch": "4.0.5",
|
||||||
|
"sqlite-vec": "0.1.9",
|
||||||
|
"tree-sitter-go": "0.25.0",
|
||||||
|
"tree-sitter-python": "0.25.0",
|
||||||
|
"tree-sitter-rust": "0.24.0",
|
||||||
|
"tree-sitter-typescript": "0.23.2",
|
||||||
|
"web-tree-sitter": "0.26.12",
|
||||||
|
"yaml": "2.9.0",
|
||||||
|
"zod": "4.2.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## README excerpts
|
||||||
|
|
||||||
|
Opening description (README.md, line 5):
|
||||||
|
|
||||||
|
> QMD combines BM25 full-text search, vector semantic search, and LLM re-ranking—all running
|
||||||
|
> locally via node-llama-cpp with GGUF models.
|
||||||
|
|
||||||
|
Install ("Quick Start" section):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Install globally (Node or Bun)
|
||||||
|
npm install -g @tobilu/qmd
|
||||||
|
# or
|
||||||
|
bun install -g @tobilu/qmd
|
||||||
|
|
||||||
|
# Or run directly
|
||||||
|
npx @tobilu/qmd ...
|
||||||
|
bunx @tobilu/qmd ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Scoring table (architecture section):
|
||||||
|
|
||||||
|
| Stage | Method | Formula | Range |
|
||||||
|
|---|---|---|---|
|
||||||
|
| FTS (BM25) | SQLite FTS5 BM25 | `Math.abs(score)` | 0 to ~25+ |
|
||||||
|
| Vector | Cosine distance | `1 / (1 + distance)` | 0.0 to 1.0 |
|
||||||
|
| Reranker | LLM 0-10 rating | `score / 10` | 0.0 to 1.0 |
|
||||||
|
|
||||||
|
Diagnostic command comment, confirming `sqlite-vec` is the vector index used at runtime, not
|
||||||
|
only a listed dependency:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Diagnose the install (runtime, sqlite-vec, embedding fingerprints, GPU probe)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Not extracted
|
||||||
|
|
||||||
|
The full README (1301 lines: library API, MCP server usage, tree-sitter code-chunking options,
|
||||||
|
config file reference, CLI subcommand reference) - only the excerpts backing the corrected
|
||||||
|
`qmd.md` claims (language, runtime, install method, search/rerank stack) were pulled. A future
|
||||||
|
qmd page expansion covering its library API or MCP server would need a fresh fetch, not a reuse
|
||||||
|
of this file's `## Not extracted` judgment.
|
||||||
+245
@@ -0,0 +1,245 @@
|
|||||||
|
# Conversation Transcript - MCP Read Server Implementation Session
|
||||||
|
|
||||||
|
> Source: Claude Code session (`claude-opus-5`), chemenu workspace
|
||||||
|
> Collected: 2026-09-02
|
||||||
|
> Participant: Torben
|
||||||
|
> Fidelity: **faithful summary transcript, not a verbatim log.** Torben's instructions are
|
||||||
|
> quoted verbatim; the assistant's design reasoning, code, and file contents are condensed to
|
||||||
|
> what a later reader needs to find the artifact - the artifacts themselves are the code, not
|
||||||
|
> this transcript. Command outputs shown (test counts, timings, the Mass-Update Gate's printed
|
||||||
|
> file list, gate refusal messages) are real, copied from the session. No credentials appeared.
|
||||||
|
> No subagents were used - all work in this transcript is first-hand.
|
||||||
|
> One of two transcripts cut from this session; the other covers fixing `kb/entities/tools/qmd.md`,
|
||||||
|
> a documentation defect noted but not fixed during this one.
|
||||||
|
|
||||||
|
Covers implementing the four-issue sequence held by #36 (the master issue for "path to the MCP
|
||||||
|
read server"): #34 (arm the Publish-Remote Gate), #33 (harden the read path), #31 (root
|
||||||
|
resolution + library boundary), #19 (the MCP server itself), plus the human-facing
|
||||||
|
`INSTALL-MCP.md` and filing #37 (the container-image follow-up) that #19's closure surfaced as
|
||||||
|
still missing. Four version bumps in sequence: 2.2.3, 2.2.4, 2.3.0, 2.4.0, then 2.4.1 for the
|
||||||
|
documentation follow-up. All five touched issues (#34, #33, #31, #19, #36) got closing comments;
|
||||||
|
#36 was left open pending #37.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Turn 1 - `/stack-dev implementiere #36`
|
||||||
|
|
||||||
|
The `stack-dev` skill loaded, switching the session into tool-development rules (ordinary
|
||||||
|
software-engineering judgment on `tools/`, `types/`, `instructions/` - no source-binding, no
|
||||||
|
`raw/` citation needed for code).
|
||||||
|
|
||||||
|
Read issue #36 (the master issue) via `gitea-mcp`. It holds no implementation itself - a
|
||||||
|
sequence table (#34 → #33 → #31 → #19), a table of platform/auth/transport decisions already
|
||||||
|
made and closed to debate, a measured baseline (593 ms per `wikitool search`, broken down:
|
||||||
|
262 ms module import, 274 ms corpus parse, 7 ms ripgrep), ten verified findings backing the
|
||||||
|
design, and an explicit "not in scope" list (Go, deployment details, a write path, #15's
|
||||||
|
research capability). Read #34, #33, #31, #19 in full including comments to get the acceptance
|
||||||
|
criteria and the reasoning behind each.
|
||||||
|
|
||||||
|
Worked the sequence in order, starting a session-scoped `WIKITOOL_SESSION_ID`.
|
||||||
|
|
||||||
|
**#34 - Publish-Remote Gate.** `.wikitool-remotes.json` did not exist in this checkout; absence
|
||||||
|
means unrestricted per `git_publish.read_allowed_push_urls()`. Created it with `origin`'s exact
|
||||||
|
push URL as the sole allowed target. Counter-checked by adding a second git remote to a made-up
|
||||||
|
URL and running `publish --remote gatecheck`: refused with **exit 42**, working tree untouched,
|
||||||
|
before any fetch. Remote removed afterward.
|
||||||
|
|
||||||
|
`doctor`'s `check_publish_remotes()` reported only whether the *file* existed, not whether the
|
||||||
|
gate was actually armed. Rewrote its three outcome messages to lead with `Gate armed:` /
|
||||||
|
`Gate not armed:` explicitly - the single-remote-no-file case stays `OK` (nothing to protect,
|
||||||
|
and a FAIL would make the file mandatory through the back door) but now says outright that every
|
||||||
|
push target passes. The check had **no tests**; added three, one per state.
|
||||||
|
|
||||||
|
Bumped `--patch` → **2.2.3**.
|
||||||
|
|
||||||
|
**#33 - Harden the read path.** Six findings, each addressed with a regression:
|
||||||
|
|
||||||
|
1. *ReDoS.* `search/ripgrep.py:_contains` passed `query.regex` to `re.search`. Deleted the
|
||||||
|
branch entirely rather than bounding it - `rg` already applies the pattern with a linear
|
||||||
|
engine before this function runs, so nothing is lost except the extra title/summary ranking
|
||||||
|
boost for a non-literal pattern (and summary/H1 lines are themselves counted by `rg`).
|
||||||
|
Regression test asserts both the timing (`< 0.5s` against `(\w+\s?)+$`) and the return value,
|
||||||
|
because a bound alone would pass with a merely-faster engine.
|
||||||
|
2. *No subprocess timeout.* Added `RIPGREP_TIMEOUT_SECONDS = 30.0`; `TimeoutExpired` translates
|
||||||
|
to the existing `RipgrepFailed` path.
|
||||||
|
3. *YAML aliases.* Frontmatter has no legitimate use for anchors/aliases, so they are refused
|
||||||
|
outright rather than budgeted, checked on the streaming *event* parse (`yaml.parse`) so the
|
||||||
|
check itself never triggers the expansion it is checking for - `*` is a necessary character
|
||||||
|
in any alias node, so its absence proves absence with zero parse cost. Constructed a
|
||||||
|
reproduction: 267 bytes of nested aliases compose into 672,603 nodes on traversal at
|
||||||
|
constant (0.2 ms) parse time, growth 9^n with nesting depth. Added a 64 KiB frontmatter size
|
||||||
|
limit and a `RecursionError` catch (PyYAML composes recursively; deep nesting is not a
|
||||||
|
`YAMLError`).
|
||||||
|
4. *`CSafeLoader` unused.* Switched to it with a `SafeLoader` fallback. Measured over this
|
||||||
|
corpus (176 pages, best of 5): **265 ms → 54 ms**.
|
||||||
|
5. *Silent frontmatter loss.* Decided: the read path now *reports* rather than swallows.
|
||||||
|
Unparseable YAML still degrades to `{}` for bulk operations, but the reason travels with it
|
||||||
|
(`Page.frontmatter_error`, set by a single parser shared between `read_page()` and
|
||||||
|
`frontmatter_error()` - previously two separate `safe_load` call sites that could describe
|
||||||
|
the same broken file differently). `search --json` now always carries an `unreadable` list
|
||||||
|
of `{path, reason}`; the table form writes the same lines to stderr. Added `read_page_strict()`
|
||||||
|
for the future ingest-quarantine path (#32), which must stop on unparseable input rather than
|
||||||
|
empty it.
|
||||||
|
6. *Corpus reparse per call.* New `chemenu/corpus_cache.py`: a `CorpusCache` object a caller
|
||||||
|
*holds* (not a module-global switch). The CLI holds none and is unaffected. Correctness
|
||||||
|
property, not just speed: **a dirty working tree is never cached** - if git cannot answer
|
||||||
|
whether the tree is clean, it is treated as dirty. `wikitool search` end-to-end (best of 5):
|
||||||
|
**593 ms → 347 ms**.
|
||||||
|
|
||||||
|
Bumped `--patch` → **2.2.4**.
|
||||||
|
|
||||||
|
**#31 - Root resolution + library boundary.** `config.ROOT` and everything derived from it were
|
||||||
|
Python module constants, bound at import time from `Path(__file__).resolve().parents[2]` - so
|
||||||
|
`monkeypatch.setattr(config, "ROOT", tmp_path)` repointed `ROOT` but left `KB_DIR`/`RAW_DIR`
|
||||||
|
aimed at the real checkout, the exact failure class `raw_dir`'s own fixture docstring already
|
||||||
|
warned about one layer up.
|
||||||
|
|
||||||
|
Rewrote `config.py`: `resolve_root()` by precedence (explicit argument → `$CHEMENU_ROOT` →
|
||||||
|
walk-up, the walk-up staying default so `tools/wikitool` is unaffected), and every derived path
|
||||||
|
resolved on *attribute access* via module `__getattr__` (PEP 562) rather than stored - so an
|
||||||
|
assignment onto `ROOT` (test or otherwise) is honored by every path under it, live. This
|
||||||
|
surfaced a subtler bug while fixing the first one: `monkeypatch`'s own undo mechanism reads the
|
||||||
|
old value (which resolves it) and writes it back as a real attribute on teardown, recreating the
|
||||||
|
stale binding the rewrite was meant to eliminate. Added `config.reset()`, called by the autouse
|
||||||
|
hermetic-environment fixture on both setup and teardown.
|
||||||
|
|
||||||
|
Split the CLI-coupled command modules into a pure core + thin adapter, three times over:
|
||||||
|
`search/service.py`, `lint_core.py`, `types_core.py` - none of them import `typer` or `rich`.
|
||||||
|
`commands/search.py`, `commands/lint.py`, `commands/types_cmd.py` became the terminal adapters,
|
||||||
|
re-exporting the same names so no existing import breaks.
|
||||||
|
|
||||||
|
New `chemenu/errors.py`: `ChemenuError` → `ValidationError` (also inherits `ValueError`, since
|
||||||
|
`PredicateError` already was one and callers catch it that way) / `BackendError`.
|
||||||
|
`PredicateError`, `FrontmatterError`, `UnknownBackend`, `RipgrepMissing`, `RipgrepFailed` moved
|
||||||
|
under this hierarchy.
|
||||||
|
|
||||||
|
New `chemenu/api.py`: `Corpus` class, the in-process entry point - takes a root, returns exactly
|
||||||
|
the CLI's `--json` shapes, raises instead of exiting, stamps every response with `commit`/
|
||||||
|
`as_of`. Wrote the acceptance test by monkeypatching `Path.read_text`/`Path.rglob` to fail on any
|
||||||
|
access under the real checkout root while a foreign root is set - this test could not have
|
||||||
|
passed before the lazy-resolution rewrite.
|
||||||
|
|
||||||
|
Two more accidental dependencies surfaced and were fixed: `TypeResolver.repo_root` was also
|
||||||
|
import-bound (fixed the same way, with `config.rooted()` as a process-wide context manager for
|
||||||
|
callers that reach `config` directly rather than taking a root argument - and `use_shipped_type_specs()`
|
||||||
|
added to the test fixtures that had been relying on it silently); and `search/registry.resolve()`
|
||||||
|
did not pass `kb_dir`/`root` through to the backend, so a caller pointing `run_search` at a
|
||||||
|
foreign corpus could still have `RipgrepBackend` read `config.KB_DIR` underneath it.
|
||||||
|
|
||||||
|
Bumped `--minor` → **2.3.0** (new capability, backward-compatible).
|
||||||
|
|
||||||
|
**#19 - MCP read server.** New package `tools/chemenu/mcp/` (`server.py`, `__main__.py`).
|
||||||
|
Installed the `mcp` SDK (`mcp>=2.0`, discovered mid-session that this pulled in the v2 API -
|
||||||
|
`FastMCP` renamed to `MCPServer`, imported from `mcp.server.mcpserver`). Five tools over
|
||||||
|
`chemenu.api.Corpus`: `search`, `types`, `describe_type`, `lint`, `status` (`status` is
|
||||||
|
server-composed, not a wrapper - there is no `wikitool status` command to wrap). No write tool,
|
||||||
|
structurally: neither the server module nor `chemenu.api` imports anything under
|
||||||
|
`chemenu.commands`.
|
||||||
|
|
||||||
|
Both transports built and smoke-tested end-to-end against the real 176-page corpus: `stdio` via
|
||||||
|
a hand-written MCP client script, and `streamable-http` (host/port bound explicitly - the
|
||||||
|
default binds loopback, wrong for a container behind a proxy) via a subprocess + HTTP client
|
||||||
|
round-trip. `sse` deliberately not offered (superseded transport).
|
||||||
|
|
||||||
|
`ChemenuError` translated to the SDK's `ToolError` at the tool-call boundary (a deliberate
|
||||||
|
refusal whose message reaches the caller) rather than left to become an `UnexpectedToolError`
|
||||||
|
(a crash whose message stays server-side).
|
||||||
|
|
||||||
|
Found and fixed a stamping bug while writing the golden test: `_stamp()` was asking the cache for
|
||||||
|
the *current* revision after the load had already happened, so a caller with a perfectly clean
|
||||||
|
tree could see `"commit": null` if the cache's cached revision lagged. Fixed by threading the
|
||||||
|
revision the load actually returned through to the stamp.
|
||||||
|
|
||||||
|
Wrote `tools/chemenu/tests/test_mcp_server.py`: the golden test runs `wikitool ... --json` as a
|
||||||
|
subprocess against the same fixture tree (via `$CHEMENU_ROOT`) and asserts the server's
|
||||||
|
structured output matches field-for-field; a before/after test captures file size+contents,
|
||||||
|
`git rev-parse HEAD`, and `git status --porcelain` around all five tool calls to prove nothing
|
||||||
|
writes; a structural test imports the server module in a fresh interpreter and checks
|
||||||
|
`sys.modules` for absence of `chemenu.commands.*`.
|
||||||
|
|
||||||
|
Added `check_trace_destination()`: the server **refuses to start** if `WIKI_TRACE` is on and
|
||||||
|
would write into the served checkout's `reports/telemetry/` - the directory the sync's
|
||||||
|
`git reset --hard` is entitled to wipe. `WIKI_TRACE=0` or `WIKI_TRACE_DIR` outside the corpus
|
||||||
|
both satisfy it.
|
||||||
|
|
||||||
|
New `instructions/mcp-read-server.md` (agent-facing operating procedure), `tools/requirements-mcp.txt`
|
||||||
|
(the SDK's dependency, kept out of `requirements.txt` so a CLI-only instance need not install
|
||||||
|
pydantic/starlette/uvicorn/cryptography), a CI step to install it (so the golden test cannot
|
||||||
|
silently be skipped), and doc updates (`tools/CONTRACT.md`, `tools/README.md`, `README.md`).
|
||||||
|
|
||||||
|
Bumped `--minor` → **2.4.0**.
|
||||||
|
|
||||||
|
Ran `docs verify`, `instructions verify`, and the full suite (**799 passed**) plus an
|
||||||
|
empty-machine run (`env -i ... pytest`) with the same result, before presenting the changeset.
|
||||||
|
|
||||||
|
`tools/wikitool publish` hit the **Mass-Update Gate**: 37 files, +3,037/-625 lines, exit 42.
|
||||||
|
Reproduced the full file-by-area breakdown verbatim in the reply (as the gate's message
|
||||||
|
requires - the output does not reach the user's screen on its own) and stopped without running
|
||||||
|
further commands, per the gate's instruction and AGENTS.md invariant 6.
|
||||||
|
|
||||||
|
## Turn 2 - "Freigegeben. Erstelle eine install-MCP.md für Menschen. Für traefik middleware verweise auf https://gitea.nehmer.net/torben/gitea-mcp-forward-auth ... Wir brauchen ein issue, der uns dicker Container für ein deployment erzeugt. Fasse dort schon mal zusammen, was du bisher weißt... Die ci scripte in https://gitea.nehmer.net/torben/gitea-mcp dürfte ein guter startpunkt sein."
|
||||||
|
|
||||||
|
Published with the token from the gate's own printed line
|
||||||
|
(`--confirm 46442f4419c1`) - commit `576df2c`.
|
||||||
|
|
||||||
|
Fetched both referenced repos via `gitea-mcp` (`get_repository_tree`, `get_file_contents`) to
|
||||||
|
ground the two follow-up documents in what actually exists rather than in assumption:
|
||||||
|
|
||||||
|
- `gitea-mcp-forward-auth`: a small Go Traefik ForwardAuth service - Bearer-token-vs-SHA-256-hash
|
||||||
|
checking, `AUTH_PROXY_TOKEN_HASHES_DIR`/`AUTH_PROXY_TOKEN_HASHES` config, `/healthz` always
|
||||||
|
open, demo token hashes checked into the repo, a registry-image test script with ephemeral
|
||||||
|
Docker/Podman login.
|
||||||
|
- `gitea-mcp`: its Dockerfile (multi-stage, `CGO_ENABLED=0`, distroless non-root, OCI labels) is
|
||||||
|
a usable pattern, but its release workflow pushes to **DockerHub** (`DOCKER_ORG: gitea`) - it
|
||||||
|
is a fork of the upstream project, so it is *not* a usable registry-push template for this
|
||||||
|
repo. Noted explicitly rather than silently copied.
|
||||||
|
|
||||||
|
Wrote `install-MCP.md` (later renamed, see Turn 3): six numbered steps (install dependency, run
|
||||||
|
stdio, wire a client, run streamable-http, put authentication in front, keep the corpus current
|
||||||
|
via polling), a runnable stdio verification script (executed for real before being written into
|
||||||
|
the doc, output: `Tools: [...]`, `Seiten: 176 | Commit: <sha>`), and a troubleshooting section
|
||||||
|
keyed to the server's actual error messages. Linked it from `INSTALL.md` and `README.md`, and
|
||||||
|
added `CHEMENU_ROOT`/`WIKI_TRACE`/`WIKI_TRACE_DIR` to `INSTALL.md`'s configuration table (both
|
||||||
|
had been in effect since earlier work but undocumented there). Added the file to `dist_cmd.py`'s
|
||||||
|
`ROOT_FILES` allowlist and verified with a real `dist export` that it ships.
|
||||||
|
|
||||||
|
Filed **issue #37** ("Container-Image für den MCP-Leseserver"): summarized what is already fixed
|
||||||
|
(startup command, required env vars, the ripgrep-in-the-image trap a naive `pip install`
|
||||||
|
Dockerfile would miss, where auth/rate-limiting belong) and the concrete templates found above,
|
||||||
|
then nine explicit open decisions (corpus baked into the image vs. mounted as a volume and
|
||||||
|
synced by a sidecar; who runs the sync; base image, since Go-style `distroless` does not carry a
|
||||||
|
Python interpreter or `rg`; how the version reaches the image; whether to build `arm64`;
|
||||||
|
**a healthcheck endpoint does not exist yet - the one item that implies new code in this repo**;
|
||||||
|
registry path; OCI labels; whether a smoke test against the built image is added). Labeled
|
||||||
|
`prio/2 size/M`.
|
||||||
|
|
||||||
|
Closed **#34, #33, #31, #19** with detailed comments each restating what was implemented against
|
||||||
|
the issue's own acceptance criteria, including the two accidental-dependency findings from #31
|
||||||
|
and the corrected alias-bomb numbers. Commented on **#36** with a summary table across all four
|
||||||
|
versions, noted its closing criterion (a consumer provably reaching the server through the
|
||||||
|
Traefik middleware) is not yet met - that needs #37's deployment - and proposed leaving #36 open
|
||||||
|
until then; also carried forward its two remaining loose ends (the wrong `qmd.md` language claim,
|
||||||
|
and the still-missing place for architecture decisions).
|
||||||
|
|
||||||
|
## Turn 3 - "Freigegeben. Nenne die Datei INSTALL-MCP.md all Caps sonst fahre mit dem Auftrag fort" (interrupting a `dist export` verification call)
|
||||||
|
|
||||||
|
Renamed `install-MCP.md` → `INSTALL-MCP.md`, fixed every reference (`INSTALL.md`, `README.md`,
|
||||||
|
`tools/chemenu/commands/dist_cmd.py`). Re-ran the full suite (**799 passed**) and `docs verify`,
|
||||||
|
bumped `--patch` → **2.4.1**, wrote the changelog entry, published (commit `83018fc`). Saved a
|
||||||
|
feedback memory (`root-docs-are-all-caps.md`): root-level human docs in this repo are named in
|
||||||
|
ALL CAPS, and a new one has to be added to `dist_cmd.ROOT_FILES` or it silently does not ship.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
- **Version:** 2.2.2 -> 2.2.3 -> 2.2.4 -> 2.3.0 -> 2.4.0 -> 2.4.1
|
||||||
|
- **Commits:** `576df2c` (2.4.0, the 37-file changeset cleared through the Mass-Update Gate),
|
||||||
|
`83018fc` (2.4.1, `INSTALL-MCP.md` and its wiring)
|
||||||
|
- **Tests:** 776 -> 786 -> 799 passed, green throughout, including on an `env -i` empty machine
|
||||||
|
- **Measured:** corpus parse 265ms -> 54ms; `wikitool search` end-to-end 593ms -> 347ms
|
||||||
|
- **Issues:** #34, #33, #31, #19 closed with detailed comments; #36 commented, left open pending
|
||||||
|
#37; #37 opened (`prio/2 size/M`)
|
||||||
|
- **CI:** `.gitea/workflows/ci.yml` updated to install `tools/requirements-mcp.txt`, otherwise
|
||||||
|
unchanged; not separately re-run in this session (publish triggers it)
|
||||||
|
- **Not done in this session, carried forward:** #37 itself (container image); #23 (env var
|
||||||
|
registration enforcement - `CHEMENU_ROOT` was added to `_WIKITOOL_ENV` by hand); the `qmd.md`
|
||||||
|
language-claim fix (separate transcript); an ADR-style home for architecture decisions
|
||||||
+2
-2
@@ -74,7 +74,7 @@ tools/wikitool <command> --help
|
|||||||
| `version show [--json]` | Print this instance's stack version and where it came from (development tree, or a distribution with its export date and origin). Bare `wikitool version` is an alias for this. Read-only, offline, and **exempt from the Iteration Budget Gate** |
|
| `version show [--json]` | Print this instance's stack version and where it came from (development tree, or a distribution with its export date and origin). Bare `wikitool version` is an alias for this. Read-only, offline, and **exempt from the Iteration Budget Gate** |
|
||||||
| `version check [--url U] [--timeout S] [--json]` | Ask the origin's release feed whether a newer stack exists, and whether the step crosses a compatibility boundary (`state: current\|update\|migration\|ahead`). **The only command in `wikitool` that makes a network call** - never reached implicitly from another command, needs no key, times out, and reports an unreachable feed as an error rather than as "up to date". The feed is `$WIKITOOL_UPDATE_URL`, else the release stamp's, else the built-in origin; `$WIKITOOL_UPDATE_TOKEN` is only needed if that feed is not readable anonymously. Read-only and exempt from the budget gate |
|
| `version check [--url U] [--timeout S] [--json]` | Ask the origin's release feed whether a newer stack exists, and whether the step crosses a compatibility boundary (`state: current\|update\|migration\|ahead`). **The only command in `wikitool` that makes a network call** - never reached implicitly from another command, needs no key, times out, and reports an unreachable feed as an error rather than as "up to date". The feed is `$WIKITOOL_UPDATE_URL`, else the release stamp's, else the built-in origin; `$WIKITOOL_UPDATE_TOKEN` is only needed if that feed is not readable anonymously. Read-only and exempt from the budget gate |
|
||||||
| `version notes [--version X.Y.Z]` | Print one version's `CHANGES.md` entry, for use as release notes (default: this tree's `VERSION`). Read-only and exempt from the budget gate |
|
| `version notes [--version X.Y.Z]` | Print one version's `CHANGES.md` entry, for use as release notes (default: this tree's `VERSION`). Read-only and exempt from the budget gate |
|
||||||
| `version bump --major\|--minor\|--patch --title "<...>" [--no-migration "<reason>"] [--dry-run]` | Raise `VERSION` and open the matching `CHANGES.md` entry - heading, date and author only; the body stays the author's to write, the way `new` writes frontmatter and leaves the prose. Refuses more or fewer than one part, an empty title, and a changelog already documenting a version that is not older than the new one. Compatibility follows the **leftmost non-zero component**, which for this stack (at `1.0.0` and up, no pre-release suffixes anywhere) means MAJOR: PATCH is a fix, MINOR a compatible capability, MAJOR requires instances to migrate. A MAJOR bump additionally requires a migration document targeting the new version, or `--no-migration "<reason>"`, which records the reason in the entry |
|
| `version bump --major\|--minor\|--patch --title "<...>" [--breaking "<what breaks>"] [--no-migration "<reason>"] [--dry-run]` | Raise `VERSION` and open the matching `CHANGES.md` entry - heading, date and author only; the body stays the author's to write, the way `new` writes frontmatter and leaves the prose. Refuses more or fewer than one part, an empty title, and a changelog already documenting a version that is not older than the new one. Compatibility follows the **leftmost non-zero component**, which for this stack (at `1.0.0` and up, no pre-release suffixes anywhere) means MAJOR: PATCH is a fix, MINOR a compatible capability, MAJOR a version that is **not a drop-in replacement** - any hand-work on update, or a downgrade that no longer works. Whether content must be migrated is a second, independent question. A MAJOR bump therefore requires `--breaking "<what stops working>"`, which is refused on any other part, and on top of it a migration document targeting the new version or `--no-migration "<reason>"`; both are recorded in the entry. Which part a change earns stays a judgment call: the command enforces that a crossing documents itself, never that the part was chosen correctly |
|
||||||
| `migrate list [--json]` | List every migration document under `instructions/migrations/`, oldest target first, with its kind. Read-only and **exempt from the Iteration Budget Gate** |
|
| `migrate list [--json]` | List every migration document under `instructions/migrations/`, oldest target first, with its kind. Read-only and **exempt from the Iteration Budget Gate** |
|
||||||
| `migrate status [--json]` | Show the migrations this instance still owes, in the order they must run: every document whose `migrates_to` lies in `(kb_version, VERSION]`. Exits 1 only when `.wikitool-kb.json` is missing - the content's shape is a question the tool refuses to answer by guessing. Read-only and exempt from the budget gate |
|
| `migrate status [--json]` | Show the migrations this instance still owes, in the order they must run: every document whose `migrates_to` lies in `(kb_version, VERSION]`. Exits 1 only when `.wikitool-kb.json` is missing - the content's shape is a question the tool refuses to answer by guessing. Read-only and exempt from the budget gate |
|
||||||
| `migrate verify --from <rev> [--path P ...] [--expect-body-change] [--json] [--fail-on-error]` | Compare `kb/` against a git revision on the invariants a content migration must not change: wikilink and citation **counts** (not sets), footnote definitions, H1, and structural frontmatter. Reports added/removed pages without failing on them. `--expect-body-change` additionally flags a page whose body did not change at all. Not migration-specific - worth running after any bulk rewrite, and the one question `lint` cannot answer, since it reads a single revision and so cannot see that something went missing. Read-only and exempt from the budget gate |
|
| `migrate verify --from <rev> [--path P ...] [--expect-body-change] [--json] [--fail-on-error]` | Compare `kb/` against a git revision on the invariants a content migration must not change: wikilink and citation **counts** (not sets), footnote definitions, H1, and structural frontmatter. Reports added/removed pages without failing on them. `--expect-body-change` additionally flags a page whose body did not change at all. Not migration-specific - worth running after any bulk rewrite, and the one question `lint` cannot answer, since it reads a single revision and so cannot see that something went missing. Read-only and exempt from the budget gate |
|
||||||
@@ -190,7 +190,7 @@ is atomic, and whether a retry is safe.
|
|||||||
| `dist export` | Target exists and is not empty, is not a directory, or the tree has no readable `VERSION` | Yes - nothing is written until every file is planned | Point `<target>` at an empty (or new) directory and retry. Never merge into a non-empty one by hand |
|
| `dist export` | Target exists and is not empty, is not a directory, or the tree has no readable `VERSION` | Yes - nothing is written until every file is planned | Point `<target>` at an empty (or new) directory and retry. Never merge into a non-empty one by hand |
|
||||||
| `version show` / `version notes` | `VERSION` is missing or unparseable; for `notes`, no `CHANGES.md` entry names the version asked for | Read-only | Fix `VERSION`, or write the changelog entry (`version bump` writes its heading). Safe to retry |
|
| `version show` / `version notes` | `VERSION` is missing or unparseable; for `notes`, no `CHANGES.md` entry names the version asked for | Read-only | Fix `VERSION`, or write the changelog entry (`version bump` writes its heading). Safe to retry |
|
||||||
| `version check` | The feed could not be reached, answered non-JSON, or carried no `tag_name`. **Never** answers "up to date" for a question it could not ask | Read-only, no local writes | A network failure is transient - retry once, then report it. HTTP 401/403 names `$WIKITOOL_UPDATE_TOKEN`; 404 means no release exists yet or the URL points at the wrong repo |
|
| `version check` | The feed could not be reached, answered non-JSON, or carried no `tag_name`. **Never** answers "up to date" for a question it could not ask | Read-only, no local writes | A network failure is transient - retry once, then report it. HTTP 401/403 names `$WIKITOOL_UPDATE_TOKEN`; 404 means no release exists yet or the URL points at the wrong repo |
|
||||||
| `version bump` | More or fewer than one of `--major/--minor/--patch`, an empty `--title`, a missing `VERSION`/`CHANGES.md`, a changelog already documenting a version not older than the new one, a boundary-crossing bump with neither a migration document nor `--no-migration`, or `--no-migration` on a bump that crosses nothing | No - `VERSION` then `CHANGES.md` | **Not idempotent**: a second run bumps again. If the outcome is uncertain, read `VERSION` and the top of `CHANGES.md` before retrying |
|
| `version bump` | More or fewer than one of `--major/--minor/--patch`, an empty `--title`, a missing `VERSION`/`CHANGES.md`, a changelog already documenting a version not older than the new one, a boundary-crossing bump without `--breaking` or with neither a migration document nor `--no-migration`, or `--breaking`/`--no-migration` on a bump that crosses nothing | No - `VERSION` then `CHANGES.md` | **Not idempotent**: a second run bumps again. If the outcome is uncertain, read `VERSION` and the top of `CHANGES.md` before retrying |
|
||||||
| `migrate list` / `migrate status` | `list` never fails; `status` exits 1 when `.wikitool-kb.json` is missing or unreadable, or `VERSION` is | Read-only | For a missing declaration: run `migrate baseline <version>` once, then retry. Safe to retry freely otherwise |
|
| `migrate list` / `migrate status` | `list` never fails; `status` exits 1 when `.wikitool-kb.json` is missing or unreadable, or `VERSION` is | Read-only | For a missing declaration: run `migrate baseline <version>` once, then retry. Safe to retry freely otherwise |
|
||||||
| `migrate verify` | Only with `--fail-on-error`: an invariant changed. Also exits 1 if `--from` is not a revision in this repository | Read-only | Exit 1 from `--fail-on-error` means "act on the findings", not "the tool is broken". A finding is never fixed by re-running - it names a page and what changed on it |
|
| `migrate verify` | Only with `--fail-on-error`: an invariant changed. Also exits 1 if `--from` is not a revision in this repository | Read-only | Exit 1 from `--fail-on-error` means "act on the findings", not "the tool is broken". A finding is never fixed by re-running - it names a page and what changed on it |
|
||||||
| `migrate done` | Unknown version, no `.wikitool-kb.json`, nothing outstanding, or a version that is not the next link in the chain | Yes - single file write | **Not idempotent**: it advances the chain. For "not the next link", run `migrate status` and apply them in the order it prints - never force the order |
|
| `migrate done` | Unknown version, no `.wikitool-kb.json`, nothing outstanding, or a version that is not the next link in the chain | Yes - single file write | **Not idempotent**: it advances the chain. For "not the next link", run `migrate status` and apply them in the order it prints - never force the order |
|
||||||
|
|||||||
@@ -64,8 +64,14 @@ DIST_TEMPLATES_DIR = Path(__file__).resolve().parent.parent / "dist_templates"
|
|||||||
# and does not load `AGENTS.md`, so it ships for the same reason
|
# and does not load `AGENTS.md`, so it ships for the same reason
|
||||||
# `.claude/settings.json` does - a distributed instance running that harness
|
# `.claude/settings.json` does - a distributed instance running that harness
|
||||||
# would otherwise start every session without the control plane.
|
# would otherwise start every session without the control plane.
|
||||||
|
#
|
||||||
|
# `INSTALL-MCP.md` ships beside `INSTALL.md` and for the same reason: the MCP
|
||||||
|
# read server is part of what an instance *has*, even though its dependency is
|
||||||
|
# optional. A distribution whose server is present but undocumented is one
|
||||||
|
# whose operator finds the module by reading the source.
|
||||||
ROOT_FILES = (
|
ROOT_FILES = (
|
||||||
"AGENTS.md", "CLAUDE.md", "README.md", "EVALS.md", "INSTALL.md", ".gitignore", "VERSION",
|
"AGENTS.md", "CLAUDE.md", "README.md", "EVALS.md", "INSTALL.md", "INSTALL-MCP.md",
|
||||||
|
".gitignore", "VERSION",
|
||||||
*config.LICENSE_FILES,
|
*config.LICENSE_FILES,
|
||||||
*config.PERSONALIZATION_TEMPLATES,
|
*config.PERSONALIZATION_TEMPLATES,
|
||||||
config.ENVIRONMENT_TEMPLATE,
|
config.ENVIRONMENT_TEMPLATE,
|
||||||
|
|||||||
@@ -473,6 +473,43 @@ def check_migration_for_boundary() -> list[str]:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def check_breaking_change_for_boundary() -> list[str]:
|
||||||
|
"""A version that crosses the compatibility boundary must say what breaks.
|
||||||
|
|
||||||
|
Separate from `check_migration_for_boundary`, because the two answer
|
||||||
|
different questions: that one asks whether the *content* has to move, this
|
||||||
|
one whether the operator was told the swap is not drop-in at all. A
|
||||||
|
boundary crossing with an untouched corpus - a renamed feed, artefact,
|
||||||
|
import name or flag - satisfies that check and still leaves every existing
|
||||||
|
instance with something to do by hand.
|
||||||
|
|
||||||
|
Only the newest entry is checked, for the same reason: older crossings are
|
||||||
|
history, and re-reporting them forever would make the check noise.
|
||||||
|
"""
|
||||||
|
changes_path = config.ROOT / version_mod.CHANGES_FILENAME
|
||||||
|
version_path = config.ROOT / version_mod.VERSION_FILENAME
|
||||||
|
if not changes_path.is_file() or not version_path.is_file():
|
||||||
|
return [] # already reported by check_version_changelog
|
||||||
|
|
||||||
|
text = changes_path.read_text(encoding="utf-8")
|
||||||
|
current = version_mod.top_changes_version(text)
|
||||||
|
previous = _second_changes_version(text)
|
||||||
|
if current is None or previous is None:
|
||||||
|
return [] # the first versioned entry has no predecessor to cross from
|
||||||
|
if current.compat_key == previous.compat_key:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if version_mod.BREAKING_CHANGE_MARKER in (version_mod.changes_section(text, current) or ""):
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [
|
||||||
|
f"{current} crosses the compatibility boundary from {previous}, so it is not a drop-in "
|
||||||
|
f"replacement - but its {version_mod.CHANGES_FILENAME} entry carries no "
|
||||||
|
f"`{version_mod.BREAKING_CHANGE_MARKER}` line saying what stops working. Add it "
|
||||||
|
"(`version bump --breaking` writes it; see instructions/dev/version-parts.md)"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@app.command("verify")
|
@app.command("verify")
|
||||||
def verify():
|
def verify():
|
||||||
"""Check the CLI/README command tables, contract presence, type-form drift, ignore rules, and version/changelog agreement."""
|
"""Check the CLI/README command tables, contract presence, type-form drift, ignore rules, and version/changelog agreement."""
|
||||||
@@ -484,6 +521,7 @@ def verify():
|
|||||||
+ check_ignored_content()
|
+ check_ignored_content()
|
||||||
+ check_version_changelog()
|
+ check_version_changelog()
|
||||||
+ check_migration_for_boundary()
|
+ check_migration_for_boundary()
|
||||||
|
+ check_breaking_change_for_boundary()
|
||||||
)
|
)
|
||||||
|
|
||||||
if issues:
|
if issues:
|
||||||
|
|||||||
@@ -189,6 +189,11 @@ def bump_command(
|
|||||||
minor: bool = typer.Option(False, "--minor", help="Bump MINOR (resets PATCH)"),
|
minor: bool = typer.Option(False, "--minor", help="Bump MINOR (resets PATCH)"),
|
||||||
patch: bool = typer.Option(False, "--patch", help="Bump PATCH"),
|
patch: bool = typer.Option(False, "--patch", help="Bump PATCH"),
|
||||||
title: str = typer.Option(..., "--title", help="One-line title for the new CHANGES.md entry"),
|
title: str = typer.Option(..., "--title", help="One-line title for the new CHANGES.md entry"),
|
||||||
|
breaking: Optional[str] = typer.Option(
|
||||||
|
None,
|
||||||
|
"--breaking",
|
||||||
|
help="What stops working, for a boundary-crossing bump (recorded in CHANGES.md). Required on one, refused on any other",
|
||||||
|
),
|
||||||
no_migration: Optional[str] = typer.Option(
|
no_migration: Optional[str] = typer.Option(
|
||||||
None,
|
None,
|
||||||
"--no-migration",
|
"--no-migration",
|
||||||
@@ -203,10 +208,14 @@ def bump_command(
|
|||||||
frontmatter and leaves the prose. `docs verify` afterwards enforces that
|
frontmatter and leaves the prose. `docs verify` afterwards enforces that
|
||||||
the two agree, so a bump with no entry cannot reach a release.
|
the two agree, so a bump with no entry cannot reach a release.
|
||||||
|
|
||||||
A bump that crosses the compatibility boundary additionally requires a
|
A bump that crosses the compatibility boundary - one whose new version is
|
||||||
migration document for the new version, or `--no-migration "<reason>"`.
|
not a drop-in replacement, whether or not any content moves - requires
|
||||||
An instance learning that it must migrate, with nothing telling it how, is
|
`--breaking "<what stops working>"`, and on top of that either a migration
|
||||||
the gap this closes."""
|
document for the new version or `--no-migration "<reason>"`. An instance
|
||||||
|
learning that it must migrate, with nothing telling it what broke or how to
|
||||||
|
cross, is the gap these close. Which part to pass stays a judgment call
|
||||||
|
this command does not make - it enforces only that a crossing says what it
|
||||||
|
costs."""
|
||||||
selected = [name for name, chosen in (("major", major), ("minor", minor), ("patch", patch)) if chosen]
|
selected = [name for name, chosen in (("major", major), ("minor", minor), ("patch", patch)) if chosen]
|
||||||
if len(selected) != 1:
|
if len(selected) != 1:
|
||||||
fail("Pass exactly one of --major / --minor / --patch")
|
fail("Pass exactly one of --major / --minor / --patch")
|
||||||
@@ -239,6 +248,23 @@ def bump_command(
|
|||||||
crossing = new_version.compat_key != current.compat_key
|
crossing = new_version.compat_key != current.compat_key
|
||||||
boundary = " (crosses a compatibility boundary - instances must migrate)" if crossing else ""
|
boundary = " (crosses a compatibility boundary - instances must migrate)" if crossing else ""
|
||||||
|
|
||||||
|
if crossing and not breaking:
|
||||||
|
fail(
|
||||||
|
f"{current} -> {new_version} crosses the compatibility boundary, so it is not a "
|
||||||
|
f"drop-in replacement - re-run with --breaking \"<what stops working, and what an "
|
||||||
|
f"instance must do about it>\".\n"
|
||||||
|
f"If that sentence is hard to write because nothing actually breaks - no hand-work "
|
||||||
|
f"on update, and the old version can still be put back - then the bump is probably "
|
||||||
|
f"not --{selected[0]}."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if breaking and not crossing:
|
||||||
|
fail(
|
||||||
|
f"--breaking only applies to a bump that crosses the compatibility boundary; "
|
||||||
|
f"{current} -> {new_version} does not."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
if crossing and not no_migration:
|
if crossing and not no_migration:
|
||||||
from chemenu import kb_state
|
from chemenu import kb_state
|
||||||
|
|
||||||
@@ -267,6 +293,7 @@ def bump_command(
|
|||||||
version_mod.insert_changes_entry(
|
version_mod.insert_changes_entry(
|
||||||
text, new_version, today_iso(), title.strip(), author,
|
text, new_version, today_iso(), title.strip(), author,
|
||||||
no_migration_reason=no_migration.strip() if no_migration else None,
|
no_migration_reason=no_migration.strip() if no_migration else None,
|
||||||
|
breaking_reason=breaking.strip() if breaking else None,
|
||||||
),
|
),
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -237,6 +237,43 @@ def test_a_migration_document_satisfies_the_check(tmp_path, monkeypatch):
|
|||||||
assert docs_verify.check_migration_for_boundary() == []
|
assert docs_verify.check_migration_for_boundary() == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_breaking_release_without_a_breaking_note_is_reported(tmp_path, monkeypatch):
|
||||||
|
"""A crossing that migrates nothing still leaves hand-work behind, so the
|
||||||
|
migration check passing is not evidence that anyone was told."""
|
||||||
|
from chemenu import version as version_mod
|
||||||
|
|
||||||
|
_boundary_tree(
|
||||||
|
tmp_path, monkeypatch, "2.0.0", "1.4.0",
|
||||||
|
marker=f"{version_mod.MIGRATION_NONE_MARKER} - nothing to change.\n\n",
|
||||||
|
)
|
||||||
|
assert docs_verify.check_migration_for_boundary() == []
|
||||||
|
issues = docs_verify.check_breaking_change_for_boundary()
|
||||||
|
assert any("2.0.0" in issue and "drop-in" in issue for issue in issues)
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_compatible_release_needs_no_breaking_note(tmp_path, monkeypatch):
|
||||||
|
_boundary_tree(tmp_path, monkeypatch, "1.5.0", "1.4.0")
|
||||||
|
assert docs_verify.check_breaking_change_for_boundary() == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_breaking_change_marker_satisfies_the_check(tmp_path, monkeypatch):
|
||||||
|
from chemenu import version as version_mod
|
||||||
|
|
||||||
|
_boundary_tree(
|
||||||
|
tmp_path, monkeypatch, "2.0.0", "1.4.0",
|
||||||
|
marker=f"{version_mod.BREAKING_CHANGE_MARKER} the feed moved.\n\n",
|
||||||
|
)
|
||||||
|
assert docs_verify.check_breaking_change_for_boundary() == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_raises_when_a_boundary_has_no_breaking_note(monkeypatch):
|
||||||
|
monkeypatch.setattr(
|
||||||
|
docs_verify, "check_breaking_change_for_boundary", lambda: ["unannounced"]
|
||||||
|
)
|
||||||
|
with pytest.raises(typer.Exit):
|
||||||
|
docs_verify.verify()
|
||||||
|
|
||||||
|
|
||||||
def test_verify_raises_when_a_boundary_has_no_migration(monkeypatch):
|
def test_verify_raises_when_a_boundary_has_no_migration(monkeypatch):
|
||||||
monkeypatch.setattr(docs_verify, "check_migration_for_boundary", lambda: ["unbridged"])
|
monkeypatch.setattr(docs_verify, "check_migration_for_boundary", lambda: ["unbridged"])
|
||||||
with pytest.raises(typer.Exit):
|
with pytest.raises(typer.Exit):
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ def test_insert_changes_entry_lands_above_the_newest_entry():
|
|||||||
def test_bump_writes_both_the_version_and_the_changelog_heading(tree):
|
def test_bump_writes_both_the_version_and_the_changelog_heading(tree):
|
||||||
version_cmd.bump_command(
|
version_cmd.bump_command(
|
||||||
major=False, minor=True, patch=False, title="Something happened",
|
major=False, minor=True, patch=False, title="Something happened",
|
||||||
no_migration=None, dry_run=False,
|
breaking=None, no_migration=None, dry_run=False,
|
||||||
)
|
)
|
||||||
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "1.1.0"
|
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "1.1.0"
|
||||||
changes = (tree / "CHANGES.md").read_text(encoding="utf-8")
|
changes = (tree / "CHANGES.md").read_text(encoding="utf-8")
|
||||||
@@ -194,7 +194,7 @@ def test_bump_writes_both_the_version_and_the_changelog_heading(tree):
|
|||||||
|
|
||||||
def test_bump_dry_run_writes_nothing(tree):
|
def test_bump_dry_run_writes_nothing(tree):
|
||||||
version_cmd.bump_command(
|
version_cmd.bump_command(
|
||||||
major=False, minor=False, patch=True, title="Nope", no_migration=None, dry_run=True
|
major=False, minor=False, patch=True, title="Nope", breaking=None, no_migration=None, dry_run=True
|
||||||
)
|
)
|
||||||
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "1.0.0"
|
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "1.0.0"
|
||||||
assert "1.0.1" not in (tree / "CHANGES.md").read_text(encoding="utf-8")
|
assert "1.0.1" not in (tree / "CHANGES.md").read_text(encoding="utf-8")
|
||||||
@@ -207,7 +207,7 @@ def test_bump_demands_exactly_one_part(tree, flags):
|
|||||||
major, minor, patch = flags
|
major, minor, patch = flags
|
||||||
with pytest.raises(typer.Exit):
|
with pytest.raises(typer.Exit):
|
||||||
version_cmd.bump_command(
|
version_cmd.bump_command(
|
||||||
major=major, minor=minor, patch=patch, title="x", no_migration=None, dry_run=False
|
major=major, minor=minor, patch=patch, title="x", breaking=None, no_migration=None, dry_run=False
|
||||||
)
|
)
|
||||||
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "1.0.0"
|
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "1.0.0"
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ def test_bump_demands_exactly_one_part(tree, flags):
|
|||||||
def test_bump_refuses_an_empty_title(tree):
|
def test_bump_refuses_an_empty_title(tree):
|
||||||
with pytest.raises(typer.Exit):
|
with pytest.raises(typer.Exit):
|
||||||
version_cmd.bump_command(
|
version_cmd.bump_command(
|
||||||
major=False, minor=False, patch=True, title=" ", no_migration=None, dry_run=False
|
major=False, minor=False, patch=True, title=" ", breaking=None, no_migration=None, dry_run=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -227,7 +227,7 @@ def test_bump_refuses_when_the_changelog_is_already_ahead(tree):
|
|||||||
)
|
)
|
||||||
with pytest.raises(typer.Exit):
|
with pytest.raises(typer.Exit):
|
||||||
version_cmd.bump_command(
|
version_cmd.bump_command(
|
||||||
major=False, minor=False, patch=True, title="x", no_migration=None, dry_run=False
|
major=False, minor=False, patch=True, title="x", breaking=None, no_migration=None, dry_run=False
|
||||||
)
|
)
|
||||||
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "1.0.0"
|
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "1.0.0"
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ def test_a_boundary_crossing_bump_without_a_migration_is_refused(tree):
|
|||||||
with pytest.raises(typer.Exit):
|
with pytest.raises(typer.Exit):
|
||||||
version_cmd.bump_command(
|
version_cmd.bump_command(
|
||||||
major=True, minor=False, patch=False, title="Breaking",
|
major=True, minor=False, patch=False, title="Breaking",
|
||||||
no_migration=None, dry_run=False,
|
breaking="the feed moved", no_migration=None, dry_run=False,
|
||||||
)
|
)
|
||||||
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "1.0.0"
|
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "1.0.0"
|
||||||
|
|
||||||
@@ -255,7 +255,8 @@ def test_a_boundary_crossing_bump_passes_with_a_migration_document(tree):
|
|||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
version_cmd.bump_command(
|
version_cmd.bump_command(
|
||||||
major=True, minor=False, patch=False, title="Breaking", no_migration=None, dry_run=False
|
major=True, minor=False, patch=False, title="Breaking",
|
||||||
|
breaking="every page is retyped", no_migration=None, dry_run=False,
|
||||||
)
|
)
|
||||||
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "2.0.0"
|
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "2.0.0"
|
||||||
|
|
||||||
@@ -263,6 +264,7 @@ def test_a_boundary_crossing_bump_passes_with_a_migration_document(tree):
|
|||||||
def test_no_migration_records_the_reason_in_the_changelog(tree):
|
def test_no_migration_records_the_reason_in_the_changelog(tree):
|
||||||
version_cmd.bump_command(
|
version_cmd.bump_command(
|
||||||
major=True, minor=False, patch=False, title="Breaking",
|
major=True, minor=False, patch=False, title="Breaking",
|
||||||
|
breaking="the release feed moved",
|
||||||
no_migration="no distributed instance exists yet", dry_run=False,
|
no_migration="no distributed instance exists yet", dry_run=False,
|
||||||
)
|
)
|
||||||
changes = (tree / "CHANGES.md").read_text(encoding="utf-8")
|
changes = (tree / "CHANGES.md").read_text(encoding="utf-8")
|
||||||
@@ -275,10 +277,50 @@ def test_no_migration_is_refused_on_a_compatible_bump(tree):
|
|||||||
with pytest.raises(typer.Exit):
|
with pytest.raises(typer.Exit):
|
||||||
version_cmd.bump_command(
|
version_cmd.bump_command(
|
||||||
major=False, minor=False, patch=True, title="Fix",
|
major=False, minor=False, patch=True, title="Fix",
|
||||||
no_migration="not needed", dry_run=False,
|
breaking=None, no_migration="not needed", dry_run=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- version bump: the breaking-change note --------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_boundary_crossing_bump_without_breaking_is_refused(tree):
|
||||||
|
"""The corpus question and the drop-in question are independent: a bump
|
||||||
|
can migrate nothing and still leave every instance with hand-work."""
|
||||||
|
with pytest.raises(typer.Exit):
|
||||||
|
version_cmd.bump_command(
|
||||||
|
major=True, minor=False, patch=False, title="Renamed the feed",
|
||||||
|
breaking=None, no_migration="kb/ keeps its shape", dry_run=False,
|
||||||
|
)
|
||||||
|
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "1.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_breaking_records_what_stops_working_in_the_changelog(tree):
|
||||||
|
version_cmd.bump_command(
|
||||||
|
major=True, minor=False, patch=False, title="Renamed the feed",
|
||||||
|
breaking="update_url points at a repo path that no longer exists",
|
||||||
|
no_migration="kb/ keeps its shape", dry_run=False,
|
||||||
|
)
|
||||||
|
changes = (tree / "CHANGES.md").read_text(encoding="utf-8")
|
||||||
|
assert version_mod.BREAKING_CHANGE_MARKER in changes
|
||||||
|
assert "update_url points at a repo path that no longer exists" in changes
|
||||||
|
# The break comes before the migration note: it is what an operator acts on.
|
||||||
|
assert changes.index(version_mod.BREAKING_CHANGE_MARKER) < changes.index(
|
||||||
|
version_mod.MIGRATION_NONE_MARKER
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_breaking_is_refused_on_a_compatible_bump(tree):
|
||||||
|
"""A compatible bump that claims a break is describing itself wrongly -
|
||||||
|
one of the two is a mistake, and the command will not guess which."""
|
||||||
|
with pytest.raises(typer.Exit):
|
||||||
|
version_cmd.bump_command(
|
||||||
|
major=False, minor=True, patch=False, title="New command",
|
||||||
|
breaking="nothing, really", no_migration=None, dry_run=False,
|
||||||
|
)
|
||||||
|
assert (tree / "VERSION").read_text(encoding="utf-8").strip() == "1.0.0"
|
||||||
|
|
||||||
|
|
||||||
# --- version notes ---------------------------------------------------------
|
# --- version notes ---------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,23 @@ ingest into a release.
|
|||||||
|
|
||||||
**Compatibility is read off the leftmost non-zero component**, the rule Cargo's
|
**Compatibility is read off the leftmost non-zero component**, the rule Cargo's
|
||||||
caret ranges use: `0.1.3 -> 0.1.4` is safe, `0.1.3 -> 0.2.0` is not, and from
|
caret ranges use: `0.1.3 -> 0.1.4` is safe, `0.1.3 -> 0.2.0` is not, and from
|
||||||
`1.0.0` on the same rule reads as the familiar "MAJOR means migration". Stating
|
`1.0.0` on the same rule reads as the familiar "MAJOR breaks". Stating it that
|
||||||
it that way is what lets the 0.x era carry the migration signal at all - under
|
way is what lets the 0.x era carry the signal at all - under a rule keyed to
|
||||||
plain "MAJOR breaks" semantics every 0.x release would be indistinguishable
|
the MAJOR component alone, every 0.x release would be indistinguishable from
|
||||||
from every other, which is exactly the signal update detection needs. Nothing
|
every other, which is exactly the signal update detection needs. Nothing about
|
||||||
about the mechanism changes at 1.0.0.
|
the mechanism changes at 1.0.0.
|
||||||
|
|
||||||
|
What that component answers is **whether the new version is a drop-in
|
||||||
|
replacement**: whether an instance can copy the new machinery over itself with
|
||||||
|
no hand-work and still put the old version back afterwards. Whether *content*
|
||||||
|
must be migrated is a **second, independent question**. It is one way to fail
|
||||||
|
the first - but a renamed release feed, artefact, import name, flag or envvar
|
||||||
|
fails it too, with `kb/` untouched, which is why `--no-migration` exists at all:
|
||||||
|
boundary-crossing bumps that migrate nothing are a real case, not an escape
|
||||||
|
hatch. Hence two markers below rather than one - `BREAKING_CHANGE_MARKER`
|
||||||
|
records the break, `MIGRATION_NONE_MARKER` records the absence of the
|
||||||
|
migration. Which part a change earns stays a judgment call made before the
|
||||||
|
bump; this module only enforces that a crossing says what it costs.
|
||||||
|
|
||||||
Paths are resolved through `config.ROOT` at call time rather than at import,
|
Paths are resolved through `config.ROOT` at call time rather than at import,
|
||||||
because the tests (and `dist export`'s own fixtures) relocate the root.
|
because the tests (and `dist export`'s own fixtures) relocate the root.
|
||||||
@@ -64,6 +76,11 @@ _SEMVER_RE = re.compile(r"^\s*v?(\d+)\.(\d+)\.(\d+)\s*$")
|
|||||||
# boundary that needs no content migration. `docs verify` accepts it in place
|
# boundary that needs no content migration. `docs verify` accepts it in place
|
||||||
# of a migration document, so the exact string is a contract between the two.
|
# of a migration document, so the exact string is a contract between the two.
|
||||||
MIGRATION_NONE_MARKER = "**Migration:** none required"
|
MIGRATION_NONE_MARKER = "**Migration:** none required"
|
||||||
|
# Written into every CHANGES.md entry whose version crosses a compatibility
|
||||||
|
# boundary, migration or not: the swap is not drop-in, and the operator of an
|
||||||
|
# existing instance has to be told what stops working. `docs verify` checks the
|
||||||
|
# newest crossing carries it, so this string too is a contract between the two.
|
||||||
|
BREAKING_CHANGE_MARKER = "**Breaking Change:**"
|
||||||
# A changelog entry that names a version. Entries predating versioning start
|
# A changelog entry that names a version. Entries predating versioning start
|
||||||
# with a date instead and are deliberately not matched - they are history, not
|
# with a date instead and are deliberately not matched - they are history, not
|
||||||
# a claim about which version the tree is.
|
# a claim about which version the tree is.
|
||||||
@@ -332,15 +349,22 @@ def insert_changes_entry(
|
|||||||
title: str,
|
title: str,
|
||||||
author: str,
|
author: str,
|
||||||
no_migration_reason: Optional[str] = None,
|
no_migration_reason: Optional[str] = None,
|
||||||
|
breaking_reason: Optional[str] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Add a heading for `version` above the newest existing entry.
|
"""Add a heading for `version` above the newest existing entry.
|
||||||
|
|
||||||
Only the skeleton: heading, date, author, and - when a compatibility
|
Only the skeleton: heading, date, author, and - when a compatibility
|
||||||
boundary is crossed without a migration - the line that says so. The
|
boundary is crossed - the line saying what breaks, plus the line saying no
|
||||||
entry's actual content is written afterwards by whoever made the change,
|
content has to change where that applies. The entry's actual content is
|
||||||
which is also why `bump` refuses to invent a title.
|
written afterwards by whoever made the change, which is also why `bump`
|
||||||
|
refuses to invent a title.
|
||||||
|
|
||||||
|
The break comes first: it is what an operator reading the release notes has
|
||||||
|
to act on, and the migration line only qualifies it.
|
||||||
"""
|
"""
|
||||||
lines = [f"## {version} - {date} - {title}", "", f"**Author:** {author}", ""]
|
lines = [f"## {version} - {date} - {title}", "", f"**Author:** {author}", ""]
|
||||||
|
if breaking_reason:
|
||||||
|
lines += [f"{BREAKING_CHANGE_MARKER} {breaking_reason}", ""]
|
||||||
if no_migration_reason:
|
if no_migration_reason:
|
||||||
lines += [f"{MIGRATION_NONE_MARKER} - {no_migration_reason}", ""]
|
lines += [f"{MIGRATION_NONE_MARKER} - {no_migration_reason}", ""]
|
||||||
entry = "\n".join(lines) + "\n---\n\n"
|
entry = "\n".join(lines) + "\n---\n\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user