Rentenversicherer/AGENTS.md
Kenearos 3c669fb003 feat: AcroForm-Fill via Claude CLI, Multi-Source, Kanagawa, Docker-Deploy
Komplettes Rework der AI-Studio-Vorlage zu einem produktiven Werkzeug fuer
deutsche AcroForm-Formulare (Reha-Antraege, Arzt-Befundberichte):

- Backend: Express spawnt headless Claude CLI ('claude -p --output-format json'
  via stdin-Pipe). Prompt enthaelt die Feldnamen als Ziel-Schema plus die
  Arbeitsregeln (Stichwortstil, feste Zeichen-Kaestchen ohne Leerzeichen,
  Vordrucke respektieren, keine geratenen Werte, nur medizinisch).
- PDF-Handling: pdfjs-dist statt pdf-lib — pdf-lib scheitert an verschluesselten
  Object-Streams in DRV-Formularen. annotationStorage + saveDocument, kein
  Flatten. Worker-Patch zur Laufzeit forciert Auto-Size und schwarze Schrift.
- Multi-Source-Upload: beliebig viele PDFs/Bilder + optional Freitext.
- Design: Kanagawa Design System (Preset aus ../kanagawa-design-system),
  Tailwind lokal gebaut statt CDN, Dark/Light-Toggle, Progress-Indicator.
- Deployment: Multi-Stage-Dockerfile, docker-compose in matrix_default-Netz,
  Claude-Credentials vom Host per Volume. PLAN.md + AGENTS.md (Alex-Schema).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 22:48:32 +02:00

6.2 KiB

AGENTS.md - Rentenversicherer Repository Guidelines

Overview

Rentenversicherer ist ein lokales Werkzeug zum halb-automatischen Ausfüllen von AcroForm-PDFs. Browser-UI + lokaler Node-Server. Der Server ruft die Claude Code CLI (claude -p) als Subprozess auf, um Daten aus einem Quelldokument in die AcroForm-Feldnamen eines Ziel-PDFs zu mappen. Das ausgefüllte PDF bleibt editierbar (kein Flatten).

Run / Dev Commands

Requires: Node.js 20+, Claude Code CLI im PATH, gültiges Claude-Code-Login.

# Install
npm install

# Development — Vite (5173) + Server (3001) parallel
npm run dev

# Production build und starten
npm run build
npm start

# Typecheck
npx tsc --noEmit

Lint / Test / Validation

Kein dediziertes Test-Framework. Validation:

# Typecheck (Client + Server, shared tsconfig)
npx tsc --noEmit

# Claude CLI verfügbar?
claude --version

# Smoke-Test (manuell):
# 1. npm run dev
# 2. http://localhost:5173
# 3. Beispiel-PDF hochladen (G2210-11_Aerztlicher_Befundbericht_Anforderung_WAG.pdf)
# 4. Quelldokument hochladen
# 5. Download
# 6. PDF in Acrobat öffnen → Feld klicken → editieren möglich?

Repo Layout

Rentenversicherer/
├── App.tsx                   # React-Einstieg, State-Machine (IDLE/PROCESSING/REVIEW)
├── components/
│   ├── FileUpload.tsx        # Drag & Drop + base64-Encoding
│   └── ReviewPanel.tsx       # List-View für extrahierte Felder + PDF-Preview
├── services/
│   ├── api.ts                # fetch('/api/process'), multipart/form-data
│   └── pdfService.ts         # pdfjs-dist: Widgets lesen + saveDocument
├── server/
│   ├── index.ts              # Express + multer, POST /api/process
│   └── claudeRunner.ts       # spawn('claude', ['-p', ...]), JSON-Parsing
├── types.ts                  # ExtractedField, FormResponse, AppStatus, FileData
├── vite.config.ts
├── tsconfig.json
├── package.json
└── PLAN.md

Code Style - TypeScript / React

Komponenten:

  • Functional Components mit Hooks; keine Class-Components.
  • Props-Interfaces am Anfang der Datei, Name endet auf Props.
  • Dateinamen: PascalCase.tsx für Komponenten, camelCase.ts für Services.

Imports:

  • React-Imports zuerst, dann 3rd-Party, dann relativ.
  • Keine *.tsx-/*.ts-Extensions in Imports.

Styling:

  • Tailwind ausschließlich. Keine CSS-Module, kein inline-style außer für dynamisch berechnete Werte.
  • Lucide-Icons (lucide-react) für alle Icons.

Types:

  • Shared Types in types.ts. Keine Duplikate in Komponenten.
  • any vermeiden. unknown + Type-Guards bevorzugen.

Code Style - Server (Node/TypeScript)

  • Module: ESM ("type": "module" in package.json).
  • tsx watch für dev; kein ts-node, kein nodemon.
  • Async/await überall, kein callback-style.
  • Fehler aus dem Claude-Subprozess als 502 ans Frontend weiterreichen, mit message im JSON-Body — niemals stdout/stderr roh leaken.

Naming Conventions

  • Komponenten: PascalCase (ReviewPanel, FileUpload).
  • Services: camelCase (pdfService, claudeRunner, api).
  • Types/Interfaces: PascalCase (ExtractedField, FormResponse).
  • Enums: PascalCase Key, UPPER_CASE Values (AppStatus.IDLE).
  • Routes: kebab-case (/api/process).

Security Rules

  1. Kein API-Key im Code und nicht in .env.local. Auth läuft über das bestehende Claude-Code-Login.
  2. Uploads nur in os.tmpdir(), pro Request eigenes Subverzeichnis, Cleanup im finally-Block.
  3. multer mit limits.fileSize (z.B. 20 MB) — keine unbegrenzten Uploads.
  4. multer mit Allowlist für MIME-Types (PDF, PNG, JPEG, WEBP).
  5. Server lauscht nur auf 127.0.0.1, nicht auf 0.0.0.0.
  6. Dateinamen aus dem Upload niemals als Pfad verwenden — immer UUID/Counter.

Architecture Rules (non-negotiable)

  1. Nie flatten — bricht das Kernversprechen, dass das PDF im Reader editierbar bleibt. pdfjs' saveDocument() flacht nicht, das ist genau der richtige Modus.
  2. Werte immer über annotationStorage.setValue(widgetId, { value }) setzen, nicht versuchen einzelne Annotation-Objekte zu mutieren.
  3. Original-PDF-Bytes bleiben strukturell unverändert. Nur Feldwerte werden gesetzt. Keine Seiten-Mutationen, keine neuen Objekte, keine Annotations.
  4. Wenn Ziel-PDF keine AcroForm-Felder hat: Hard-Fail mit klarer User-Meldung. Kein Fallback auf Overlay/Koordinaten-Modus.
  5. claude-Subprozess immer mit --permission-mode bypassPermissions, sonst blockiert er auf Tool-Prompts.
  6. Der Server gibt nur strukturiertes JSON an das Frontend weiter — Claude-Stdout nie ungeparst durchreichen.
  7. Temp-Verzeichnisse werden nach jedem Request gelöscht, auch im Fehlerfall.

Error Handling

  • Client zeigt alle Fehler im roten Banner auf der Upload-Seite.
  • Server-Errors: JSON { error: string, details?: string }, HTTP-Status 4xx/5xx passend.
  • Claude-CLI-Exitcode ≠ 0 oder Timeout → 502 + message: "Claude CLI failed" + Exit-Code.
  • Kein Retry auf Server-Seite. Bei Bedarf triggert der User den Request erneut.
  • Unhandled rejections im Server: process.on('unhandledRejection', ...) loggen, nicht crashen.

Claude CLI Interface

Der Aufruf aus server/claudeRunner.ts:

claude \
  -p "<prompt>" \
  --output-format json \
  --permission-mode bypassPermissions

Prompt-Grobskizze (Deutsch, weil Formulare deutsch sind):

Du füllst ein deutsches Behördenformular aus.

TARGET-FORM: <tempdir>/form.pdf
SOURCE:      <tempdir>/source.pdf

Die Feldnamen im TARGET-Formular sind:
  - feld1 (PDFTextField)
  - feld2 (PDFCheckBox)
  ...

Lies beide Dateien mit dem Read-Tool, extrahiere die Werte aus dem
SOURCE und gib NUR ein JSON-Objekt zurück im Format:

{
  "summary": "...",
  "fields": [
    { "key": "feldname", "label": "...", "value": "...",
      "sourceContext": "...",
      "validation": { "status": "VALID"|"WARNING"|"INVALID",
                      "message": "...", "suggestion": "..." } }
  ]
}

Deutsches Format:
- Datum: DD.MM.YYYY
- Zahlen: Komma als Dezimaltrenner
- Checkbox-Wert: "X" wenn angekreuzt, "" sonst

Das JSON landet als String im result-Feld des CLI-Output-Wrappers (--output-format json). claudeRunner.ts zieht es raus und JSON.parset es.