CommunitySchreiben & Editierengithub.com

FalkWoldmann/skills

Verwenden Sie diese Fähigkeit für jede verhaltensbewahrende Umstrukturierung eines sans-IO Rust-Protokollkerns. Das heißt, eine Crate, die `now: Instant` nimmt und durch `handle_*`-Aufrufe mit Eingaben versorgt und über `poll_transmit/poll_event/poll_timeout` abgeleitet wird, wie z.B. quinn-proto, str0m oder ein Resolver-, Handshake-, Framing- oder Client-Kern. Verwenden Sie sie, wenn der Benutzer den Zustandsautomaten lesbarer oder leichter nachvollziehbar machen möchte, Flags in Zustands-Enums entwirrt, Handler vereinfacht, Code zur Klarheit umgestaltet oder eine große Datei in Module aufgeteilt oder neu organisiert werden soll, selbst wenn der Benutzer nur sagt „verschiebe einfach Code“. Kurze Anfragen wie „Dieser resolver.rs ist schwer zu verfolgen“ zählen ebenfalls. Die Fähigkeit bildet die Zustände, Eingaben, Ausgaben und Timer ab, gestaltet in kleinen Schritten um, die gegen das Original überprüft werden, behält die öffentliche API und die Ausgabereihenfolge identisch bei und endet mit einer Zusammenfassung. Nicht geeignet zum Beheben von Fehlern, Hinzufügen von Funktionen, Optimieren der Leistung, Überprüfen eines PRs, Schreiben von benutzerorientierten Dokumentationen oder Konvertieren von asynchronem oder socket-basiertem Code zu sans-IO.

Was ist skills?

Diese Fähigkeit ist speziell für die Umgestaltung von sans-IO Rust-Protokollkernen konzipiert, bei denen der Zustandsautomat unabhängig von der I/O-Implementierung ist. Der Fokus liegt darauf, das Innenleben solcher Bibliotheken lesbarer und wartbarer zu machen, indem Zustände, Übergänge, Ausgaben und Zeitsteuerung klar durch Typen und Code sichtbar werden. Sie unterstützt Plattformen wie Claude Code, Cursor und Codex und ermöglicht Entwicklern, komplexe Zustandsautomaten zu entwirren, ohne das Verhalten zu ändern. Dies wird erreicht, indem zuerst der aktuelle Zustand abgebildet, dann schrittweise (z.B. durch Einführen von Zustands-Enums oder benannten Übergängen) umgestaltet und schließlich eine Zusammenfassung der Änderungen bereitgestellt wird. Die Fähigkeit stellt sicher, dass die öffentliche API identisch bleibt, Verhaltenstests bestehen und nur private oder `pub(crate)`-Elemente geändert werden.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/FalkWoldmann/skills/tree/HEAD/sansio-readability

Installed? Explore more Schreiben & Editieren skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Lesbare sans-IO Zustandsautomaten

Eine sans-IO Bibliothek ist ein Zustandsautomat, bei dem die E/A entfernt wurde. Der Aufrufer versorgt ihn mit Eingaben (handle_*, jeweils mit now), leert seine Ausgaben (poll_transmit, poll_event, poll_timeout) und besitzt die Sockets und Timer. Code dieser Art ist nur dann wertvoll, wenn man darüber nachdenken kann: der nächste Maintainer, der Autor eines Treibers, ein Reviewer, der ihn gegen eine RFC überprüft. Jeder von ihnen sollte vier Fragen schnell beantworten können:

  1. Zustände: In welchen Zuständen kann sich jede langlebige Entität (Verbindung, Abfrage, Stream, Sitzung) befinden, und welche Daten existieren in jedem?
  2. Übergänge: Welche Eingaben bewegen sie von einem Zustand zum anderen, und wo im Code findet jeder Übergang statt?
  3. Ausgaben: Was wird durch jeden Übergang in die Warteschlange gestellt (Übertragungen, Ereignisse, Schreibvorgänge), und in welcher Reihenfolge?
  4. Zeit: Wann muss sie aufgeweckt werden, und welche Regel setzt jede Frist?

Ihre Aufgabe ist es, diese Antworten in den Typen und im Code sichtbar zu machen, während die Maschine weiterhin genau das tut, was sie tat. Sie bearbeiten den Arbeitsbaum und beenden mit einer Zusammenfassung.

<skill> bezieht sich unten auf das Basisverzeichnis dieser Fähigkeit, den Ordner, der diese SKILL.md enthält.

Grundregeln

  • Das Verhalten bleibt identisch. In einer sans-IO Crate umfasst das Verhalten Dinge, die kosmetisch aussehen:

    • die Reihenfolge, in der Ausgaben in die Warteschlange gestellt werden
    • die Iterationsreihenfolge von Maps (eine BTreeMap oder eine einfügeordnete Vec entscheidet, wer zuerst geht)
    • welchen Instant poll_timeout zurückgibt
    • ob ein leerer Schreibvorgang oder ein No-Op-Ereignis überhaupt ausgegeben wird
    • mit welchem Fehler eine Entität fehlschlägt oder schließt
    • die Ausgabe eines #[derive(Debug)]-Typs, dessen Felder Sie ändern, wenn ein Snapshot, eine Log-Assertion oder eine Fehlermeldung ihn ausgibt

    Tests, die durch die öffentliche API gehen, und jeder Snapshot, müssen unverändert bestehen. Wenn einer bearbeitet werden muss, hat dieser Schritt das Verhalten geändert: Machen Sie ihn rückgängig. Unit-Tests innerhalb der Crate, die private Elemente benennen, benötigen möglicherweise dieselbe mechanische Bearbeitung wie der Code (ein Flag, das zu einer Variante wurde, eine umbenannte Hilfsfunktion). Führen Sie nur diese Bearbeitung durch, behalten Sie bei, was jede Assertion prüft, und listen Sie den Test in der Zusammenfassung auf.

  • Lassen Sie die öffentliche API in Ruhe. Behalten Sie die Namen, Signaturen, Varianten und Felder von pub-Elementen so, wie sie sind, da Treiber und nachgelagerte Crates von ihnen abhängen (und von Semver). pub(crate) und private Elemente sind erlaubt. Verbesserungen der API setzen Sie als Vorschläge in die Zusammenfassung. Wie Sie dies überprüfen, finden Sie in Schritt 5.

  • Opfern Sie keine Leistung für Lesbarkeit. Keine neuen Klone, Allokationen oder zusätzlichen Durchläufe über die Maps auf Pfaden, die pro Eingabe (pro Paket, Nachricht oder Abfrage) ausgeführt werden. Ein mem::replace oder mem::take ist kostenlos; das Klonen einer Vec, um den Borrow-Checker zu umgehen, nicht.

  • Arbeiten Sie in kleinen Schritten. Jeder Schritt baut und besteht die Überprüfungen, sodass ein fehlgeschlagener Test auf eine Änderung hinweist und der Diff Schritt für Schritt überprüft werden kann.

  • Folgen Sie dem Projekt. Lesen Sie vor der Bearbeitung die Agenten- und Mitwirkendendokumente (AGENTS.md, CLAUDE.md, CONTRIBUTING), die Lint-Konfiguration und den CI-Workflow. Passen Sie die umgebende Kommentardichte und den Idiomstil an. Wenn das Projekt Dokumentationen auf dem neuesten Stand mit dem Code hält (eine Lebenszyklustabelle, ein Kommentar, der Zeilen auf Funktionen abbildet, eine Spezifikationsabdeckungstabelle), aktualisieren Sie diese im selben Schritt.

Größe der Arbeit an die Änderung anpassen

Das Sicherheitsnetz besteht, um Verhaltensänderungen zu erkennen, die die vorhandenen Tests übersehen würden, passen Sie es daher an die geplante Änderung an:

  • Klein. Ein Handler oder einige Hilfsfunktionen, keine Änderung daran, welche Felder den Zustand halten. Führen Sie die Überprüfungen des Projekts einmal vorher und einmal nachher durch und überspringen Sie alles, was unten als groß markiert ist. In der Zusammenfassung bilden Sie nur die Entität ab, die Sie berührt haben.
  • Mittel. Mehrere Übergänge oder Ausgabehilfsfunktionen in einem Modul. Führen Sie die Überprüfungen vor jedem Schritt und am Ende durch. Bauen Sie einen differentiellen Testrahmen nur, wenn kein vorhandener Test die genauen Ausgaben der Pfade, die Sie ändern, festschreibt, und zwar nur über diese Pfade. Kein Fuzzing, keine zusätzlichen Property-Test-Fälle, keine Benchmarks: Die eigenen Überprüfungen des Projekts mit ihren normalen Einstellungen sind hier das richtige Maß.
  • Groß. Flags, die durch ein Phasen-Enum ersetzt wurden, viele Schreiber eines Zustandsfelds, die in Übergänge zusammengeführt wurden, die meisten Handler umgeformt. Verwenden Sie den gesamten Workflow.

Bestimmen Sie die Größe nach der Abbildung (Schritt 2). Wenn die Änderung während der Arbeit wächst, wechseln Sie zu einer größeren Größe und sagen Sie dies in der Zusammenfassung.

Workflow

1. Sicherheitsnetz einrichten

Führen Sie dies vor der ersten Bearbeitung durch, sodass jeder spätere Fehler von Ihnen stammt und nicht bereits vorhanden ist.

  • Basis notieren. Führen Sie git status aus und notieren Sie den Basis-Commit: Jeder spätere Vergleich erfolgt gegen ihn. Wenn Dateien, die Sie bearbeiten möchten, bereits unbestätigte Änderungen enthalten, informieren Sie den Benutzer, bevor Sie Ihre Bearbeitungen mit ihren vermischen. Wenn Sie auf deren Basis fortfahren, verwenden Sie git stash create als Basis: Es zeichnet sie als Commit auf, ohne den Baum zu berühren.
  • Einen Check-Befehl fixieren. Verwenden Sie den eigenen des Projekts, wenn es einen hat (einen CI-Workflow, ein Justfile, ein Xtask, die Befehle in den Agentendokumenten). Andernfalls verwenden Sie das gebündelte Gate: <skill>/scripts/check.sh <repo> default all. Listen Sie die Feature-Sets auf, die CI verwendet, z.B. default all none tls. Das Gate prüft die Formatierung ohne Änderung, führt dann Clippy und die Tests für jedes Feature-Set aus und beendet mit einem Fehlercode, wenn etwas fehlschlägt. Begrenzen Sie es auf das Paket, das Sie ändern, und die Mitglieder, die davon abhängen, mit CHECK_PACKAGES="-p <crate> -p <dependent>", und führen Sie den gesamten Arbeitsbereich einmal am Ende aus. Wenn CI auch etwas anderes ausführt (eine MSRV-Prüfung, zusätzliche Lint-Flags), führen Sie dies ebenfalls aus.
  • Wenn die Basis nicht grün ist, reparieren Sie sie nicht. Notieren Sie, welche Checks fehlschlagen und wie, informieren Sie den Benutzer, und erwarten Sie ab dann dieselben Fehler und keine neuen. Formatieren Sie nur die Dateien, die Sie bearbeiten. Ein Fehler, der aus der Umgebung und nicht aus dem Code stammt (eine globale Cargo-Konfiguration mit Nightly-only-Flags, eine fehlende Toolchain oder ein fehlendes Tool), wird unter „Verifikation" als „konnte nicht ausgeführt werden" mit dem Grund vermerkt. Wenn das Repository kein CI hat, nehmen Sie die Toolchain-Untergrenze aus rust-version, der Edition und den verwendeten Sprachfeatures und sagen Sie, was Sie ausführen konnten und was nicht.
  • Große Änderungen: Bauen Sie einen differentiellen Testrahmen, wenn die vorhandenen Tests nicht die genauen Ausgaben der Pfade, die Sie ändern, festschreiben. Festschreiben bedeutet Trace- oder Snapshot-Tests, die die Ausgabensequenz und die Fristen wörtlich bestätigen; Assertionen zum endgültigen Zustand allein zählen nicht. Der Rahmen führt die Basis und den Arbeitsbaum nebeneinander auf denselben zufälligen Aufrufen aus und vergleicht jede Ausgabe. <skill>/scripts/difftest_scaffold.py erstellt das Crate-Paar und eine Vorlage außerhalb des Repos. Das Ausfüllen der Vorlage ist die eigentliche Arbeit, planen Sie Zeit dafür ein: Lesen Sie zuerst references/differential.md. Überprüfen Sie, dass er eine gepflanzte Einzeilen-Mutante erkennt, bevor Sie sich darauf verlassen; eine reicht.
  • Halten Sie den Rahmen günstiger als die Umgestaltung. Treiben Sie die Pfade, die Sie umstrukturieren, nicht die gesamte Crate. Wenn das Abdecken eines Pfads mehr kosten würde als die Änderung daran, melden Sie die Lücke stattdessen unter Verifikation und verlassen Sie sich auf die vorhandenen Tests dieses Pfads. Beispiele: ein Pfad hinter TLS oder Verschlüsselung, Nichtdeterminismus, den Sie normalisieren müssten, ein Peer, den Sie simulieren müssten.
  • Nur für große Änderungen: Führen Sie die langsamen Checks einmal mit mehr Fällen aus. Führen Sie die Property-Tests aus, bemessen, um in einigen Minuten fertig zu sein (zehnmal der Standard, oder PROPTEST_CASES=10000, wenn Fälle billig sind), und noch einmal am Ende. Führen Sie ein Fuzz-Ziel nur aus, wenn es Code erreicht, den Sie ändern werden, zeitlich begrenzt auf 30–60 s. Führen Sie Benchmarks nur aus, wenn eine Änderung Arbeit auf einem Pro-Eingabe-Pfad hinzufügt; das Umstrukturieren des Kontrollflusses benötigt sie nicht.
  • Füllen Sie Abdeckungslücken, die die Änderung überdauern sollten. Wenn ein Pfad, den Sie umstrukturieren werden, keine Abdeckung hat und Sie keinen Rahmen bauen, fügen Sie zuerst Tests hinzu, die seine aktuellen Ausgaben festschreiben. Wenn Sie einen bauen, deckt er die Lücke für die Umgestaltung ab; fügen Sie nur einen Test für ein Verhalten hinzu, das er aufdeckt und das es wert ist, behalten zu werden (eine Kuriosität, die Sie absichtlich beibehalten haben). Setzen Sie neue Tests in eine neue Datei oder ein neues Modul, als eigenen Schritt, damit die vorhandenen Tests unverändert bleiben. Die Ausnahme ist die eine mod-Zeile, die ein Ein-Crate-Test-Layout wie tests/main.rs benötigt.

2. Die Maschine abbilden

Bilden Sie die Entitäten ab, um die es in der Anfrage geht, und jede Entität, die eine geplante Änderung berühren würde. In einer großen Crate, in der die Anfrage nicht sagt, welcher Teil wichtig ist, wählen Sie die Entität, die der Benutzer genannt hat, und sagen Sie, was Sie ausgelassen haben. Schreiben Sie die Abbildung auf, bevor Sie etwas ändern (eine temporäre Datei ist in Ordnung). Sie wird zur Zustandsabbildung der Zusammenfassung und oft zu den Moduldokumentationen.

  • Entitäten. Listen Sie für jede Entität im Geltungsbereich ihre Felder auf und gruppieren Sie sie nach der Phase, in der sie eine Bedeutung haben. Kombinationen von bools und Options, die eine Phase zwischen ihnen codieren, sind das Hauptziel.
  • Eingaben. Jede pub fn, die externe Daten oder now nimmt, und was jede auslösen kann.
  • Schreiber. Für jedes Zustandsfeld jede Stelle, die es zuweist (suchen Sie nach .field =, mem::replace, .take(), += 1). Ein Feld mit vielen Schreibern ist ein Übergang, der über die Datei verteilt ist.
  • Ausgaben. Jedes Pushen auf eine Ausgabewarteschlange und jede Frist, die poll_timeout liest.
  • Invarianten. Was immer gelten muss: eine ID in genau einer Map, eine Frist, die gesetzt ist, wenn etwas wartet, eine referenzierte Verbindung, die existiert.
  • Kuriositäten. Verhalten, das versehentlich aussieht: nachsichtiges Parsen, seltsame Priorität zwischen zwei Fehlern, eine Antwort, die auf Müll gesendet wird, Eingabe, die auch nach einer Frist noch akzeptiert wird. Sie behalten es (Regel 1), listen es aber für den Benutzer auf.

Schreiben Sie dann die Abbildung als Tabelle: Zustand, Eingabe, nächster Zustand, Ausgaben. Wo Sie sie nicht aus dem Code ausfüllen können, ohne mehrere Funktionen zu durchlaufen, ist der Code noch nicht lesbar, und dort wird die Arbeit investiert.

3. Die Änderungen auswählen

references/patterns.md ist der Katalog mit Vorher/Nachher-Beispielen und den Fällen, in denen sich jedes Muster nicht lohnt. Lesen Sie sein Inhaltsverzeichnis, dann die Abschnitte für das, was Ihre Abbildung ergeben hat, einschließlich ihrer „Nicht lohnenswert"-Absätze. Lesen Sie die gesamte Datei für eine große Änderung. Bewerten Sie Kandidatenänderungen danach, wie sehr jede die Antwort auf eine der vier Fragen verkürzt, im Verhältnis zur Größe des Diffs. Die Änderungen, die sich normalerweise am meisten auszahlen:

  • ein Enum, dessen Varianten die Daten jeder Phase tragen, anstelle von Flags und Optionen, die die Phase codieren (Muster 1)
  • eine benannte Funktion pro Übergang, sodass jedes Zustandsfeld wenige Schreiber hat (Muster 2)
  • Handler, die die Eingabe matchen, entscheiden, den Übergang anwenden und die Ausgaben in die Warteschlange stellen, entlang einer konsistenten Dispatch-Achse (Muster 3)
  • eine Hilfsfunktion pro Art von Ausgabe, benannt nach ihrer Absicht (Muster 4)
  • jede Fristregel einmal geschrieben, mit poll_timeout abgeleitet vom Zustand (Muster 5)
  • der Lebenszyklus dort niedergeschrieben, wo ein Leser landet, und Invarianten auf den Feldern (Muster 9)

Wenn der Benutzer eine Funktion oder einen Teil genannt hat, ändern Sie nur diesen und das, was er benötigt. Eine Hilfsfunktion, die Sie extrahieren, kann von ihren natürlichen Geschwistern wiederverwendet werden, aber überarbeiten Sie Code außerhalb der Anfrage nicht nur, weil die Hilfsfunktion auch dort passt. Listen Sie diese Verbesserungen stattdessen unter Vorschläge auf: Ein Reviewer, der nach einer Funktion gefragt hat, sollte keine zweite überprüfen müssen.

Lassen Sie Änderungen aus, die nur Code verschieben, Umbenennungen lokaler Variablen, um einem Geschmack zu entsprechen, und alles, was Code länger macht, ohne eine Frage leichter zu beantworten. Wenn der Benutzer nach der Verschiebung selbst fragt (Aufteilen einer langen Datei in Module), sehen Sie Muster 11. Code, der bereits gut lesbar ist, kann so bleiben, wie er ist: Sagen Sie dies in der Zusammenfassung, anstatt Arbeit zu erfinden.

4. In Schritten anwenden

Ordnen Sie die Schritte so, dass jeder überprüfbar ist und keiner Code hinzufügt, den ein späterer Schritt löscht:

  1. Reine Hinzufügungen und Extraktionen: Ausgabehilfsfunktionen, benannte Prädikate, Fristenhelfer, feature-gated Schritte, die in Hilfsfunktionen verschoben wurden, Drahtkodierung und Parsing, das in Funktionen verschoben wurde (Muster 4, 5, 6, 10).
  2. Die Kernänderung: ein Phasen-Enum, zusammen mit den Übergängen und Handlerformen, die es erzwingt (Muster 1–3, 7, 12). Dies muss oft als ein Schritt landen. Wenn ja, sagen Sie warum.
  3. Namen (Muster 8).
  4. Doks (Muster 9).
  5. Neuanordnung von Methoden, zuletzt und für sich allein, wenn überhaupt (Muster 11).

Zwischen den Bearbeitungen verwenden Sie eine schnelle Schleife: Clippy und die Tests des Pakets, das Sie ändern, im Standard-Feature-Set, plus den Rahmen, wenn Sie einen gebaut haben. Am Ende jedes Schritts führen Sie den Check-Befehl aus Schritt 1 aus. Machen Sie dann den Schritt zu einem Commit, wenn der Benutzer Commits möchte (sie haben gefragt, oder der Workflow des Projekts erwartet sie); andernfalls lassen Sie die Arbeit unbestätigt und listen Sie die Schritte in der Zusammenfassung auf. Unterdrücken Sie keinen Lint mit einem Gerüst, das der nächste Schritt entfernt (ein cfg-Gate oder ein #[allow], das nur hinzugefügt wurde, um einen Schritt grün zu bekommen). Ordnen Sie stattdessen die Schritte neu.

Brechen Sie ein Muster an jeder Stelle ab, an der der Borrow-Checker es in Verrenkungen zwingt, da verrenkter Code schlechter lesbar ist als das, womit Sie begonnen haben. Die üblichen Anzeichen:

  • eine Methode, die ein Geschwisterfeld von self (eine Ausgabewarteschlange wie &mut VecDeque<Transmit>) oder ihren eigenen Map-Schlüssel als Argument nimmt, nur um einen Borrow zu umgehen
  • eine &self-Methode, die eine Unterstruktur nur nimmt, damit sie die Konfiguration lesen kann
  • geklonte Schlüssel oder eine zusätzliche Lookup

Die üblichen Korrekturen:

  • geben Sie die Ausgabe zurück und lassen Sie den Aufrufer sie in die Warteschlange stellen
  • setzen Sie die Hilfsfunktion auf die Entität und übergeben Sie den Copy-Konfigurationswert, den sie benötigt
  • halten Sie den Code inline

5. Verifizieren und zusammenfassen

Führen Sie den Check-Befehl ein letztes Mal aus, über den gesamten Arbeitsbereich, plus alles andere, was Sie am Anfang ausgeführt haben: die langsamen Checks und den Rahmen.

Überprüfen Sie dann die öffentliche API, wenn der Diff ein pub-Element, ein Derive, eine Trait-Implementierung, einen Feldtyp (automatische Traits wie Send folgen privaten Feldern) oder ein cfg auf einem Element berührt:

  • Verwenden Sie cargo semver-checks --baseline-rev <base> einmal pro Feature-Set, mit expliziten Flags: --default-features, --all-features, --only-explicit-features (keine), und --only-explicit-features --features x.
  • Es erkennt nur Breaking Changes, bestätigen Sie daher auch, dass nichts neu öffentlich wurde: Verwenden Sie cargo public-api diff, wenn installiert, oder lesen Sie git diff <base> | grep -E '^\+.*\bpub\b'.
  • Wenn kein Tool installiert ist, fragen Sie vor der Installation, und melden Sie andernfalls die API-Prüfung als nicht ausgeführt.
  • Wenn der Diff nur Funktionskörper und private Elemente berührt, sagen Sie das.

Verwandte Skills