# Cai — Priorisierter Verbesserungs-Backlog (v3.3.1)

> **Status (2026-05-29): Alle 9 Items implementiert.** Je mit Tests, typecheck + build grün
> (537 Tests, +69). Neue Module: `verify-baseline.ts`, `accept.ts`, `verify-hook.ts`,
> `verify-changed.ts`; erweitert: `verify.ts`, `cli.ts`, `git.ts` (`getHead`),
> `mcp/predrift-index.ts`, `session-auto.ts`, `mcp/server.ts`. Ehrlichkeits-Korrekturen
> der Vorschläge (#6 Go-Verhaltensänderung, #8 stop_hook_active nur Re-Entrancy, #9
> annotate-only, kein Re-Gating) wurden wie vermerkt umgesetzt.


> Erzeugt durch eine Multi-Agent-Analyse: Map (was Cai heute kann) + Web-Research (wie der
> agentische Coding-Loop wirklich abläuft) → Gap-Findung → adversariale Verifikation jeder
> Idee gegen die reale Codebase → Priorisierung. Alle 9 Punkte haben die Verifikation mit
> Datei-/Zeilenbeleg überstanden; nichts ist spekulativ.

## Wo Cai im Agenten-Loop sitzt

Der agentische Coding-Loop ist ein geschlossener Feedback-Zyklus: **Phase 0** lädt Standing-Context (CLAUDE.md/AGENTS.md, `.cai/`), **Phase 1** Intent, **Phase 2** Context-Gathering (Glob/Grep/MCP — Quelle des „80%-Problems": Code außerhalb des Fensters wird stillschweigend verfehlt), **Phase 3** Plan, **Phase 4** Implement, **Phase 5** Verify (Back-Pressure: Tests/Linter/Typecheck drücken zurück, bis grün), **Phase 6** Human Review, **Phase 7** Learn & Persist (meist übersprungen → Lernen geht über Sessions verloren). Cai bedient drei dieser Phasen: **Phase 0/2** über den MCP-Context-Router und die PreDrift-/Session-Hooks, **Phase 5** über `cai verify` als Stop-Hook, **Phase 7** über die Drift-/Learn-Maschinerie. Dieser Backlog priorisiert nach Impact-zu-Aufwand und bleibt strikt im Cai-Profil: **deterministisch, keine AI-Calls, kein Netzwerk, niemals Schreiben außerhalb `cai:start`/`cai:end`, idempotent.**

## Priorisierte Tabelle

| Rang | Verbesserung | Loop-Phase | Impact | Aufwand | Warum (Kurz) |
|---|---|---|---|---|---|
| 1 | Verify-Baseline → Stop-Hook meldet **Regressionen** statt absoluter Failures | P5 / P7 | **hoch** | M | Macht aus dem blinden Gate einen „war grün, jetzt rot"-Detektor — das wertvollste Back-Pressure-Signal. `verify.ts` ist heute zustandslos. |
| 2 | **Acceptance-Criteria-Gate** via `cai:accept-start/end`-Block | P5 / P7 | **hoch** | M | Verwandelt agenten-selbsterklärtes „done" in ein objektives, Stop-Hook-erzwingbares Gate — ganz ohne AI. |
| 3 | Per-Path-PreDrift-Injection statt einmal pro Session | P2 | mittel | S | Adressiert das 80%-Problem für später im Lauf editierte Dateien. ~15 Zeilen in einer Funktion. |
| 4 | Standing-Context-Map + ROUTER-Pointer in `session --auto` | P0 | mittel | S | Frisches Fenster erfährt erstmals, *welche* Context-Files existieren. Eine Zeile, additiv. |
| 5 | Per-Section-Freshness-Signale im `cai_context_for_task`-Router | P2 | mittel | S | Stale-aber-relevante Sections passieren den bevorzugten Agenten-Einstieg heute ungeflaggt. Wiederverwendet vorhandene Maschinerie. |
| 6 | `cai verify --full`-Tier, der die Testsuite ausführt | P5 | mittel | S | Schließt eine dokumentierte Doc-vs-Code-Lücke; Tests sind das höchste Signal. *Nicht* rein additiv (siehe unten). |
| 7 | Diff-aware Section-Dedup im Router (`getReadState`) | P2 | mittel | S | Spart Tokens bei Mehrfach-Router-Queries pro Session; nur bei bereits gelesenen, unveränderten Files. |
| 8 | `stop_hook_active`-Guard + Retry-Budget + Verify-Event-Log | P5 | mittel | M | Begrenzt einen realen Unbounded-Retry-Kostenpfad + fehlende Hook-Observability. |
| 9 | Verify-Failures auf eigene Edits scopen (`--changed`) | P5 / P2 | mittel | M–L | Spart Turns bei Per-File-Lint-Steps, aber Typecheck/Build bleiben global; Changed-Path-Heuristik fehleranfällig. |

---

## Top-Items im Detail

### 1. Verify-Baseline → Regressions-Detektor (P5/P7, hoch / M)
**Problem.** `runVerify()` in `src/verify.ts` baut bei jedem Aufruf ein frisches `VerifyResult` (kein `writeFileSync`, kein HEAD-Read; `VerifyStep` kennt nur `passed|failed|skipped`). Der `--hook`-Pfad (`cli.ts:713-722`) macht `if (result.passed) return;` sonst `exit(2)` — er kann eine frische Regression nicht von geerbter Breakage unterscheiden. Ein in rotem Zustand gestartetes Repo wird endlos genervt. Cai persistiert bereits einen Drift-*Score* (`drift/history.ts`, `.cai/.cache/drift-history.jsonl`), aber Verify selbst hat kein Gedächtnis.

**Vorschlag.** Bei jedem `cai verify` ein kleines lokales Baseline-JSON schreiben (`.cai/.verify-baseline.json`, gitignored wie übriger `.cai`-State), keyed auf git-HEAD, mit Per-Step-Status + Content-Hash des fehlschlagenden Outputs. Delta `was/now`: Step war `passed`, ist jetzt `failed` → **REGRESSION** (`exit 2`); war schon `failed` → **PRE-EXISTING** (kein `exit 2`). `drift/history.ts` (~80 LOC, append-only, never-throw) ist die saubere Vorlage; HEAD über die vorhandene `simple-git`-Instanz in `src/git.ts`.

**Philosophie-Fit.** Deterministisch, keine AI, kein Netzwerk, nur lokales JSON unter `.cai/.cache/`, rührt `cai:start/end`-Content nicht an. Spiegelt die bereits etablierte Sparkline-Persistenz.

### 2. Acceptance-Criteria-Gate via Marker-Block (P5/P7, hoch / M)
**Problem.** `runVerify` führt nur 4 generische Projekt-Health-Phasen aus (Typecheck, Build, Adapter, Drift) — keine aufgabenspezifische Prüfung. Der Agent erklärt sich selbst für fertig, ohne Bezug zu den gestellten Akzeptanzkriterien (`grep` über `src/` für acceptance/criteria/predicate: null Treffer). Das ist die Eval-as-Gate-Lücke, die die Forschung als Verification-Backbone benennt.

**Vorschlag.** `cai:accept-start`/`cai:accept-end`-Block (konsistent mit `cai:start/end` aus `utils/merge.ts:5-6` und `cai:learn-start/end`) mit deterministischen Prädikaten: `file-exists:`, `grep:` (glob ist bereits Dep), `cmd:`, `no-drift`. Ein Runner ruft die vorhandene `runShell`-Maschinerie (`spawnSync`, ENOENT→skipped, Per-Step-Timeout, Truncation) und `VerifyStep` wieder. Via `--accept` ins Stop-Hook-Gate verdrahtbar.

**Wrinkle (ehrlich).** `runShell` macht `command.split(/\s+/)` ohne Shell und nimmt „keine Metazeichen" an. Beliebige `cmd:`-Zeilen (Pipes/Quotes) und `grep:`-Globs brauchen einen eigenen Parser — kein reiner Flag-Passthrough.

**Philosophie-Fit.** Deterministisch, keine AI, kein Netzwerk; liest nur innerhalb der Marker. Verwandelt selbsterklärtes „done" in ein objektives Gate.

### 3. Per-Path-PreDrift-Injection (P2, mittel / S)
**Problem.** `runPredriftHook` (`src/mcp/predrift-index.ts:121-126`) injiziert Scaffold-Guidance nur **einmal pro Session** über den Marker `/tmp/cai-predrift-${sessionId}`. Editierst du zuerst `auth/middleware.ts`, bekommst du das Auth-Pattern; spätere Files (`billing/charge.ts`, `audit/log.ts`) im selben Lauf bekommen null Injection — genau das 80%-Problem. Der Hook feuert auf jedem `Edit|Write` (`install.ts:245`); die Limitierung ist rein der interne Marker-Key.

**Vorschlag.** Marker-Key auf `/tmp/cai-predrift-${sessionId}-${shortHash(normPath)}` umstellen, `wx`-Race-Guard behalten. Jede distinkte Quelldatei bekommt ihre referenzierende Scaffold-/Pattern-Content genau einmal. Optionaler Per-Session-Cap-Counter, da `/tmp`-Marker heute nie aufgeräumt werden (ein Leak pro Session; per-Path multipliziert das).

**Impact-Ehrlichkeit.** Wert ist gedeckelt durch die Zahl editierter Files mit tatsächlichen Refs in `pattern-refs.json`; Single-File-Sessions sehen keine Änderung.

**Philosophie-Fit.** `pattern-refs.json` ist read-only zur Hook-Zeit → deterministisch, keine AI, kein Netzwerk, kein User-File-Write.

### 4. Standing-Context-Map in `session --auto` (P0, mittel / S)
**Problem.** `renderAutoContext` (`src/session-auto.ts:132-158`) injiziert bei jedem Prompt Drift-Score, geänderte Files, Commits, Hot-Files — aber **kein** Inventar der dokumentierten Context-Files und keinen ROUTER-Pointer. CLAUDE.md sagt „read `.cai/ROUTER.md` first", doch der Always-on-UserPromptSubmit-Hook (`install.ts:219`, `cli.ts:680`) liefert keinen. Ein frisches Fenster weiß nicht, dass `architecture.md`/`conventions.md`/`decisions.md` existieren.

**Vorschlag.** Eine kompakte Zeile, deterministisch aus `readdirSync` von `.cai/context` (`.md` strippen, nur existierende Files, geordnet nach `STABILITY_ORDER` aus `context-router.ts:136` — *nicht* der Beispiel-String): `[cai] context available: decisions, stack, architecture, conventions, setup — start with cai_list_context or read .cai/ROUTER.md`. Innerhalb des 800-Char-Caps; bei Knappheit die Namensliste zuerst kürzen. **Achtung:** Der aktuelle Render sliced den ganzen Join — eine angehängte Zeile würde zuerst abgeschnitten, daher **prependen**.

**Philosophie-Fit.** Wiederverwendet das `readdirSync`/`.md`-Muster aus `session.ts:50`; deterministisch, lokal, kein Netzwerk, rein additiv.

### 5. Per-Section-Freshness im Router (P2, mittel / S)
**Problem.** `buildFreshnessNote` ist in `cai_get_context` verdrahtet (`server.ts:699-701`), aber der Router-Pfad `cai_context_for_task` (`server.ts:751-813`) emittiert null Staleness-Signal — er kann Sections aus einem 40-Commits-alten Doc ungewarnt zurückgeben, ausgerechnet am bevorzugten Agenten-Einstieg.

**Vorschlag.** Nach Section-Auswahl pro Section-Quellfile via vorhandener `getFileGitInfo`/`buildFreshnessNote` (bereits importiert) die Staleness berechnen und an die Header-Zeile (`server.ts:787`) anhängen. `ScoredSection` hat kein `file` → relpath aus `s.id.split('#')[0]` ableiten (Format dokumentiert, Präzedenz via `buildVerifiedPaths`). **Per-relpath-Git-Cache ist Pflicht**, nicht optional: mehrere Sections teilen Files; sonst gefährden N sequenzielle `getFileGitInfo`-Spawns die „Millisekunden-Laufzeit". Stale Sections **nicht** droppen — flaggen schlägt verstecken.

**Philosophie-Fit.** git-CLI-only, deterministisch, keine AI/kein Netzwerk.

### 6. `cai verify --full` mit Testsuite (P5, mittel / S)
**Problem.** `verify.ts:16` dokumentiert „run a separate `cai verify --full`" — aber **kein `--full`-Flag existiert** in `cli.ts` (nur `--json/--hook/--skip-drift/--skip-adapters`). Tests sind das höchste Signal; `detectNodeChecks` fügt nur Lint + react-doctor hinzu, Python nur ruff/mypy. Einzige Ausnahme: Go-Tests laufen **immer** (`verify.ts:149`).

**Vorschlag.** `--full` + `runTests: boolean` zu `VerifyOptions`; pro Stack einen Test-Step aus derselben deterministischen Detection (`npm test` wenn `test`-Script existiert — bare `node --test`-Placeholder skippen; `pytest`; `cargo test`; `mvn -q test`/`./gradlew test`). `runShell` + Per-Step-Timeout wiederverwenden. Default-`verify` und Stop-Hook bleiben schnell (keine Tests).

**Ehrlichkeit (wichtig).** **Nicht rein additiv:** Go's always-on `go test` hinter `--full` zu re-gaten ist eine Verhaltensänderung — bestehende Go-User verlieren das Default-Signal. Impact ist **mittel, nicht hoch**, weil opt-in und bewusst *nicht* im Default-Stop-Hook.

**Philosophie-Fit.** Reiner lokaler Subprozess, keine AI/kein Netzwerk; nutzt vorhandene Infra.

---

## Kurz-Begründungen Rang 7–9

- **7 — Diff-aware Dedup (S):** `cai_context_for_task` (`server.ts:785-791`) pusht `s.content` unbedingt; `getReadState` (md5 vs. `sessionReads`) wird nur in `cai_get_context` genutzt. Stub-on-unchanged via `Section.file` ist direkt implementierbar. **Caveat:** `sessionReads` ist File-granular, nicht Section-granular — sah der Agent nur Section A eines Files, würde Section B fälschlich gestubbt. Sicher nur mit File-Level-Gate (alle Sections eines Files gemeinsam stubben, wie das Whole-File-Modell von `cai_get_context`).
- **8 — Hook-Guard + Budget + Log (M):** Stop-Hook ist verbatim `"cai verify --hook"`; `grep` für `stop_hook_active`/`session_id` liefert null — Loop-Break-Feld wird ignoriert, kein Retry-Ceiling (`exit 2` bei jedem Stop). `telemetry/query-log.ts` ist die JSONL-Vorlage. **Caveat:** `stop_hook_active===true ⇒ exit 0` kollidiert mit Back-Pressure; nur zur Re-Entrancy-Erkennung für den Counter nutzen, das Retry-Budget ist der tragende Mechanismus.
- **9 — Verify scopen `--changed` (M–L):** `getChangedFiles()` (`git.ts:197`) existiert und wird bereits von Drift `--incremental` genutzt — aber nie für Verify-Compiler-Steps. **Schlechtestes Ratio:** Typecheck (Step 1, häufigstes Gate) bleibt global+annotate-only → die Kern-Pain bleibt dort; die „errors touch your files"-Intersection labelt Shared-Symbol-Breakage fälschlich als „nicht deins" und reimportiert das 80%-Problem. Annotation braucht Per-Ökosystem-Compiler-Output-Parsing → M–L, nicht trivial.

---

## Anhang: Bewusst verworfen

**Keine.** Auf dieser Stufe wurde nichts verworfen. Alle als `ALREADY_EXISTS` oder `OUT_OF_SCOPE` eingestuften Kandidaten wurden bereits *vor* dieser Liste entfernt; die neun hier gelisteten haben die Verifikation gegen die reale Codebase überstanden (jeweils mit Datei-/Zeilenbeleg). Wo die ursprüngliche Selbsteinschätzung eines Vorschlags zu optimistisch war (insb. #6 „nicht rein additiv", #9 „schlechtes Ratio + unsichere Heuristik"), ist die Korrektur in den Detail-Abschnitten vermerkt statt verschwiegen.
