Prozesse
Diese Kategorie dokumentiert die automatisierten Prozesse und Workflows, die bei der Arbeit mit Claude Code aktiv sind.
Inhalt
- Claude Pre-/Post-Hook - Automatische Aktionen bei Code-Erstellung und -Änderung
- Quality Gates - Automatische Code-Qualitätsprüfung (SRP, MVC, PSR, OOP, DRY, SOLID)
Übersicht
Das Hook-System ermöglicht:
- Automatische Protokollierung aller Aktionen
- Sicherheitsvalidierung vor Ausführung
- File-Backups vor Änderungen
- Berechtigungskorrektur nach Änderungen
- Quality Gates - 30 Regeln zur Code-Qualität mit Task-Integration
MCP Server Refactoring Plan
Version: 1.2
Erstellt: 2025-12-28
Aktualisiert: 2025-12-28 (nach Supervision Runde 2)
Status: Final
Task: #507
1. Executive Summary
Ist-Zustand
- 5 MCP-Server: mcp-db, mcp-tasks, mcp-contracts, mcp-docs, mcp-code
- 91 Python-Dateien (ohne venv)
- 9.248 LOC gesamt
- Signifikante Code-Duplikation (~25% redundanter Code)
- Inkonsistente Patterns zwischen Servern
Ziel
Konsolidierung zu einer modularen, wartbaren Architektur unter Einhaltung von:
- DRY (Don't Repeat Yourself)
- SRP (Single Responsibility Principle)
- SOLID Principles
- Clean Architecture
- KISS (Keep It Simple, Stupid)
- YAGNI (You Aren't Gonna Need It)
Erwartete Verbesserungen
- ~30% weniger Code durch Elimination von Duplikaten
- Einheitliche Patterns für alle Server
- Verbesserte Wartbarkeit durch Modularisierung
- Schnellere Feature-Entwicklung durch shared Components
2. Analyse der Code-Duplikation
2.1 Kritische Duplikate (Sofort beheben)
db_connection.py (4 Dateien, ~240 LOC dupliziert)
| Server | Zeilen | Bibliothek | Besonderheit |
|---|---|---|---|
| mcp-db | 59 | mysql.connector + Pooling | Dynamisches DB-Switching via USE |
| mcp-tasks | 62 | pymysql | Festes DB_NAME |
| mcp-contracts | 60 | pymysql | Festes DB_NAME |
| mcp-docs | 60 | pymysql | Festes DB_NAME |
Problem: 3 von 4 Dateien sind nahezu identisch (mcp-tasks, mcp-contracts, mcp-docs).
Lösung: Zentralisieren in shared/infrastructure/simple_db_connection.py
WICHTIG: Diese Klasse heißt bewusst
SimpleDbConnection, nichtDatabaseConnection. Sie bietet kein Pooling und ist nicht für High-Throughput geeignet. Für mcp-db (100+ Queries/Minute) bleibt die eigene Pooling-Implementierung.
# shared/infrastructure/simple_db_connection.py
"""
Einfache DB-Verbindung ohne Pooling.
NICHT geeignet für:
- High-Throughput (> 50 Queries/Minute)
- Connection Pooling Requirements
- mcp-db Server
Für diese Fälle: Eigene Implementierung mit mysql.connector.pooling
"""
from contextlib import contextmanager
from typing import Generator
import pymysql
from pymysql.connections import Connection
from shared.config_base import AppDatabaseConfig, LogDatabaseConfig
class SimpleDbConnection:
"""
Einfache Datenbankverbindung ohne Pooling.
Für Server mit geringem Query-Volumen (< 50/Minute).
"""
@classmethod
@contextmanager
def get_connection(
cls,
config: AppDatabaseConfig,
database: str | None = None,
autocommit: bool = False
) -> Generator[Connection, None, None]:
"""
Context Manager für App-DB Connection.
ACHTUNG: Keine weiteren Parameter hinzufügen!
Bei Bedarf für mehr Logik: Neue Methode erstellen.
Args:
config: Server-Konfiguration mit DB-Credentials
database: Optional - überschreibt config.DB_NAME
autocommit: True für Logging, False für Transaktionen
"""
conn = None
db_name = database or getattr(config, 'DB_NAME', None)
try:
conn = pymysql.connect(
host=config.DB_HOST,
port=getattr(config, 'DB_PORT', 3306),
user=config.DB_USER,
password=config.DB_PASSWORD,
database=db_name,
charset="utf8mb4",
cursorclass=pymysql.cursors.DictCursor,
autocommit=autocommit,
)
yield conn
if not autocommit:
conn.commit()
except Exception:
if conn and not autocommit:
conn.rollback()
raise
finally:
if conn:
conn.close()
@classmethod
@contextmanager
def get_log_connection(
cls,
config: LogDatabaseConfig
) -> Generator[Connection, None, None]:
"""Separate Verbindung für Logging (autocommit=True)."""
conn = None
try:
conn = pymysql.connect(
host=config.LOG_DB_HOST,
user=config.LOG_DB_USER,
password=config.LOG_DB_PASSWORD,
database=config.LOG_DB_NAME,
charset="utf8mb4",
cursorclass=pymysql.cursors.DictCursor,
autocommit=True,
)
yield conn
finally:
if conn:
conn.close()
protokoll_logger.py (4 Dateien, ~240 LOC dupliziert)
Lösung: Zentralisieren mit strikter Clean Architecture Trennung
# shared/domain/log_entry.py
"""
Domain Entity für Log-Einträge.
REINE DOMAIN - keine Infrastructure-Abhängigkeiten!
"""
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
@dataclass(frozen=True)
class LogEntry:
"""Standardisierter Log-Eintrag für alle MCP-Server."""
timestamp: datetime
client_name: str
request: str
status: str
duration_ms: int
error_message: Optional[str] = None
tool_name: Optional[str] = None
context_id: Optional[int] = None # task_id, contract_id, etc.
# shared/infrastructure/protokoll_logger.py
"""
Infrastructure Logger für mcp_log Tabelle.
Importiert LogEntry aus Domain - definiert ihn NICHT selbst.
"""
import logging
import sys
from typing import TYPE_CHECKING
from shared.domain.log_entry import LogEntry # Import aus Domain!
from shared.infrastructure.simple_db_connection import SimpleDbConnection
if TYPE_CHECKING:
from shared.config_base import LogDatabaseConfig
class ProtokollLogger:
"""Fail-Safe Logger für mcp_log Tabelle."""
def __init__(self, client_name: str, config: "LogDatabaseConfig"):
self.client_name = client_name
self.config = config
self._logger = logging.getLogger(f"mcp.{client_name}")
def log(self, entry: LogEntry) -> None:
"""Schreibt Log-Eintrag. Fehler gehen nur zu stderr."""
try:
with SimpleDbConnection.get_log_connection(self.config) as conn:
with conn.cursor() as cursor:
request_str = self._format_request(entry)
cursor.execute(
"""INSERT INTO mcp_log
(timestamp, client_name, request, status, duration_ms, error_message)
VALUES (%s, %s, %s, %s, %s, %s)""",
(
entry.timestamp,
self.client_name,
request_str[:500],
entry.status,
entry.duration_ms,
entry.error_message[:500] if entry.error_message else None,
)
)
except Exception as e:
print(f"CRITICAL: {self.client_name} log failed: {e}", file=sys.stderr)
def _format_request(self, entry: LogEntry) -> str:
parts = []
if entry.tool_name:
parts.append(f"[{entry.tool_name}]")
if entry.context_id:
parts.append(f"id={entry.context_id}")
parts.append(entry.request[:400] if entry.request else "")
return " ".join(parts)
# === Registry mit Test-Support ===
_logger_instances: dict[str, ProtokollLogger] = {}
def get_logger(client_name: str, config: "LogDatabaseConfig") -> ProtokollLogger:
"""Singleton-Factory für Logger."""
if client_name not in _logger_instances:
_logger_instances[client_name] = ProtokollLogger(client_name, config)
return _logger_instances[client_name]
def clear_logger_registry() -> None:
"""
Setzt die Logger-Registry zurück.
NUR FÜR TESTS! Ermöglicht isolierte Test-Ausführung.
"""
_logger_instances.clear()
config.py - Strikte Statische Konfiguration
# shared/config_base.py
"""
Basis-Konfiguration für alle MCP-Server.
WICHTIG: Diese Klasse wird NICHT instanziiert!
Alle Zugriffe erfolgen über Klassenattribute.
Falsch: config = BaseConfig()
Richtig: host = BaseConfig.DB_HOST
"""
import os
from typing import ClassVar, Protocol, runtime_checkable
@runtime_checkable
class AppDatabaseConfig(Protocol):
"""Protocol für App-Datenbank Konfiguration."""
DB_HOST: str
DB_PORT: int
DB_USER: str
DB_PASSWORD: str
DB_NAME: str
@runtime_checkable
class LogDatabaseConfig(Protocol):
"""Protocol für Log-Datenbank Konfiguration."""
LOG_DB_HOST: str
LOG_DB_NAME: str
LOG_DB_USER: str
LOG_DB_PASSWORD: str
class BaseConfig:
"""
Statische Konfiguration - NICHT INSTANZIIEREN!
Verwendung:
from shared.config_base import BaseConfig
host = BaseConfig.DB_HOST # Korrekt
config = BaseConfig() # FALSCH!
"""
# App-Datenbank
DB_HOST: ClassVar[str] = os.getenv("DB_HOST", "localhost")
DB_PORT: ClassVar[int] = int(os.getenv("DB_PORT", "3306"))
DB_USER: ClassVar[str] = os.getenv("DB_USER", "root")
DB_PASSWORD: ClassVar[str] = os.getenv("DB_PASSWORD", "")
DB_NAME: ClassVar[str] = os.getenv("DB_NAME", "ki_dev")
# Log-Datenbank
LOG_DB_HOST: ClassVar[str] = os.getenv("LOG_DB_HOST", "localhost")
LOG_DB_NAME: ClassVar[str] = os.getenv("LOG_DB_NAME", "ki_dev")
LOG_DB_USER: ClassVar[str] = os.getenv("LOG_DB_USER", "mcp_logger")
LOG_DB_PASSWORD: ClassVar[str] = os.getenv("LOG_DB_PASSWORD", "")
def __init__(self):
raise TypeError("BaseConfig ist statisch und darf nicht instanziiert werden")
3. SRP-Verletzungen
3.1 ContractValidator (409 Zeilen) - Pragmatische Teilzerlegung
Empfohlenes Refactoring:
validators/
├── contract_validator.py # Orchestrierung (< 150 Zeilen)
├── scope_resolver.py # Pfad-Auflösung + Glob (< 100 Zeilen)
└── rule_evaluator.py # Domain Service für Regelauswertung
HINWEIS:
rule_evaluator.pyist ein Domain Service, kein Repository! Es führt keine CRUD-Operationen durch, sondern Business-Logik. Keine Repository-Nomenklatur für Validatoren verwenden.
Aktuelle Verantwortlichkeiten von rule_evaluator:
- Regel-Parsing
- Regel-Auswertung
- Regel-Aggregation
Status: Als Übergang akzeptabel, da reines In-Memory-Domain-Modul ohne IO.
Grenze: Sobald eine Regel externen Zustand berührt → weitere Zerlegung nötig.
4. Inkonsistenzen
4.1 Datenbank-Bibliotheken - Hybride Strategie
| Server | Bibliothek | Pooling | Begründung |
|---|---|---|---|
| mcp-db | mysql.connector | Ja | 100+ Queries/Min, Performance-kritisch |
| andere | pymysql | Nein | < 50 Queries/Min, Einfachheit > Performance |
Trade-off explizit dokumentiert:
- pymysql ohne Pooling: ~50-100ms Connection-Overhead pro Request
- Für mcp-tasks/contracts/docs/code: Akzeptabel bei < 50 Queries/Minute
- Für mcp-db: Inakzeptabel, daher eigenes Pooling
4.2 Zwei DB-Abstraktionen (konzedierte Redundanz)
| Modul | Zweck | Pooling |
|---|---|---|
shared/infrastructure/simple_db_connection.py | Standard-Server | Nein |
mcp-db/infrastructure/db_connection.py | High-Throughput | Ja |
Semantische Abgrenzung:
SimpleDbConnection= Explizit "einfach", nicht für Hochlast- mcp-db behält eigene Implementierung mit
MySQLConnectionPool
5. Ziel-Architektur
5.1 Verzeichnisstruktur (nach Package-Umbenennung)
/var/www/mcp-servers/
├── shared/ # Gemeinsame Komponenten
│ ├── __init__.py
│ ├── config_base.py # Statische BaseConfig + Protocols
│ ├── constants.py # ✓ Existiert bereits
│ ├── server_factory.py # Server Creation Factory
│ ├── domain/
│ │ ├── __init__.py
│ │ └── log_entry.py # LogEntry Dataclass (REINE DOMAIN)
│ └── infrastructure/
│ ├── __init__.py
│ ├── simple_db_connection.py # Ohne Pooling, nicht für Hochlast
│ └── protokoll_logger.py # Importiert LogEntry aus domain/
│
├── mcp_db/ # Umbenannt von mcp-db!
│ ├── server.py
│ ├── config.py
│ ├── infrastructure/
│ │ └── db_connection.py # EIGENE Version mit Pooling
│ └── tools/
│
├── mcp_tasks/ # Umbenannt von mcp-tasks!
│ ├── server.py
│ ├── config.py
│ ├── domain/
│ ├── infrastructure/
│ │ └── task_repository.py
│ └── tools/
│
├── mcp_contracts/ # Umbenannt!
│ ├── server.py
│ ├── config.py
│ ├── validators/
│ │ ├── contract_validator.py
│ │ ├── scope_resolver.py
│ │ └── rule_evaluator.py # Domain Service, KEIN Repository!
│ └── tools/
│
├── mcp_docs/ # Umbenannt!
│ └── ...
│
└── mcp_code/ # Umbenannt!
└── ...
5.2 Clean Architecture Schichten
┌─────────────────────────────────────────────────────────┐
│ SHARED/DOMAIN │
│ ┌───────────────────────────────────────────────────┐ │
│ │ log_entry.py (reine Dataclass, keine Deps) │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
▲
│ importiert
┌─────────────────────────────────────────────────────────┐
│ SHARED/INFRASTRUCTURE │
│ ┌───────────────────────────────────────────────────┐ │
│ │ simple_db_connection.py, protokoll_logger.py │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
▲
│ importiert
┌─────────────────────────────────────────────────────────┐
│ SERVER-LAYER │
│ ┌─────────┐ ┌───────────┐ ┌─────────┐ ┌─────────┐ │
│ │ mcp_db │ │ mcp_tasks │ │mcp_docs │ │mcp_code │ │
│ └─────────┘ └───────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────┘
6. Implementierungsplan mit Quality Gates
Phase 0: Package-Umbenennung (VORAUSSETZUNG)
Dauer: 0.5 Tage
| Schritt | Aktion | Quality Gate |
|---|---|---|
| 0.1 | mcp-db → mcp_db | Import from mcp_db import ... funktioniert |
| 0.2 | mcp-tasks → mcp_tasks | Import funktioniert |
| 0.3 | mcp-contracts → mcp_contracts | Import funktioniert |
| 0.4 | mcp-docs → mcp_docs | Import funktioniert |
| 0.5 | mcp-code → mcp_code | Import funktioniert |
| 0.6 | systemd-Units aktualisieren | systemctl status mcp-* = active |
| 0.7 | Claude MCP Config aktualisieren | Alle Tools erreichbar |
Gate:
- [ ] Keine
sys.path.insertmehr notwendig - [ ]
pyproject.tomlkonsistent - [ ] Alle Server starten und antworten
ACHTUNG: Phase 0 ist nicht optional! Ohne korrekte Package-Namen ist das gesamte Refactoring auf Sand gebaut.
Phase 1: Shared Foundation
Dauer: 2 Tage
| Schritt | Aktion | Quality Gate |
|---|---|---|
| 1.1 | shared/config_base.py | < 60 LOC, mypy clean |
| 1.2 | shared/domain/log_entry.py | < 25 LOC, frozen dataclass |
| 1.3 | shared/infrastructure/simple_db_connection.py | < 80 LOC |
| 1.4 | shared/infrastructure/protokoll_logger.py | < 70 LOC, importiert LogEntry |
| 1.5 | shared/server_factory.py | < 60 LOC |
| 1.6 | Unit Tests | Domain: 100% Coverage, Infra: 90-95% |
Phase 1 Abnahmekriterien:
- [ ]
ruff check shared/= 0 Fehler - [ ]
mypy shared/= 0 Fehler - [ ] Domain Coverage: 100%
- [ ] Infrastructure Coverage: >= 90%
- [ ]
clear_logger_registry()existiert und funktioniert - [ ] Kein
sys.path.insertin shared/
Phase 2: Server Migration
Phase 2a: mcp_docs (einfachstes)
Dauer: 0.5 Tage
Gate:
- [ ] Nutzt
SimpleDbConnection - [ ] Nutzt
ProtokollLoggeraus shared/ - [ ] Kein
sys.path.insert - [ ] LOC-Reduktion: >= 50 Zeilen
Phase 2b-2e: Weitere Server
(analog, siehe v1.1)
Universelle Regel für alle Server:
- [ ] Kein
sys.path.insertnach Migration - [ ] Nur relative Imports oder Package-Imports
Phase 3: ContractValidator Refactoring
| Schritt | Aktion | Quality Gate |
|---|---|---|
| 3.1 | scope_resolver.py | < 100 LOC, 100% Coverage |
| 3.2 | rule_evaluator.py (Domain Service) | < 200 LOC, 100% Coverage |
| 3.3 | contract_validator.py refactored | < 150 LOC |
7. Verbote und Grenzen
7.1 Absolute Verbote nach Phase 1
| Verbot | Begründung |
|---|---|
sys.path.insert() | Non-deterministisch, fragil |
logging.basicConfig() | Nicht deterministisch |
lru_cache mit nicht-hashbaren Args | TypeError zur Laufzeit |
| LogEntry Definition in Infrastructure | Clean Architecture Verletzung |
| Config Instanziierung | Statisch-Only Design |
7.2 Komplexitätsgrenzen
| Konstrukt | Grenze | Bei Überschreitung |
|---|---|---|
get_connection() Parameter | 3 | Neue Methode erstellen |
| Datei-Länge | 300 LOC | Split erforderlich |
| Klassen-Verantwortlichkeiten | 3 | Weitere Zerlegung |
8. Qualitätssicherung
Coverage-Strategie (differenziert)
| Layer | Ziel-Coverage | Begründung |
|---|---|---|
Domain (shared/domain/) | 100% | Reine Logik, keine Mocks nötig |
| Infrastructure | 90-95% | DB-Code mit 100% führt zu Mock-Orgien |
| Server-Layer | 85-90% | Integration, nicht Unit |
Test-Isolation
# In jedem Test-Setup:
from shared.infrastructure.protokoll_logger import clear_logger_registry
def setup_function():
clear_logger_registry() # Isolierte Tests
9. Metriken
Vorher (Baseline)
| Metrik | Wert |
|---|---|
| Dateien | 91 |
| LOC | 9.248 |
| Duplizierter Code | ~2.300 LOC (~25%) |
Ziel Nachher (mit Phase-Gates)
| Phase | Metrik | Ziel |
|---|---|---|
| Phase 0 | sys.path.insert Vorkommen | 0 |
| Phase 1 | shared/ LOC | < 350 |
| Phase 1 | shared/domain Coverage | 100% |
| Phase 1 | shared/infrastructure Coverage | >= 90% |
| Phase 2 | Gelöschte Duplikate | >= 4 Dateien |
| Gesamt | LOC | ~6.500 (-30%) |
| Gesamt | Duplizierter Code | < 800 LOC (< 12%) |
10. Entscheidungen (Final)
Entscheidung 1: Package-Umbenennung
Entscheidung: Phase 0 (Voraussetzung, nicht optional)
Entscheidung 2: Datenbank-Bibliothek
Entscheidung: Hybrid mit klarer Semantik
SimpleDbConnectionfür Standard-Server (kein Pooling)- Eigene Implementierung für mcp_db (mit Pooling)
Entscheidung 3: Singleton Pattern
Entscheidung: Dict-Registry mit clear_registry() für Tests
Entscheidung 4: Clean Architecture
Entscheidung: Strikte Trennung Domain/Infrastructure
- LogEntry nur in
shared/domain/log_entry.py - Infrastructure importiert aus Domain, nicht umgekehrt
Entscheidung 5: sys.path.insert
Entscheidung: Vollständig verboten nach Phase 0
Anhang A: Korrekturen nach Supervision
Runde 1 (v1.0 → v1.1)
| Problem | Korrektur |
|---|---|
| .env Pfad falsch | Expliziter Parameter |
| lru_cache nicht hashbar | Dict-Registry |
| DB Config gemischt | Separate Protocols |
Runde 2 (v1.1 → v1.2)
| Problem | Korrektur |
|---|---|
| Package-Umbenennung unterschätzt | Phase 0 als Voraussetzung |
| LogEntry in Infrastructure | Strikt in Domain |
| sys.path.insert als Fallback | Vollständig verboten |
| Logger Registry ohne Lifecycle | clear_logger_registry() |
| 100% Coverage auf Infra | Differenziert: Domain 100%, Infra 90-95% |
| SimpleDbConnection nicht benannt | Expliziter Name, dokumentierte Grenzen |
| rule_engine als CRUD | Umbenannt zu rule_evaluator (Domain Service) |
| Config instanziierbar | __init__ wirft TypeError |
Dokument Version 1.2 - Final für Implementierung.
Claude Pre-/Post-Hook System
Das Hook-System greift automatisch bei jeder Interaktion mit Claude Code ein und stellt sicher, dass alle Aktionen protokolliert, validiert und abgesichert werden.
Operationale Übersicht: Claude Hooks (Betrieb)
Hook-Typen
| Hook-Event | Zeitpunkt | Zweck |
|---|---|---|
SessionStart |
Session-Beginn | Protokollierung des Session-Starts |
UserPromptSubmit |
Nach User-Eingabe | Prompt-Logging |
PreToolUse |
Vor Tool-Ausführung | Validierung, Backup, Blockierung |
PostToolUse |
Nach Tool-Ausführung | Permissions, Response-Logging |
Stop |
Task-Ende | Abschluss-Protokollierung |
SessionEnd |
Session-Ende | Session-Abschluss |
Konfiguration
Die Hooks werden in /root/.claude/settings.json konfiguriert.
Ablaufdiagramm: Edit/Write Operation
┌─────────────────┐
│ User-Prompt │
└────────┬────────┘
│
▼
┌─────────────────┐
│ UserPromptSubmit│──▶ log_to_db.py (Logging)
└────────┬────────┘
│
▼
┌─────────────────┐
│ PreToolUse │
│ (Edit/Write) │
└────────┬────────┘
│
┌────┴────┐
│ │
▼ ▼
┌───────┐ ┌───────────┐ ┌────────────┐
│Block- │ │file_backup│ │log_to_db.py│
│DB.py │ │_hook.py │ │(Logging) │
└───┬───┘ └─────┬─────┘ └──────┬─────┘
│ │ │
│ Backup erstellt │
│ │ │
└───────────┴──────────────┘
│
┌──────┴──────┐
│ Tool │
│ Execution │
└──────┬──────┘
│
▼
┌─────────────────┐
│ PostToolUse │
│ (Edit/Write) │
└────────┬────────┘
│
┌────┴────┐
│ │
▼ ▼
┌───────────┐ ┌────────────┐
│fix-permi- │ │log_to_db.py│
│ssions.sh │ │(Response) │
└─────┬─────┘ └──────┬─────┘
│ │
▼ │
Permissions │
korrigiert │
└──────────────┘
Unterkapitel
- Pre-Hook Ablauf - Was passiert VOR der Tool-Ausführung
- Post-Hook Ablauf - Was passiert NACH der Tool-Ausführung
- Hook-Scripts - Übersicht aller Scripts
- Protokollierung - Logging in ki_dev.protokoll
- Task-Completion Guard - Workflow-Validierung
Verwandte Themen
- Claude Hooks (Betrieb) - Alle Hooks mit Konfiguration
- Quality Gates - Code-Qualitätsprüfung
RAG-Prozess
Status: Aktualisiert 2025-12-31
Übersicht
Der RAG-Prozess (Retrieval Augmented Generation) bildet das Herzstück der KI-gestützten Wissensgenerierung. Die Architektur trennt strikt zwischen Offline-Pipeline (Wissensaufbau) und Online-Pipeline (Wissenskonsum).
Grundprinzip
- Die Offline-Pipeline erzeugt und strukturiert Wissen (Python-Skripte)
- Die Online-Pipeline konsumiert Wissen, ohne es zu verändern (PHP-Services)
- Zwischen beiden Pipelines besteht keine Rückkopplung zur Laufzeit
Offline-Pipeline: Tatsächlicher Ablauf (IST)
Pfad: /var/www/scripts/pipeline/
Orchestrator: pipeline.py
Step-Module: step_extract.py, step_load.py, step_transform.py, step_embed.py, step_semantic.py
Phase 0: Detection
| Step | Modul | Beschreibung |
|---|---|---|
| detect | detect.py | Dateien scannen, Hash-Vergleich |
| queue | detect.py | Neue/geänderte Dateien in Queue |
Phase 1: Extraction (pro Dokument)
| Step | Modul | Beschreibung |
|---|---|---|
| extract | step_extract.py → extract.py | Text aus PDF/DOCX/PPTX/MD/TXT |
| hash | step_extract.py | SHA256 Hash berechnen |
| rotation | orientation.py | Seitenrotation erkennen (PDF) |
Phase 2: Load
| Step | Modul | Beschreibung |
|---|---|---|
| doc_create | step_load.py | Dokument in documents anlegen |
| page_store | step_load.py | Seiten in document_pages speichern |
Phase 3: Vision (nur PDF)
| Step | Modul | Beschreibung |
|---|---|---|
| vision | step_transform.py → vision.py | Seiten mit llama3.2-vision:11b analysieren |
| vision_store | vision.py | Ergebnisse in document_pages.vision_analysis |
Phase 4: Chunking
| Step | Modul | Beschreibung |
|---|---|---|
| chunk | step_transform.py → chunk.py | Text in semantische Chunks zerlegen |
| chunk_store | step_load.py | Chunks in chunks speichern |
Phase 5: Enrichment (nur PDF)
| Step | Modul | Beschreibung |
|---|---|---|
| enrich | step_transform.py → enrich.py | Vision-Kontext zu Chunks hinzufügen |
Phase 6: Embedding (Layer 3 - Dokument wird suchbar)
| Step | Modul | Beschreibung |
|---|---|---|
| embed | step_embed.py → embed.py | Embeddings mit mxbai-embed-large |
| qdrant_store | embed.py | Vektoren in Qdrant speichern |
| status_embedded | step_load.py | Status = "embedded" |
Nach Phase 6 ist das Dokument im Chat suchbar!
Phase 7: Semantic Analysis (Layer 4 - optional/async)
| Step | Modul | Beschreibung |
|---|---|---|
| entity_extract | analyzers/entity_extractor.py | Entitäten extrahieren (Anthropic/Ollama) |
| entity_store | analyzers/entity_extractor.py | Entitäten in entities speichern |
| entity_normalize | analyzers/entity_normalizer.py | canonical_name setzen |
| relation_extract | analyzers/relation_extractor.py | Relationen extrahieren |
| relation_store | analyzers/document_analyzer.py | In entity_relations speichern |
| taxonomy_classify | analyzers/taxonomy_classifier.py | Taxonomie-Kategorien zuweisen |
| ontology_classify | analyzers/ontology_classifier.py | Ontologie-Klassen zuweisen |
| chunk_entity_link | analyzers/document_analyzer.py | Chunks mit Entitäten verknüpfen |
| chunk_taxonomy | analyzers/document_analyzer.py | Taxonomie auf Chunks propagieren |
| entity_taxonomy | analyzers/document_analyzer.py | Taxonomie auf Entitäten |
| chunk_semantics | analyzers/semantic_analyzer.py | Chunk-Semantik analysieren |
Phase 8: Finalization
| Step | Modul | Beschreibung |
|---|---|---|
| status_done | step_load.py | Status = "done" |
Datenfluss-Diagramm
┌─────────────────────────────────────────────────────────────────┐
│ Phase 0: DETECTION │
│ detect.py: scan_directory() → queue_files() │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Phase 1: EXTRACTION │
│ step_extract.py → extract.py │
│ • Text aus PDF/DOCX/PPTX/MD/TXT │
│ • SHA256 Hash │
│ • Rotationserkennung (orientation.py) │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Phase 2: LOAD │
│ step_load.py │
│ • documents Eintrag erstellen │
│ • document_pages speichern │
└─────────────────────────────┬───────────────────────────────────┘
│
▼ (nur PDF)
┌─────────────────────────────────────────────────────────────────┐
│ Phase 3: VISION │
│ step_transform.py → vision.py │
│ • Seiten als Bilder rendern │
│ • llama3.2-vision:11b Analyse │
│ • Überschriften, Bilder, Tabellen, Layout erkennen │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Phase 4: CHUNKING │
│ step_transform.py → chunk.py │
│ • Semantisches Chunking nach Struktur │
│ • Überlappung: 10% │
│ • Min: 100, Max: 2000 Zeichen │
└─────────────────────────────┬───────────────────────────────────┘
│
▼ (nur PDF)
┌─────────────────────────────────────────────────────────────────┐
│ Phase 5: ENRICHMENT │
│ step_transform.py → enrich.py │
│ • Vision-Kontext zu Chunks hinzufügen │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Phase 6: EMBEDDING (Layer 3) │
│ step_embed.py → embed.py │
│ • mxbai-embed-large (1024 dim) │
│ • Qdrant: Collection "documents" │
│ • Status: "embedded" ✓ SUCHBAR │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Phase 7: SEMANTIC (Layer 4) - Optional │
│ step_semantic.py → analyzers/* │
│ • Entity-Extraktion (Anthropic oder Ollama) │
│ • Relationen │
│ • Taxonomie-Klassifikation │
│ • Ontologie-Zuordnung │
│ • Chunk-Semantik (summary, keywords, sentiment) │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Phase 8: FINALIZATION │
│ • Status: "done" ✓ │
└─────────────────────────────────────────────────────────────────┘
Konfiguration
Quelle: config.py
| Parameter | Wert | Beschreibung |
|---|---|---|
| NEXTCLOUD_PATH | /var/www/nextcloud/data/root/files/Documents | Quellverzeichnis |
| SUPPORTED_EXTENSIONS | [".pdf", ".pptx", ".docx", ".md", ".txt"] | Dateitypen |
| EMBEDDING_MODEL | mxbai-embed-large | Ollama Embedding-Modell |
| EMBEDDING_DIMENSION | 1024 | Vektordimension |
| MIN_CHUNK_SIZE | 100 | Min. Chunk-Größe |
| MAX_CHUNK_SIZE | 2000 | Max. Chunk-Größe |
| CHUNK_OVERLAP_PERCENT | 10 | Überlappung |
| OCR_ENABLED | True | OCR für Scans |
| OCR_LANGUAGE | deu | OCR-Sprache |
| ROTATION_DETECTION_ENABLED | True | Seitenrotation erkennen |
| ROTATION_OSD_CONFIDENCE_THRESHOLD | 2.0 | OSD Confidence-Schwelle |
| SEMANTIC_SYNC | True | Synchrone Semantik-Analyse |
| SEMANTIC_USE_ANTHROPIC | False | Anthropic oder Ollama |
Datei-Referenzen
Pipeline-Kern
/var/www/scripts/pipeline/
├── pipeline.py # Orchestrator
├── config.py # Konfiguration
├── db.py # Datenbank-Wrapper
├── detect.py # Datei-Erkennung
├── extract.py # Text-Extraktion
├── orientation.py # Rotationserkennung (NEU!)
├── chunk.py # Chunking
├── embed.py # Embedding
├── vision.py # Vision-Analyse
├── enrich.py # Vision-Enrichment
Step-Module
├── step_extract.py # Extraktions-Step
├── step_load.py # Lade-Step
├── step_transform.py # Transform-Step (Vision, Chunk, Enrich)
├── step_embed.py # Embedding-Step
└── step_semantic.py # Semantik-Step
Analyzer-Module
├── analyzers/
│ ├── document_analyzer.py # Haupt-Orchestrator
│ ├── entity_extractor.py # Entitäten
│ ├── entity_normalizer.py # Normalisierung
│ ├── relation_extractor.py # Relationen
│ ├── taxonomy_classifier.py # Taxonomie
│ ├── ontology_classifier.py # Ontologie
│ ├── semantic_analyzer.py # Chunk-Semantik
│ └── statement_analyzer.py # SPO-Aussagen
CLI-Befehle
# Vollständiger Durchlauf
python pipeline.py all
# Mit Tracking (für Web-UI)
python pipeline.py all --pipeline-id=4 --run-id=6
# Einzelne Phasen
python pipeline.py scan # Nur scannen
python pipeline.py process # Queue abarbeiten
python pipeline.py embed # Ausstehende Embeddings
python pipeline.py semantic 123 # Semantik für Dokument 123
python pipeline.py status # Status anzeigen
# Einzeldatei
python pipeline.py file /path/to/document.pdf
Datenbank-Tabellen
Content-Datenbank (ki_content)
| Tabelle | Zweck |
|---|---|
| documents | Importierte Dokumente |
| document_pages | Einzelne Seiten mit Vision-Analyse |
| chunks | Text-Chunks |
| entities | Extrahierte Entitäten |
| entity_relations | Relationen zwischen Entitäten |
| chunk_entities | Chunk-Entity-Verknüpfungen |
| chunk_semantics | Chunk-Analyse (summary, keywords) |
| taxonomy_terms | Taxonomie-Begriffe |
| document_taxonomy | Dokument-Taxonomie-Zuordnung |
| entity_taxonomy_mapping | Entity-Taxonomie-Zuordnung |
| ontology_classes | Ontologie-Klassen |
| pipeline_configs | Pipeline-Konfigurationen |
| pipeline_steps | Pipeline-Schritte |
| pipeline_runs | Pipeline-Ausführungen |
| pipeline_queue | Pipeline-Warteschlange |
| semantic_queue | Semantik-Warteschlange |
Dev-Datenbank (ki_dev)
| Tabelle | Zweck |
|---|---|
| pipeline_log | Pipeline-Logs |
Referenz
- Task #442: PDF-Rotationserkennung implementiert
- Doc #143: PDF-Rotationserkennung Details
Verwandte Themen
- Prozesse - Übersicht aller Prozesse
- Scheduling - Cron-Jobs für Pipeline
Quality Gates
Automatische Code-Qualitätsprüfung via Claude Code Hooks mit Task-Integration.
| Status | Aktiv |
|---|---|
| Implementiert | 2025-12-23 (Phase 1+2+3: 2025-12-28) |
| Regeln | 31 BLOCK + 50 WARN = 81 total |
| Task-Integration | Automatisch bei Violations |
Architektur
/var/www/tools/ki-protokoll/claude-hook/
├── hook_dispatcher.py ← Stabiler Einstiegspunkt
├── .env ← DB-Credentials
└── quality/
├── pre_rules.py ← BLOCK-Regeln Dispatcher
├── post_rules.py ← WARN-Regeln Dispatcher
├── pre_rules_guard.py ← P1.x SRP, P4.x OOP
├── pre_rules_mvc.py ← P2.x MVC
├── pre_rules_validation.py ← P3.x PSR
├── pre_rules_layers.py ← P6.x SOLID
├── pre_rules_htmx.py ← HTMX-C1-C5
├── pre_rules_constants.py ← P7.x Magic Numbers
├── pre_rules_deterministic.py ← P8.x Deterministic
├── pre_rules_tests.py ← P14.x Test Isolation
├── pre_rules_python.py ← PP1.x Python
├── rules_quality.py ← W1.x-W6.x Quality
├── rules_security.py ← Security Warnings
├── rules_style.py ← Style Warnings
├── rules_constants.py ← W7.x Magic Numbers
├── rules_failfast.py ← W8.x Fail Fast
├── rules_failsafe.py ← W9.x Fail Safe (Phase 3)
├── rules_tradeoffs.py ← W10.x Trade-offs (Phase 3)
├── rules_leastsurprise.py ← W15.x Least Surprise
├── rules_testisolation.py ← W14.x Test Isolation
├── phpmetrics_check.py ← W13.x Cohesion (Phase 3)
└── task_creator.py ← Violations → Tasks
Prüfungen (15 Prinzipien)
| # | Prüfung | Pre-Hook (BLOCK) | Post-Hook (WARN) |
|---|---|---|---|
| 1 | SRP + KISS | P1.1 Header, P1.2 Müllhalden | W1.1-W1.5 Metriken |
| 2 | MVC + CRUD | P2.1-P2.4 SQL, Trans, echo | W2.1-W2.2 Keywords |
| 3 | PSR + Types | P3.1-P3.4 strict, NS, Return | W3.1, W3.3 Params |
| 4 | OOP | P4.1 Public Property | W4.1-W4.4 Anämie |
| 5 | DRY | - | W5.1-W5.2 Duplikate |
| 6 | SOLID + DIP | P6.1-P6.2 Layer-Imports | W6.1-W6.2 Interface |
| 7 | Constants | P7.1-P7.3 Magic Numbers | W7.1-W7.5 Magic Strings |
| 8 | Deterministic | P8.1-P8.9 time, rand, global | W8.1-W8.5 Fail Fast |
| 9 | Fail Safe | - | W9.1-W9.6 try-finally, cleanup |
| 10 | Trade-offs | - | W10.1-W10.6 ADR, Factory, Cache |
| 13 | Cohesion | - | W13.1-W13.6 LCOM, Coupling |
| 14 | Test Isolation | P14.1-P14.6 Prod-DB, TRUNCATE | W14.1-W14.7 tearDown, sleep |
| 15 | Least Surprise | - | W15.1-W15.6 Getter Side Effects |
Fett = Neu in Phase 3 (2025-12-28)
Phase 3: Neue Regeln
W9.x Fail Safe (WARN)
- W9.1: try-Block mit Ressourcen ohne finally
- W9.2: DB-Operationen ohne Exception-Handling
- W9.3: catch ohne Logging oder Rethrow
- W9.4: Ressourcen-Properties ohne __destruct
- W9.5: exit/die ohne Shutdown-Handler
- W9.6: Connection ohne passenden Close
W10.x Trade-off Documentation (WARN)
- W10.1: Factory Pattern ohne ADR-Referenz
- W10.2: Event-Architektur ohne Dokumentation
- W10.3: Caching ohne Invalidation-Strategie
- W10.4: 3+ Traits ohne Kompositions-Doku
- W10.5: External Service ohne Fehlerbehandlungs-Doku
- W10.6: @deprecated ohne Migrations-Hinweis
W13.x Cohesion (via PHPMetrics)
- W13.1: LCOM > 4 (Low Cohesion)
- W13.2: Afferent Coupling > 10
- W13.3: Efferent Coupling > 15
- W13.4: Sehr stabile Klasse mit vielen Dependents
- W13.5: Methoden-Komplexität CCN > 15
- W13.6: WMC > 50 (Class too complex)
PHPMetrics Integration
# Installation
/opt/php-tools/vendor/bin/phpmetrics
# Analyse ausführen
/opt/php-tools/vendor/bin/phpmetrics --report-json=/tmp/phpmetrics.json /var/www/dev.campus.systemische-tools.de/src
# Cohesion-Check (Hook-Integration)
python3 /var/www/tools/ki-protokoll/claude-hook/quality/phpmetrics_check.py
ADR Template
Für Trade-off Dokumentation: /var/www/dev.campus.systemische-tools.de/docs/templates/adr-template.md
Prinzipien-Abdeckung
| Kategorie | Anzahl | Anteil |
|---|---|---|
| ✅ Gut abgedeckt (≥70%) | 12/15 | 80% |
| ⚠️ Teilweise (30-69%) | 2/15 | 13% |
| ❌ Nicht automatisierbar | 1/15 | 7% |
Detaillierte Dokumentation
- Claude Pre-/Post-Hook Prozess
- Python-Erweiterung
/var/www/dev.campus.systemische-tools.de/docs/codequality/ist-soll-analyse.md
Chat UI Redesign: 3-Spalten-Layout
Task #424 - KORRIGIERTER Implementierungsplan
Version: 2.0 (nach kritischem Review)
Datum: 2025-12-27
Architektur-Entscheidungen
Wiederverwendung bestehender Infrastruktur
| Komponente | Status | Aktion |
|---|---|---|
ManageConfigUseCase | Existiert | Wiederverwenden |
ContentConfigRepository | Existiert | Wiederverwenden |
ConfigController (Web) | Existiert | Nicht ändern |
Controller\Api\ConfigController | Fehlt | NEU erstellen |
Korrigierte Prinzipien
| Prinzip | Alter Plan | Korrektur |
|---|---|---|
| SRP | ChatController erweitern | Neuer Api\ConfigController |
| DRY | Doppelte Dropdowns | Settings-Bereich entfernen |
| MVC | Version-Logik im Controller | UseCase nutzen |
| ISP | Interface unvollständig | Interface erweitern |
| YAGNI | History-Browser | Nur Save = neue Version |
| WCAG | Fehlende ARIA | Vollständige A11y |
Dateien und Änderungen
1. NEUER Controller: src/Controller/Api/ConfigController.php
<?php
declare(strict_types=1);
namespace Controller\Api;
// @responsibility: JSON-API für Config-Operationen (Chat-Integration)
use Framework\Controller;
use UseCases\Config\ManageConfigUseCase;
class ConfigController extends Controller
{
public function __construct(
private ManageConfigUseCase $configUseCase
) {
}
/**
* GET /api/v1/config/{id}
* Lädt Config mit Content für Anzeige
*/
public function show(string $id): void
{
$config = $this->configUseCase->getById((int) $id);
if ($config === null) {
$this->json(['error' => 'Config nicht gefunden'], 404);
return;
}
$this->json([
'id' => $config->id,
'name' => $config->name,
'type' => $config->type,
'content' => $config->content,
'version' => $config->version,
]);
}
/**
* POST /api/v1/config/{id}
* Speichert neue Version (auto-increment)
*/
public function update(string $id): void
{
$this->requireCsrf();
$config = $this->configUseCase->getById((int) $id);
if ($config === null) {
$this->json(['error' => 'Config nicht gefunden'], 404);
return;
}
$newContent = $_POST['content'] ?? '';
// Auto-increment Version
$currentVersion = $config->version ?? '1.0';
$versionParts = explode('.', $currentVersion);
$newVersion = $versionParts[0] . '.' . ((int) ($versionParts[1] ?? 0) + 1);
$result = $this->configUseCase->update(
id: (int) $id,
name: $config->name,
slug: $config->slug,
description: $config->description,
content: $newContent,
newVersion: $newVersion,
changeDescription: 'Chat UI Update',
status: $config->status,
parentId: $config->parentId
);
if (!$result->success) {
$this->json(['error' => $result->message], 400);
return;
}
$this->json([
'success' => true,
'version' => $newVersion,
'message' => "Version {$newVersion} gespeichert",
]);
}
}
Zeilen: ~70 | Dependencies: 1 (ManageConfigUseCase) | SRP: Nur Config-API
2. Routes: routes/api.php
Hinzufügen:
// Config API (für Chat-Integration)
use Controller\Api\ConfigController;
$router->get('/api/v1/config/{id}', [ConfigController::class, 'show']);
$router->post('/api/v1/config/{id}', [ConfigController::class, 'update']);
3. Interface erweitern: src/Domain/Repository/ContentConfigRepositoryInterface.php
interface ContentConfigRepositoryInterface
{
// ... bestehende Methoden ...
/**
* Save version to history before update.
*/
public function saveHistory(
int $configId,
string $content,
string $version,
string $changedBy,
string $changeDescription
): void;
/**
* Get version history for a config.
*
* @return array<int, array<string, mixed>>
*/
public function getHistory(int $configId, int $limit = 10): array;
}
4. View: src/View/chat/index.php
ENTFERNEN aus .chat-settings--advanced:
select[name="author_profile_id"]select[name="system_prompt_id"]select[name="structure_id"]
NEU einfügen nach <aside class="chat-sidebar">...</aside>:
<!-- Config Panel -->
<aside class="chat-config" id="configPanel">
<div class="chat-config__header">
<span>Konfiguration</span>
</div>
<!-- System Prompt -->
<div class="chat-config__section" data-config-type="system_prompt">
<label for="configSystemPrompt" class="chat-config__label">System Prompt</label>
<div class="chat-config__row">
<select name="system_prompt_id" id="configSystemPrompt" class="chat-config__select">
<?php foreach ($systemPrompts ?? [] as $prompt): ?>
<option value="<?= $prompt['id'] ?>"
data-content="<?= htmlspecialchars($prompt['content'] ?? '{}') ?>">
<?= htmlspecialchars($prompt['name']) ?>
</option>
<?php endforeach; ?>
</select>
<button type="button"
class="chat-config__toggle"
aria-expanded="false"
aria-controls="systemPromptEdit"
aria-label="System Prompt bearbeiten">
<span aria-hidden="true">▸</span>
</button>
</div>
<div class="chat-config__edit" id="systemPromptEdit" hidden>
<textarea id="systemPromptText" class="chat-config__textarea" rows="8"></textarea>
<button type="button" class="chat-config__save" data-config-id="">
Speichern
</button>
</div>
</div>
<!-- ... weitere Sections (Struktur, Autor-Profil) ... -->
</aside>
5. CSS: public/css/chat-redesign.css
/* CONFIG PANEL (3-Spalten-Layout) */
.chat-config {
width: 300px;
background: var(--chat-sidebar-bg);
border-right: 1px solid var(--chat-border);
display: flex;
flex-direction: column;
flex-shrink: 0;
overflow-y: auto;
}
.chat-config__header {
padding: 16px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--chat-text-secondary);
border-bottom: 1px solid var(--chat-border);
}
.chat-config__section { padding: 12px 16px; }
.chat-config__label { display: block; font-size: 11px; margin-bottom: 6px; }
.chat-config__row { display: flex; gap: 8px; align-items: center; }
.chat-config__select { flex: 1; padding: 8px 10px; border-radius: 6px; }
.chat-config__toggle { padding: 6px 10px; border-radius: 6px; cursor: pointer; }
.chat-config__textarea { width: 100%; font-family: monospace; font-size: 12px; }
.chat-config__save { width: 100%; padding: 8px; background: var(--chat-accent); }
@media (max-width: 1024px) {
.chat-config { display: none; }
}
Zusammenfassung der Änderungen
| Datei | Aktion | Zeilen |
|---|---|---|
src/Controller/Api/ConfigController.php | NEU | ~70 |
routes/api.php | ERWEITERN | +3 |
src/Domain/Repository/ContentConfigRepositoryInterface.php | ERWEITERN | +15 |
src/View/chat/index.php | ÄNDERN | ~100 |
public/css/chat-redesign.css | ERWEITERN | ~120 |
Total neue Zeilen: ~310 | Geänderte Dateien: 5 | Neue Dateien: 1
Checkliste vor Implementation
ManageConfigUseCaseim DI-Container registriert?- PHPStan Level 5+ prüfen
- WCAG: Alle ARIA-Attribute korrekt?
- Contract #5 (layered-architecture): Kein SQL im Controller?
- Contract #4 (js-browser): Keine == Vergleiche?
- Contract #2 (css): Focus-Styles vorhanden?
Nicht implementiert (YAGNI)
Version-History-Browser→ Nur Save = neue VersionHistory-Wiederherstellung→ Via /config/{id} Web-UIKomplexe Version-Auswahl→ Automatisches Inkrement
Verwandte Themen
- HTMX - Interaktivitäts-Patterns
- Architektur - Clean Architecture
db.py Refactoring Plan
Status
- Task ID: 505
- Erstellt: 2025-12-28
- Status: Plan erstellt
Ausgangslage
Datei: /var/www/scripts/pipeline/db.py
Zeilen: 834
Klassen: 2 (Database: 707 Zeilen, PipelineProgress: 109 Zeilen)
Problem: 14 verschiedene Verantwortlichkeiten in der Database-Klasse (SRP-Verletzung)
Ziele
- Jedes Modul unter 500 Zeilen (Code Hygiene Limit)
- Strikte Einhaltung von SRP (Single Responsibility Principle)
- 100% Rückwärtskompatibilität - alle bestehenden Imports funktionieren weiterhin
- DRY, KISS, SOLID Prinzipien
Architektur: Mixin-Pattern
Python Mixins ermöglichen das Aufteilen einer Klasse in logische Einheiten bei gleichzeitiger Beibehaltung der Abwärtskompatibilität.
db_core.py ─┐
db_documents.py │
db_queue.py ├── Mixins ──► db.py (Database erbt von allen)
db_logging.py │
db_semantic.py │
db_prompts.py ─┘
Modul-Aufteilung
1. db_core.py (~100 Zeilen)
Verantwortung: Connection Management
class DatabaseCore:
def __init__(self): ...
def connect(self): ...
def disconnect(self): ...
def execute(self, query, params=None): ...
def commit(self): ...
2. db_documents.py (~150 Zeilen)
Verantwortung: Document, Page, Chunk CRUD
class DocumentsMixin:
# Documents
def document_exists(self, file_path): ...
def document_is_done(self, file_path): ...
def insert_document(self, file_path, title, file_type, file_size, file_hash): ...
def update_document_status(self, doc_id, status, error_message=None): ...
# Pages
def insert_page(self, doc_id, page_number, text_content, token_count=None): ...
def get_page_id(self, doc_id, page_number): ...
# Chunks
def insert_chunk(self, doc_id, chunk_index, content, heading_path, ...): ...
def get_chunks_for_embedding(self, limit=DEFAULT_LIMIT): ...
def update_chunk_qdrant_id(self, chunk_id, qdrant_id): ...
3. db_queue.py (~60 Zeilen)
Verantwortung: Pipeline Queue Operations
class QueueMixin:
def add_to_queue(self, file_path, action="process"): ...
def get_pending_queue_items(self, limit=10): ...
def update_queue_status(self, queue_id, status, error_message=None): ...
4. db_logging.py (~180 Zeilen)
Verantwortung: Alle Logging-Operationen
class LoggingMixin:
def log(self, level, message, context=None): ...
def log_to_protokoll(self, client_name, request, response=None, ...): ...
def log_provenance(self, artifact_type, artifact_id, source_type, ...): ...
5. db_semantic.py (~250 Zeilen)
Verantwortung: Entity Types, Stopwords, Taxonomy, Synonyms
class SemanticMixin:
# Entity Types
def get_entity_types(self, active_only=True): ...
def get_entity_type_codes(self): ...
def build_entity_prompt_categories(self): ...
# Stopwords
def get_stopwords(self, active_only=True): ...
def is_stopword(self, word): ...
def _normalize_stopword(self, word): ...
# Synonyms (internal, nicht extern genutzt)
def find_entity_by_synonym(self, synonym): ...
def add_synonym(self, entity_id, synonym, ...): ...
# Chunk Taxonomy
def add_chunk_taxonomy(self, chunk_id, term_id, ...): ...
def get_chunk_taxonomies(self, chunk_id): ...
# Entity Taxonomy
def add_entity_taxonomy(self, entity_id, term_id, ...): ...
def get_entity_taxonomies(self, entity_id): ...
def get_taxonomy_terms(self): ...
6. db_prompts.py (~70 Zeilen)
Verantwortung: Prompt-Verwaltung
class PromptsMixin:
def get_prompt(self, name, version=None): ...
def get_prompt_by_use_case(self, use_case, version=None): ...
7. db.py (~100 Zeilen - Kompositions-Layer)
Verantwortung: Rückwärtskompatibilität
from db_core import DatabaseCore
from db_documents import DocumentsMixin
from db_queue import QueueMixin
from db_logging import LoggingMixin
from db_semantic import SemanticMixin
from db_prompts import PromptsMixin
class Database(
DatabaseCore,
DocumentsMixin,
QueueMixin,
LoggingMixin,
SemanticMixin,
PromptsMixin
):
'''Vollständige Database-Klasse mit allen Operationen.'''
pass
class PipelineProgress:
# ... (unverändert, eigene Klasse)
# Globale Instanz für Rückwärtskompatibilität
db = Database()
Zeilen-Verteilung
| Modul | Zeilen | Verantwortlichkeiten |
|---|---|---|
| db_core.py | ~100 | Connection |
| db_documents.py | ~150 | Documents, Pages, Chunks |
| db_queue.py | ~60 | Queue |
| db_logging.py | ~180 | Log, Protokoll, Provenance |
| db_semantic.py | ~250 | Types, Stopwords, Taxonomy |
| db_prompts.py | ~70 | Prompts |
| db.py | ~100 | Komposition + PipelineProgress |
| Total | ~910 | (verteilt auf 7 Dateien) |
Rückwärtskompatibilität
Alle existierenden Imports funktionieren weiterhin:
# Diese Imports bleiben gültig:
from db import db
from db import Database
from db import PipelineProgress
from db import db, PipelineProgress
Implementierungs-Reihenfolge
- db_core.py erstellen (Basis für alle anderen)
- db_logging.py erstellen (wird von vielen benötigt)
- db_prompts.py erstellen (eigenständig)
- db_documents.py erstellen (eigenständig)
- db_queue.py erstellen (eigenständig)
- db_semantic.py erstellen (eigenständig)
- db.py umschreiben (Komposition)
- Tests durchführen (alle Pipeline-Scripts testen)
Risiken & Mitigationen
| Risiko | Mitigation |
|---|---|
| Import-Fehler nach Refactoring | Kompositions-Layer in db.py exportiert alles |
| Circular Imports | Jedes Modul importiert nur db_core.py |
| Connection-Sharing | Mixins nutzen self.connection von DatabaseCore |
| Tests fehlschlagen | Schrittweise Tests nach jedem Modul |
Cleanup-Möglichkeiten
- Entfernen:
find_entity_by_synonym,add_synonym(nicht extern genutzt) - Vereinfachen:
get_page_idkönnte ininsert_pageintegriert werden - Optimieren: Redundante Cursor-Handling kann vereinheitlicht werden
Refactoring-Session 22.12.2025 - Abgeschlossen
Zusammenfassung
Umfangreiche Architektur-Verbesserungen basierend auf externer Supervision. Alle 9 Phasen erfolgreich implementiert.
Abgeschlossene Phasen
| Phase | Beschreibung | Status |
|---|---|---|
| 1.1 | DatabaseFactory als einzige DB-Quelle | OK |
| 1.2 | Repository-Pattern konsequent umgesetzt | OK |
| 2.1 | Application Services eingefuehrt | OK |
| 2.2 | Command/Query-Objekte | OK |
| 2.3 | DI-Container fuer Controller | OK |
| 3.1 | Response-Pipeline vereinheitlicht | OK |
| 3.2 | Formatter/Renderer-Klassen | OK |
| 4.1 | State Machines fuer Domain-Objekte | OK |
| 4.2 | Audit-Logging systematisiert | OK |
Neue Komponenten (Phase 3-4)
Formatter (src/Infrastructure/Formatter/)
ApiResponseFormatter.php- Einheitliche API-Responses (success, error, list, notFound)DateFormatter.php- Konsistente Datumsformatierung (short, dateTime, relative)
State Machines (src/Domain/ValueObject/)
TaskStatus.php- 5 States mit Transition-ValidierungContentOrderStatus.php- 7 States (draft bis published)PipelineRunStatus.php- 5 States fuer Pipeline-Runs
Logging (src/Infrastructure/Logging/)
AuditLogger.php- Zentrale Logging-Klasse mit Correlation-ID
Middleware (src/Framework/Middleware/)
CorrelationMiddleware.php- Request-Tracing via X-Correlation-ID
Exception (src/Domain/Exception/)
InvalidStateTransitionException.php- Fuer State Machine Violations
Datenbank
audit_logTabelle in ki_dev mit Indizes fuer correlation_id, event, created_at
Validierung Phase 6
Contract-Validierung
db-access-security-protocol: PASSED (0 Violations)code-quality-standards: 4 major (vorbestehende God-Classes)
Controller LOC-Pruefung
15 von 17 Controllern unter 300 LOC. ContentController (397) und ContentPipelineController (631) noch zu gross.
PDO-Pruefung
Keine direkten PDO-Zugriffe in Controllern - OK
PHP-Quality-Check
- PHPStan: OK (keine Fehler)
- PHP-CS-Fixer: OK (0 Fixes)
- Composer Audit: Keine Schwachstellen
- Semgrep: Keine Schwachstellen
Bekannte Probleme (vorbestehend)
- 4 God-Classes ueber 500 LOC (SemanticExplorerRepository, ContentPipelineController, ChunkSyncService, SystemExplorerRepository)
- Unit-Tests benoetigen Autoload-Konfiguration in composer.json
Architektur nach Refactoring
Request
|
v
CorrelationMiddleware (X-Correlation-ID)
|
v
Router -> Controller
|
v
Application Service
|
+-> Repository (DB via DatabaseFactory)
|
+-> Domain ValueObjects (State Machines)
|
+-> AuditLogger (Logging)
|
v
Formatter (API/Date)
|
v
Response (View/JSON/Redirect)