Zurück zum Blog
TypeScript
10 Min. Lesezeit

Wie mein Type-Checker die KI vom Lügen abgehalten hat

KI nutzt `any`, um Typfehlern auszuweichen. Sie fügt eslint-disable hinzu. Sie lügt dich an. So haben wir die Feedbackschleife repariert.

KI lügt dich nicht an, weil sie es will. Sie lügt dich an, weil du es zulässt.

Die kaputte Feedbackschleife

Bitte Claude Code, ein Feature hinzuzufügen. Macht es. ESLint laufen lassen - besteht. TypeScript laufen lassen - Fehler. Bitte Claude Code, den TypeScript-Fehler zu fixen. Macht es. ESLint nochmal - schlägt jetzt fehl. Hin und her. Drei Iterationen. Die KI ist bei jedem Schritt überzeugt. Jedes Mal: "Ist gefixt."

War es nie.

Es war Whack-a-Mole mit einem Tool, das immer nur einen Linter gleichzeitig ausführt und nie das Gesamtbild sieht.

Die KI fixt den TypeScript-Fehler, indem sie eine Zeile schreibt und sagt: "Der Typfehler ist behoben."

typescript
1const result = data as unknown as MyExpectedType;

Ja. Technisch gesehen. So wie Klebeband über dem Rauchmelder einen Feueralarm behebt.

Dafür hab ich @next-vibe/checker gebaut.

Das `any`-Problem

Warum 98 % Typsicherheit dasselbe ist wie 0 %

TypeScripts Typsystem ist ein Graph. Jeder Typ fließt von der Definition zur Nutzung. Wenn eine Funktion `string` zurückgibt, weiß der Aufrufer, dass es ein String ist. Die gesamte Kette wird geprüft.

any
result
transformed
output
Component
hook
schema

Ein `any`-Typ breitet sich durch den Graph aus und korrumpiert die nachgelagerte Typinferenz

`any` ist ein Loch in diesem Graph.

Eine als `any` typisierte Variable sagt dem Compiler: Hör hier auf zu prüfen. Nicht nur für diese Variable - für alles, was sie berührt. Der Fehler taucht nicht beim `any` auf. Er taucht drei Dateien weiter auf, wenn ein zusammenhangsloses Refactoring eine Annahme bricht, die nie durchgesetzt wurde.

0

TypeScript-Fehler

47

`any`-Stellen

Null TypeScript-Fehler heißt gar nichts, wenn du 47 ungeprüfte `any`-Stellen hast.

`as unknown as Whatever` ist noch schlimmer. Das ist eine doppelte Typbehauptung. Du sagst dem Compiler: Ich weiß, dass das falsch ist, und behaupte mich trotzdem durch. Das ist der Lieblings-Fluchtweg der KI.

Die verbotenen Muster in dieser Codebase:

anyno-explicit-any
as unknown asno-unsafe-assignment
@ts-expect-errorban-ts-comment
throwrestricted-syntax
unknownrestricted-syntax
objectrestricted-syntax

Keine Warnungen. Fehler. Die Prüfung schlägt fehl. Claude Code muss die Grundursache beheben oder kann nicht ausliefern.

Der Grund, warum das Fehler und keine Warnungen sind, ist genauso psychologisch wie technisch. KI-Modelle behandeln Warnungen als optional. Fehler schließen die Schleife.

@next-vibe/checker

Ein Befehl. Drei Tools. Keine Fluchtwege.

bash
1$ vibe check

Das ist der Befehl. Ein einziger. Er führt drei Tools parallel aus und gibt dir eine einheitliche Fehlerliste.

Oxlint

Rust-basierter Linter. Hunderte Regeln. Läuft in Millisekunden, selbst bei großen Codebases.

ESLint

Das, was Oxlint noch nicht kann: React Hooks Lint, React Compiler-Regeln, Import-Sortierung.

TypeScript

Vollständige Typprüfung. Nicht nur die Datei, die du gerade bearbeitest - den gesamten Graph.

parallel

Einheitliche Fehlerliste

Eine Ausgabe. Fixen, bis es sauber ist.

Bei dieser Codebase - 4.400 Dateien - dauert das vollständige TypeScript etwa 12 Sekunden. Oxlint unter einer Sekunde. ESLint ein paar Sekunden. Parallel sind es insgesamt 12.

Es gibt außerdem einen `vibe-check mcp`-Befehl, der einen MCP-Server mit einem `check`-Tool startet. Die KI führt keinen Shell-Befehl aus - sie ruft ein Tool auf, das strukturierte Fehlerdaten zurückgibt. Paginierung eingebaut. Filterung nach Pfad.

Eigene Plugins

Der Linter ist die Dokumentation. Und er wird durchgesetzt.

jsx-capitalization Plugin

Es markiert JSX-Elemente in Kleinbuchstaben und die Fehlermeldung sagt dir genau, was du stattdessen importieren sollst:

<button><Button> from "next-vibe-ui/ui/button"
<a><Link> from "next-vibe-ui/ui/link"
<p><P> from "next-vibe-ui/ui/typography"

Ich hab keine Dokumentation geschrieben, die Claude Code anweist, `next-vibe-ui` zu verwenden. Ich hab es nicht zum System-Prompt hinzugefügt. Wenn Claude Code das erste Mal `<div>` in einer Komponente schreibt, gibt der Checker einen Fehler aus. Die Fehlermeldung enthält den genauen Import-Pfad. Claude Code liest den Fehler, wendet die Korrektur an und merkt sich die Konvention.

restricted-syntax Plugin

Es verbietet drei Dinge:

`throw`-Anweisungen

Die Fehlermeldung sagt: "Nutze stattdessen ordentliche `ResponseType<T>`-Muster." Claude Code stößt darauf, liest es, sucht `ResponseType` und übernimmt das korrekte Fehlerbehandlungsmuster für den Rest der Aufgabe.

Nackter `unknown`-Typ

"Ersetze 'unknown' durch eine vorhandene typisierte Schnittstelle. Richte dich an den Codebase-Typen aus, statt zu konvertieren oder neu zu erstellen." Das stoppt Claude Code daran, generische Typ-Fluchtwege zu schreiben.

Nackter `object`-Typ

`object` ist fast immer falsch. Entweder kennst du die Form - dann schreib das Interface - oder du meinst `Record<string, SomeType>`. Rohes `object` ist ein Zeichen dafür, dass die KI aufgegeben hat.

Verbotene Muster

VERBOTENrestricted-syntax
typescript
1const result = response as unknown as MyType;
KORREKT
typescript
1const result: MyType = parseResponse(response);
VERBOTENno-explicit-any
typescript
1function foo(x: any): any { return x.data; }
KORREKT
typescript
1function foo<T>(response: ResponseType<T>): T | null {
2  if (!response.success) return null;
3  return response.data;
4}
VERBOTENrestricted-syntax
typescript
1throw new Error("Something failed");
KORREKT
typescript
1return fail({
2  message: t("errors.server.title"),
3  errorType: ErrorResponseTypes.INTERNAL_ERROR,
4});

Live-Demo: Das 3-Runden-Muster

Schau zu, wie Claude Code dreimal gegen den Checker anrennt, bevor es den richtigen Typ findet

1

Runde 1 - KI schreibt `any`

Frag Claude Code: "Schreib eine Hilfsfunktion, die ein rohes API-Antwortobjekt nimmt und das Datenfeld extrahiert. Die Antwort kann verschiedene Formen haben - nimm whatever type makes this work."

Claude Code schreibt die einfache Lösung:

typescript
1export function parseApiResponse(response: any): any {
2  return response.data;
3}
bash
1  1:37  error  Unexpected any.  typescript/no-explicit-any
2  1:44  error  Unexpected any.  typescript/no-explicit-any
3
42 errors found.
2

Runde 2 - KI versucht `unknown`

Schau, was es als Nächstes macht. Das ist der entscheidende Teil. Es versucht den nächsten Fluchtweg:

Den Trick kennt der Checker.

typescript
1export function parseApiResponse(response: unknown): unknown {
2  return (response as Record<string, unknown>).data;
3}
bash
1  1:37  error  Replace 'unknown' with typed interface.  restricted-syntax
2  1:44  error  Replace 'unknown' with typed interface.  restricted-syntax
3  1:56  error  Replace 'unknown' with typed interface.  restricted-syntax
4
53 errors found.
3

Runde 3 - KI findet den echten Typ

Jetzt macht Claude Code das, was es gleich hätte tun sollen. Es schaut nach, wie vorhandene API-Antworten in dieser Codebase typisiert sind. Es findet `ResponseType<T>`.

Null Fehler. Und die Funktion ist jetzt tatsächlich korrekt.

typescript
1import type { ResponseType } from "@/response-type";
2
3export function parseApiResponse<T>(
4  response: ResponseType<T>
5): T | null {
6  if (!response.success) return null;
7  return response.data;
8}
bash
1  Oxlint: 0 errors
2  ESLint: 0 errors
3  TypeScript: 0 errors
4
50 errors found.

Der Checker hat den Code nicht geschrieben. Aber er hat die Abkürzung zweimal verhindert, bis Claude Code sich mit dem eigentlichen Problem auseinandersetzen musste.

Die Endpoint-Verbindung

Ein Zod-Schema - vier nachgelagerte Verbraucher

Jeder Endpoint hat eine Definitionsdatei. Diese Datei enthält ein Zod-Schema für die Anfrage und eines für die Antwort.

Der `schema`-Schlüssel ist ein Zod-Validator. Dasselbe Zod-Schema wird:

typescript
1name: requestField(st, {
2  schema: z.string().min(1).max(255),
3  label: "name",
4  description: "description",
5  placeholder: "placeholder",
6}),

Die Validierungsregel am Web-API-Endpoint

Der TypeScript-Typ für den Eingabeparameter des React-Hooks

Das `--name`-Flag in der CLI mit angewandten Min/Max-Constraints

Die Parameterbeschreibung im KI-Tool-Schema

Hier tötet dich normalerweise Drift. Du aktualisierst die API. Du vergisst das KI-Tool-Schema. Die KI ruft den Endpoint mit alten Parameternamen auf. Es schlägt lautlos fehl.

Wenn es ein Schema gibt, gibt es nichts zu synchronisieren.

Und weil der TypeScript-Checker auch darüber läuft: Wenn du das Schema so änderst, dass der abgeleitete Typ downstream bricht, bekommst du einen Compiler-Fehler. Das KI-Tool-Schema ist typgeprüft. Die CLI-Flags sind typgeprüft. Der React-Hook ist typgeprüft.

245

245 Endpoints

0

Null `any`

0

Null `unknown`-Casts

0

Null `@ts-expect-error`

Nicht per Konvention. Durch den Checker.

@next-vibe/checker installieren

Funktioniert mit jedem TypeScript-Projekt. Nicht nur next-vibe.

Der Checker ist als eigenständiges npm-Paket verfügbar. Er funktioniert mit jedem TypeScript-Projekt - nicht nur NextVibe. Du brauchst keinen anderen Teil des Frameworks.

bash
1bun add -D @next-vibe/checker\n# or\nnpm install -D @next-vibe/checker

Dann ausführen:

bash
1vibe-check config    # scaffold check.config.ts\nvibe-check           # run all checks\nvibe-check --fix     # auto-fix linting issues

MCP-Integration

Füg es zu deiner Claude Code- oder Cursor-MCP-Konfiguration hinzu. Dann ruft Claude Code `check` als Tool auf, nicht als Shell-Befehl. Strukturierte Fehler. Paginiert. Nach Pfad filterbar.

json
1{
2  "mcpServers": {
3    "vibe-check": {
4      "command": "vibe-check",
5      "args": ["mcp"],
6      "env": { "PROJECT_ROOT": "/path/to/project" }
7    }
8  }
9}

Auf der npm-Seite gibt es außerdem einen Migrations-Prompt. Kopier ihn in Claude Code oder Cursor und er prüft deine Codebase, konfiguriert den Checker und migriert dich zu den verbotenen Mustern.

Open Source. GPL-3.0 für das Framework, MIT für das Checker-Paket.

Bau das System so, dass Lügen unmöglich ist

Vorher:

KI-unterstützte Entwicklung war eine Verhandlung. Fix den Lint. Oh, jetzt sind die Typen kaputt. Fix die Typen. Jetzt gibt's ein `any`, das du nicht bemerkt hast. Fix das. Drei separate Tools laufen lassen. Drei separate Meinungen bekommen. Nie sicher, ob es wirklich sauber ist.

Nachher:

Ein Befehl. Ein Fehlermodus. Entweder es besteht oder nicht. Die KI weiß genau, was sie fixen muss, weil die Fehler ihr sagen, was falsch ist und was stattdessen zu tun ist. Keine Verhandlung.

Bau das System so, dass Lügen unmöglich ist. Genau dafür gibt es einen Type-Checker.