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.
- TypeScript
- Node.js
- Commander
- Model Context Protocol
- tree-sitter
- Tauri · Rust
- vitest
- ● live · npm
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
- 10 deterministische Drift-Checker (Pfade, Commands, Dependencies, Env-Vars, tote Links/Anchors, Workspace-Mismatches, Tool-Config-Drift u.a.), jeder als reine Funktion registriert in einer deklarativen Registry
- MCP-Server über stdio, der Agenten fokussierten Projekt-Kontext auf Abruf liefert (
headings/summary/full) statt eine große Instruktionsdatei bei jedem Turn zu laden cai verifyals Back-Pressure-Gate vor "Task erledigt": Typecheck, Build, sprachspezifische Adapter (Python/Go/Rust/Java/Ruby) und Drift-Check in einem Lauf, inkl. Baseline-Vergleich gegen den letzten grünen Stand- Hot-Path-gewichtetes Scoring: gewichtet Drift-Befunde danach, wie oft ein Agent die betroffene Datei tatsächlich abfragt (lokale Telemetrie), nicht nur nach roher Fehleranzahl
- Lokales Correction-Learning (
cai learn): erkennt wiederkehrende Nutzerkorrekturen in Prompts und exportiert sie als Regeln oder providerspezifischeSKILL.md-Dateien — komplett opt-in, nichts verlässt die Maschine - Desktop-Sidecar (separates Tauri/Rust-Frontend) als visuelles Pendant zu
cai health, das ausschließlich über die CLI als Backend spricht
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
- Claim-Extraktion aus Markdown ist unscharf. Negierte Aussagen ("nutzt KEIN X") mussten erkannt und von echten Claims unterschieden werden, ohne False Positives bei Platzhaltern (
<name>,/your-project/) oder gescopten npm-Packages (@scope/pkg) zu erzeugen. Das ist über iterative Filterlisten (TEMPLATE_PLACEHOLDER,COMMON_NON_PACKAGE_TERMS) gewachsen, nicht durch ein einzelnes cleveres Regex. - Zwei parallele Watch-Mechanismen (chokidar + mtime-Polling) mussten koexistieren, weil chokidar auf macOS-Cloud-Sync-Ordnern (Dropbox) zuverlässig Events verschluckt. Die Lösung war kein Fallback-nach-Fehler, sondern ein Fallback-nach-Stille (5s ohne chokidar-Event aktiviert den Poller) — subtiler Unterschied, der einige Debug-Runden gekostet hat.
- CI-Release-Pipeline hatte einen YAML-Fehler, der den Workflow beim Start crashen ließ, plus einen falschen
npm cigegen ein Repo-Root-package.jsonstatt denCai/-Workspace. Beide Fehler waren lange unsichtbar, weil lokale Tests grün liefen.
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
- Determinismus als Architektur-Constraint zwingt zu einfacheren, testbareren Lösungen — auch wenn "KI könnte das doch einfach lösen" oft verlockend ist.
- Ein Tool, das Agenten-Verhalten beeinflussen soll, muss selbst im Agenten-Workflow leben (Hooks, MCP, Stop-Gates) statt eine separate Oberfläche zu verlangen, die zusätzliche Kontextwechsel erzeugt.
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.
cai verify als Back-Pressure-Gatecai learn)@temroi/cai