KI-Code & Mensch: Hand ♥ Hand Ein echtes Praxis-Beispiel
Was kann man in 8 bis 30 Minuten mit einem guten KI-System schaffen?
Auf der Seite Mensch + KI-Code Prozess findest Du eine ausführliche Beschreibung, wie Du ein System entwickelst, um mit Claude Code in relativ kurzer Zeit erwartungskonformen Code zu erstellen.
Aber wie funktioniert das tatsächlich in der Praxis?
Ich brauchte für mein internes Web-System zwei Ansichten aller Rechnungen aus zwei GmbH-Gesellschaften:
- sortierbare
- filterbare
- durchsuchbare
- paginierte
Tabelle inklusive aller Details.
Hätte ich das von Hand gemacht, hätte ich vielleicht einen funktionalen, aber irgendwie doch murksigen Code erstellt, denn: Ich kann vielleicht an 20 Dinge denken, aber nicht an 162 Faktoren (so viele Regeln prüft unser Gate-System) und schon gar nicht an tausende Faktorkombinationen, wenn es drauf ankommt, die Wechselwirkungen auch noch zu berücksichtigen!
Also greife ich zu Claude Code, denn das zugehörige Gate-System ist mittlerweile sehr weit entwickelt.
Viel Spaß beim Lesen und vielleicht auch mit- und nachbauen!
Ziel
Integration von Lexoffice-Rechnungen in die FREUND-GUI mit vollständiger HTMX-Tabelle, Sortierung, Filterung und Detailansicht.
Zeitraum
Spoiler: Im Kern gedauert hat das 8 Minuten. Mit allem Drumherum ca. 30 Minuten.
Was dieses Beispiel zeigt
- Iterative Prompt-Entwicklung: Von einem naiven ersten Entwurf zu einem vollständig spezifizierten Prompt (v3.0)
- Supervision durch KI: Automatische Analyse und Feedback zu Prompt-Lücken
- Task-Dekomposition: Zerlegung in 11 atomare Sub-Tasks mit Master-Task
- Gate-System in Aktion: 16 Contracts, 162 Regeln, 20 Hooks überwachen die Ausführung
- Delegierte Ausführung: Externe KI implementiert autonom ohne menschliches Eingreifen
- Akzeptanztests: Automatisierte Validierung aller Ergebnisse
Obacht, long read, aber es lohnt sich!
Schnellnavigation
- Prompt-Entwicklung
- Prompt v1.0: Der erste Entwurf
- Prompt v2.0: Mit Schema und UI-Details
- Prompt v3.0: Vollständige Spezifikation
- Task-Dekomposition
- Task-Erstellung und Delegation
- 1836: databases.json erweitern
- 1837: .htaccess Routing
- 1838: Container.php erweitern
- 1839: InvoiceRepository erstellen
- 1840: Admin karlscore.php
- 1841: Admin 369wohlbefinden.php
- 1842: Detail-Ansicht detail.php
- 1843: HTMX list.php Fragment
- 1844: Navigation erweitern
- 1845: CSS-Styles
- 1846: Akzeptanztest
- Gate-System
- Architektur-Überblick
- Contracts (16 Regelwerke)
- Rules (162 Einzelregeln)
- Hooks (20 Event-Handler)
- Transitions (Zustandsübergänge)
- Interventionen (Session-Log)
- Monitoring & Ergebnisse
- Implementation-Monitoring
- Akzeptanztests
- Ergebnis und Screenshots
- Live-Demo: Rechnungstabelle
- Original-Code (Ergebnis)
- InvoiceRepository.php
- list.php (HTMX-Endpoint)
- karlscore.php
- 369wohlbefinden.php
- detail.php
- Container.php
- Qualität & Erkenntnisse
- Code-Quality-Report
- Was können wir alles Tolles daraus lernen?
Prompt v1.0: Der erste Entwurf
Der erste Prompt-Entwurf beschreibt die Grundanforderung: Zwei Admin-Seiten für Lexoffice-Rechnungen. Er enthält die wesentlichen Informationen, lässt aber viele Details offen.
Eine Ermutigung vorweg
Wenn ein Kind laufen lernt, sieht das meist erstmal seltsam aus: dauernd fällt es aufs Gesicht. Aber wir helfen ihm immer wieder auf. Niemand käme da auf die Idee zu sagen: „Bleib liegen, das wird nix. Wir machen mit Deinem Bruder Jädd Tschippiti weiter."
Dieser Artikel soll genauso eine Ermutigung sein:
- a) Umgefallene Kinder wieder aufzuheben
- b) Eine durch ChatGPT vermurkste KI-ndheit zu überwinden
Let's go:
Prompt-Design-Prinzipien
- Kontext zuerst: Datenquellen und Schema definieren
- Explizite Ziele: Was soll am Ende funktionieren?
- Referenz-Code: Bestehende Patterns als Vorlage nennen
- Negative Definitionen: Was soll NICHT passieren?
prompt-v1.0.txt
Prompt v1.0 anzeigen
================================================================================
PROMPT: LEXOFFICE RECHNUNGEN INTEGRATION - FREUND GUI
================================================================================
Version: 1.0 (Erster Entwurf)
Projekt: schnuedeldue.oerg
================================================================================
1. AUFGABENBESCHREIBUNG
================================================================================
Implementiere zwei neue Admin-Seiten:
/admin/rechnungen/karlscore - Rechnungen der karlsCORE GmbH
/admin/rechnungen/369wohlbefinden - Rechnungen der 369 Wohlbefinden GmbH
Beide Seiten sind identisch aufgebaut, greifen aber auf unterschiedliche
Datenbanken zu.
================================================================================
2. DATENQUELLEN
================================================================================
Account | Datenbank | Anzahl
------------------------|------------------------------------|---------
karlsCORE GmbH | freund_lexoffice_karlscore | 2.555
369 Wohlbefinden GmbH | freund_lexoffice_369wohlbefinden | 808
================================================================================
3. UI-ANFORDERUNGEN
================================================================================
- Tabellenansicht mit Sortierung
- Filter nach Status und Datum
- Detailansicht per Klick
- Mobile-responsive
================================================================================
4. REFERENZ
================================================================================
Orientiere dich an:
- public/admin/emails/business.php (Listenansicht)
- public/htmx/emails/list.php (HTMX-Pattern)
Das verheerende Supervision-Feedback zu v1.0
Die Supervision analysierte den Prompt und identifizierte folgende Lücken:
supervision-feedback-v1.txt
Supervision-Feedback anzeigen
SUPERVISION ANALYSIS - Prompt v1.0
==================================
KRITISCHE LÜCKEN:
1. SCHEMA-DEFINITION FEHLT
- Welche Spalten existieren in der DB?
- Welche Datentypen?
- Welche Felder sind NULL-fähig?
2. SPALTEN-AUSWAHL UNKLAR
- Welche 6 Spalten in der Tabelle?
- Welche Sortier-Optionen?
- Format für Datum, Betrag?
3. STATUS-LOGIK UNDEFINIERT
- Welche Status-Werte gibt es?
- Wie werden sie dargestellt?
- Was bedeutet "überfällig"?
4. FILTER-VERHALTEN UNSPEZIFIZIERT
- Case-sensitive oder nicht?
- Welche Felder durchsuchen?
- Verhalten bei Filterwechsel?
5. PAGINATION FEHLT KOMPLETT
- Wie viele Einträge pro Seite?
- Wie sieht die Navigation aus?
6. DETAILANSICHT UNDEFINIERT
- Welche Felder anzeigen?
- Layout?
- Rücknavigation?
EMPFEHLUNG: Prompt mit expliziten Definitionen erweitern.
Prompt v2.0: Mit Schema und UI-Details
Nachdem ich mich von der Supervisions-Schelle erholt hatte (meine Güte, wie kann man so viel vergessen?) habe ich auf Basis einiger Richtlinien und Constraints den Prompt erweitert:
Änderungen von v1.0 zu v2.0
- Schema: Alle 35 Spalten mit Datentypen dokumentiert
- Spaltenauswahl: Explizite 6-Spalten-Definition für Tabelle
- Status-Badges: Farben und CSS-Klassen definiert
- Overdue-Logik: Exakte Bedingungen für „überfällig"
- Pagination: 25 pro Seite, Algorithmus für Seitenlinks
- Filter: Case-insensitive, durchsuchte Felder, Verhalten
prompt-v2.0.txt
Prompt v2.0 anzeigen
================================================================================
PROMPT: LEXOFFICE RECHNUNGEN INTEGRATION - FREUND GUI
================================================================================
Version: 2.0 (Mit Schema und UI-Details)
Projekt: schnuedeldue.oerg
================================================================================
1. AUFGABENBESCHREIBUNG
================================================================================
Implementiere zwei neue Admin-Seiten:
/admin/rechnungen/karlscore - Rechnungen der karlsCORE GmbH
/admin/rechnungen/369wohlbefinden - Rechnungen der 369 Wohlbefinden GmbH
================================================================================
2. DATENQUELLEN UND SCHEMA
================================================================================
Account | Datenbank | Anzahl
------------------------|------------------------------------|---------
karlsCORE GmbH | freund_lexoffice_karlscore | 2.555
369 Wohlbefinden GmbH | freund_lexoffice_369wohlbefinden | 808
SCHEMA (verifiziert):
id VARCHAR(36) PRIMARY KEY
voucher_number VARCHAR(50)
voucher_date DATE
voucher_status VARCHAR(20) -- draft, open, paid, voided
due_date DATE -- NULL bei draft
address_name VARCHAR(255)
total_gross_amount DECIMAL(12,2)
line_items LONGTEXT -- JSON Array
...
================================================================================
3. UI-ANFORDERUNGEN
================================================================================
3.1 TABELLEN-SPALTEN (6 Spalten)
--------------------------------
Nr. | voucher_number | String
Datum | voucher_date | DD.MM.YYYY
Kunde | address_name | max 40 Zeichen
Betrag | total_gross_amount | X.XXX,XX €
Status | voucher_status | Badge mit Farbe
Fällig | due_date | DD.MM.YYYY oder "-"
3.2 STATUS-BADGES
-----------------
draft = grau (.status-draft)
open = blau (.status-open)
paid = grün (.status-paid)
voided = rot (.status-voided)
3.3 OVERDUE-LOGIK
-----------------
Überfällig wenn:
status = 'open' AND due_date < CURDATE()
3.4 PAGINATION
--------------
25 Einträge pro Seite
Navigation: [<<] [<] [1] ... [5] [6] [7] ... [20] [>] [>>]
3.5 FILTER
----------
- Textsuche in voucher_number, address_name, title
- Case-insensitive
- Status-Dropdown
- Datumsbereich
================================================================================
4. REFERENZ-DATEIEN
================================================================================
- public/admin/emails/business.php
- public/htmx/emails/list.php
- src/Repositories/Email/NextcloudEmailRepository.php
Der Prompt v2.0 ging erneut durch die Supervision.
Supervision-Feedback zu v2.0
Die Supervision erkannte Verbesserungen, fand aber weitere offene Punkte:
supervision-feedback-v2.txt
Supervision-Feedback anzeigen
SUPERVISION ANALYSIS - Prompt v2.0
==================================
VERBESSERUNGEN ERKANNT:
✓ Schema dokumentiert
✓ Spalten definiert
✓ Status-Logik klar
✓ Pagination spezifiziert
VERBLEIBENDE LÜCKEN:
1. DETAILANSICHT FEHLT
- Welche Felder anzeigen?
- Positionen-Tabelle?
- Summen-Darstellung?
2. ROUTING NICHT DEFINIERT
- .htaccess Regeln?
- Detail-URL-Pattern?
3. REPOSITORY-PATTERN UNKLAR
- Container-Integration?
- DI-Muster?
4. DATABASE CONFIG FEHLT
- Wie werden die DBs eingebunden?
- Readonly-Modus?
5. NEGATIVE DEFINITION FEHLT
- Welche Felder NICHT anzeigen?
- archived-Filter?
6. AKZEPTANZKRITERIEN FEHLEN
- Wie wird Erfolg gemessen?
- Testbare Kriterien?
EMPFEHLUNG: Vollständige technische Spezifikation ergänzen.
Prompt v3.0: Einmal Prompt mit alles!
Die finale Version enthält alles: Vollständiges Schema, Detailansicht, Routing, Repository-Code, CSS-Klassen und 22 messbare Akzeptanzkriterien.
Änderungen von v2.0 zu v3.0
- Belegnachweis: SQL-Counts und Status-Verteilung dokumentiert
- Detailansicht: Alle Felder mit Layout-Definition
- Routing: Exakte .htaccess Regeln
- Repository: Vollständiger PHP-Code als Referenz
- HTMX-Endpoint: Komplette list.php Implementation
- Negative Definition: Felder die NICHT angezeigt werden
- Akzeptanzkriterien: 22 testbare Punkte
prompt-v3.0.txt (ca. 400 Zeilen)
Prompt v3.0 anzeigen
================================================================================
PROMPT: LEXOFFICE RECHNUNGEN INTEGRATION - FREUND GUI
================================================================================
Version: 3.0 (Alle Supervision-Punkte behoben)
Projekt: schnuedeldue.oerg
Datum: 2026-01-06
================================================================================
0. BELEGNACHWEIS (verifiziert via db_sql am 2026-01-06)
================================================================================
COUNTS:
SELECT COUNT(*) FROM freund_lexoffice_karlscore.invoices; -- 2555
SELECT COUNT(*) FROM freund_lexoffice_369wohlbefinden.invoices; -- 808
STATUS-WERTE:
karlscore: draft=4, open=2354, paid=24, voided=173
369wohlbefinden: open=798, paid=3, voided=7
================================================================================
1. AUFGABENBESCHREIBUNG
================================================================================
Implementiere zwei neue Admin-Seiten:
/admin/rechnungen/karlscore - Rechnungen der karlsCORE GmbH
/admin/rechnungen/369wohlbefinden - Rechnungen der 369 Wohlbefinden GmbH
================================================================================
2. DATENQUELLEN UND SCHEMA
================================================================================
2.1 SCHEMA (35 Spalten, vollständig dokumentiert)
-------------------------------------------------
id VARCHAR(36) PRIMARY KEY NOT NULL
voucher_number VARCHAR(50) NULL
voucher_date DATE NULL
voucher_status VARCHAR(20) NULL -- draft, open, paid, voided
due_date DATE NULL -- NULL bei draft
address_name VARCHAR(255) NULL
address_supplement VARCHAR(255) NULL
address_street VARCHAR(255) NULL
address_zip VARCHAR(20) NULL
address_city VARCHAR(100) NULL
total_net_amount DECIMAL(12,2) NULL
total_gross_amount DECIMAL(12,2) NULL
total_tax_amount DECIMAL(12,2) NULL
line_items LONGTEXT NULL -- JSON Array
payment_term_label VARCHAR(500) NULL
payment_term_duration INT NULL
title VARCHAR(255) NULL
archived TINYINT DEFAULT 0
...
2.2 line_items JSON-SCHEMA
--------------------------
{
"name": "Produktname",
"description": "Beschreibung",
"quantity": 12,
"unitName": "Monate",
"unitPrice": { "netAmount": 3.00 },
"discountPercentage": 0,
"lineItemAmount": 36.00
}
2.3 FELDER DIE NICHT ANGEZEIGT WERDEN (Negativ-Definition)
----------------------------------------------------------
organization_id, version, language, tax_amounts, tax_type,
shipping_date, shipping_type, shipping_end_date, introduction,
remark, related_vouchers, closing_invoice, synced_at, raw_json
================================================================================
3. UI-ANFORDERUNGEN
================================================================================
3.1 LAYOUT [CRITICAL]
---------------------
- KEINE Cards - Direkte Tabellenansicht
- Mobile-First - Responsive ab 320px
- HTMX - Alle Interaktionen ohne Page Reload
3.3 TABELLEN-SPALTEN (6)
------------------------
Nr. | voucher_number | String | Sortierbar
Datum | voucher_date | DD.MM.YYYY | Sortierbar (Default DESC)
Kunde | address_name | max 40 Zeichen | Sortierbar
Betrag | total_gross_amount | X.XXX,XX € | Sortierbar
Status | voucher_status | Badge | Sortierbar
Fällig | due_date | DD.MM.YYYY oder "-" | Sortierbar
3.5 OVERDUE-LOGIK
-----------------
Überfällig wenn ALLE erfüllt:
1. voucher_status = 'open'
2. due_date IS NOT NULL
3. due_date < CURDATE()
3.6 STATUS-BADGE-FARBEN
-----------------------
draft = grau (#6c757d)
open = blau (#0d6efd)
paid = grün (#198754)
voided = rot (#dc3545)
3.7 DETAILANSICHT
-----------------
Route: /admin/rechnungen/{account}/{id}
KOPFDATEN: voucher_number, voucher_date, voucher_status, due_date, title
KUNDE: address_name, address_supplement, address_street, address_zip, address_city
POSITIONEN: Tabelle aus line_items (name, description, quantity, unitPrice, discount, amount)
SUMMEN: total_net_amount, total_tax_amount, total_gross_amount
ZAHLUNGSBEDINGUNGEN: payment_term_label, payment_term_duration
3.8 PAGINATION
--------------
25 Einträge pro Seite (fix)
Bei <= 7 Seiten: [1] [2] [3] [4] [5] [6] [7]
Bei > 7 Seiten: [1] ... [4] [5] [6] ... [20]
3.9 META-ANZEIGE (PFLICHT)
--------------------------
"{von}-{bis} von {gesamt} Rechnungen"
================================================================================
4. TECHNISCHE IMPLEMENTIERUNG
================================================================================
4.1 DATEI-STRUKTUR
------------------
public/admin/rechnungen/karlscore.php
public/admin/rechnungen/369wohlbefinden.php
public/admin/rechnungen/detail.php
public/htmx/invoices/list.php
src/Repositories/Lexoffice/InvoiceRepository.php
4.2 ROUTING (.htaccess)
-----------------------
RewriteRule ^admin/rechnungen/karlscore/?$ /admin/rechnungen/karlscore.php [L,QSA]
RewriteRule ^admin/rechnungen/369wohlbefinden/?$ /admin/rechnungen/369wohlbefinden.php [L,QSA]
RewriteRule ^admin/rechnungen/(karlscore|369wohlbefinden)/([a-f0-9-]{36})$ /admin/rechnungen/detail.php?account=$1&id=$2 [L,QSA]
RewriteRule ^htmx/invoices/list$ /htmx/invoices/list.php [L,QSA]
4.3 DATABASE CONFIG
-------------------
"lexoffice_karlscore": { "database": "freund_lexoffice_karlscore", "readonly": true }
"lexoffice_369wohlbefinden": { "database": "freund_lexoffice_369wohlbefinden", "readonly": true }
4.5 REPOSITORY (vollständiger Code)
-----------------------------------
[~150 Zeilen PHP-Code mit list(), count(), findById()]
4.6 HTMX ENDPOINT (vollständiger Code)
--------------------------------------
[~180 Zeilen PHP-Code mit Tabellen-Rendering, Pagination, Filter]
================================================================================
8. AKZEPTANZKRITERIEN (22 Punkte)
================================================================================
ROUTING:
[ ] /admin/rechnungen/karlscore erreichbar
[ ] /admin/rechnungen/369wohlbefinden erreichbar
[ ] Detail-URLs funktionieren
TABELLE:
[ ] 6 Spalten korrekt
[ ] Alle Spalten sortierbar
[ ] Default-Sortierung: Datum DESC
[ ] Überfällige Rechnungen rot markiert
FILTER:
[ ] Textsuche funktioniert
[ ] Status-Filter funktioniert
[ ] Datumsbereich funktioniert
[ ] Archived immer ausgeschlossen
PAGINATION:
[ ] 25 pro Seite
[ ] Seitennavigation korrekt
[ ] Meta-Anzeige vorhanden
DETAIL:
[ ] Alle Kopfdaten angezeigt
[ ] Kundenadresse vollständig
[ ] Positionen-Tabelle korrekt
[ ] Summen korrekt formatiert
RESPONSIVE:
[ ] 320px funktioniert
[ ] 768px funktioniert
[ ] 1200px funktioniert
HURRA! Der Supervision sind die Worte ausgegangen!
Das bedeutet: Alle Lücken im definierten Rahmen sind geschlossen und der Prompt ist bereit zur Ausführung!
supervision-approved.txt
Supervision-Bestätigung anzeigen
SUPERVISION ANALYSIS - Prompt v3.0
==================================
STATUS: ✓ APPROVED
PRÜFPUNKTE:
✓ Schema vollständig dokumentiert
✓ Belegnachweis mit echten Daten
✓ Spaltenauswahl explizit
✓ Status-Logik eindeutig
✓ Overdue-Bedingungen exakt
✓ Detailansicht vollständig definiert
✓ Routing-Regeln spezifiziert
✓ Repository-Code als Referenz
✓ HTMX-Endpoint als Referenz
✓ Negative Definition vorhanden
✓ Akzeptanzkriterien messbar
EMPFEHLUNG: Prompt zur Ausführung freigeben.
Task-Erstellung und Delegation
Der Prompt wurde in 11 atomare Sub-Tasks zerlegt und einem Master-Task zugeordnet. Diese Struktur ermöglicht parallele Bearbeitung und klare Fortschrittsverfolgung.
Sub-Tasks (1836-1846)
| ID | Beschreibung | Priorität |
|---|---|---|
| 1836 | databases.json erweitern | 1 |
| 1837 | .htaccess Routing | 2 |
| 1838 | Container.php erweitern | 3 |
| 1839 | InvoiceRepository erstellen | 4 |
| 1840 | Admin karlscore.php | 5 |
| 1841 | Admin 369wohlbefinden.php | 5 |
| 1842 | Detail-Ansicht detail.php | 6 |
| 1843 | HTMX list.php Fragment | 7 |
| 1844 | Navigation erweitern | 8 |
| 1845 | CSS-Styles | 8 |
| 1846 | Akzeptanztest | 9 |
Master-Task 1847
- Typ: master_task
- Sub-Tasks: 1836-1846 (11 Tasks)
- Delegation: Handoff an externe KI
- Status-Tracking: ki_protokoll.ki_tasks
Handoff-Mechanismus
Der Master-Task wurde mit Status pending und allen Kontext-Informationen an eine externe KI-Session übergeben. Die supervisierende KI (claude-code) beobachtet nur, greift nicht ein.
Task 1836: databases.json erweitern
Der erste Sub-Task legt die Grundlage: Die Datenbank-Verbindungen für beide Lexoffice-Accounts müssen in der zentralen Konfiguration registriert werden.
Aufgabe
Die Datei config/databases.json enthält alle verfügbaren Datenbankverbindungen. Für die Lexoffice-Integration müssen zwei neue readonly-Connections hinzugefügt werden.
task-1836-databases.txt
Vollständiger Task-Prompt anzeigen
LEXOFFICE-1: databases.json erweitern
DATEI: config/databases.json
ÄNDERUNG: Unter "connections" hinzufügen:
"lexoffice_karlscore": {
"driver": "mysql",
"host": "localhost",
"database": "freund_lexoffice_karlscore",
"charset": "utf8mb4",
"collation": "utf8mb4_unicode_ci",
"readonly": true,
"user": "lexoffice_sync",
"pass": "Lex0ff1ce_Sync_2026!"
},
"lexoffice_369wohlbefinden": {
"driver": "mysql",
"host": "localhost",
"database": "freund_lexoffice_369wohlbefinden",
"charset": "utf8mb4",
"collation": "utf8mb4_unicode_ci",
"readonly": true,
"user": "lexoffice_sync",
"pass": "Lex0ff1ce_Sync_2026!"
}
WICHTIG:
- readonly: true (nur Lesezugriff)
- Credentials aus bestehendem Lexoffice-Sync übernehmen
- JSON-Syntax validieren nach Änderung
Ergebnis
Nach Abschluss stehen die Verbindungen lexoffice_karlscore und lexoffice_369wohlbefinden im Container zur Verfügung.
Task 1837: .htaccess erweitern
Die Routing-Regeln bestimmen, welche URLs auf welche PHP-Dateien gemappt werden. Ohne diese Regeln wären die neuen Admin-Seiten nicht erreichbar.
Aufgabe
Vier neue RewriteRules für die Rechnungs-URLs: Zwei für die Listenansichten, eine für die Detailansicht mit UUID-Validierung, eine für den HTMX-Endpoint.
task-1837-htaccess.txt
Vollständiger Task-Prompt anzeigen
LEXOFFICE-2: .htaccess erweitern
DATEI: public/.htaccess
EINFÜGEN VOR der Catch-All-Regel:
# Admin Rechnungen routes
RewriteRule ^admin/rechnungen/karlscore/?$ /admin/rechnungen/karlscore.php [L,QSA]
RewriteRule ^admin/rechnungen/369wohlbefinden/?$ /admin/rechnungen/369wohlbefinden.php [L,QSA]
RewriteRule ^admin/rechnungen/(karlscore|369wohlbefinden)/([a-f0-9-]{36})$ /admin/rechnungen/detail.php?account=$1&id=$2 [L,QSA]
# HTMX Invoices
RewriteRule ^htmx/invoices/list$ /htmx/invoices/list.php [L,QSA]
DETAILS:
- [L,QSA] = Last rule, Query String Append
- UUID-Pattern: [a-f0-9-]{36} für Lexoffice-IDs
- account wird als GET-Parameter übergeben
- Optional trailing slash mit /?$
Ergebnis
URLs wie /admin/rechnungen/karlscore/abc-123-def werden korrekt auf detail.php?account=karlscore&id=abc-123-def geroutet.
Task 1838: Container.php erweitern
Der DI-Container ist das Herzstück der Dependency Injection. Die neue Factory-Methode ermöglicht typsicheren Zugriff auf das InvoiceRepository.
Aufgabe
Eine neue Methode getLexofficeInvoiceRepository(string $account) hinzufügen, die das Repository mit der richtigen Datenbankverbindung instantiiert.
task-1838-container.txt
Vollständiger Task-Prompt anzeigen
LEXOFFICE-3: Container.php erweitern
DATEI: src/Container.php
IMPORT HINZUFÜGEN:
use Freund\Repositories\Lexoffice\InvoiceRepository;
METHODE HINZUFÜGEN:
/**
* Get Lexoffice Invoice Repository for specific account
*
* @param string $account 'karlscore' or '369wohlbefinden'
* @return InvoiceRepository
*/
public function getLexofficeInvoiceRepository(string $account): InvoiceRepository
{
$key = "lexoffice_invoice_repo_{$account}";
return $this->resolve($key, function () use ($account) {
$connName = InvoiceRepository::getConnectionName($account);
$pdo = $this->createConnection($connName, true); // readonly
return new InvoiceRepository($pdo, $account);
});
}
DETAILS:
- Lazy Loading via resolve()
- Separate Instanz pro Account (karlscore vs 369)
- Readonly-Connection erzwingen
- Type Hints für IDE-Unterstützung
Ergebnis
Aufruf: $container->getLexofficeInvoiceRepository('karlscore') liefert ein konfiguriertes Repository.
Task 1839: InvoiceRepository erstellen
Das Repository kapselt alle Datenbankzugriffe. Es implementiert die Methoden für Listenabfrage, Zählung und Einzelabruf: jeweils mit Filterung und Sortierung.
Aufgabe
Neue Klasse InvoiceRepository mit drei Hauptmethoden: list(), count(), findById(). Inklusive Whitelist-Validierung für Sortierung und Account-Mapping.
task-1839-repository.txt
Vollständiger Task-Prompt anzeigen
LEXOFFICE-4: InvoiceRepository.php erstellen
DATEI: src/Repositories/Lexoffice/InvoiceRepository.php
STRUKTUR:
- Namespace: Freund\Repositories\Lexoffice
- Constructor: PDO $pdo, string $account
- Static: getConnectionName(string $account): string
- Static: getAllowedAccounts(): array
METHODEN:
list(array $filters, string $sort, string $order, int $limit, int $offset): array
- Filter: q (Textsuche), status, date_from, date_to
- Textsuche: LOWER() für case-insensitive
- Whitelist: ALLOWED_SORTS für SQL-Injection-Schutz
- Immer: WHERE archived = 0
count(array $filters): int
- Gleiche Filter wie list()
- Für Pagination-Berechnung
findById(string $id): ?array
- UUID-Validierung: preg_match('/^[a-f0-9-]{36}$/', $id)
- Return null wenn nicht gefunden
SICHERHEIT:
- Prepared Statements überall
- bindValue mit expliziten Types
- Whitelist für ORDER BY
Ergebnis
Ein vollständiges Repository mit ~150 Zeilen, das alle CRUD-Read-Operationen für Rechnungen abdeckt.
Task 1840: karlscore.php erstellen
Die Admin-Seite für karlsCORE GmbH. Sie enthält das HTML-Gerüst mit Filtern und einem leeren Container, der per HTMX die Tabelle lädt.
Aufgabe
Vollständige Admin-Seite mit Header, Navigation, Filter-Formular und HTMX-Target für die Rechnungstabelle.
task-1840-karlscore.txt
Vollständiger Task-Prompt anzeigen
LEXOFFICE-5: karlscore.php erstellen
DATEI: public/admin/rechnungen/karlscore.php
STRUKTUR:
1. Auth-Check (mit isLocalAccess() Bypass)
2. Layout-Header einbinden
3. Filter-Formular:
- Textsuche (input type="search")
- Status-Dropdown (Alle, Entwurf, Offen, Bezahlt, Storniert)
- Datum von/bis (type="date")
4. HTMX-Container:
- id="invoices-table"
- hx-get="/htmx/invoices/list?account=karlscore"
- hx-trigger="load"
5. Layout-Footer einbinden
FILTER-FORMULAR:
<form class="invoice-filters">
<input type="search" name="q" placeholder="Suche..."
hx-get="/htmx/invoices/list?account=karlscore"
hx-trigger="keyup changed delay:300ms"
hx-target="#invoices-table"
hx-include=".invoice-filters">
<select name="status" hx-get="..." hx-trigger="change">
<option value="">Alle Status</option>
<option value="draft">Entwurf</option>
<option value="open">Offen</option>
<option value="paid">Bezahlt</option>
<option value="voided">Storniert</option>
</select>
<input type="date" name="date_from" hx-get="..." hx-trigger="change">
<input type="date" name="date_to" hx-get="..." hx-trigger="change">
</form>
HTMX-TARGET:
<div id="invoices-table"
hx-get="/htmx/invoices/list?account=karlscore"
hx-trigger="load">
Lade Rechnungen...
</div>
Ergebnis
Vollständige Admin-Seite unter /admin/rechnungen/karlscore, die beim Laden automatisch die Tabelle via HTMX abruft.
Task 1841: 369wohlbefinden.php erstellen
Die Admin-Seite für 369 Wohlbefinden GmbH. Strukturell identisch zu karlscore.php, nur mit anderem Account-Parameter.
Aufgabe
Kopie von karlscore.php mit angepasstem Account-Parameter. Der HTMX-Endpoint unterscheidet anhand des account-Parameters, welche Datenbank abgefragt wird.
task-1841-369wohlbefinden.txt
Vollständiger Task-Prompt anzeigen
LEXOFFICE-6: 369wohlbefinden.php erstellen
DATEI: public/admin/rechnungen/369wohlbefinden.php
ÄNDERUNGEN gegenüber karlscore.php:
- $account = '369wohlbefinden'
- Alle hx-get URLs: account=369wohlbefinden
- Seiten-Titel: "Rechnungen 369 Wohlbefinden"
REST IDENTISCH:
- Auth-Check
- Filter-Formular
- HTMX-Container
- Layout-Includes
BEISPIEL hx-get:
hx-get="/htmx/invoices/list?account=369wohlbefinden"
Ergebnis
Zweite Admin-Seite unter /admin/rechnungen/369wohlbefinden mit Zugriff auf die 369-Datenbank.
Task 1842: detail.php erstellen
Die Detailansicht zeigt alle Informationen einer einzelnen Rechnung: Kopfdaten, Kunde, Positionen, Summen und Zahlungsbedingungen.
Aufgabe
Eine shared Detailseite, die beide Accounts bedient. Der Account wird via GET-Parameter übergeben, die UUID validiert und die Rechnung aus dem Repository geladen.
task-1842-detail.txt
Vollständiger Task-Prompt anzeigen
LEXOFFICE-7: detail.php erstellen
DATEI: public/admin/rechnungen/detail.php
PARAMETER:
- $_GET['account'] - 'karlscore' oder '369wohlbefinden'
- $_GET['id'] - UUID der Rechnung
VALIDIERUNG:
- Account gegen Whitelist prüfen
- UUID-Format: preg_match('/^[a-f0-9-]{36}$/', $id)
- 404 wenn Rechnung nicht gefunden
ANZEIGE-BEREICHE:
1. BREADCRUMB:
Rechnungen karlsCORE > RE-2024-0001
2. KOPFDATEN:
- Rechnungsnummer (h1)
- Status-Badge
- Rechnungsdatum
- Fälligkeit
- Titel (falls vorhanden)
3. KUNDE:
- address_name
- address_supplement (falls vorhanden)
- address_street
- address_zip + address_city
- address_country_code (nur wenn nicht 'DE')
4. POSITIONEN (Tabelle):
| Pos | Bezeichnung | Menge | Einheit | Einzelpreis | Rabatt | Gesamt |
- line_items JSON parsen
- description unter name (klein, grau)
- Rabatt nur anzeigen wenn > 0
5. SUMMEN:
- Netto: total_net_amount
- MwSt: total_tax_amount
- Brutto: total_gross_amount (fett)
6. ZAHLUNGSBEDINGUNGEN:
- payment_term_label
- payment_term_duration (Tage)
7. AKTIONEN:
- Button "PDF herunterladen" (disabled, title="Funktion folgt")
- Link "Zurück zur Liste"
Ergebnis
Vollständige Detailansicht mit allen Rechnungsinformationen und Breadcrumb-Navigation zurück zur Liste.
Task 1843: HTMX list.php erstellen
Der HTMX-Endpoint ist das Herzstück der Interaktivität. Er rendert die Tabelle als HTML-Fragment und wird bei jeder Filter-, Sortier- oder Paginierungsaktion aufgerufen.
Aufgabe
Ein reines HTML-Fragment (kein vollständiges Dokument) mit Tabelle, Pagination und Meta-Anzeige. Reagiert auf GET-Parameter für Filter, Sortierung und Seite.
task-1843-htmx-list.txt
Vollständiger Task-Prompt anzeigen
LEXOFFICE-8: list.php (HTMX) erstellen
DATEI: public/htmx/invoices/list.php
HEADER:
header('Content-Type: text/html; charset=utf-8');
PARAMETER:
- account (required, whitelist-validiert)
- q (optional, Textsuche)
- status (optional, draft|open|paid|voided)
- date_from, date_to (optional, DATE)
- sort (optional, default: voucher_date)
- order (optional, default: desc)
- page (optional, default: 1)
ABLAUF:
1. Auth-Check (mit isLocalAccess() Bypass)
2. Account validieren
3. Repository via Container holen
4. Filter/Sort/Page aus GET
5. list() + count() aufrufen
6. HTML rendern
TABELLE:
- Sortierbare Header mit hx-get Links
- Zeilen mit Overdue-Klasse
- Status-Badges
- Detail-Links
PAGINATION:
- Nur wenn totalPages > 1
- Algorithmus: Erste, aktuelle±1, Letzte
- hx-get mit hx-include für Filter
META:
echo '<p class="table-meta">' . $from . '-' . $to . ' von ' . $total . ' Rechnungen</p>';
FUNKTIONEN:
- renderInvoiceRow(array $item, string $account): void
- renderPagination(...): void
- renderPageLink(...): void
Ergebnis
~180 Zeilen PHP, die bei jedem HTMX-Request ein aktualisiertes Tabellen-Fragment liefern.
Task 1844: Navigation erweitern
Die Haupt-Navigation muss um zwei neue Einträge ergänzt werden, damit Benutzer die Rechnungsseiten erreichen können.
Aufgabe
Im $navItems-Array zwei neue Einträge für die Rechnungs-Admin-Seiten hinzufügen.
task-1844-nav.txt
Vollständiger Task-Prompt anzeigen
LEXOFFICE-9: nav.php erweitern
DATEI: templates/layout/nav.php
IM $navItems ARRAY HINZUFÜGEN:
$navItems = [
'dashboard' => 'Übersicht',
'emails' => 'E-Mails',
'wissen' => 'Wissen',
'aufgaben' => 'Aufgaben',
'regeln' => 'Regeln',
'autorenprofil' => 'Profil',
'dokumente' => 'Dokumente',
'suche' => 'Suche',
'contacts' => 'Kontakte',
// NEU: Admin-Rechnungen
'admin/rechnungen/karlscore' => 'RE karlsCORE',
'admin/rechnungen/369wohlbefinden' => 'RE 369',
];
KURZFORM:
- "RE" steht für "Rechnungen"
- Kompakte Labels für Navigation
- Sortierung: Nach bestehenden Einträgen
Ergebnis
Die Navigation zeigt jetzt "RE karlsCORE" und "RE 369" als klickbare Links.
Task 1845: CSS-Styles erweitern
Neue CSS-Klassen für Status-Badges, Überfällig-Markierung und Pagination. Die Styles nutzen CSS Custom Properties für konsistente Farben.
Aufgabe
In app.css die Styles für .status-*, .row-overdue, .cell-due und Pagination-Elemente hinzufügen.
task-1845-css.txt
Vollständiger Task-Prompt anzeigen
LEXOFFICE-10: app.css erweitern
DATEI: public/assets/css/app.css
STATUS-BADGES:
.status-draft {
background: #6c757d;
color: white;
padding: 0.2rem 0.5rem;
border-radius: 3px;
font-size: 0.85em;
}
.status-open { background: #0d6efd; color: white; ... }
.status-paid { background: #198754; color: white; ... }
.status-voided { background: #dc3545; color: white; ... }
OVERDUE-STYLES:
.row-overdue {
background-color: rgba(220, 53, 69, 0.1);
}
.row-overdue .cell-due {
color: #dc3545;
font-weight: 600;
}
PAGINATION:
.pagination ul {
list-style: none;
display: flex;
gap: 0.25rem;
padding: 0;
margin: 1rem 0;
}
.pagination li a {
display: block;
padding: 0.5rem 0.75rem;
border: 1px solid var(--border-color);
border-radius: 3px;
text-decoration: none;
}
.pagination li.active a {
background: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
.pagination li.disabled span {
color: var(--text-muted);
cursor: not-allowed;
}
TABLE-META:
.table-meta {
color: var(--text-muted);
font-size: 0.875rem;
margin-top: 0.5rem;
}
Ergebnis
~85 neue Zeilen CSS für konsistentes Styling der Rechnungstabelle.
Task 1846: Akzeptanztest durchführen
Der finale Task: Alle 22 Akzeptanzkriterien aus dem Prompt systematisch prüfen und dokumentieren.
Aufgabe
HTTP-Tests für alle Endpoints, manuelle Prüfung der UI-Funktionen, Responsive-Tests auf verschiedenen Viewports.
task-1846-akzeptanztest.txt
Vollständiger Task-Prompt anzeigen
LEXOFFICE-11: Akzeptanztest
ROUTING-TESTS:
curl -s -o /dev/null -w "%{http_code}" https://schnuedeldue.oerg/admin/rechnungen/karlscore
# Erwartung: 200
curl -s -o /dev/null -w "%{http_code}" https://schnuedeldue.oerg/admin/rechnungen/369wohlbefinden
# Erwartung: 200
curl -s -o /dev/null -w "%{http_code}" https://schnuedeldue.oerg/htmx/invoices/list?account=karlscore
# Erwartung: 200
curl -s -o /dev/null -w "%{http_code}" https://schnuedeldue.oerg/htmx/invoices/list?account=invalid
# Erwartung: 400
TABELLEN-TESTS:
[ ] 6 Spalten: Nr., Datum, Kunde, Betrag, Status, Fällig
[ ] Sortierung: Klick auf Header ändert Sortierung
[ ] Default: Nach Datum absteigend
[ ] Overdue: Rote Markierung bei überfälligen Rechnungen
FILTER-TESTS:
[ ] Textsuche: Eingabe filtert Tabelle
[ ] Status: Dropdown ändert Ergebnisse
[ ] Datum: Von/Bis grenzt Zeitraum ein
[ ] Reset: Filter-Änderung setzt Seite auf 1
PAGINATION-TESTS:
[ ] 25 Einträge pro Seite
[ ] Navigation: Erste/Letzte Seite
[ ] Meta: "1-25 von 2555 Rechnungen"
DETAIL-TESTS:
[ ] Klick öffnet Detailansicht
[ ] Alle Kopfdaten vorhanden
[ ] Kundenadresse vollständig
[ ] Positionen-Tabelle korrekt
[ ] Summen formatiert
[ ] Zurück-Link funktioniert
RESPONSIVE-TESTS (Chromium Screenshot):
[ ] 320px: Mobil-Layout
[ ] 768px: Tablet-Layout
[ ] 1200px: Desktop-Layout
Ergebnis
Alle 22 Kriterien bestanden. Dokumentation der Testergebnisse im Task-Log.
Gate-System: Architektur-Überblick
Das Gate-System ist das Qualitätssicherungs-Framework, das jeden KI-generierten Code überwacht. Es besteht aus vier Hauptkomponenten, die ineinandergreifen.
Die vier Säulen
| Komponente | Anzahl | Funktion |
|---|---|---|
| Contracts | 16 | Regelsammlungen für spezifische Domänen |
| Rules | 162 | Einzelne Prüfregeln mit Severity-Level |
| Hooks | 20 | Event-Handler für Tool-Aufrufe |
| Transitions | – | Zustandsübergänge und Gates |
gate-system --architecture
Architektur-Diagramm anzeigen
┌─────────────────────────────────────────────────────────────────┐
│ GATE SYSTEM │
└─────────────────────────────────────────────────────────────────┘
┌─────────────┐
│ Claude │
│ Code │
└──────┬──────┘
│
Tool-Aufruf (Edit, Write, Bash...)
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ HOOK DISPATCHER │
│ bootstrap.py → Analysiert Event-Typ und Tool-Namen │
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ PreToolUse │ │ PostToolUse │ │ SessionStart │
│ - Validierung │ │ - Nachbereitung │ │ - Initialisierung│
│ - Blockierung │ │ - Analyse │ │ - Task-Loader │
│ - Guidance │ │ - Permissions │ │ - Context │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ CONTRACT VALIDATOR │
│ Lädt relevante Contracts → Prüft gegen 162 Rules │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
CRITICAL MAJOR MINOR
→ BLOCK → WARN → INFO
Datenspeicherung
- Contracts & Rules:
masterdev.contracts,masterdev.contract_rules - Hook-Logs:
ki_protokoll.hook_log - Validierungen:
ki_protokoll.contract_validation_log - Code-Entities:
masterdev.code_entities+ Qdrant
Ablauf bei jedem Tool-Aufruf
- PreToolUse: Validierung gegen Contracts, ggf. Blockierung
- Tool-Ausführung: Nur wenn PreToolUse passiert
- PostToolUse: Permissions, Code-Analyse, Logging
- Feedback: Zurück an Claude Code als System-Reminder
Contracts: Die 16 Regelwerke
Ein Contract ist eine Sammlung thematisch zusammengehöriger Regeln. Jeder Contract definiert, was in seinem Geltungsbereich erlaubt, verboten oder empfohlen ist.
Aktive Contracts (Stand: 2026-01-06)
| Contract | Rules | Critical | Major | Minor |
|---|---|---|---|---|
| API Response Contract | 5 | 3 | 2 | 0 |
| Architecture Gate Contract | 6 | 5 | 1 | 0 |
| Code Quality Standards | 10 | 4 | 5 | 1 |
| CSS Contract | 6 | 2 | 4 | 0 |
| DB Access Security Protocol | 8 | 8 | 0 | 0 |
| HTML Tables Contract | 13 | 7 | 5 | 1 |
| HTMX Patterns Contract | 12 | 8 | 3 | 1 |
| JS Browser Architecture | 12 | 9 | 3 | 0 |
| Karl Kratz Content Style | 6 | 0 | 4 | 2 |
| Karl Kratz Glossar-Eintrag | 13 | 2 | 9 | 2 |
| Karl Kratz Technischer Beitrag | 26 | 3 | 13 | 10 |
| Layered Architecture Validation | 9 | 9 | 0 | 0 |
| Python Pipeline Contract | 12 | 7 | 3 | 2 |
| Task Prompt Standards | 6 | 4 | 2 | 0 |
| Text Quality Standards | 10 | 5 | 5 | 0 |
| View Structure Contract | 8 | 7 | 1 | 0 |
| Gesamt | 162 | 83 | 60 | 19 |
contract_text_validator list
Contract-Kategorien anzeigen
CODE-QUALITÄT (4 Contracts, 41 Rules)
├── code-quality-standards Controller-Patterns, DI, Error-Handling
├── architecture-gate-contract Layered Architecture, Dependency Rules
├── layered-architecture Verzeichnisstruktur, Namespace-Checks
└── view-structure-contract CRUD-Views, Template-Patterns
FRONTEND (4 Contracts, 43 Rules)
├── css-contract Custom Properties, keine Inline-Styles
├── html-tables-contract Semantic HTML, Accessibility
├── htmx-patterns-contract hx-Attribute, Endpoint-Patterns
└── js-browser-architecture ES6 Modules, Event-Handling
API & DATEN (3 Contracts, 25 Rules)
├── api-response-contract JSON-Struktur, HTTP-Codes
├── db-access-security Prepared Statements, keine Raw-SQL
└── python-pipeline-contract Pipeline-Patterns, Error-Handling
CONTENT & TEXT (3 Contracts, 45 Rules)
├── karl-kratz-content Autorenprofil, Relativierung
├── karl-kratz-glossar 150-300 Wörter, Definition + Beispiele
└── karl-kratz-technischer Didaktik, Code-Beispiele, Boxen
PROZESS (2 Contracts, 16 Rules)
├── task-prompt-standards Prompt-Struktur, Akzeptanzkriterien
└── text-quality-standards Rechtschreibung, Stil, Formatierung
Relevante Contracts für diese Session
Bei der Lexoffice-Integration wurden folgende Contracts aktiv geprüft:
- Code Quality Standards: 10 Rules für PHP-Code
- HTMX Patterns Contract: 12 Rules für HTMX-Endpoints
- CSS Contract: 6 Rules für Styles
- DB Access Security: 8 Rules für Repository-Pattern
Contract-Struktur (YAML)
Jeder Contract ist in YAML definiert und enthält Metadaten, Schwellwerte und Regelreferenzen:
code-quality-standards.yaml (Auszug)
Beispiel-Contract anzeigen
contract_key: code-quality-standards
name: Code Quality Standards
version: 2.1
scope: global
thresholds:
pass_critical: 0 # Keine Critical-Violations erlaubt
pass_major: 2 # Max 2 Major-Violations
pass_minor: 5 # Max 5 Minor-Violations
rules:
- id: CQS-001
severity: critical
type: forbidden
pattern: "\\$_GET|\\$_POST|\\$_REQUEST"
message: "Direkter Superglobal-Zugriff verboten"
- id: CQS-002
severity: major
type: forbidden
pattern: "echo.*<"
message: "Controller dürfen kein HTML ausgeben"
Rules: Die 162 Einzelregeln
Jede Rule hat eine eindeutige ID, ein Severity-Level und einen Pattern-Matcher. Bei Verletzung wird je nach Severity blockiert, gewarnt oder informiert.
Severity-Level
| Level | Anzahl | Aktion | Beispiel |
|---|---|---|---|
| CRITICAL | 83 | Blockiert Operation | SQL-Injection, direkter DB-Zugriff |
| MAJOR | 60 | Warnung + Guidance | Hardcoded URLs, fehlende Typen |
| MINOR | 19 | Info-Hinweis | Code-Style, Formatierung |
contract_text_validator rules -c code-quality-standards
Beispiel: Code Quality Standards (10 Rules)
CRITICAL (4 Rules) → Operation wird BLOCKIERT
CQ-SQL-001 Controller dürfen keine direkten SQL-Queries enthalten
Pattern: SELECT|INSERT|UPDATE|DELETE.*FROM in Controller
CQ-DI-001 Controller müssen DI nutzen, keine Static Facades
Pattern: ::getInstance()|new.*Repository()
PHP-ERR-001 Catch-Block mit nur return ohne Logging ist verboten
Pattern: catch.*\{.*return.*\} ohne Logger
PHP-ERR-002 @ Error Suppression ist verboten
Pattern: @\w+\(
───────────────────────────────────────────────────────────
MAJOR (5 Rules) → Warnung + Guidance
CQ-HTML-001 Controller dürfen kein HTML direkt ausgeben
Pattern: echo.*<|print.*<
CQ-HTTP-001 Controller müssen $this->notFound() nutzen
Pattern: http_response_code\(404\)
PHP-CFG-001 Hardcoded URLs sind verboten
Pattern: https?://[^"'\s]+(?!config)
PHP-CFG-002 Hardcoded localhost URLs sind verboten
Pattern: localhost:\d+|127\.0\.0\.1
PHP-KISS-001 Mehr als 5 Parameter → Builder-Pattern erwägen
Pattern: function.*\(.*,.*,.*,.*,.*,
───────────────────────────────────────────────────────────
MINOR (1 Rule) → Info-Hinweis
CQ-TYPE-001 Properties sollten PHP 8+ Typen haben
Pattern: private \$\w+ ohne Typ-Deklaration
Rule-Anatomie
Jede Rule besteht aus diesen Komponenten:
rule-anatomy.yaml
Rule-Struktur anzeigen
rule_id: CQ-SQL-001 # Eindeutige ID
rule_type: forbidden # forbidden | required | warning
severity: critical # critical | major | minor
pattern: "SELECT|INSERT|UPDATE|DELETE.*FROM"
pattern_type: regex # regex | contains | exact
applies_to:
extensions: [".php"]
paths: ["src/Controllers/*", "public/*"]
excludes: ["src/Repositories/*"]
message: "Controller dürfen keine direkten SQL-Queries enthalten"
fix_suggestion: |
Verschiebe die Query in ein Repository:
1. Erstelle src/Repositories/{Entity}Repository.php
2. Implementiere die Query als Methode
3. Injiziere das Repository via Container
guideline: |
Repositories kapseln Datenbankzugriffe und ermöglichen:
- Testbarkeit durch Mocking
- Wiederverwendbarkeit
- Zentrale Änderungen
Validierung in dieser Session
Bei der Lexoffice-Integration wurden alle 6 PHP-Dateien gegen relevante Rules geprüft:
- InvoiceRepository.php: 0 Violations (Repository-Pattern korrekt)
- list.php: 0 Violations (HTMX-Endpoint sauber)
- karlscore.php: 0 Violations (Admin-View konform)
- 369wohlbefinden.php: 0 Violations
- detail.php: 0 Violations
- Container.php: 0 Violations (DI-Pattern befolgt)
Ergebnis: 0 Critical, 0 Major, 0 Minor: Alle Gates passiert.
Hooks: Die 20 Event-Handler
Hooks sind Python-Module, die bei bestimmten Events ausgeführt werden. Sie validieren, transformieren oder protokollieren: je nach Typ vor oder nach der Tool-Ausführung.
Hook-Typen
| Event | Zeitpunkt | Typische Hooks |
|---|---|---|
SessionStart |
Sitzungsbeginn | task_loader, state_context, project_registry |
PreToolUse |
Vor Tool-Ausführung | contract_validator, permission_guard, state_write_guard |
PostToolUse |
Nach Tool-Ausführung | fix_permissions, code_analyzer, log_to_db |
UserPromptSubmit |
Nutzereingabe | text_contract_pre, phase_guidance |
Stop |
Antwort fertig | text_contract_post, task_reminder |
ls /var/www/tools/ki-protokoll/claude-hook/modules/
Alle 20 Hook-Module anzeigen
VALIDIERUNG & BLOCKIERUNG (PreToolUse)
├── contract_validator.py Prüft Code gegen Contracts
├── permission_guard.py Prüft Dateizugriffsrechte
├── state_write_guard.py Verhindert Schreiben in geschützte Pfade
├── block_direct_db.py Blockiert direkten MySQL-Zugriff
├── block_direct_qdrant.py Blockiert direkten Qdrant-Zugriff
├── docs_guardrail.py Verhindert /docs/*.md Erstellung
└── quality_gate.py Prüft Qualitäts-Schwellwerte
NACHBEREITUNG (PostToolUse)
├── fix_permissions.py Setzt www-data:www-data nach Edit/Write
├── code_analyzer.py Extrahiert Entities, queued Embeddings
├── file_backup.py Erstellt Backup vor Änderungen
├── log_to_db.py Protokolliert alle Tool-Aufrufe
└── quality_validator.py Validiert Ergebnis nach Ausführung
KONTEXT & GUIDANCE (SessionStart/UserPrompt)
├── task_loader.py Lädt offene Tasks aus ki_tasks
├── state_context.py Speichert Session-Zustand
├── project_registry.py Erkennt Projekt aus Pfad
├── contract_loader.py Lädt relevante Contracts
├── phase_guidance.py Gibt Phase-spezifische Hinweise
└── doku_awareness.py Zeigt zugehörige Dokumentation
CONTENT & TEXT (Stop/UserPrompt)
├── text_contract_pre.py Text-Contracts vor Generierung
├── text_contract_post.py Validiert generierten Text
├── doku_reminder.py Erinnert an Doku-Updates
├── task_reminder.py Erinnert an offene Tasks
└── gate_status.py Zeigt aktuellen Gate-Status
Hook-Ausführung in dieser Session
Während der Lexoffice-Implementation wurden folgende Hooks aktiv:
hook_log --session lexoffice-integration
Session-Hooks anzeigen
19:52:12 PostToolUse fix_permissions
InvoiceRepository.php → www-data:www-data ✓
19:52:13 PostToolUse code_analyzer
InvoiceRepository.php → 12 Entities extrahiert
Queued: 3 Methods, 2 Constants, 1 Class ✓
19:53:28 PreToolUse contract_validator
Container.php → code-quality-standards PASS
19:53:28 PostToolUse fix_permissions
Container.php → www-data:www-data ✓
19:54:15 PreToolUse permission_guard
.htaccess → Schreibzugriff erlaubt ✓
19:55:03 PreToolUse contract_validator
list.php → htmx-patterns-contract PASS
19:55:04 PostToolUse code_analyzer
list.php → 3 Functions extrahiert ✓
19:58:47 PreToolUse contract_validator
app.css → css-contract PASS
───────────────────────────────────────────────────────────
ZUSAMMENFASSUNG:
PreToolUse: 4 Validierungen, 0 Blockierungen
PostToolUse: 5 Permissions-Fixes, 2 Code-Analysen
Gesamt: 9 Hook-Ausführungen, alle erfolgreich
Hook-Anatomie (Python)
Jeder Hook ist ein Python-Modul mit einer run()-Funktion:
fix_permissions.py (vereinfacht)
Hook-Code anzeigen
"""PostToolUse Hook: Setzt Dateiberechtigungen nach Edit/Write"""
import os
import subprocess
EVENTS = ["PostToolUse"]
TOOLS = ["Edit", "Write"]
def run(context: dict) -> dict:
"""Wird nach jedem Edit/Write aufgerufen"""
file_path = context.get("file_path")
if not file_path or not os.path.exists(file_path):
return {"success": True}
# Setze www-data:www-data
subprocess.run([
"chown", "www-data:www-data", file_path
], check=True)
return {
"success": True,
"message": f"{file_path} → www-data:www-data"
}
Transitions: Zustandsübergänge und Gates
Transitions beschreiben die Zustandsübergänge während einer KI-Session. Jeder Übergang muss ein Gate passieren: eine Validierungsschwelle basierend auf den Contract-Ergebnissen.
Session-Phasen
| Phase | Beschreibung | Gate-Bedingung |
|---|---|---|
INIT |
Session-Start, Kontext laden | project_registry erfolgreich |
PLANNING |
Task analysieren, Approach planen | task_prompt_standards erfüllt |
IMPLEMENTING |
Code schreiben, Dateien ändern | Alle PreToolUse-Validierungen bestanden |
VALIDATING |
Tests, Code-Review, Contracts | 0 Critical, ≤2 Major Violations |
COMPLETE |
Task abgeschlossen | Alle Akzeptanzkriterien erfüllt |
gate-system --transitions lexoffice-session
Session-Flow anzeigen
LEXOFFICE-INTEGRATION SESSION FLOW
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
19:50:00 ● INIT
│ task_loader: Task 1847 geladen
│ project_registry: schnuedeldue.oerg
│ contract_loader: 4 Contracts aktiviert
│
▼ GATE PASSED (Kontext vollständig)
19:50:30 ● PLANNING
│ Prompt v3.0 analysiert
│ 11 Sub-Tasks identifiziert (1836-1846)
│ Abhängigkeiten gemappt
│
▼ GATE PASSED (task_prompt_standards: 0 Violations)
19:52:00 ● IMPLEMENTING
│
│ 19:52:12 Edit InvoiceRepository.php
│ └─ ✓ code-quality-standards
│ └─ ✓ db-access-security
│
│ 19:53:28 Edit Container.php
│ └─ ✓ architecture-gate-contract
│
│ 19:54:15 Edit .htaccess
│ └─ ✓ permission_guard
│
│ 19:55:03 Write list.php
│ └─ ✓ htmx-patterns-contract
│
│ 19:56:21 Write karlscore.php
│ 19:56:45 Write 369wohlbefinden.php
│ 19:57:32 Write detail.php
│ └─ ✓ view-structure-contract
│
│ 19:58:14 Edit nav.php
│ 19:58:47 Edit app.css
│ └─ ✓ css-contract
│
▼ GATE PASSED (9 Edits, 0 Blockierungen)
19:59:00 ● VALIDATING
│ code_analyzer: 6 Dateien analysiert
│ quality_validator: 833 LoC, 98.5% Score
│ contract_validator: 0 Critical, 0 Major, 0 Minor
│
▼ GATE PASSED (Alle Schwellwerte unterschritten)
20:00:00 ● COMPLETE
│ 22/22 Akzeptanzkriterien erfüllt
│ Task 1847 → Status: completed
│
└─ SESSION ERFOLGREICH
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ZUSAMMENFASSUNG:
Phasen durchlaufen: 5/5
Gates passiert: 5/5 ✓
Blockierungen: 0
Gesamtdauer: ~10 Minuten
Gate-Logik
Jedes Gate prüft die Ergebnisse der Contract-Validierung gegen definierte Schwellwerte:
gate-logic.py (Pseudocode)
Gate-Entscheidungslogik anzeigen
def check_gate(validation_result, contract):
"""Prüft ob Gate passiert werden kann"""
critical = validation_result.count_critical()
major = validation_result.count_major()
minor = validation_result.count_minor()
# Schwellwerte aus Contract laden
thresholds = contract.thresholds
if critical > thresholds.pass_critical: # Default: 0
return GateResult.BLOCKED
if major > thresholds.pass_major: # Default: 2
return GateResult.WARNING
if minor > thresholds.pass_minor: # Default: 5
return GateResult.INFO
return GateResult.PASSED
# Ergebnis-Behandlung
match gate_result:
case GateResult.BLOCKED:
# Operation abbrechen, Fehler zurückgeben
raise GateBlockedException(violations)
case GateResult.WARNING:
# Warnung in system-reminder, aber fortfahren
emit_warning(violations)
case GateResult.PASSED:
# Alles gut, keine Aktion nötig
pass
Was passiert bei Blockierung?
Wenn ein Gate eine Operation blockiert:
- Die Tool-Ausführung wird abgebrochen
- Claude Code erhält einen system-reminder mit:
- Welcher Contract verletzt wurde
- Welche spezifischen Rules
- Fix-Suggestions für jede Violation
- Claude Code muss den Code korrigieren und erneut versuchen
In dieser Session gab es keine Blockierungen: alle Gates wurden beim ersten Versuch passiert.
Gate-System-Interventionen
Das Gate-System überwacht alle Dateioperationen und greift bei Bedarf korrigierend ein. Während dieser Session wurden mehrere Hook-Aktivierungen protokolliert.
Aktive Hooks
| Hook | Typ | Funktion |
|---|---|---|
fix_permissions |
PostToolUse | Setzt www-data:www-data nach Edit/Write |
state_write_guard |
PreToolUse | Verhindert Schreiben in geschützte Pfade |
code_analyzer |
PostToolUse | Analysiert PHP-Dateien, aktualisiert Knowledge Base |
Protokollierte Eingriffe
| Zeit | Hook | Aktion |
|---|---|---|
| 19:52:12 | fix_permissions | InvoiceRepository.php → www-data:www-data |
| 19:52:13 | code_analyzer | InvoiceRepository analysiert, 12 Entities extrahiert |
| 19:55:03 | fix_permissions | list.php → www-data:www-data |
| 19:55:04 | code_analyzer | list.php analysiert, 3 Functions extrahiert |
| 19:58:47 | fix_permissions | app.css → www-data:www-data |
Keine Blockierungen
Während dieser Session wurden keine Operationen blockiert. Alle Schreibvorgänge erfolgten in erlaubten Pfaden und entsprachen den definierten Constraints.
Implementation-Monitoring
Die externe KI implementierte Task 1847 autonom. Die Beobachtung erfolgte über Datei-Monitoring und Log-Analyse ohne Eingriff.
Erstellte Dateien (Reihenfolge)
| Zeit | Datei | Größe |
|---|---|---|
| 19:52:12 | src/Repositories/Lexoffice/InvoiceRepository.php | 4.611 B |
| 19:53:28 | src/Container.php (modifiziert) | +20 Zeilen |
| 19:54:15 | public/.htaccess (modifiziert) | +3 Rules |
| 19:55:03 | public/htmx/invoices/list.php | 7.649 B |
| 19:56:21 | public/admin/rechnungen/karlscore.php | 2.841 B |
| 19:56:45 | public/admin/rechnungen/369wohlbefinden.php | 2.856 B |
| 19:57:32 | public/admin/rechnungen/detail.php | 5.123 B |
| 19:58:14 | templates/layout/nav.php (modifiziert) | +12 Zeilen |
| 19:58:47 | public/assets/css/app.css (modifiziert) | +85 Zeilen |
Beobachteter Fehler
In list.php wurde zunächst ein falscher Pfad verwendet:
// Fehlerhaft (19:55:03)
dirname(__DIR__, 2) . '/src/bootstrap.php'
// Korrekt (19:59:48 - autonome Korrektur)
dirname(__DIR__, 3) . '/src/bootstrap.php'
Die externe KI erkannte den Fehler beim ersten HTTP-Test und korrigierte ihn selbstständig.
Supervisor-Disziplin: Strikte Rollentrennung
Die beobachtende KI (claude-code) versuchte einmalig, den Pfad-Fehler zu korrigieren. Der menschliche Supervisor intervenierte mit einer klaren Rollendefinition:
„Die Supervisor-Instanz ist ausschließlich zur Beobachtung, Dokumentation und Erläuterung autorisiert. Aktive Eingriffe in den Implementierungsprozess sind untersagt."
Die unerlaubte Änderung wurde unmittelbar revertiert. Die externe KI korrigierte den Fehler 60 Sekunden später vollständig autonom, was die Validität des Supervision-Konzepts bestätigte.
Akzeptanztests
Nach Abschluss der Implementation wurden automatisierte HTTP-Tests durchgeführt, um alle Akzeptanzkriterien zu validieren.
Test-Suite
| Endpoint | Erwartung | Ergebnis |
|---|---|---|
| /admin/rechnungen/karlscore | HTTP 200 | PASS |
| /admin/rechnungen/369wohlbefinden | HTTP 200 | PASS |
| /htmx/invoices/list?account=karlscore | HTTP 200, HTML-Table | PASS |
| /htmx/invoices/list?account=369wohlbefinden | HTTP 200, HTML-Table | PASS |
| /admin/rechnungen/karlscore/{uuid} | HTTP 200, Detail-View | PASS |
| /htmx/invoices/list?account=invalid | HTTP 400 | PASS |
Funktionale Tests
- Sortierung: Alle 6 Spalten sortierbar (ASC/DESC)
- Filter: Freitext, Status, Datumsbereich funktional
- Pagination: Korrekt bei 25+ Einträgen
- Detail-Link: Navigation zur Detailansicht
- Breadcrumb: Zurück-Navigation vorhanden
Responsive-Test
Viewport-Tests bei 320px, 768px, 1200px bestanden. Tabelle scrollt horizontal auf kleinen Screens, Filter stacken vertikal.
Ergebnis und Screenshots
Die fertige Implementation zeigt eine vollständige Rechnungsverwaltung mit Listenansicht und Detailseiten.
Rechnungsliste

Listenansicht mit Sortierung, Filter und Pagination
Rechnungsdetail

Detailansicht mit Positionen, Summen und Kundenadresse
Code-Metriken
| Metrik | Wert |
|---|---|
| Neue Dateien | 6 |
| Modifizierte Dateien | 3 |
| Zeilen Code (neu) | ~450 |
| Zeilen CSS (neu) | ~85 |
| Implementationszeit | ~8 Minuten |
Babbeln kann jeder, hier die Live-Demo
Das hier ist fast 1:1 die Tabelle aus dem echten System. Der geilen Optik halber wollte ich das hier noch in das niedliche Terminalfenster pressen. Dafür habe ich auf HTMX verzichtet und stattdessen reines JavaScript verwendet.
Aber dennoch: Auch diese Demo ist nach dem hier beschriebenen Verfahren innerhalb weniger Minuten sehr präzise und ohne jede weitere Korrekturschleife entstanden. Das spart eine Menge Zeit, Nerven und Tokens.
Datenquelle
Die Daten stammen aus einem Demo-Datensatz des MariaDB Datenbankservers (karlkratz_de.demo_invoices).
php // Alle Daten laden (einmalig beim Seitenaufruf) $invoices = []; $statusCounts = ['draft' = 0, 'open' => 0, 'paid' => 0, 'voided' => 0]; try { // Verwende DatabaseManager statt direktem tenant.json Zugriff $pdo = \App\Services\DatabaseManager::getConnection('mariadb'); $stmt = $pdo->query(" SELECT id, voucher_number, voucher_date, voucher_status, due_date, address_name, total_gross_amount, total_net_amount, total_tax_amount, address_street, address_zip, address_city, line_items FROM demo_invoices WHERE archived = 0 ORDER BY voucher_date DESC "); $invoices = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($invoices as $inv) { $status = $inv['voucher_status'] ?? 'open'; if (isset($statusCounts[$status])) $statusCounts[$status]++; } } catch (Exception $e) { echo '
Datenbankfehler: ' . htmlspecialchars($e->getMessage()) . '
'; } $statusLabels = ['draft' => 'Entwurf', 'open' => 'Offen', 'paid' => 'Bezahlt', 'voided' => 'Storniert']; ?>
SELECT * FROM demo_invoicesEntwurf: = $statusCounts['draft'] ? Offen: = $statusCounts['open'] ? Bezahlt: = $statusCounts['paid'] ? Storniert: = $statusCounts['voided'] ?
Alle Entwurf Offen Bezahlt Storniert
| Nr. | Datum | Kunde | Betrag | Status | Fällig |
|---|
0 Rechnungen← Zurück
Datum:
Fällig:
Technische Details
- Tabelle:
karlkratz_de.demo_invoices(= count($invoices) ? Einträge) - Features: Client-side Filter, Sortierung, Inline-Details (kein Page-Reload)
Original-Code: InvoiceRepository.php
Das InvoiceRepository ist die zentrale Datenzugriffsschicht fuer Lexoffice-Rechnungen. Es implementiert das Repository-Pattern mit Multi-Account-Support.
Architektur-Entscheidungen
- Multi-Account: ACCOUNT_MAP verwaltet zwei Lexoffice-Konten
- Whitelist-Sortierung: ALLOWED_SORTS verhindert SQL-Injection bei ORDER BY
- Parameter-Binding: Alle Filter werden als Prepared Statements gebunden
- UUID-Validierung: findById() validiert Format vor Datenbankzugriff
src/Repositories/Lexoffice/InvoiceRepository.php (157 Zeilen)
<?php
declare(strict_types: 1);
namespace Schnuedeldue\Repositories\Lexoffice;
use InvalidArgumentException;
use PDO;
/**
* InvoiceRepository for Lexoffice invoice data
*
* Handles data access for Lexoffice invoices from synced databases.
* Supports multiple accounts (karlscore, 369wohlbefinden).
*/
class InvoiceRepository
{
private PDO $pdo;
private string $account;
private const ACCOUNT_MAP = [
'karlscore' => 'lexoffice_karlscore',
'369wohlbefinden' => 'lexoffice_369wohlbefinden',
];
private const ALLOWED_SORTS = [
'voucher_number',
'voucher_date',
'address_name',
'total_gross_amount',
'voucher_status',
'due_date',
];
public function __construct(PDO $pdo, string $account)
{
if (!isset(self::ACCOUNT_MAP[$account])) {
throw new InvalidArgumentException(
"Unknown account: {$account}. Allowed: " . implode(', ', array_keys(self::ACCOUNT_MAP))
);
}
$this->pdo = $pdo;
$this->account = $account;
}
public static function getConnectionName(string $account): string
{
if (!isset(self::ACCOUNT_MAP[$account])) {
throw new InvalidArgumentException("Unknown account: {$account}");
}
return self::ACCOUNT_MAP[$account];
}
public static function getAllowedAccounts(): array
{
return array_keys(self::ACCOUNT_MAP);
}
public function list(array $filters, string $sort, string $order, int $limit, int $offset): array
{
$sql = "SELECT id, voucher_number, voucher_date, voucher_status,
due_date, address_name, total_gross_amount
FROM invoices
WHERE archived = 0";
$params = [];
if (!empty($filters['q'])) {
$sql .= " AND (LOWER(voucher_number) LIKE LOWER(?)
OR LOWER(address_name) LIKE LOWER(?)
OR LOWER(title) LIKE LOWER(?))";
$q = '%' . $filters['q'] . '%';
$params[] = $q;
$params[] = $q;
$params[] = $q;
}
if (!empty($filters['status'])) {
$sql .= " AND voucher_status = ?";
$params[] = $filters['status'];
}
if (!empty($filters['date_from'])) {
$sql .= " AND voucher_date >= ?";
$params[] = $filters['date_from'];
}
if (!empty($filters['date_to'])) {
$sql .= " AND voucher_date <= ?";
$params[] = $filters['date_to'];
}
$sort = in_array($sort, self::ALLOWED_SORTS, true) ? $sort : 'voucher_date';
$order = strtoupper($order) === 'ASC' ? 'ASC' : 'DESC';
$sql .= " ORDER BY {$sort} {$order} LIMIT ? OFFSET ?";
$stmt = $this->pdo->prepare($sql);
$i = 1;
foreach ($params as $param) {
$stmt->bindValue($i++, $param, PDO::PARAM_STR);
}
$stmt->bindValue($i++, $limit, PDO::PARAM_INT);
$stmt->bindValue($i, $offset, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function count(array $filters): int
{
$sql = "SELECT COUNT(*) FROM invoices WHERE archived = 0";
$params = [];
// ... identische Filterlogik wie list() ...
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return (int) $stmt->fetchColumn();
}
public function findById(string $id): ?array
{
if (!preg_match('/^[a-f0-9-]{36}$/', $id)) {
return null;
}
$stmt = $this->pdo->prepare(
"SELECT * FROM invoices WHERE id = ? AND archived = 0"
);
$stmt->execute([$id]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result ?: null;
}
}
Sicherheitsaspekte
- SQL-Injection Prevention: Alle Filter nutzen Prepared Statements mit PDO::PARAM_*
- ORDER BY Whitelist: Sortierfelder werden gegen ALLOWED_SORTS validiert
- UUID-Regex: findById() prueft Format
/^[a-f0-9-]{36}$/vor Query - Readonly-Connection: Container bindet Repository an READ ONLY Session
Original-Code: list.php (HTMX-Endpoint)
Der HTMX-Endpoint liefert HTML-Fragmente fuer die Rechnungstabelle. Er wird per AJAX geladen und ermoeglicht Sortierung, Filterung und Pagination ohne Seitenreload.
HTMX-Konzept
- HTML-over-Wire: Server liefert fertige HTML-Fragmente, kein JSON
- hx-get: Triggert GET-Request bei Klick/Input/Change
- hx-target: Ersetzt Inhalt des Ziel-Elements (#invoices-table)
- hx-include: Sendet alle Filter-Inputs mit jedem Request
public/htmx/invoices/list.php (199 Zeilen)
<?php
declare(strict_types: 1);
require_once dirname(__DIR__, 3) . '/src/bootstrap.php';
use Schnuedeldue\Container;
use Schnuedeldue\Repositories\Lexoffice\InvoiceRepository;
header('Content-Type: text/html; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
http_response_code(405);
echo '<p class="message message-error">Method not allowed</p>';
exit;
}
$container = Container::getInstance();
$user = isLocalAccess()
? ['id' => 0, 'email' => 'local@bypass', 'name' => 'Local']
: $container->getSessionService()->getCurrentUser();
if (!$user) {
http_response_code(401);
echo '<p class="message message-error">Nicht autorisiert</p>';
exit;
}
// Account-Validierung
$allowedAccounts = InvoiceRepository::getAllowedAccounts();
$account = $_GET['account'] ?? '';
if (!in_array($account, $allowedAccounts, true)) {
http_response_code(400);
echo '<p class="message message-error">Ungueltiger Account</p>';
exit;
}
try {
$repository = $container->getLexofficeInvoiceRepository($account);
// Filter extrahieren
$filters = [];
if (!empty($_GET['q'])) {
$filters['q'] = trim($_GET['q']);
}
if (!empty($_GET['status'])) {
$filters['status'] = $_GET['status'];
}
if (!empty($_GET['date_from'])) {
$filters['date_from'] = $_GET['date_from'];
}
if (!empty($_GET['date_to'])) {
$filters['date_to'] = $_GET['date_to'];
}
$sort = $_GET['sort'] ?? 'voucher_date';
$order = $_GET['order'] ?? 'desc';
$page = max(1, (int) ($_GET['page'] ?? 1));
$perPage = 25;
$offset = ($page - 1) * $perPage;
$items = $repository->list($filters, $sort, strtoupper($order), $perPage, $offset);
$total = $repository->count($filters);
$totalPages = (int) ceil($total / $perPage);
$hxInclude = '.invoice-filters input, .invoice-filters select';
$baseUrl = "/htmx/invoices/list?account=" . htmlspecialchars($account, ENT_QUOTES, 'UTF-8');
// Tabellen-Header mit sortierbaren Spalten
echo '<div class="table-responsive">';
echo '<table class="data-table">';
echo '<thead><tr>';
$columns = [
'voucher_number' => 'Nr.',
'voucher_date' => 'Datum',
'address_name' => 'Kunde',
'total_gross_amount' => 'Betrag',
'voucher_status' => 'Status',
'due_date' => 'Faellig',
];
foreach ($columns as $col => $label) {
$isSorted = ($sort === $col);
$nextOrder = ($isSorted && $order === 'asc') ? 'desc' : 'asc';
$sortedClass = $isSorted ? " sorted-{$order}" : '';
echo '<th class="col-' . $col . ' sortable' . $sortedClass . '">';
echo '<a href="#" hx-get="' . $baseUrl . '&sort=' . $col . '&order=' . $nextOrder . '" ';
echo 'hx-target="#invoices-table" hx-include="' . $hxInclude . '">' . $label . '</a>';
echo '</th>';
}
echo '</tr></thead>';
// Tabelleninhalt
if (empty($items)) {
echo '<tbody><tr><td colspan="6" class="empty-state">Keine Rechnungen gefunden</td></tr></tbody>';
} else {
echo '<tbody>';
foreach ($items as $item) {
renderInvoiceRow($item, $account);
}
echo '</tbody>';
}
echo '</table></div>';
// Pagination
if ($totalPages > 1) {
renderPagination($page, $totalPages, $baseUrl, $sort, $order, $hxInclude);
}
// Meta-Info
$from = $total > 0 ? ($page - 1) * $perPage + 1 : 0;
$to = min($page * $perPage, $total);
echo '<p class="table-meta">' . $from . '-' . $to . ' von ' . $total . ' Rechnungen</p>';
} catch (Throwable $e) {
error_log('[InvoicesListHTMX] Error: ' . $e->getMessage());
echo '<p class="message message-error">Fehler beim Laden</p>';
}
// === HELPER FUNCTIONS ===
function renderInvoiceRow(array $item, string $account): void
{
$id = htmlspecialchars($item['id'], ENT_QUOTES, 'UTF-8');
$number = htmlspecialchars($item['voucher_number'] ?? '-', ENT_QUOTES, 'UTF-8');
$date = $item['voucher_date'] ? date('d.m.Y', strtotime($item['voucher_date'])) : '-';
$customer = htmlspecialchars(mb_substr($item['address_name'] ?? '-', 0, 40), ENT_QUOTES, 'UTF-8');
$amount = number_format((float) ($item['total_gross_amount'] ?? 0), 2, ',', '.') . ' EUR';
$status = $item['voucher_status'] ?? 'open';
$dueDate = $item['due_date'] ? date('d.m.Y', strtotime($item['due_date'])) : '-';
// Ueberfaelligkeits-Check
$isOverdue = $status === 'open'
&& $item['due_date'] !== null
&& strtotime($item['due_date']) < strtotime('today');
$rowClass = $isOverdue ? ' class="row-overdue"' : '';
$statusLabels = [
'draft' => 'Entwurf',
'open' => 'Offen',
'paid' => 'Bezahlt',
'voided' => 'Storniert',
];
$statusLabel = $statusLabels[$status] ?? $status;
$detailUrl = "/admin/rechnungen/{$account}/{$id}";
echo '<tr' . $rowClass . '>';
echo '<td data-label="Nr."><a href="' . $detailUrl . '">' . $number . '</a></td>';
echo '<td data-label="Datum">' . $date . '</td>';
echo '<td data-label="Kunde">' . $customer . '</td>';
echo '<td data-label="Betrag" class="text-right">' . $amount . '</td>';
echo '<td data-label="Status"><span class="status status-' . $status . '">' . $statusLabel . '</span></td>';
echo '<td data-label="Faellig" class="cell-due">' . $dueDate . '</td>';
echo '</tr>';
}
function renderPagination(int $page, int $totalPages, string $baseUrl, string $sort, string $order, string $hxInclude): void
{
// Intelligente Pagination mit Ellipsis bei >7 Seiten
// ...
}
Request-Response-Flow
- Admin-Seite laedt mit
hx-trigger="load" - HTMX sendet GET an
/htmx/invoices/list?account=karlscore - PHP rendert HTML-Tabelle mit Daten aus Repository
- HTMX ersetzt
#invoices-tablemit Response - Bei Sort/Filter: erneuter Request mit hx-include-Parametern
Original-Code: karlscore.php
Die Admin-Seite fuer karlsCORE GmbH ist ein minimalistisches Template, das die Rechnungstabelle per HTMX laedt. Die gesamte Komplexitaet liegt in list.php.
Aufbau
- Auth-Check: Einbindung von
auth.phpfuer Session-Validierung - Filter-UI: Input/Select-Elemente mit HTMX-Attributen
- Lazy Loading:
hx-trigger="load"laedt Tabelle beim Seitenaufruf - Account-Isolation: Konstante
$account = 'karlscore'
public/admin/rechnungen/karlscore.php (68 Zeilen)
<?php
declare(strict_types: 1);
require_once dirname(__DIR__, 3) . '/templates/layout/auth.php';
$pageTitle = 'Rechnungen karlsCORE - SCHNUEDELDUE';
$currentPage = 'admin/rechnungen/karlscore';
$account = 'karlscore';
require dirname(__DIR__, 3) . '/templates/layout/header.php';
require dirname(__DIR__, 3) . '/templates/layout/nav.php';
?>
<main class="app-main">
<h1>Rechnungen karlsCORE GmbH</h1>
<section class="invoices-section">
<div class="invoice-filters">
<!-- Suchfeld -->
<input type="text" name="q" id="filter-q"
class="filter-input"
placeholder="Suchen..."
hx-get="/htmx/invoices/list?account=<?= $account ?>"
hx-target="#invoices-table"
hx-trigger="input changed delay:300ms, search"
hx-include=".invoice-filters input, .invoice-filters select"
hx-indicator="#table-spinner">
<!-- Status-Filter -->
<select name="status" id="filter-status"
class="filter-select"
hx-get="/htmx/invoices/list?account=<?= $account ?>"
hx-target="#invoices-table"
hx-trigger="change"
hx-include=".invoice-filters input, .invoice-filters select"
hx-indicator="#table-spinner">
<option value="">Status: Alle</option>
<option value="draft">Entwurf</option>
<option value="open">Offen</option>
<option value="paid">Bezahlt</option>
<option value="voided">Storniert</option>
</select>
<!-- Datumsfilter -->
<input type="date" name="date_from" id="filter-date-from"
class="filter-input filter-date"
hx-get="/htmx/invoices/list?account=<?= $account ?>"
hx-target="#invoices-table"
hx-trigger="change"
hx-include=".invoice-filters input, .invoice-filters select"
hx-indicator="#table-spinner"
placeholder="Von">
<input type="date" name="date_to" id="filter-date-to"
class="filter-input filter-date"
hx-get="/htmx/invoices/list?account=<?= $account ?>"
hx-target="#invoices-table"
hx-trigger="change"
hx-include=".invoice-filters input, .invoice-filters select"
hx-indicator="#table-spinner"
placeholder="Bis">
<span id="table-spinner" class="htmx-indicator spinner"></span>
</div>
<!-- Tabellen-Container: wird per HTMX befuellt -->
<div id="invoices-table"
hx-get="/htmx/invoices/list?account=<?= $account ?>"
hx-trigger="load"
hx-include=".invoice-filters input, .invoice-filters select"
hx-indicator="#table-spinner">
<p class="text-muted">Lade Rechnungen...</p>
</div>
</section>
</main>
<?php require dirname(__DIR__, 3) . '/templates/layout/footer.php'; ?>
HTMX-Attribute
| Attribut | Funktion |
|---|---|
hx-get |
AJAX GET-Request an URL |
hx-target |
Element, das mit Response ersetzt wird |
hx-trigger |
Event, das Request ausloest (load, change, input) |
hx-include |
Selector fuer zusaetzliche Inputs im Request |
hx-indicator |
Element, das waehrend Request sichtbar wird |
Original-Code: 369wohlbefinden.php
Die Admin-Seite fuer 369 Wohlbefinden GmbH ist strukturell identisch mit karlscore.php - nur mit anderem Account-Parameter.
DRY-Analyse
Die beiden Admin-Seiten zeigen Code-Duplikation - ein typisches Beispiel, wo ein generisches Template mit Account-Parameter eleganter waere. Der KI-generierte Code hat hier das KISS-Prinzip ueber DRY gestellt: Zwei einfache Dateien statt einer komplexeren Routing-Logik.
public/admin/rechnungen/369wohlbefinden.php (68 Zeilen)
<?php
declare(strict_types: 1);
require_once dirname(__DIR__, 3) . '/templates/layout/auth.php';
$pageTitle = 'Rechnungen 369 Wohlbefinden - SCHNUEDELDUE';
$currentPage = 'admin/rechnungen/369wohlbefinden';
$account = '369wohlbefinden'; // <-- Einziger Unterschied
require dirname(__DIR__, 3) . '/templates/layout/header.php';
require dirname(__DIR__, 3) . '/templates/layout/nav.php';
?>
<main class="app-main">
<h1>Rechnungen 369 Wohlbefinden GmbH</h1>
<section class="invoices-section">
<div class="invoice-filters">
<!-- Identisches Filter-UI wie karlscore.php -->
<input type="text" name="q" id="filter-q"
class="filter-input"
placeholder="Suchen..."
hx-get="/htmx/invoices/list?account=<?= $account ?>"
hx-target="#invoices-table"
hx-trigger="input changed delay:300ms, search"
hx-include=".invoice-filters input, .invoice-filters select"
hx-indicator="#table-spinner">
<select name="status" id="filter-status"
class="filter-select"
hx-get="/htmx/invoices/list?account=<?= $account ?>"
hx-target="#invoices-table"
hx-trigger="change"
hx-include=".invoice-filters input, .invoice-filters select"
hx-indicator="#table-spinner">
<option value="">Status: Alle</option>
<option value="draft">Entwurf</option>
<option value="open">Offen</option>
<option value="paid">Bezahlt</option>
<option value="voided">Storniert</option>
</select>
<input type="date" name="date_from"
hx-get="/htmx/invoices/list?account=<?= $account ?>"
...>
<input type="date" name="date_to"
...>
<span id="table-spinner" class="htmx-indicator spinner"></span>
</div>
<div id="invoices-table"
hx-get="/htmx/invoices/list?account=<?= $account ?>"
hx-trigger="load"
hx-include=".invoice-filters input, .invoice-filters select"
hx-indicator="#table-spinner">
<p class="text-muted">Lade Rechnungen...</p>
</div>
</section>
</main>
<?php require dirname(__DIR__, 3) . '/templates/layout/footer.php'; ?>
Trade-off: DRY vs. KISS
Alternative Loesung (DRY): Eine generische rechnungen.php mit $account = $_GET['account']
Gewaehlt (KISS): Zwei separate Dateien mit festen Account-Werten
Vorteil: Klare URL-Struktur (/admin/rechnungen/karlscore), einfachere Navigation, keine Validierungslogik in der View
Original-Code: detail.php
Die Rechnungs-Detailansicht zeigt alle Informationen einer Rechnung: Kundendaten, Positionen, Summen und Zahlungsbedingungen.
Komponenten
- Breadcrumb: Navigation zurueck zur Liste
- Header: Rechnungsnummer + Status
- Meta: Datum, Faelligkeit, Titel
- Kunde: Vollstaendige Adresse
- Positionen: Tabelle mit Menge, Preis, Rabatt
- Summen: Netto, MwSt, Brutto
public/admin/rechnungen/detail.php (182 Zeilen)
<?php
declare(strict_types: 1);
require_once dirname(__DIR__, 3) . '/templates/layout/auth.php';
use Schnuedeldue\Container;
use Schnuedeldue\Repositories\Lexoffice\InvoiceRepository;
// Parameter-Validierung
$allowedAccounts = InvoiceRepository::getAllowedAccounts();
$account = $_GET['account'] ?? '';
$id = $_GET['id'] ?? '';
if (!in_array($account, $allowedAccounts, true)) {
http_response_code(400);
exit('Ungueltiger Account');
}
if (!preg_match('/^[a-f0-9-]{36}$/', $id)) {
http_response_code(400);
exit('Ungueltige ID');
}
// Rechnung laden
$container = Container::getInstance();
$repository = $container->getLexofficeInvoiceRepository($account);
$invoice = $repository->findById($id);
if (!$invoice) {
http_response_code(404);
exit('Rechnung nicht gefunden');
}
// Labels vorbereiten
$accountLabels = [
'karlscore' => 'karlsCORE GmbH',
'369wohlbefinden' => '369 Wohlbefinden GmbH',
];
$accountLabel = $accountLabels[$account] ?? $account;
$statusLabels = [
'draft' => 'Entwurf',
'open' => 'Offen',
'paid' => 'Bezahlt',
'voided' => 'Storniert',
];
$status = $invoice['voucher_status'] ?? 'open';
$statusLabel = $statusLabels[$status] ?? $status;
// Line Items aus JSON parsen
$lineItems = [];
if (!empty($invoice['line_items'])) {
$lineItems = json_decode($invoice['line_items'], true) ?? [];
}
$pageTitle = 'Rechnung ' . htmlspecialchars($invoice['voucher_number'] ?? '-') . ' - SCHNUEDELDUE';
$currentPage = "admin/rechnungen/{$account}";
require dirname(__DIR__, 3) . '/templates/layout/header.php';
require dirname(__DIR__, 3) . '/templates/layout/nav.php';
?>
<main class="app-main">
<!-- Breadcrumb-Navigation -->
<nav class="breadcrumb">
<a href="/admin/rechnungen/<?= htmlspecialchars($account) ?>">
Rechnungen <?= htmlspecialchars($accountLabel) ?>
</a>
<span>›</span>
<span><?= htmlspecialchars($invoice['voucher_number'] ?? '-') ?></span>
</nav>
<article class="invoice-detail">
<!-- Header mit Nummer und Status -->
<header class="invoice-header">
<h1><?= htmlspecialchars($invoice['voucher_number'] ?? '-') ?></h1>
<span class="status status-<?= htmlspecialchars($status) ?>">
<?= htmlspecialchars($statusLabel) ?>
</span>
</header>
<!-- Meta-Informationen -->
<section class="invoice-meta">
<dl>
<dt>Rechnungsdatum</dt>
<dd><?= $invoice['voucher_date'] ? date('d.m.Y', strtotime($invoice['voucher_date'])) : '-' ?></dd>
<dt>Faelligkeit</dt>
<dd><?= $invoice['due_date'] ? date('d.m.Y', strtotime($invoice['due_date'])) : '-' ?></dd>
</dl>
</section>
<!-- Kundenadresse -->
<section class="invoice-customer">
<h2>Kunde</h2>
<address>
<?= htmlspecialchars($invoice['address_name'] ?? '-') ?><br>
<?= htmlspecialchars($invoice['address_street'] ?? '') ?><br>
<?= htmlspecialchars($invoice['address_zip'] ?? '') ?>
<?= htmlspecialchars($invoice['address_city'] ?? '') ?>
</address>
</section>
<!-- Positionen-Tabelle -->
<?php if (!empty($lineItems)): ?>
<section class="invoice-items">
<h2>Positionen</h2>
<table class="data-table">
<thead>
<tr>
<th>Pos</th>
<th>Bezeichnung</th>
<th class="text-right">Menge</th>
<th>Einheit</th>
<th class="text-right">Einzelpreis</th>
<th class="text-right">Rabatt</th>
<th class="text-right">Gesamt</th>
</tr>
</thead>
<tbody>
<?php foreach ($lineItems as $i => $item): ?>
<tr>
<td><?= $i + 1 ?></td>
<td><?= htmlspecialchars($item['name'] ?? '-') ?></td>
<td class="text-right"><?= number_format((float)($item['quantity'] ?? 0), 2, ',', '.') ?></td>
<td><?= htmlspecialchars($item['unitName'] ?? '') ?></td>
<td class="text-right"><?= number_format((float)($item['unitPrice']['netAmount'] ?? 0), 2, ',', '.') ?> EUR</td>
<td class="text-right">
<?= ($item['discountPercentage'] ?? 0) > 0 ? number_format((float)$item['discountPercentage'], 1, ',', '.') . '%' : '-' ?>
</td>
<td class="text-right"><?= number_format((float)($item['lineItemAmount'] ?? 0), 2, ',', '.') ?> EUR</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</section>
<?php endif; ?>
<!-- Summen-Block -->
<section class="invoice-totals">
<h2>Summen</h2>
<dl class="totals-list">
<dt>Netto</dt>
<dd><?= number_format((float)($invoice['total_net_amount'] ?? 0), 2, ',', '.') ?> EUR</dd>
<dt>MwSt</dt>
<dd><?= number_format((float)($invoice['total_tax_amount'] ?? 0), 2, ',', '.') ?> EUR</dd>
<dt class="total-gross">Brutto</dt>
<dd class="total-gross"><?= number_format((float)($invoice['total_gross_amount'] ?? 0), 2, ',', '.') ?> EUR</dd>
</dl>
</section>
<!-- Actions -->
<section class="invoice-actions">
<button type="button" class="btn btn-secondary" disabled>PDF herunterladen</button>
<a href="/admin/rechnungen/<?= htmlspecialchars($account) ?>" class="btn btn-outline">
Zurueck zur Liste
</a>
</section>
</article>
</main>
<?php require dirname(__DIR__, 3) . '/templates/layout/footer.php'; ?>
Lexoffice-Datenstruktur
Die line_items werden als JSON-Array in der DB gespeichert (Lexoffice API-Format):
{
"name": "Beratung KI-Strategie",
"quantity": 8.0,
"unitName": "Stunden",
"unitPrice": { "netAmount": 250.00 },
"discountPercentage": 0,
"lineItemAmount": 2000.00
}
Original-Code: Container.php (DI-Erweiterung)
Der Dependency Injection Container wurde um die Factory-Methode getLexofficeInvoiceRepository() erweitert. Sie erstellt das Repository mit der korrekten Datenbankverbindung fuer den jeweiligen Account.
Container-Prinzip
- Singleton: Eine Container-Instanz pro Request
- Lazy Loading: Services werden erst bei Bedarf erstellt
- Caching: Einmal erstellte Instanzen werden wiederverwendet
- Account-Isolation: Jeder Lexoffice-Account erhaelt eigene Repository-Instanz
src/Container.php (Auszug: Zeilen 141-149)
<?php
declare(strict_types: 1);
namespace Schnuedeldue;
use PDO;
use Schnuedeldue\Container\RepositoryFactoryTrait;
use Schnuedeldue\Container\ServiceFactoryTrait;
use Schnuedeldue\Repositories\Lexoffice\InvoiceRepository;
/**
* Simple DI Container
*
* Manages service instantiation and dependency injection.
* Implements lazy loading with singleton pattern.
*/
class Container
{
use RepositoryFactoryTrait;
use ServiceFactoryTrait;
private static ?Container $instance = null;
private array $services = [];
private function __construct() {}
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// ... andere Methoden (getPdo, createConnection, resolve) ...
/**
* Get Lexoffice Invoice Repository for specific account
*
* @param string $account 'karlscore' or '369wohlbefinden'
* @return InvoiceRepository
*/
public function getLexofficeInvoiceRepository(string $account): InvoiceRepository
{
// Cache-Key pro Account, damit beide Repos koexistieren koennen
$key = "lexoffice_invoice_repo_{$account}";
return $this->resolve($key, function () use ($account) {
// Connection-Name aus Repository holen (Kapselt Account-Mapping)
$connName = InvoiceRepository::getConnectionName($account);
// Readonly-Connection erstellen (Sicherheit)
$pdo = $this->createConnection($connName, true);
// Repository mit PDO und Account instanziieren
return new InvoiceRepository($pdo, $account);
});
}
}
Schluesselkonzepte
| Konzept | Implementierung |
|---|---|
| Account-spezifischer Cache-Key | lexoffice_invoice_repo_{$account} |
| Lazy Closure | Factory wird erst bei erstem Zugriff ausgefuehrt |
| Connection-Delegation | Repository kennt Account→DB-Mapping |
| Readonly-Flag | createConnection($name, true) setzt READ ONLY |
databases.json
Die Verbindungsdaten sind in config/databases.json definiert:
{
"connections": {
"lexoffice_karlscore": {
"host": "localhost",
"database": "schnuedeldue_lexoffice_karlscore",
"charset": "utf8mb4"
},
"lexoffice_369wohlbefinden": {
"host": "localhost",
"database": "schnuedeldue_lexoffice_369wohlbefinden",
"charset": "utf8mb4"
}
}
}
Das Ergebnis: Messbare Code-Qualität
Was passiert, wenn der KI-Code-Mensch-Prozess konsequent angewendet wird? Der Code, der in ~8 Minuten autonom erstellt wurde, durchläuft dieselben Qualitätsprüfungen wie jeder andere Code im System.
code-quality-check --full-analysis /freund/lexoffice-integration
● Code Quality Analyse: Lexoffice Rechnungen Integration
Projekt-Übersicht
┌────────────────────────────┬─────────────────┐
│ Metrik │ Wert │
├────────────────────────────┼─────────────────┤
│ Codezeilen gesamt │ 833 │
│ Durchschnittliche Qualität │ 98.5% │
│ Security Score │ 100% │
│ Implementationszeit │ ~8 min │
└────────────────────────────┴─────────────────┘
┌────────────────────────────────────────┬────────┬────────┬───────────┐
│ Datei │ LoC │ Score │ Security │
├────────────────────────────────────────┼────────┼────────┼───────────┤
│ InvoiceRepository.php │ 157 │ 92.2% │ 100% │
│ list.php (HTMX) │ 199 │ 99.4% │ 100% │
│ karlscore.php │ 68 │ 100% │ 100% │
│ 369wohlbefinden.php │ 68 │ 100% │ 100% │
│ detail.php │ 182 │ 99.1% │ 100% │
│ Container.php (erweitert) │ 159 │ 100% │ 100% │
└────────────────────────────────────────┴────────┴────────┴───────────┘
═══════════════════════════════════════════════════════════════════════
● PHP Analysis (833 LoC)
Contract-Verstöße
CRITICAL (0)
✓ CQ-SQL-001 Prepared Statements überall
✓ CQ-DI-001 Repository via Container (DI)
✓ PHP-SEC-001 Keine SQL-Injection möglich
✓ PHP-SEC-002 XSS-Escaping via htmlspecialchars()
MAJOR (0)
✓ CQ-AUTH-001 Auth-Check in allen Endpunkten
✓ CQ-VAL-001 Input-Validierung (Account-Whitelist, UUID-Pattern)
✓ PHP-ERR-001 Error-Logging im Catch-Block
MINOR (3)
┌─────────────┬─────────────────────────────┬──────────────────────────────┐
│ Rule │ Datei │ Problem │
├─────────────┼─────────────────────────────┼──────────────────────────────┤
│ PHP-KISS-01 │ list.php:153 │ 6 Parameter (max: 5) │
│ PHP-KISS-01 │ list.php:194 │ 6 Parameter (max: 5) │
│ PHP-DRY-001 │ InvoiceRepository.php:65 │ Filter-Logik dupliziert │
└─────────────┴─────────────────────────────┴──────────────────────────────┘
Code Quality Principles
┌────────────┬────────┬─────────────────────────────────────────────────┐
│ Prinzip │ Score │ Details │
├────────────┼────────┼─────────────────────────────────────────────────┤
│ DRY │ 93.5% │ Filter-Logik in list() und count() ähnlich │
│ KISS │ 100% │ Avg Complexity: 4.3, Max: 6 (list) │
│ SRP │ 100% │ Repository = Data, Views = Presentation │
│ OOP │ 100% │ InvoiceRepository kapselt DB-Zugriff │
└────────────┴────────┴─────────────────────────────────────────────────┘
PSR Compliance
┌──────────┬─────────┬───────────────────────────────────────────────────┐
│ Standard │ Score │ Beschreibung │
├──────────┼─────────┼───────────────────────────────────────────────────┤
│ PSR-1 │ 100% │ Basic Coding Standard │
│ PSR-4 │ 100% │ Autoloading (Namespace = Pfad) │
│ PSR-12 │ 96.2% │ 15 Zeilen > 120 Zeichen (HTMX-URLs) │
└──────────┴─────────┴───────────────────────────────────────────────────┘
═══════════════════════════════════════════════════════════════════════
● Security Analysis
┌──────────────────────┬─────────┬────────────────────────────────────────┐
│ Check │ Status │ Details │
├──────────────────────┼─────────┼────────────────────────────────────────┤
│ SQL Injection │ PASS │ Prepared Statements, bindValue() │
│ XSS │ PASS │ htmlspecialchars(ENT_QUOTES, UTF-8) │
│ Auth Bypass │ PASS │ Session-Check + Local-IP-Bypass │
│ Input Validation │ PASS │ Account-Whitelist, UUID-Regex │
│ Path Traversal │ PASS │ Keine dynamischen Includes │
│ Sensitive Data │ PASS │ Credentials in config/databases.json │
└──────────────────────┴─────────┴────────────────────────────────────────┘
Overall Security Level: 100% ✓
═══════════════════════════════════════════════════════════════════════
● Architecture & Design Patterns
┌────────────────────────┬─────────┬──────────────────────────────────────┐
│ Pattern/Regel │ Status │ Details │
├────────────────────────┼─────────┼──────────────────────────────────────┤
│ Dependency Injection │ PASS │ Container->getLexofficeInvoiceRepo() │
│ Repository Pattern │ PASS │ InvoiceRepository kapselt DB-Zugriff │
│ Whitelist Validation │ PASS │ ALLOWED_SORTS, getAllowedAccounts() │
│ Mobile-First CSS │ PASS │ Responsive Tabelle, data-label │
│ HTMX Fragments │ PASS │ list.php rendert nur HTML-Fragment │
│ No Page Reload │ PASS │ Sort/Filter/Pagination via HTMX │
└────────────────────────┴─────────┴──────────────────────────────────────┘
═══════════════════════════════════════════════════════════════════════
● Analyse abgeschlossen
Gesamtergebnis: 98.5% Code-Qualität
Kritische Issues: 0
Major Issues: 0
Minor Issues: 3 (Parameter-Count, DRY)
Analysedauer: 0.8s für 833 LoC (6 Dateien)
✓ Alle Gates passiert
✓ Bereit für Deployment
Was das bedeutet
833 Zeilen funktionsfähiger, sicherer Code: autonom generiert in ~8 Minuten. Kein SQL-Injection-Risiko, kein XSS, saubere Architektur mit Repository-Pattern und Dependency Injection. Die 3 Minor Issues (zu viele Parameter in Hilfsfunktionen, duplizierte Filter-Logik) sind dokumentiert und könnten in einem Refactoring-Pass behoben werden.
Was können wir alles Tolles daraus lernen?
Diese Session demonstriert mehrere Aspekte der KI-Mensch-Zusammenarbeit im Software-Engineering. Schauen wir uns die wichtigsten Learnings an:
Prompt-Engineering macht den Unterschied
Was ist das? Prompt-Engineering ist die Kunst, einer KI so präzise zu sagen, was man will, dass sie es auch wirklich versteht. Klingt trivial, ist es aber nicht.
Warum ist das wichtig? Ein vager Prompt führt zu vagen Ergebnissen. Die KI „rät" dann, was Du gemeint haben könntest und das geht meistens schief. Ein präziser Prompt hingegen liefert präzise Ergebnisse.
Was lernen wir daraus?
- Strukturierte Prompts mit expliziten Referenzen führen zu besseren Ergebnissen: Wir haben gesehen, wie aus einem 20-Zeilen-Prompt (v1.0) ein 400-Zeilen-Prompt (v3.0) wurde. Jede zusätzliche Zeile war eine potenzielle Fehlerquelle weniger. Konkrete Beispiele, Schema-Definitionen und Referenz-Code eliminieren Interpretationsspielraum.
- Akzeptanzkriterien ermöglichen objektive Validierung: Statt „mach eine schöne Tabelle" haben wir 22 messbare Kriterien definiert. Das ist wie ein Vertrag: Beide Seiten wissen genau, was „fertig" bedeutet. Keine Diskussionen, keine Nachbesserungen.
- Kontext aus existierendem Code reduziert Halluzinationen: Wenn die KI echten Code aus dem Projekt als Referenz bekommt, erfindet sie keine Fantasy-APIs. Sie orientiert sich an dem, was funktioniert. Das ist wie ein Praktikant, der erstmal zuschaut, bevor er loslegt.
Autonome Fehlerkorrektur funktioniert
Was ist das? Die Fähigkeit einer KI, eigene Fehler zu erkennen und zu korrigieren ohne dass ein Mensch eingreifen muss.
Warum ist das wichtig? Wenn Du bei jedem Fehler manuell eingreifen musst, bist Du der Flaschenhals. Die KI kann nur so schnell arbeiten, wie Du korrigierst. Autonome Fehlerkorrektur entfesselt das volle Potenzial.
Was lernen wir daraus?
- Die externe KI erkannte und korrigierte Fehler selbstständig: Als ein Pfad nicht stimmte, hat die KI das Problem analysiert, die Ursache gefunden und den Fix implementiert. Alles ohne menschliches Zutun.
- Keine menschliche Intervention nötig für technische Korrekturen: Das spart nicht nur Zeit, sondern auch Nerven. Du musst nicht jeden Fehler verstehen, um ihn beheben zu lassen.
- Supervisor-Rolle: Beobachten, nicht eingreifen: Der Mensch wechselt von „Ich mache alles selbst" zu „Ich beobachte und greife nur ein, wenn nötig". Das ist ein fundamentaler Paradigmenwechsel.
Das Gate-System als Qualitätsnetz
Was ist das? Ein System aus Contracts, Regeln und Hooks, das automatisch die Code-Qualität überwacht und bei Bedarf eingreift.
Warum ist das wichtig? Menschen machen Fehler. KIs machen Fehler. Ein automatisches Qualitätsnetz fängt diese Fehler ab, bevor sie Schaden anrichten.
Was lernen wir daraus?
- Automatische Permission-Fixes verhindern Deployment-Probleme: Wie oft ist ein Deploy gescheitert, weil jemand die Dateiberechtigungen vergessen hat? Mit dem Gate-System: nie mehr.
- Code-Analyse baut kontinuierlich Knowledge Base auf: Jede neue Datei wird analysiert und in die Wissensbasis aufgenommen. Das System wird mit jeder Zeile Code schlauer.
- Kein Hook blockierte: Das ist das schönste Ergebnis. Die Implementation entsprach von Anfang an allen Constraints. Das Supervision-Feedback hatte seinen Job getan.
Effizienz: 30 Minuten für ein komplettes Feature
Was bedeutet das? In der Zeit, in der man sonst vielleicht die Requirements diskutiert hätte, war das Feature fertig, getestet und dokumentiert.
| Phase | Dauer | Was passierte |
|---|---|---|
| Prompt-Entwicklung | ~10 min | 3 Iterationen bis zur vollständigen Spezifikation |
| Task-Erstellung | ~2 min | 11 Sub-Tasks automatisch generiert |
| Implementation (extern) | ~8 min | 6 PHP-Dateien, 1 CSS-Datei, Routing |
| Akzeptanztests | ~3 min | 22 Kriterien automatisch validiert |
| Dokumentation | ~7 min | Diese Seite hier |
| Gesamt | ~30 min | Produktionsreifer Code |
Und jetzt Du!
Wenn Du bis hierhin gelesen hast: Respekt! Das war ein langer Artikel mit viel technischem Detail. Aber genau das macht den Unterschied zwischen „KI-Spielerei" und „KI-Produktivität".
Die KI-Gemeinschaft
Dieses Wissen ist Teil dessen, was wir in der KI-Gemeinschaft vermitteln und gemeinsam weiterentwickeln.
Was erwartet Dich dort?
- Monatliche Live-Seminare zu KI-Themen
- Zugang zu allen Materialien und Systemen
- Direkte Begleitung bei Deinen KI-Projekten
- Eine Community von Gleichgesinnten
Die KI-Gemeinschaft ist für Menschen, die KI nicht nur nutzen, sondern verstehen und meistern wollen.
Weiterlesen
Schlusswort
KI-Systeme wie Claude Code sind keine Magie. Sie sind Werkzeuge. Mächtige Werkzeuge, ja, aber eben Werkzeuge. Der Unterschied zwischen „Das funktioniert irgendwie" und „Das funktioniert präzise und reproduzierbar" liegt in der Methodik.
Diese Methodik kann man lernen. Diesen Artikel zu lesen war ein erster Schritt. Der nächste Schritt: Selber machen. Und wenn Du dabei Unterstützung möchtest: Du weißt ja jetzt, wo Du sie findest.
Liebe Grüße, Karl