Jede handgeschriebene Naht braucht einen Guard. Crossweft, eine sprachübergreifende Vertragsprüfung für KI-Agenten
Autorin: Anastasiia Butova, Ingenieurin für ComfyUI und Diffusionsmodelle, Belgrad · veröffentlicht 2026-10-08
Übersetzung der englischen Fassung: English
Unsere teuersten Fehler lagen nie innerhalb einer Datei. Sie lagen zwischen Dateien. Ein Wert stand an zwei oder drei Stellen, in zwei Sprachen, jemand änderte eine davon, alles baute noch, die Tests waren grün, und die Installation brach auf dem Rechner eines Kunden. Dies ist, wie wir diese Stellen explizit und prüfbar gemacht haben, und wie wir die Prüfung in die KI-Agenten eingebaut haben, die inzwischen einen großen Teil unseres Codes schreiben. Das Werkzeug ist Open Source: github.com/AnastasiyaW/crossweft, Projektseite: happyin.work/crossweft.
Zehn Grenzen, die der Compiler nicht sieht
Wir bauen ein Retusche-Plugin für Photoshop, das ein neuronales Netz auf dem eigenen Rechner des Nutzers ausführt. Zwischen dem Fotografen und dem Modell liegen zehn Vertrauensgrenzen: ein geschützter Kanal zwischen dem Plugin und einem Systemdienst, Geräteregistrierung, signierte Artefakte mit vertrauenswürdigen Zeitstempeln, Build-Attestierung, der Update-Kanal, Modellverschlüsselung und ein Wasserzeichen. Die Teile sind in C++ und Go geschrieben, laufen in verschiedenen Prozessen und kommen über verschiedene Installer.
Keine dieser Grenzen ist ein Modul. Jede ist eine Vereinbarung zwischen zwei Seiten: derselbe Dienstname, dieselbe Protokollversion, dieselbe Menge an Routen, dieselbe Bestätigungszeichenkette. Heute tragen 54 Verbindungen über diese Grenzen 188 Wertvergleiche (468 Stellen im Code) und 105 Mengenvergleiche. Der Compiler sieht keinen davon, weil die beiden Seiten in verschiedenen Sprachen stehen.
Wir haben angefangen, diese Stellen Nähte zu nennen: Punkte, an denen zwei Komponenten exakt übereinstimmen müssen.
- die Route, die ein Client aufruft, und die Route, die der Server registriert;
- ein Protokollversions-Header;
- die Felder eines DTO in Go und in TypeScript;
- die Umgebungsvariablen, die ein Dienst liest, und jene, die in
.env.examplestehen; - eine Rundungsregel für Preise, zweimal geschrieben, in zwei Sprachen.
Typen, Tests und Linter funktionieren innerhalb einer Sprache gut. Solche Vereinbarungen prüfen sie selten, deshalb leben sie im Gedächtnis der Leute.
Der Name kommt vom Schuss, dem Faden, der quer zur Kette läuft und das Gewebe zusammenhält.
Drei Geschichten
Ein Bezeichner, drei Kopien. Der Dienstname wurde in drei fast identischen Funktionen aufgelöst. Auf einem sauberen Rechner war der Dienst noch nicht installiert, die Suche schlug fehl, eine leere Maschine sah aus wie eine alte, inkompatible Installation, und der Installer brach mit Exitcode 76 ab. Wir haben eine Kopie korrigiert. Die anderen beiden blieben kaputt.
Zwei Hälften einer Abhängigkeit. Der Build holte eine Bibliothek aus einem Installationsverzeichnis und ihre Werkzeuge aus einem anderen. Die Konfiguration gab nur eine Warnung aus, deshalb kam das Problem viel später ans Licht, dort, wo die eigentliche Ursache schwer zu finden war.
Ein Guard, der nichts bewachte. Ein Validator für eine unserer Sicherheitsregeln hatte einen falschen Pfad. Wochenlang prüfte er null Dateien und meldete jedes Mal OK. Er bestand genau deshalb, weil er nicht lief.
Die Ursache ist in allen drei Fällen dieselbe: Eine Vereinbarung hat zwei oder drei Seiten, und ob sie übereinstimmen, hängt davon ab, dass jemand aufpasst.
Warum KI-Agenten das verschlimmern und wie sie helfen
Ein großer Teil unseres Codes wird von Agenten geschrieben, meist Claude Code und Codex. Mit Vereinbarungen zwischen Komponenten kommen sie schlechter zurecht als Menschen. Der typische Fall: Der Agent ändert eine Go-Struktur, führt die Tests aus, sieht Grün und sagt „fertig“, ohne jemals das TypeScript-Interface auf der anderen Seite geöffnet zu haben.
- Lokale Änderungen. Die andere Seite der Naht liegt in einem anderen Ordner, einer anderen Sprache, einem anderen Prozess.
- Begrenzter Kontext. Der Agent hat nicht das ganze Produkt im Kopf, und der Compiler sagt ihm nicht, dass irgendein Client irgendwo von dieser Struktur abhängt.
- Ein enges Verständnis von „fertig“. Für einen Agenten heißt fertig: „Jede Prüfung, die ich sehe, ist bestanden.“ Wenn keine Prüfung die Naht ansieht, bleibt die Abweichung unsichtbar, bis etwas bricht.
Agenten folgen präzisen lokalen Anweisungen gut. Sag einem Agenten direkt nach der Änderung, welche Datei auf der anderen Seite er öffnen und was dort übereinstimmen muss, und er tut es. Lass ihn nicht fertig werden, solange die beiden Seiten einander widersprechen, dann erreicht der Fehler nie einen Commit.
Die Regel: jede Verbindung sagt, wie sie in Übereinstimmung gehalten wird
Jede Verbindung zwischen Komponenten erklärt, wie ihre beiden Seiten in Übereinstimmung bleiben. Wenn beide Seiten von Hand geschrieben sind, braucht die Verbindung einen Guard: eine Prüfung, die beide Seiten liest.
Verbindungen liegen in JSON-Dateien neben dem Code. Die Karte hat Blöcke (Programme, Dienste, Speicher, Konfigurationsdateien) und Verbindungen zwischen ihnen (HTTPS, eine benannte Pipe, Dateien, Codegenerierung, Umgebungsvariablen). Jede Aussage in der Karte ist an ein Literal in einer Quelldatei verankert, sodass die Karte nicht stillschweigend die Vergangenheit beschreiben kann, wenn sich der Code bewegt. Dann schlägt die Prüfung fehl.
Jede Verbindung hat ein Feld contract.enforcement:
| Durchsetzung | Wer hält die Seiten gleich | Was die Prüfung verlangt |
|---|---|---|
shared-code |
ein Symbol, das beide Seiten importieren | defined_in nennt das Symbol |
generated |
ein Generator aus einer Quelle | defined_in nennt die Quelle |
schema-tests |
gemeinsame Testvektoren | defined_in nennt die Vektoren |
duplicated |
niemand, es wird von Hand kopiert | ein Guard, der auf jeder Seite eine Datei liest |
convention |
niemand, es ist vereinbart | ein Guard, der auf jeder Seite eine Datei liest |
none |
niemand | ein Guard, der auf jeder Seite eine Datei liest |
Schon die Klassifizierung ist nützlich. „Wo verlassen wir uns noch auf das Gedächtnis von jemandem?“ wird zu einer Liste, die man zählen kann. Ein Guard, der nur eine Seite liest, zählt nicht: Das Werkzeug prüft, dass er Dateien an zwei verschiedenen Enden der Verbindung liest.
Drei Guards: join, set und pair
Die Guards sind bewusst einfach: reguläre Ausdrücke, die Werte oder Mengen von Werten vergleichen. Eine Prüfung braucht Millisekunden, keinen Build und funktioniert mit jeder Sprache.
join: der Wert ist überall gleich. Die API-Version, die der TypeScript-Client sendet, und die, die der Go-Server erwartet:
{"id": "api-version", "link": "web-orders", "points": [
{"path": "web/src/api.ts", "side": "web", "regex": "API_VERSION = \"([^\"]+)\""},
{"path": "server/main.go", "side": "api", "regex": "APIVersion = \"([^\"]+)\""}]}
Jedes Vorkommen wird verglichen, nicht nur das erste. Unsere erste Version prüfte nur die erste Übereinstimmung, sodass eine zweite Kopie weiter unten in einer Datei durchrutschen konnte.
set: die Mitglieder stimmen überein. TypeScript-Interface-Felder gegen Go-JSON-Tags; die Statuswerte, die ein Python-Worker schreibt, gegen die Go-Konstanten; die Umgebungsvariablen, die der Code liest, gegen .env.example. Eine beabsichtigte Abweichung kommt in allow mit einer Begründung, und ein allow-Eintrag, der nicht mehr gebraucht wird, lässt die Prüfung selbst fehlschlagen.
pair: duplizierte Logik ohne einzelnen Wert zum Vergleichen. Zum Beispiel die Preisrundung in Go und in TypeScript. Beide Bereiche sind mit crossweft:begin price-rounding und crossweft:end price-rounding markiert, und Crossweft erstellt einen Fingerabdruck davon. Ändert sich einer, schlägt die Prüfung fehl, bis jemand das Gegenstück liest und crossweft attest price-rounding --reason "..." ausführt. Der Grund landet in der Lock-Datei und im Review.
Der kürzeste Weg aus einem roten Paar für einen Agenten ist, die Seite zu attestieren, die er geändert hat, und die andere nie anzufassen. Wenn also nur ein Bereich seit der letzten Attestierung geändert wurde, verweigert attest den Dienst. Erst das Gegenstück korrigieren, oder, wenn beide wirklich gleichwertig sind, das ausdrücklich mit --other-side-unchanged erklären.
Es gibt auch größere Prüfungen. Jede Route, die der Server registriert, muss auf der Karte stehen; Go-chi wird nativ gelesen (außerdem Express und Fastify, FastAPI, Flask, gin und echo), alles andere über einen regulären Ausdruck. Jeder /v1/...-Aufruf im Client-Code muss durch eine Verbindung erklärt sein. Ein Block darf nur Daten senden, die er selbst erzeugt oder empfangen hat. Jeder Quellordner muss auf der Karte stehen.
Wenn die eingebauten Arten nicht reichen, ergänzt ein Repository eigene Arten als Plug-ins. Wir haben 13. Eines vergleicht 916 Stellen, an denen der Go-Server mit SQL spricht, mit 161 Tabellen und 276 Triggern im Schema; ein anderes prüft 185 Serverrouten; ein drittes prüft 27 „Wartezeit ≥ Arbeit + Puffer“-Paare für Timeouts. Ein Plug-in, das sich nicht importieren lässt, abstürzt oder nichts vergleicht, ist ein Fehler und nie ein grünes Ergebnis.
Was der Agent sieht
Die Prüfung ist die halbe Sache. Der Agent muss von einer Naht sofort nach der Änderung erfahren, nicht erst, wenn der Code commit-bereit ist. Drei Hooks erledigen das:
- Sitzungsbeginn: ein kurzer Hinweis, dass das Repository eine Nahtkarte hat, wie viele Nähte es gibt und wie man damit arbeitet.
- Nach jeder Änderung: Liegt die Datei auf einer Naht, erfährt der Agent die andere Seite und den Guard.
- Vor dem Abschluss:
crossweft checkläuft. Ist eine Naht nicht in Übereinstimmung, kann der Agent nicht fertig werden, und er erfährt, was zu tun ist.
Nach einer Änderung am Go-Server in der Demo sieht der Agent:
crossweft: server/main.go is part of 3 cross-component seam(s). Keep both sides in agreement:
- link web-orders (web -> api, https; Orders API [duplicated]): you changed its to side.
Re-read: web/src/api.ts. Guarded by: join:api-version, join:version-header,
set:order-fields, set:web-statuses, pair:price-rounding.
...
Run `crossweft check` before you finish.
Wenn er die API-Version in Go hochzählt und aufhören will, schickt ihn der Hook zurück:
crossweft check fails:
- join:api-version disagrees: web@web/src/api.ts:3='2026-09-01'; api@server/main.go:14='2026-10-01'
[server/main.go:14] (key: join:api-version:b5dfb7e5)
A seam is out of agreement: bring the other side in line with the one you changed
(`crossweft impact <file>` names it).
Der Rat hängt von der Art des Problems ab. Ist ein Literal, auf das die Karte zeigt, verschwunden, wird der Agent angewiesen, die Verankerung zu aktualisieren oder den Code wiederherzustellen, nicht „die andere Seite zu korrigieren“. Der Schlüssel endet mit einem Digest darüber, welche Datei welchen Wert hält, sodass eine festgehaltene Ausnahme nur genau diese Abweichung entschuldigt.
Ein Stop-Hook hat zwei klassische Fehlermuster: eine Endlosschleife und ein stilles Bestehen. Unserer schickt den Agenten nur zurück, solange er Fortschritt macht (die Menge der fehlschlagenden Schlüssel ändert sich), höchstens viermal hintereinander. Ohne Fortschritt darf der Agent aufhören, und die Person bekommt eine klare Meldung, dass die Prüfung noch rot ist.
Für Claude Code gibt es ein Plug-in mit Hooks und einem Skill. Für Codex gibt es ein Skill-Plug-in. Jeder Agent, der MCP spricht, kann crossweft mcp verbinden und die Prüfung, die Analyse der Auswirkungen und „welche Datei soll ich erneut lesen“ als nur lesende Werkzeuge bekommen. Die Hook-Adapter für Gemini CLI, Copilot und Cursor sind noch experimentell: Ihr Ausgabeformat ist getestet, die vollständige Schleife noch nicht.
Prüfungen, die nicht still bestehen können
Die dritte Geschichte war ein Validator, der wochenlang nichts scannte. Nicht jede Regel ist „A gleich B“; manchmal muss man beweisen, dass eine Konstante genau eine Quelle hat. Solche Regeln liegen in kleinen Skripten, und genau diese brechen still.
Der Runner crossweft validators lässt einen Validator fehlschlagen, der mit einem Wert ungleich null beendet wird, SCANNED: files=<n> items=<n> nicht ausgibt oder Nullen ausgibt, keinen Selbsttest hat oder ihn nicht besteht (SELF-TEST: checks=<n>, gegen einen eingeschleusten Fehler ausgeführt), oder die Zeit überschreitet. Ein Validator, der wirklich nicht zutrifft, gibt APPLICABILITY: <reason> aus und bekommt SKIP, nicht PASS.
Crossweft selbst funktioniert genauso. Es gibt drei Ergebnisse statt zwei: 0 heißt, alles stimmt überein, 1 heißt Abweichung, 2 heißt, die Prüfung hat nicht stattgefunden (das Modell kann nicht gelesen werden, es wurde nichts gescannt). Ein Exitcode 2 sieht nie wie ein Erfolg aus.
Ein Register bekannter Abweichungen, das nicht verrottet
Nicht jede Abweichung lässt sich im selben Pull Request beheben. Dann hältst du einen Befund fest: den Problemschlüssel, einen Verantwortlichen und einen nächsten Schritt. Solange das Problem besteht, besteht die Prüfung. Ist es behoben, veraltet der Befund und lässt die Prüfung fehlschlagen, bis jemand ihn schließt. Kaputte Verankerungen lassen sich so nicht entschuldigen: Die Karte darf den Code nicht falsch beschreiben.
Für ein Repository, das bereits Abweichungen hat, hält crossweft baseline alles als offene Befunde mit Verantwortlichem fest. Die Prüfung wird grün, neue Abweichungen lassen sie weiter fehlschlagen, und was behoben ist, verlangt, geschlossen zu werden.
Was es verändert hat
Ehrlicher Kontext zuerst. Ab Mitte Juli startete unser Installer den Systemdienst auf einer sauberen virtuellen Maschine nie. Wir haben am 26. bis 28. September geprüfte Nähte ergänzt, und am 29. September startete der Dienst zum ersten Mal. Das ist ein Zusammentreffen in der Zeit, kein Beweis für die Ursache: Andere Korrekturen kamen zur selben Zeit.
Was sich tatsächlich geändert hat, ist die Art des Fehlers. Drei der ersten Fehler am 28. September waren Nähte: Exitcode 76 (der Dienstname in drei Kopien); der Server lehnte einen Loader ab, der unter der Artefaktversion registriert war, aber unter der Protokollversion angefragt wurde; und HTTP 500, weil zwei Veröffentlichungspfade das alte Paket entfernten und der dritte nicht. Jeder hat jetzt seinen eigenen Guard. Die nächsten zwei Probleme waren gewöhnliche Fehler, nicht „ein Wert an mehreren Stellen“.
Was die Karte zeigt, neu gezählt aus der Repository-Historie:
| 27. Sep | 29. Sep | 4. Okt | |
|---|---|---|---|
| Blöcke / Verbindungen | 130 / 160 | 134 / 166 | 138 / 192 |
| join-Guards | 10 | 220 (529 Stellen) | 261 (644 Stellen) |
| set-Guards | 4 | 125 | 138 |
| Befunde | 58, alle offen | 106: 93 geschlossen, 10 offen, 3 akzeptiert | 146: 117 geschlossen, 25 offen, 4 akzeptiert |
Den ersten Durchlauf „jede Verbindung erklärt ihre Durchsetzung“ haben die Agenten selbst gemacht. An einem Tag fügten sie 185 join- und 107 set-Guards hinzu und fanden im Code 15 echte Abweichungen. Von 93 geschlossenen Befunden haben 91 einen behebenden Commit, und 58 dieser Commit-Nachrichten nennen die Befund-ID aus der Karte.
Eine Karte ohne Prüfung veraltet in einer Woche. Eine Woche nach der ersten Messung gab es 96 Probleme, und 94 davon waren die Karte, die veraltet war, und keine Fehler im Code. Deshalb wird die Karte jedes Mal geprüft, wenn ein Agent anhält. Wir sind von 4 Validatoren im Juni auf 50 gekommen, und jeder muss beweisen, dass er etwas gescannt hat.
Öffnen und was der erste CI-Lauf fand
Das Werkzeug wuchs innerhalb eines kommerziellen Produkts, deshalb haben wir es vorsichtig geöffnet. Es gibt zwei Branches: das öffentliche oss, das nur das Werkzeug enthält, und ein privates internal, das oss plus einen Ordner mit unseren eigenen Regeln ist. Änderungen fließen nur von oss nach internal. Was öffentlich geht, ist ein Snapshot des Baums, ein Commit pro Release mit neutraler Signatur, nicht unsere Historie. Vor der Veröffentlichung prüft ein Scanner den Snapshot und die Commit-Metadaten gegen eine Liste privater Wörter, und ein frischer, unabhängiger Prüfer liest das Diff seit dem letzten Release, denn ein Scanner kennt nur die Wörter, die man ihm gegeben hat. Die erste Prüfung fand drei Formulierungen in der Dokumentation, die andeuteten, wie unser Produkt gebaut ist, und wir haben sie vor dem Release ersetzt.
Ein Release-Tag wird erst gesetzt, wenn CI auf Linux, Windows und macOS grün ist, denn Tags sind unveränderlich. Der allererste Lauf war rot. Er fand zwei echte Fehler, die 157 lokale Tests übersehen hatten:
- der Code verwendete ein Argument, das es erst ab Python 3.10 gibt, obwohl wir 3.9 zusagen;
- ein Plug-in, das innerhalb einer Sekunde zweimal auf dieselbe Länge bearbeitet wurde, lief aus veraltetem Bytecode. Python unterscheidet Cache-Dateien nach Sekunden und Größe. Lokal konnten wir es nicht sehen: Das Schreiben von Bytecode ist auf unserem Entwicklungsrechner systemweit abgeschaltet.
Wir haben beides behoben, der zweite Lauf war grün, und v0.1.0 ging hinaus. Zwei Tage später brachte v0.2.0 den MCP-Server, check --changed, aus OpenAPI- und Proto-Schemata gebaute Guards und Routen-Scanner für fünf weitere Frameworks. Es zeigt den Kern dieses Artikels: Ein grüner Check sagt nur etwas über die Fehlerarten, an die jemand gedacht hat.
Gibt es das nicht schon?
Jedes Teil davon gibt es irgendwo. Was wir nicht gefunden haben, ist die Kombination und die Regel, dass jede Verbindung ihre Übereinstimmung erklärt und jede handgeschriebene Naht einen Guard hat.
| Werkzeug | Was es tut | Wie es sich unterscheidet |
|---|---|---|
Google LINT.IfChange, ifttt-lint, if-changed |
„Wenn dieser Block sich ändert, ändere auch jene Datei“ | prüft, dass das Diff die Datei berührt; ein join vergleicht Werte bei jedem Lauf |
| clevis | ein Wert, über Dateien hinweg gleich | keine Mengen, keine Bereiche, keine Karte |
| Pact, buf, oasdiff | Vertragstests und Schema-Kompatibilität | die richtige Antwort, wenn ein Schema existiert; Crossweft baut aus OpenAPI und Proto Guards für die handgeschriebene Seite |
| ArchUnit, dependency-cruiser, import-linter | Architekturregeln | innerhalb einer Sprache |
| archagent | Architekturinvarianten und Skills für Agenten | im Geist am nächsten, aber Struktur statt Werte über Sprachen hinweg |
| GitNexus, codegraph | Codegraphen und Auswirkungsanalyse für Agenten, über MCP | ein Graph, den man abfragt, keine Prüfung, die fehlschlägt; sie ergänzen sich |
| fiberplane/drift, Swimm | an Code verankerte Dokumentation | ein Hash schlägt bei jeder Änderung fehl; ein Anker schlägt nur fehl, wenn der Fakt selbst verschwunden ist |
Der Ansatz ist auch nicht unserer. Birgitta Böckeler schreibt über Harness Engineering: Anleitungen, die handeln, bevor der Agent Code schreibt, und Sensoren, die danach prüfen; deterministische Prüfungen gegenüber Prüfungen durch ein Sprachmodell. OpenAI schreibt über dasselbe in Harness engineering: leveraging Codex in an agent-first world. Crossweft ist ein deterministischer Sensor für Nähte zwischen Komponenten, dessen Ausgabe dem Agenten genau sagt, was er beheben soll.
Grenzen
- Reguläre Ausdrücke, kein Parsen. Formatierst du eine Deklaration um, schlägt die Prüfung laut fehl; dann aktualisierst du das Muster. Aber ein zu breites Muster kann trotzdem eine echte Abweichung übersehen, deshalb die extrahierten Werte ansehen und das Laufzeitverhalten mit gewöhnlichen Tests prüfen.
- Ein Mensch prüft noch, dass die Durchsetzung ehrlich ist. Agenten bauen die Karte, und Crossweft lässt sie keine Verbindung erfinden und keinen Ordner überspringen. Aber ob eine Verbindung wirklich
shared-codeist oder zwei Kopien, weiß manchmal nur ein Entwickler. - Die Einführung kostet Arbeit. Bei einem kleinen Projekt ist der Gewinn klein. Es lohnt sich dort, wo viele Sprachen und Prozesse zusammenkommen.
- Bisher sind wir die einzigen Nutzer. Die Claude-Code-Schleife ist durchgängig (von Ende zu Ende) erprobt; andere Agenten verbinden sich über MCP und den Skill, und ihre Hook-Adapter sind experimentell.
check --changedbeschleunigt den Pre-Commit, aber noch nicht, wenn die Änderung eine Router- oder Client-Datei betrifft.
Ausprobieren
Der schnellste Weg, den Mechanismus zu sehen, ist, die Demo zu brechen (eine Go-API, ein TypeScript-Client, ein Python-Worker):
pip install "git+https://github.com/AnastasiyaW/[email protected]"
git clone --branch v0.2.8 --depth 1 https://github.com/AnastasiyaW/crossweft
cd crossweft/examples/polyglot-shop && crossweft check # RESULT: PASS
# now change API_VERSION in web/src/api.ts only:
crossweft check # join:api-version disagrees
Jede Zeile unten wurde auf einer Kopie der Demo geprüft: Die Prüfung schlägt mit Exitcode 1 fehl und nennt den Guard.
| Änderung | Gefangen von |
|---|---|
API_VERSION in web/src/api.ts allein ändern |
join:api-version |
ein JSON-Feld nur in der Go-Struktur Order ergänzen |
set:order-fields |
| einen Status nur im Python-Worker ergänzen | set:worker-statuses |
eine neue Umgebungsvariable im Worker lesen, die nicht in .env.example steht |
set:env-worker-vars |
/v1/refunds aus dem Client aufrufen |
route:client-unexplained |
die Rundung in server/pricing.go ändern |
pair:price-rounding |
In deinem eigenen Repository: crossweft init (init --example zeigt zuerst eine funktionierende Naht), dann den Agenten bitten, das Repository zu kartieren; der Skill führt ihn durch, und crossweft check hält es ehrlich. In Claude Code: /plugin marketplace add AnastasiyaW/crossweft, dann /plugin install crossweft@crossweft. In CI: uses: AnastasiyaW/[email protected], oder crossweft check --format sarif für GitHub Code Scanning.
Das Crossweft-Repository prüft seine eigenen Nähte mit Crossweft: die Version in drei Dateien, die Hook-Ereignisse, die Befehle in der Dokumentation. Während wir das README schrieben, schlug der Guard „dokumentierte Befehle existieren“ dreimal bei Zeilen an, die er für Befehle hielt, aber Prosa waren. Auch diese haben wir behoben.
Crossweft steht unter Apache-2.0. Issues und Pull Requests sind willkommen auf github.com/AnastasiyaW/crossweft.