0
Kai Detmers
Back to all work
Case Study · 2026 09

CAI — a CLI linter for AI-coding agents.

Deterministic drift detection between CLAUDE.md, AGENTS.md, Cursor rules & Copilot instructions and the actual repo. No LLM, no network — fast enough for CI and pre-commit hooks. Published as @temroi/cai on npm.

cai health — Terminal dashboard showing real drift and token metrics
Real terminal output of cai health — score, drift findings grouped by file, with the actual call-to-action. No mockup.

Kurzbeschreibung

CAI ist ein CLI-Linter für AI-Coding-Agents: Es erkennt, wenn CLAUDE.md, AGENTS.md, Cursor-Regeln oder Copilot-Instructions vom tatsächlichen Repo abweichen — falsche Pfade, umbenannte npm-Scripts, veraltete Dependencies, tote Markdown-Links. Läuft komplett deterministisch (kein LLM-Call, kein Netzwerk), damit es in CI und als Pre-Commit-Hook einsetzbar ist. Veröffentlicht als @temroi/cai auf npm.

Was ich gebaut habe

cai --help — Command overview showing the full CLI surface
cai --help — die Funktionsbreite der CLI auf einen Blick.

Tech-Stack

TypeScript (strict, ESM-only) mit Commander für die CLI-Oberfläche, web-tree-sitter für AST-basierte Code-Karten, simple-git für Git-Historie-Checks und remark für Markdown-Claim-Extraktion. Bewusst kein LLM im Drift-Pfad — nur Dateisystem und Git, damit Checks in Millisekunden laufen und in Tests deterministisch geprüft werden können. Die optionale GUI ist separat als Tauri-App (Rust-Shell + System-Webview) gehalten, damit die CLI selbst dependency-light bleibt.

Architektur & Tech-Entscheidungen

Warum kein LLM im Drift-Pfad?

Die Kernentscheidung des Projekts: cai check und cai fix machen null AI- oder Netzwerk-Calls. Alternative wäre semantisches Matching per LLM gewesen — verworfen wegen Latenz, Kosten und Nicht-Determinismus. Ein CI-Gate muss reproduzierbar sein; ein Check, der je nach Modell-Sampling unterschiedlich ausfällt, ist als Gate wertlos. Die Konsequenz zieht sich durchs ganze System: jeder Checker muss synchron in Millisekunden laufen, und sein Output lässt sich 1:1 in Tests assertieren. KI kommt einzig in cai sync vor — dort explizit nutzergetriggert, um gezielte Prompts für Doku-Updates zu bauen.

Registry-Pattern für Checker

Jeder Drift-Checker ist eine reine Funktion (check*(...)), keine Klasse, kein Interface-Overhead. Die Registrierung erfolgt deklarativ über ein CheckerDeclaration-Objekt mit name, needs (welche Felder aus dem DriftContext gelesen werden) und einem run(ctx)-Body aus genau einem Funktionsaufruf. Der Runner projiziert den vollen Kontext über bindCheckerDeclaration auf die angeforderten Felder herunter, bevor er run aufruft — das hält den Vertrag jedes Checkers eng und macht neue Checker in fünf klar dokumentierten Zeilen ergänzbar.

MCP-Kontext statt dicker Instruktionsdatei

Agenten laden nicht mehr die gesamte CLAUDE.md bei jedem Turn, sondern fragen cai-mcp gezielt ab: headings fürs grobe Layout, summary für die wichtigsten Punkte, full für den seltenen Vollabruf. Das hält den Token-Verbrauch pro Turn klein, ohne die Fähigkeit zur Detailtiefe aufzugeben. Der Server läuft über stdio, damit er sich nahtlos in Claude Desktop, Cursor und Editor-Agents einklinkt, ohne dass ein weiterer Daemon existieren muss.

Hot-Path-gewichtetes Scoring

Ein klassischer Linter zählt Fehler. cai misst, wie oft eine Datei im Agenten-Loop tatsächlich gelesen wird (lokal, anonymisiert) und gewichtet Drift-Befunde entsprechend. Ein Pfad, der in 0 % der Turns abgefragt wird, fällt im Score weniger ins Gewicht als einer, der jeden dritten Turn geladen wird. Das verschiebt den Blick von "wie viele Probleme" zu "was stört den Agenten wirklich".

Warum die GUI ein separates Repo-Bürger ist

Die Tauri-App lebt im gui/-Workspace und wird unabhängig vom npm-Paket gebaut. Die Rust-Shell spawnt die cai-CLI als Kindprozess und adaptiert deren Rohausgabe über einen versionierten, parity-getesteten Contract (toDriftViewContract, toHealthViewContract, etc.). So bleibt die CLI dependency-light — wer nur das Tool will, zieht kein Rust und kein Tauri.

Design-Analyse

Design-Prinzip: die CLI ist die einzige Quelle der Wahrheit

Jede Oberfläche — Terminal-Output, MCP-Antworten, GUI — geht durch dieselbe Engine. Die GUI reimplementiert nie Drift-Logik; sie ruft cai check --json etc. auf und rendert das Ergebnis. Das verhindert Divergenz zwischen "was die CLI sagt" und "was die GUI zeigt" — ein Klassiker bei Tools mit mehreren Frontends.

Terminal-Output als Primärsprache

Weil das Kernprodukt ein CLI-Linter ist, war die eigentliche UI-Arbeit die Lesbarkeit der Terminalausgabe: Score zuerst, dann gruppiert nach Datei, mit Symbolen (✔/⚠/✖/ℹ) statt Textlabels, gedimmten Sekundärinfos (opacity-Äquivalent im Terminal via ANSI dim) und einer klaren nächsten Handlung (→ cai sync --warnings). Das reduziert kognitive Last stärker als jede grafische Oberfläche es könnte, weil es im Workflow bleibt, den Entwickler ohnehin schon offen haben.

Was gut funktioniert hat

Die harte Trennung zwischen deterministischem Kern und optionaler KI-Schicht hat sich als robuste Leitplanke erwiesen — jede neue Funktion musste sich fragen "braucht das wirklich einen Modellaufruf, oder reicht Dateisystem + Git?" Das hat Scope-Creep in Richtung "KI für alles" verlässlich verhindert.

Challenges & Learnings

Größte Herausforderungen

Was ich anders machen würde

Die Registry-Pattern-Disziplin für Checker (ein !-Non-Null-Assertion-Workaround, weil TypeScript nicht inferieren kann, dass needs bestimmte Felder garantiert) ist bekannte Tech-Debt. Ein Mapped Type, der needs auf Pflichtfelder des CheckerCallContext abbildet, wäre sauberer gewesen — wurde aus Zeitgründen zurückgestellt.

Key Takeaways

Timeline

Aktive Entwicklung von Ende März bis Anfang Juli 2026, über 225 Commits. Kernfunktionen (Drift-Checker, Scaffold-Generierung) zuerst, dann MCP-Server und Verify-Gate, zuletzt Hot-Path-Scoring, Correction-Learning und der Desktop-Sidecar.

Mar 2026Kickoff · Drift-Checker, Scaffold-Generierung
Apr → MayMCP-Server · cai verify als Back-Pressure-Gate
Jun 2026Hot-Path-Scoring · Correction-Learning (cai learn)
Jul 2026Desktop-Sidecar (Tauri/Rust) · npm-Release @temroi/cai