Zurück zu den Updates
New releaseJul 24, 2026

trailmark v0.5.0

Erstellen und Abfragen einer Graphdatenbank-Repräsentation von Quellcode

Teilen

Trailmark

CI Mutation Testing

Analysiere Quellcode in abfragbare Graphen von Funktionen, Klassen, Aufrufen und semantischen Annotationen für die Sicherheitsanalyse.

Trailmark verwendet tree-sitter für sprachunabhängiges AST-Parsing und rustworkx für leistungsstarke Graphendurchläufe. Die langfristige Vision ist es, diesen Graphen mit Mutationstests und coverage-gesteuertem Fuzzing zu kombinieren, um Lücken zwischen Annahmen und Testabdeckung zu identifizieren, die über Benutzereingaben erreichbar sind.

Wie es funktioniert

Trailmark arbeitet in drei Phasen: Parsen, Indexieren und Abfragen.```mermaid flowchart TD A["Source Files"] --> B["tree-sitter Parser"] B --> C["CodeGraph (nodes + edges)"] C --> D["rustworkx GraphStore"] D --> E["QueryEngine"] E --> F["JSON / Summary / Hotspots"]

classDef src fill:#007bff26,stroke:#007bff,color:#007bff
classDef parse fill:#28a74526,stroke:#28a745,color:#28a745
classDef data fill:#6f42c126,stroke:#6f42c1,color:#6f42c1
classDef query fill:#ffc10726,stroke:#e6a817,color:#e6a817

class A src
class B parse
class C,D data
class E,F query
### 1. Parsen

Ein sprachspezifischer Parser durchläuft das Verzeichnis, parst jede Datei in einen tree-sitter AST und extrahiert:

- **Knoten** — Funktionen, Methoden, Klassen, Strukturen, Schnittstellen, Traits, Enumerationen, Module, Namensräume
- **Kanten** — Aufrufe, Vererbung, Implementierung, Enthalten, Importe
- **Metadaten** — Typannotationen, zyklomatische Komplexität, Verzweigungen, Dokumentationsstrings, Ausnahmetypen

### Unterstützte Sprachen

| Sprache | Erweiterungen | Wichtige Konstrukte |
| --- | --- | --- |
| Python | `.py` | Funktionen, Klassen, Methoden |
| JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | Funktionen, Klassen, Arrow-Funktionen |
| TypeScript | `.ts`, `.tsx` | Funktionen, Klassen, Schnittstellen, Enums |
| PHP | `.php` | Funktionen, Klassen, Schnittstellen, Traits |
| Ruby | `.rb` | Methoden, Klassen, Module |
| C | `.c`, `.h` | Funktionen, Strukturen, Enums |
| C++ | `.cpp`, `.hpp`, `.cc`, `.hh`, `.cxx`, `.hxx` | Funktionen, Klassen, Strukturen, Namensräume |
| C# | `.cs` | Methoden, Klassen, Schnittstellen, Strukturen, Enums, Namensräume |
| Java | `.java` | Methoden, Klassen, Schnittstellen, Enums |
| Go | `.go` | Funktionen, Methoden, Strukturen, Schnittstellen |
| Rust | `.rs` | Funktionen, Strukturen, Traits, Enums, Impl-Blöcke |
| Solidity | `.sol` | Verträge, Schnittstellen, Bibliotheken, Funktionen, Modifikatoren, Strukturen, Enums |
| Cairo | `.cairo` | Funktionen, Traits, Strukturen, Enums, Impl-Blöcke, StarkNet-Verträge |
| Circom | `.circom` | Vorlagen, Funktionen, Signale, Komponenten |
| Haskell | `.hs` | Funktionen, Datentypen, Typklassen, Instanzen |
| Erlang | `.erl` | Funktionen, Datensätze, Verhalten, Module |
| Miden Assembly | `.masm` | Prozeduren, Einstiegspunkte, Konstanten, Aufrufe |
| Swift | `.swift` | Funktionen, Klassen, Strukturen, Enums, Protokolle, Erweiterungen |
| Objective-C | `.m`, `.mm`, `.h` | C-Funktionen, Klassen, Methoden (selektorbasierte Namensgebung) |
| Kotlin | `.kt`, `.kts` | Funktionen, Klassen, Schnittstellen, Datenklassen, Objekte, Methoden |
| Dart | `.dart` | Funktionen, Klassen, abstrakte Klassen, Methoden, Konstruktoren |
| Move | `.move` | Module, Funktionen, Importe, direkte Aufrufe |
| Tact | `.tact` | Verträge, Strukturen, Empfänger, Funktionen |
| Func | `.fc`, `.func` | Funktionen, Includes, direkte Aufrufe |
| Sway | `.sw` | ABI-Schnittstellen, Strukturen, Impl-Methoden, Funktionen |
| Rego | `.rego` | Pakete, Importe, Policy-Regeln, Regelaufrufe |
| Proto | `.proto` | Dienste, RPCs, Nachrichten, Felder, Enums |
| Thrift | `.thrift` | Dienste, Funktionen, Strukturen, Felder, Enums |
| GraphQL | `.graphql`, `.gql` | Objekttypen, Root-Operationen, Felder, Enums |
| SQL | `.sql` | Schemata, Tabellen, Ansichten, Funktionen, Prozeduren |```mermaid
flowchart TD
    subgraph "Per-File Parsing"
        F["Source file"] --> TS["tree-sitter AST"]
        TS --> EX["Extract nodes"]
        TS --> EC["Extract call edges"]
        TS --> EB["Count branches"]
        TS --> ET["Resolve types"]
    end

    EX --> CG["CodeGraph"]
    EC --> CG
    EB --> CG
    ET --> CG

    classDef src fill:#007bff26,stroke:#007bff,color:#007bff
    classDef parse fill:#28a74526,stroke:#28a745,color:#28a745
    classDef extract fill:#ffc10726,stroke:#e6a817,color:#e6a817
    classDef data fill:#6f42c126,stroke:#6f42c1,color:#6f42c1

    class F src
    class TS parse
    class EX,EC,EB,ET extract
    class CG data

Node-IDs folgen dem Schema module:function, module:Class oder module:Class.method für eindeutige Nachschlagevorgänge. Die Verzeichnisanalyse löst bloße dateiübergreifende Aufrufe auf, wenn eine eindeutige Definition existiert; mehrdeutige dateiübergreifende Aufrufe werden auf dem ursprünglichen bestmöglichen Ziel belassen und mit uncertain markiert. Die Kantenkonfidenz wird als certain (direkte Aufrufe, self.method()), inferred (Attributzugriff auf Nicht-self-Objekte) oder uncertain (dynamischer Dispatch oder mehrdeutige Auflösung) gekennzeichnet.

2. Index

Der GraphStore lädt den CodeGraph in einen rustworkx PyDiGraph und erstellt bidirektionale ID/Index-Zuordnungen für schnelle Traversierung.

3. Abfrage

Die QueryEngine bietet eine High-Level-API über den indizierten Graphen:

MethodeBeschreibung
callers_of(name)Direkte Aufrufer des benannten Ziels
callees_of(name)Direkte Angerufene der benannten Quelle
ancestors_of(name)Jede Funktion, die das Ziel transitiv erreichen kann (aufwärtiger Slice)
reachable_from(name)Jede Funktion, die transitiv von der Quelle aus erreichbar ist
paths_between(src, dst)Alle einfachen Aufrufpfade zwischen zwei Knoten
connect_subgraphs(source, target)Pfade, die zwei benannte Teilgraphen verbinden
entrypoint_paths_to(name)Pfade von jedem erkannten Einstiegspunkt zum Ziel
attack_surface()Einstiegspunkte, die mit Vertrauensstufe, Asset-Wert und Parser-Attributen (falls vorhanden) gekennzeichnet sind
complexity_hotspots(n)Funktionen mit zyklomatischer Komplexität ≥ n
functions_that_raise(exc)Funktionen, deren parser-erkannte Ausnahmeliste exc enthält
generic_parameters(name)Generische Typparameter, die von einem Knoten deklariert werden
type_references(name)Parameter-, Rückgabe-, Ausnahme- und generische Grenz-Typreferenzen
annotate(name, kind, description, source)Füge eine semantische Annotation zu einem Knoten hinzu
annotations_of(name, kind=None)Erhalte Annotationen für einen Knoten, optional nach Art gefiltert
nodes_with_annotation(kind)Jeder Knoten, der mit der angegebenen Annotationsart gekennzeichnet ist
clear_annotations(name, kind=None)Entferne Annotationen von einem Knoten
diff_against(other)Struktureller Diff dieses Graphen des Engines gegen einen anderen
preanalysis()Führe die integrierten Voranalyse-Durchläufe aus und speichere Annotationen/Teilgraphen
augment_sarif(path)Füge SARIF-Ergebnisse in den Graphen ein
augment_weaudit(path)Füge weAudit-Ergebnisse in den Graphen ein
augment_binary(path)Füge eine externe Binäranalyse-Graphen-JSON-Datei ein
findings(kind=None)Gib Knoten zurück, die Annotationen im Ergebnisstil tragen
subgraph(name)Gib die Knoten in einem benannten Teilgraphen zurück
subgraph_edges(name)Gib die induzierten Kanten innerhalb eines benannten Teilgraphen zurück
subgraph_names()Liste jeden benannten Teilgraphen auf, der sich derzeit auf dem Graphen befindet
summary()Knotenzahlen, Kantenzahlen, Abhängigkeiten
to_json()Vollständiger Graph-Export

Datenmodell```mermaid

classDiagram class CodeGraph { language: str root_path: str nodes: dict[str, CodeUnit] edges: list[CodeEdge] annotations: dict[str, list[Annotation]] entrypoints: dict[str, EntrypointTag] dependencies: list[str] add_annotation(node_id, annotation) clear_annotations(node_id, kind=None) merge(other) }

class CodeUnit {
    id: str
    name: str
    kind: NodeKind
    location: SourceLocation
    parameters: tuple[Parameter]
    return_type: TypeRef
    exception_types: tuple[TypeRef]
    cyclomatic_complexity: int
    branches: tuple[BranchInfo]
    docstring: str
}

class CodeEdge {
    source_id: str
    target_id: str
    kind: EdgeKind
    confidence: EdgeConfidence
}

class Annotation {
    kind: AnnotationKind
    description: str
    source: str
}

class EntrypointTag {
    kind: EntrypointKind
    trust_level: TrustLevel
    description: str
    asset_value: AssetValue
}

CodeGraph "1" *-- "*" CodeUnit
CodeGraph "1" *-- "*" CodeEdge
CodeGraph "1" *-- "*" Annotation
CodeGraph "1" *-- "*" EntrypointTag
**Node kinds:** `function`, `method`, `class`, `module`, `struct`, `interface`, `trait`, `enum`, `namespace`, `contract`, `library`, `template`, `proxy`

**Node origins:** `source`, `proxy`, `binary`, `synthetic`

**Edge kinds:** `calls`, `inherits`, `implements`, `contains`, `imports`, `resolves_to`, `type_uses`, `specializes`, `corresponds_to`

**Edge confidence:** `certain`, `inferred`, `uncertain`

Nicht aufgelöste Aufrufe werden als Proxy-Knoten materialisiert, wie z. B.
`proxy.unresolved:<raw-symbol>`, sodass Traversierungsergebnisse zeigen können, wo die
Quellanalyse die Auflösung verloren hat, anstatt diesen Zweig stillschweigend zu löschen. Die
Binäranalyse unterstützt den Import von externen JSON-Aufrufgraphen; Trailmark zerlegt
ausführbare Dateien nicht selbst.

### Beispielgraph

Gegeben dieser Python-Code:```python
class Auth:
    def verify(self, token: str) -> bool:
        return self._check_sig(token)

    def _check_sig(self, token: str) -> bool:
        ...

def handle_request(req: Request) -> Response:
    auth = Auth()
    if auth.verify(req.token):
        return process(req)
    return deny()

Trailmark erzeugt einen Graphen wie:```mermaid graph TD HR["handle_request"] -->|calls| AV["Auth.verify"] HR -->|calls| P["process"] HR -->|calls| D["deny"] AV -->|calls| CS["Auth._check_sig"] A["Auth"] -->|contains| AV A -->|contains| CS

classDef fn fill:#007bff26,stroke:#007bff,color:#007bff
classDef cls fill:#6f42c126,stroke:#6f42c1,color:#6f42c1

class HR,P,D fn
class A,AV,CS cls
## Installation

Die folgenden Beispiele beziehen sich auf den aktuellen Entwicklungszweig. Für das aktuellste veröffentlichte Paket installieren Sie von PyPI. Für den hier beschriebenen genauen Funktionsumfang installieren Sie aus einem Checkout und führen Sie Befehle mit `uv run` aus.```bash
# Latest published release
uv pip install trailmark

# Current checkout / development branch
uv sync --all-groups

Erfordert Python ≥ 3.12.

Trailmark verwendet tree-sitter-language-pack für die meisten Grammatiken. Aktuelle Versionen verwenden den Plattform-Zertifikatsspeicher für Grammatik-Downloads. In TLS-geprüften oder Offline-Umgebungen befüllen Sie den Paket-Cache vorab mit python -c "import tree_sitter_language_pack as p; p.download_all()" auf einer passenden Plattform und kopieren dann das resultierende tree-sitter-language-pack-Cache-Verzeichnis auf das Zielsystem. HTTPS_PROXY wird ebenfalls unterstützt. Die SQL-Grammatik wird als tree-sitter-sql-Wheel-Abhängigkeit ausgeliefert und verwendet diesen Cache nicht.

Verwendung```bash

Report the installed version

trailmark --version # or: trailmark -V trailmark version # subcommand form

Full JSON graph (Python, the default)

trailmark analyze path/to/project

Analyze a different language

trailmark analyze --language rust path/to/project trailmark analyze --language javascript path/to/project

Polyglot: auto-detect and merge every supported language found in the

tree, or pass an explicit comma-separated list.

trailmark analyze --language auto path/to/project trailmark analyze --language python,rust,solidity path/to/project

Summary statistics

trailmark analyze --summary path/to/project

Complexity hotspots (threshold >= 10)

trailmark analyze --complexity 10 path/to/project

Augment the graph with external findings (SARIF from static analyzers,

weAudit findings from the VS Code extension). Each --sarif / --weaudit

flag is repeatable. Add --json to print the augmented graph.

trailmark augment --sarif results.sarif path/to/project trailmark augment --weaudit findings.json path/to/project trailmark augment --sarif a.sarif --sarif b.sarif --json path/to/project

List detected entrypoints (attack surface). Uses heuristic detection

(main() functions, pyproject.toml [project.scripts]) plus an optional

override file at .trailmark/entrypoints.toml (see below).

trailmark entrypoints path/to/project trailmark entrypoints --json path/to/project

Structural diff between two code graphs. Accepts directory paths or

git refs (branches, tags, commits). Surfaces added/removed nodes,

call-edge changes, and — most usefully — attack-surface changes.

trailmark diff before/ after/ trailmark diff --repo . main HEAD # compare git refs trailmark diff --json before/ after/ # machine-readable output

Generate a Mermaid diagram from the code graph. --type is required; the

choices are call-graph, class-hierarchy, module-deps, containment,

complexity, and data-flow. Use --focus to scope large graphs.

trailmark diagram --target path/to/project --type call-graph trailmark diagram -t path/to/project -T call-graph -f parse_file --depth 3 trailmark diagram -t path/to/project -T complexity --threshold 5 --direction LR

### Entrypoint-Erkennung

Trailmark befüllt automatisch `graph.entrypoints`, damit `attack_surface()`, Taint-Propagation und Privilegien-Grenzüberschreitungen Daten zur Verfügung haben. Die Erkennung erfolgt in vier Schichten, wobei jede die vorherige überschreibt:

1. **Generische `main`-Heuristik.** Jede Funktion namens `main` in jeder Sprache. Gekennzeichnet als `user_input` / `trusted_internal` / `low`.
2. **Framework-bewusster Scan.** Dekorator-, Attribut- und Sichtbarkeitsmuster pro Sprache – siehe Tabelle unten.
3. **`pyproject.toml [project.scripts]`.** Explizite CLI-Ziele erhalten eine verbesserte Vertrauens-/Asset-Klassifizierung.
4. **Repo-lokale Überschreibungsdatei.** Handgepflegte Einstiegspunkte in `.trailmark/entrypoints.toml` gewinnen immer.

Framework-Abdeckung:

| Sprache | Erkannte Frameworks |
| --- | --- |
| Python | Flask, FastAPI, aiohttp, Click, Typer, Celery |
| JavaScript / TypeScript | NestJS, Next.js (App Router + Pages API), AWS Lambda |
| Java | Spring MVC / WebFlux, JAX-RS, Kafka-Listener, Servlets |
| C# | ASP.NET Core, Azure Functions |
| PHP | Symfony `#[Route]`-Attribute + Legacy-Annotationen |
| Rust | actix-web, rocket, FFI-Exporte (`#[no_mangle]`, `pub extern "C"`), async-main-Attribute |
| Solidity | `external`- / `public`-Sichtbarkeit |
| Cairo / StarkNet | `#[external]`, `#[view]`, `#[l1_handler]`, `#[constructor]` |
| Circom | `component main`-Deklarationen |
| Miden Assembly | `export.<name>`-Direktiven |
| Haskell | Top-Level `main ::` / `main =` |
| Erlang | Funktionen, die in `-export([...])` aufgeführt sind |
| Swift | `@main`-App-Attribut |
| Objective-C | `UIApplicationDelegate`-Lebenszyklus-Selektoren (z.B. `application:openURL:options:`) |
| Kotlin | Spring MVC / WebFlux-Annotationen (gemeinsam mit Java), Android-Komponenten-Lebenszyklusmethoden (`onCreate`, `onReceive`, `onBind`, ...) |
| Dart | `@pragma('vm:entry-point')`-native-callable Marker |
| Go | `http.HandleFunc` / `http.Handle`-stdlib-Registrierungen, gin/chi/echo-artige `<router>.GET/POST/...`-Handler-Registrierungen |
| Ruby | Rails-Controller-Aktionen (Klassen, die von `ApplicationController` / `ActionController::*` erben), Sidekiq-Worker-`perform`-Methoden |
| C / C++ | `extern "C"`-Linkage, `__attribute__((visibility("default")))`, `__declspec(dllexport)` |

Für alles, was die Heuristiken übersehen, Einstiegspunkte explizit in `.trailmark/entrypoints.toml` im Projektstammverzeichnis deklarieren. Die Datei unterstützt sowohl einzelne Knoten als auch regelbasierte Einträge:```toml
# Single-node entry
[[entrypoint]]
node = "my_module:handle_request"  # node id, or "module.path:function"
kind = "api"                       # user_input | api | database | file_system | third_party
trust = "untrusted_external"       # untrusted_external | semi_trusted_external | trusted_internal
asset_value = "high"               # high | medium | low
description = "HTTP POST /auth"

# Rule: every PHP script under public_html/ is a web-exposed entrypoint.
[[entrypoint]]
file_glob = "public_html/**/*.php"
kind = "user_input"
trust = "untrusted_external"
asset_value = "high"
description = "Web-exposed PHP script"

# Rule: any function that takes a PSR-7 request object.
[[entrypoint]]
param_type = "ServerRequestInterface"
kind = "api"
trust = "untrusted_external"
asset_value = "high"
description = "PSR-7 HTTP handler"

# Rule: functions named `handle_*`.
[[entrypoint]]
name_regex = "^handle_"
kind = "api"
trust = "untrusted_external"

# Rule: conditions compose with AND — web.py files AND name starts with handle_.
[[entrypoint]]
file_glob = "public/*.py"
name_regex = "^handle_"
kind = "api"
trust = "untrusted_external"

Spätere Einträge überschreiben frühere, wenn zwei Regeln denselben Knoten markieren, also setze zuerst breite Regeln und danach spezifische Korrekturen.

Siehe docs/entrypoint-patterns.md für die vollständige Referenz, einschließlich noch nicht implementierter Frameworks (Express / Koa / Fastify, Laravel, Cobra, axum, warp, clap und andere) mit grep-bereiten Mustern, die Mitwirkende verwenden können, um neue Detektoren hinzuzufügen.

Die Solidity-Erkennung verwendet Parser-Metadaten anstelle von Signatur-Regexes. Schnittstellendeklarationen werden ausgeschlossen und eine abgeleitete Überschreibung unterdrückt die passende Basisimplementierung. Konkrete public- und external-Funktionen bleiben Einstiegspunkte, einschließlich view- und pure-Funktionen; ihre solidity_visibility- und solidity_mutability-Attribute werden von attack_surface() zurückgegeben, sodass Aufrufer die schreibgeschützte Exposition unterscheiden können. attack_surface() enthält parser-spezifische Einstiegspunktattribute, wenn sie am zugrunde liegenden Graphknoten angehängt sind.

Polyglot-Parsing vereint Sprachgraphen, aber viele RPC-, FFI-, Subprozess- und Host-/Vertragsbeziehungen sind in der Quellsyntax nicht sichtbar. Deklarieren Sie diese deterministisch in .trailmark/links.toml:```toml [[link]] source = "backend:submit" target = "contract:Verifier.verify" kind = "calls" # defaults to calls confidence = "certain" # defaults to inferred description = "JSON-RPC eth_call"

[[link]] source = "backend:notify" target = "payments-webhook" external = true # required when either endpoint is unresolved

Referenzen können exakte Knoten-IDs oder eindeutige Namen/Suffixe sein. Mehrdeutige Referenzen, unbekannte interne Endpunkte, ungültige Aufzählungswerte und fehlerhaftes TOML lösen `ValueError` aus. Durch Setzen von `external = true` werden unaufgelöste Endpunkte explizit erlaubt und Proxy-Knoten erstellt. Diese Datei ist eine stabile öffentliche Konfigurationsschnittstelle.

### Analyse-Einschränkungen

- `entrypoint_paths_to()` berichtet die Erreichbarkeit des Aufrufgraphen, nicht den angreiferkontrollierten Datenfluss. Verwenden Sie die Taint-Ergebnisse der Voranalyse als grobes separates Signal; Trailmark führt noch keine interprozedurale Taint-Analyse durch.
- TypeScript löst direkte Aufrufe und einfache Empfänger auf, die mit `new ConcreteClass()` zugewiesen werden. Die Dispatch-Schnittstelle über Manifeste, berechnete Eigenschaftsnamen, Dependency-Injection-Container und andere dynamische Mechanismen erfolgt nach bestem Wissen und Gewissen (best-effort).
- Die SQL-Unterstützung ist PostgreSQL-orientiert und extrahiert Schemata, Tabellen, Views, Funktionen, Prozeduren sowie Routine-/View-Abhängigkeiten. Es ist kein vollständiger SQL-Dialekt-Validator oder Abfrage-Semantikanalysator.

### Programmgesteuerte API```python
from trailmark.parse import parse_directory, parse_file
from trailmark.query.api import QueryEngine

# Parse-only API: get the raw CodeGraph without building GraphStore/QueryEngine.
graph = parse_file("path/to/file.py")
graph = parse_directory("path/to/project", language="auto")

# Single-language (default) or auto-detect + merge across all languages
engine = QueryEngine.from_directory("path/to/project")
engine = QueryEngine.from_directory("path/to/project", language="auto")
engine = QueryEngine.from_directory("path/to/project", language="python,rust")

# Direct neighbors
engine.callers_of("handle_request")
engine.callees_of("handle_request")

# Transitive slicing — who could reach this sink, or what could it reach?
engine.ancestors_of("Auth._check_sig")
engine.reachable_from("handle_request")

# Attack-surface paths from any detected entrypoint
engine.entrypoint_paths_to("Auth._check_sig")

# All call paths between two nodes
engine.paths_between("handle_request", "Auth._check_sig")

# Functions with cyclomatic complexity >= 10
engine.complexity_hotspots(10)

# What functions can raise a given exception? (uses parser-detected
# exception_types; no runtime tracing required)
engine.functions_that_raise("PermissionError")

# Add and query semantic annotations
from trailmark.models.annotations import AnnotationKind

engine.annotate(
    "handle_request",
    AnnotationKind.ASSUMPTION,
    "Caller has already authenticated the session token",
    source="llm",
)
engine.annotations_of("handle_request")
engine.nodes_with_annotation(AnnotationKind.FINDING)

# Diff against an earlier snapshot of the same codebase
before = QueryEngine.from_directory("before/")
diff = engine.diff_against(before)
# diff contains: summary_delta, nodes {added/removed/modified},
# edges {added/removed}, entrypoints {added/removed/modified}

# Run the built-in audit-oriented preanalysis passes
engine.preanalysis()
engine.findings()
engine.subgraph_names()

# Programmatic augmentation hooks for external tooling
engine.augment_sarif("results.sarif")
engine.augment_weaudit("findings.json")

NodeKind.SCHEMA, TABLE, VIEW, and PROCEDURE sind additiv in v0.5.0; Verbraucher, die Enum-Werte erschöpfend abgleichen, sollten Fälle für sie hinzufügen.

Entwicklung```bash

Install package and dev dependencies

uv sync --all-groups

Lint and format

uv run ruff check --fix uv run ruff format

Type check

uv tool install ty && ty check

Tests

uv run pytest -q tests/

Mutation testing (on macOS, set this env var to avoid rustworkx fork segfaults)

OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES uv run mutmut run

## Lizenz

Apache-2.0

Kategorien