Ein Regelwerk importieren
Was der Importer heute wirklich erkennt
POST /import/claude-md ist ein deterministischer Scanner ohne Sprachmodell
— kein vollständiger Markdown-Parser, und ohne Wissen über die Struktur
einer bestimmten Datei. Er versucht zwei Wege, in dieser Reihenfolge:
- Das nummerierte Regelformat zuerst: ein
## 0. ...-Abschnitt mit### R<n> — Titel (P<n>)-Überschriften darin. Findet dieser Weg mindestens eine Regel, laufen ausschließlich diese Treffer —regel_textist dann der erste Anweisungssatz des ersten Prosa-Absatzes unter der jeweiligen Überschrift (Metadaten-Absätze wie**SoT:** ...oder**Erzwingungsgrad:** ...werden übersprungen). Ein### R<n>-Header ohne(P<n>)-Suffix im Titel (z. B. eine aufgehobene/reservierte Nummer wie R12) liefert trotzdem einen Vorschlag, wird aber zusätzlich inunklargemeldet. - Sonst — der übliche Fall — Freiform-Rückfall:
nur wenn Weg 1 nichts liefert (kein
## 0.-Abschnitt oder keine### R<n>-Überschrift darin), sucht derselbe Aufruf genau zwei freie Formen: - Eine Überschrift (
#bis####), gefolgt von einem Prosa-Absatz: jeder Satz in diesem Absatz, der mit einem Imperativ-Marker beginnt, wird zum Kandidaten. - Ein Stichpunkt oder nummeriertes Listenelement, dessen Text mit einem Imperativ-Marker beginnt.
Die Marker sind fest und groß-/kleinschreibungsunabhängig — Englisch:
Always, Never, Do not, Don't, Use, Prefer, Must, Avoid;
Deutsch: Immer, Nie, Niemals, Kein, Keine, Nutze, Verwende,
Vermeide, Muss. Code-Blöcke (```) und Tabellenzeilen (Zeilen mit
führendem |) sind nie eine Quelle.
Jeder Vorschlag trägt den Originalwortlaut, keine Umformulierung:
regel_text ist genau der gefundene Satz bzw. Stichpunkttext (oder, beim
nummerierten Regelformat, der erste Anweisungssatz), nur von Markdown
(**fett**, `code`, [links](url)) befreit. titel ist beim
nummerierten Regelformat der Titel aus der ### R<n> — Titel-Überschrift,
beim Freiform-Weg derselbe Text wie regel_text, gekürzt auf die ersten 8
Wörter für einen lesbaren Sachen-Titel; sache ist ein daraus abgeleiteter
Kebab-Slug, bei Kollision mit -2, -3, … eindeutig gemacht.
Ehrlich benannte Grenzen
- Die Sonderbehandlung für das nummerierte Regelformat greift nur
innerhalb eines
## 0. ...-Abschnitts.### R<n> — Titel-Überschriften außerhalb eines solchen Abschnitts (oder ganz ohne(P<n>)-Konvention) werden nicht gefunden — dafür bräuchte es entweder den## 0.-Rahmen oder den Freiform-Rückfall (der dann keine Sonderbehandlung für die Regelnummer kennt: sie landet im Titel, wenn der Prosa-Satz selbst mit einem Imperativ-Marker beginnt). Weder der Überschriftentext noch eine Regelnummer werden für sich allein zu einem Vorschlag — eine Überschrift ohne markerführenden Satz darunter (Freiform-Weg) oder ganz ohne Prosa-Absatz (nummeriertes Regelformat) liefert nichts. VieleKernsatz-Absätze im Freiform-Weg beginnen zufällig nicht mit einem erkannten Marker-Wort (sie beschreiben die Regel, statt sie zu befehlen), und sie werden nicht zu einem Vorschlag. Seit dem 25.09.2026 werden sie aber auch nicht mehr stillschweigend übergangen:unklarnennt sie gezählt (siehe unten). Bei so einer Datei die Vorschlagsliste als Teiltreffer behandeln — wie viele Stellen daneben liegen, steht jetzt in der Antwort. (Ein separates, eigenständiges Werkzeug trägt dieselbe Zwei-Wege-Logik — es ist aber über die API nicht erreichbar.) - Die Marker-Erkennung im Freiform-Weg ist rein lexikalisch, genau am
Anfang des Satzes oder Stichpunkts — sie versteht nicht, was der Rest des
Satzes sagt.
„Use of tabs is discouraged in this repository." beginnt mit
Useund wird deshalb zum Vorschlag, obwohl der Satz gerade keine Handlungsanweisung ist. Jedenregel_textlesen, bevor er übernommen wird. - Prosa ohne Marker am Anfang wird nie zur Regel, egal wie klar sie eine Vorgabe formuliert. Entweder die Quelle mit einem führenden Marker umformulieren, oder die Aussage danach von Hand schreiben (siehe Regel setzen und ablösen).
- Ein Stichpunkt, dessen Marker nicht am Anfang steht (z. B. „Siehe die
Doku — ihr müsst sie immer zuerst prüfen") wird nicht still verworfen: er
landet als Beinahe-Treffer in
unklar— diese Liste vor der Annahme prüfen, die Vorschläge seien die ganze Geschichte.
Was unklar sagt — zwei Arten Eintrag
Seit dem 25.09.2026 verschweigt die Liste nichts mehr. Sie enthält, in dieser Reihenfolge:
- Einzelne Zweifelsfälle, je einer mit Zeilennummer — eine Stelle, die fast eine Regel war (der Marker stand nur nicht am Anfang).
- Je Grund eine gezählte Zeile mit Anzahl und erstem Beispiel samt Zeilennummer, für alles, was gar nicht als Regel gelesen wurde: Zitatblöcke, Tabellen, Stichpunkte ganz ohne Marker, Absätze ohne Überschrift darüber, und Sätze unter einer Überschrift ohne führenden Marker.
Ein Beispiel, gemessen an der CLAUDE.md von COMPASS selbst (189 Zeilen):
7 Vorschläge
4 unklar:
9 Zeilen wurden nicht als Regel gelesen: in Zitatblöcken (> ...) …
8 Zeilen wurden nicht als Regel gelesen: in Tabellen …
8 Stellen wurden nicht als Regel gelesen: Stichpunkte ohne Imperativ-Marker …
60 Stellen wurden nicht als Regel gelesen: Sätze unter einer Überschrift, die
nicht mit einem Imperativ-Marker beginnen …
Vorher stand dort 0 unklar bei denselben 7 Vorschlägen. Gezählt und nicht
einzeln aufgeführt, weil 85 Zeilen niemand liest — wer eine bestimmte Stelle
sucht, hat die Zeilennummer des ersten Falls je Grund.
art: derselbe Scan, ein anderes Schreiben
art (Formularfeld, Standard claude-md) ist claude-md | agents-md |
cursor-rules. Alle drei laufen durch genau denselben Scan oben — nur was
apply anschließend schreibt, unterscheidet sich (s. u.). agents-md/
cursor-rules verwenden, wenn diese Dateien statt einer CLAUDE.md
importiert werden.
Schritt 1 — Vorschau
curl -sS -X POST "$COMPASS_URL/import/claude-md" \
-H "X-Compass-Key: $COMPASS_KEY_RO" \
-F 'text=## Security
- Always encrypt data at rest.
- Never log a password in plain text.'
Es wird noch nichts geschrieben. Die Antwort:
{
"neu": [
{"sache": "always-encrypt-data-at-rest", "titel": "Always encrypt data at rest",
"frage": "Was gilt für 'Always encrypt data at rest'?",
"regel_text": "Always encrypt data at rest.", "quelle_zeile": 3},
{"sache": "never-log-a-password-in-plain-text", "titel": "Never log a password in plain text",
"frage": "Was gilt für 'Never log a password in plain text'?",
"regel_text": "Never log a password in plain text.", "quelle_zeile": 4}
],
"unveraendert": [],
"weicht_ab": [],
"unklar": [],
"art": "claude-md"
}
Vier Gruppen, und sie sind der eigentliche Zweck dieses Schritts. Die Vorschau liest den Bestand und sagt je Vorschlag, wie er sich zu dem verhält, was bei euch schon gilt:
| Gruppe | Bedeutung | Was apply damit tut |
|---|---|---|
neu |
zu der Sache gilt noch keine Aussage dieser Art | anlegen |
unveraendert |
es gilt bereits derselbe Wortlaut | nichts — wird nicht zweimal geschrieben |
weicht_ab |
es gilt ein anderer Wortlaut | die geltende Aussage wird abgelöst (ersetzt) |
unklar |
der Scanner war sich nicht sicher | nichts, nur anzeigen |
Einträge in unveraendert und weicht_ab tragen zusätzlich geltende mit
id, text, gilt_seit und quelle — ihr seht vor dem Anwenden, was
genau abgelöst würde.
Damit ist ein zweiter Import derselben Datei nicht mehr dieselbe Arbeitsliste wie der erste.
unklar lesen, bevor irgendetwas angewendet wird — die Liste nennt, was der
Scanner gesehen, aber nicht sicher genug für einen Vorschlag fand.
Wenn die Datei gar keine Regeldatei ist
Der Datei-Weg (-F "file=@…") prüft vor dem Zerlegen zweierlei und liest
im Fehlerfall nichts:
| Fall | Antwort |
|---|---|
| größer als 1 MB | 422 mit feld: "file", byte (die gemessene Größe) und grenze |
| keine Textdatei | 422 mit feld: "file" und byte — der Byte-Versatz, ab dem es kippt (kein Zeichen-Index) |
„Keine Textdatei" heißt: entweder ist das Byte an dieser Stelle kein gültiges UTF-8, oder dort steht ein Nullbyte (eine UTF-16-Datei ist streckenweise gültiges UTF-8 und trotzdem keine Textdatei).
Es wird nichts geraten: kein zweiter Versuch mit einer anderen Kodierung, kein Kürzen auf die Grenze. Eine falsch geratene Kodierung schriebe „spätestens" als „sp?testens" in euer Regelwerk, und eine stumm verstümmelte Regel ist teurer als eine abgelehnte Datei.
Eine Grenze, damit sie niemanden überrascht:
sacheentsteht aus dem Titel. Bei Stichpunkten ist der Stichpunkt der Titel — wer eine Stichpunkt-Regel umformuliert, erzeugt damit einen neuen Slug, und der Vorschlag erscheint alsneu, nicht alsweicht_ab; die alte Regel gilt weiter. Nur wo der Titel getrennt vom Regeltext steht (Überschriften,### R<n> — Titel), erkennt der Abgleich eine Umformulierung.
Schritt 2 — prüfen, dann anwenden
POST /import/claude-md/apply schreibt sowohl die Sache als auch
ihre Aussage — eine regel bei art=claude-md, ein fakt bei
art=agents-md/art=cursor-rules (s. u.). Die Vorschläge direkt als items
durchreichen:
curl -sS -X POST "$COMPASS_URL/import/claude-md/apply" \
-H "X-Compass-Key: $COMPASS_KEY_RW" -H "Content-Type: application/json" \
-d '{"items": [
{"sache": "always-encrypt-data-at-rest", "titel": "Always encrypt data at rest",
"frage": "Was gilt für '"'"'Always encrypt data at rest'"'"'?",
"regel_text": "Always encrypt data at rest.", "quelle_zeile": 3}
]}'
Oder stattdessen per Slug auswählen, mit demselben Original-text — die
Vorschau wird dann serverseitig erneut erzeugt und gefiltert; nützlich, wenn
die vollständigen Vorschlags-Objekte nicht herumkopiert werden sollen:
curl -sS -X POST "$COMPASS_URL/import/claude-md/apply" \
-H "X-Compass-Key: $COMPASS_KEY_RW" -H "Content-Type: application/json" \
-d '{"auswahl": ["always-encrypt-data-at-rest"], "text": "## Security\n\n- Always encrypt data at rest.\n- Never log a password in plain text.\n"}'
items oder (auswahl + text) ist Pflicht — 422 ohne eines von beiden,
422 wenn auswahl einen Slug nennt, den die neu erzeugte Vorschau nicht
enthält.
Die Antwort:
{
"geschrieben": [{"sache": "always-encrypt-data-at-rest", "aussage_id": 101}],
"unveraendert": [],
"abgeloest": [],
"konflikt": [],
"quelle_ref_widerspruch": []
}
- Die Sache wird angelegt (oder unverändert wiederverwendet, falls sie schon
existiert — idempotent auf
slug, wie beiPOST /sachen). quelleder Aussage ist"<Dateiname>:<Zeile>"— der Dateiname kommt aus demquelle-Feld des Aufrufs, Standard jeart:CLAUDE.md/AGENTS.md/cursor-rules.mdc;quelle_refist"import:<art>:<Dateiname>:<Zeile>".- Gilt schon derselbe Wortlaut, wird nichts geschrieben — der Slug
wandert nach
unveraendert. Das gilt für beide Wege, über die COMPASS das erkennt: denselben Vorschlag erneut anwenden (idempotent überquelle_ref) und denselben Regeltext unter anderer Herkunft. - Weicht der Wortlaut ab, wird die geltende Aussage abgelöst (nur bei
art=claude-md, also einerregel): die neue Aussage zeigt überersetztauf die alte, und der Eintrag erscheint inabgeloest: [{sache, aussage_id, loest_ab}]. Gelöscht wird nichts — die alte Aussage behält ihre Geltungsspanne und bleibt im Verlauf sichtbar. Dass es passieren wird, hat die Vorschau vorher alsweicht_abgesagt. - Der frühere Weg (nichts schreiben,
konfliktmelden) galt bis zum 24.09.2026.konfliktbleibt für den Wettlauf: schreibt jemand anderes zwischen Vorschau und Anwenden eine geltende Regel zur selben Sache, wird gemeldet statt geraten. Derselbequelle_refmit abweichendemregel_textschreibt ebenfalls nichts; stattdessen ein Eintrag inquelle_ref_widerspruch: [{sache, quelle_ref, bestehend}]— nie ein stilles Überschreiben. - Trägt die Sache bereits eine geltende Regel unter einem anderen
quelle_ref(z. B. eine andere Zeile derselben Datei, oder ein früheres manuellesPOST /aussagen), wird sie nicht ersetzt: der Eintrag landet inkonflikt: [{sache, grund, geltende}], nichts wird geschrieben. Das gilt nur fürart=claude-md— nurregel-Aussagen unterliegen der Ein-Regel-Invariante.
Zeilenschutz gilt wie überall: jede Zeile gehört dem Mandanten des
präsentierten Schlüssels; ein anderer Mandant sieht nichts davon, auch nicht
bei identischem sache-Slug (Primärschlüssel ist (kunde_slug, slug)).
AGENTS.md oder Cursor-Regeln importieren
Dieselben zwei Aufrufe, mit art=agents-md bzw. art=cursor-rules:
curl -sS -X POST "$COMPASS_URL/import/claude-md" \
-H "X-Compass-Key: $COMPASS_KEY_RO" \
-F "file=@AGENTS.md" -F "art=agents-md"
curl -sS -X POST "$COMPASS_URL/import/claude-md/apply" \
-H "X-Compass-Key: $COMPASS_KEY_RW" -H "Content-Type: application/json" \
-d '{"art": "agents-md", "items": [ ...aus der Vorschau... ]}'
Die hier geschriebene Aussage ist ein fakt (herkunft=gesagt), keine
regel — AGENTS.md und .cursor/rules/*.mdc erklären sich beide selbst
als nachrangig zu CLAUDE.md („Warum fakt, nicht regel"), und eine zweite
regel auf derselben Sache würde mit der
Ein-Regel-Invariante kollidieren und abgewiesen werden. Das heißt: ein
art=agents-md-Import neben einer bestehenden art=claude-md-Regel auf
derselben Sache kollidiert nie — es sind unterschiedliche Aussage-Arten. Für
.cursor/rules/*.mdc beide Aufrufe je Datei einmal ausführen.
Entscheidungen statt Überschriften importieren
Wer ein Protokoll datierter Entscheidungen hat statt eines Regeldokuments,
ist bei POST /import/decisions besser aufgehoben:
{items: [{z, quote, context, decided_at, sache?, ersetzt_z?}]}, idempotent
auf die z-Nummer, jeder Widerspruch landet in einer widerspruch-Liste
statt still übersprungen oder überschrieben zu werden. Eine Entscheidung ohne
(bekannte) sache wird trotzdem gespeichert — als Ereignis statt als
Aussage, damit aus dem Protokoll nichts verloren geht. Das ist eine
bruecke-Route — ein bruecke-Schlüssel funktioniert hier, obwohl er sonst
fast nichts lesen oder schreiben kann.
Assistent-Vorschau (Status: vorbereitet)
Für Text, den der deterministische Scanner oben NICHT erkennt (Prosa ohne
Imperativ-Marker, Sätze, die eine Vorgabe beschreiben statt sie zu befehlen),
gibt es einen zweiten, optionalen Weg: POST /assistent/vorschlaege
{text, art?: claude-md|agents-md|cursor-rules|prosa}. Er ruft zuerst
denselben Scanner wie /import/claude-md auf — alles, was der schon
erkennt, bekommt keinen Modellaufruf — und schickt nur den Rest an ein
Sprachmodell, sofern einer konfiguriert ist.
Dieser Weg ist heute vorbereitet, nicht standardmäßig aktiv. Die
Anbieter-Wahl ist bewusst offen: Anthropic (ein aktuelles
Haiku-Modell) ODER ein lokales, OpenAI-kompatibles Modell (z. B. Ollama) —
Standard ist aus. GET /assistent/status → {anbieter, bereit, grund,
verbrauch_heute, deckel} zeigt, ob überhaupt ein Anbieter läuft; ohne
Konfiguration ist bereit=false mit einem Klartext-Grund (z. B.
„COMPASS_MODELL_ANBIETER ist 'aus' (Standard)"), POST
/assistent/vorschlaege antwortet dann mit 503, sofern der Scanner nicht
schon alles abgedeckt hat — deckt er alles ab, bleibt die Antwort 200, auch
ohne Anbieter.
Jeder Modell-Vorschlag trägt zusätzlich woertlich (nur true, wenn
regel_text wirklich als Teilstring im Original vorkommt — serverseitig
geprüft, nie ungeprüft vom Modell übernommen), optional bereich und
optional pruefweg: {modul, parameter} (nur mit einem der drei tatsächlich
vorhandenen Prüfweg-Module github_ruleset/ci_status/konfig_wert).
Auch dieser Weg schreibt nichts — die Übernahme läuft über denselben
POST /import/claude-md/apply wie oben, ein Vorschlag ist absichtlich so
geformt, dass er sich 1:1 als items-Eintrag eignet (die Zusatzfelder vorher
weglassen). Ein Tagesdeckel je Kunde (COMPASS_MODELL_DECKEL_AUFRUFE_TAG/
COMPASS_MODELL_DECKEL_TOKEN_TAG, Standard 200 Aufrufe/400000 Token)
verhindert, dass ein aktivierter Anbieter unbemerkt übers Budget läuft —
überschritten → 429. Die genaue Vorschlagsform steht in der REST-Referenz unter „Assistent".
Was diese Anleitung nicht abdeckt
Für keinen der Import-Wege gibt es ein MCP-Werkzeug — alle vier
(/import/claude-md, /import/claude-md/apply, /import/decisions,
/assistent/vorschlaege) sind heute reines REST.