Docs: AGENTS.md mit CLAUDE.md konsolidiert und Einzeldatei entfernt

Code-Style-Richtlinien (Imports, Type Annotations, Naming, Logging,
Docstrings), UI-Import-Pattern, Thread-Pattern, RAM-Optimierung und
Test-Infos aus AGENTS.md übernommen. Veraltete Einträge korrigiert
(qdarktheme entfernt, _execute_sql_query → DatabaseQueryThread,
XslDependencyDialog dokumentiert).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 15:25:10 +01:00
parent 3dcbf783b1
commit 64408157ba
2 changed files with 350 additions and 549 deletions
-365
View File
@@ -1,365 +0,0 @@
# AGENTS.md
**Entwicklerrichtlinien für DocuMentor (KI-Coding-Agenten)**
Diese Datei enthält wichtige Richtlinien für Coding-Agenten, die in diesem Repository arbeiten.
## Sprache
**Alle Kommunikation, Code-Kommentare und UI-Texte auf Deutsch!**
- Kommentare: Deutsch
- Docstrings: Deutsch
- Log-Meldungen: Deutsch
- Variablennamen: Deutsch wo kontextuell passend
- UI-Labels: Deutsch
## Build & Lint Kommandos
### Paketmanager: `uv` (NICHT pip oder poetry!)
```bash
# Abhängigkeiten installieren
uv sync
# Anwendung starten
uv run python src/main.py
# Code-Style prüfen (Zeilenlänge: 120)
uv run ruff check
# Code automatisch formatieren
uv run ruff format
```
### Tests ausführen
```bash
# Alle Hash-Tests
uv run python test_hash_implementation.py
# Duplikatserkennung testen
uv run python test_xml_hash_duplicate_detection.py
# Einzelnen Test ausführen (direkter Python-Aufruf)
uv run python test_hash_implementation.py
```
**Hinweis:** Dieses Projekt verwendet KEINE pytest/unittest-Frameworks. Tests sind standalone Python-Skripte mit if __name__ == "__main__".
## Code-Style-Richtlinien
### Import-Organisation
**Reihenfolge (keine Leerzeilen zwischen Gruppen):**
```python
# 1. Standard Library
import os
import sys
import logging
from pathlib import Path
from typing import Optional, TYPE_CHECKING
# 2. Drittanbieter (Third-Party)
from PySide6.QtCore import Qt, QThread, Signal
from PySide6.QtWidgets import QDialog, QMainWindow
from pydantic import BaseModel, Field
# 3. Lokale Imports (IMMER absolute Imports, KEINE relativen .imports)
from conf import app_settings, TreeNode, XslFile
from ui.MainWindow import MainWindow
from ui.JavaVmConfigDialog_ui import Ui_JavaVmConfigDialog
```
**Wichtig:**
- Immer `from pathlib import Path` verwenden
- NIEMALS String-Pfade verwenden, IMMER `Path`-Objekte
- `TYPE_CHECKING` für zirkuläre Import-Vermeidung nutzen
- Keine relativen Imports (`.` oder `..`)
### Type Annotations
**Moderne Union-Syntax verwenden:**
```python
# RICHTIG
def transform(xml_path: Path, params: dict[str, str]) -> tuple[bool, str]:
result: str | None = None
files: list[Path] = []
# FALSCH
def transform(xml_path, params): # Keine Annotations
result: Optional[str] = None # Alte Union-Syntax
files: List[Path] = [] # Großgeschriebene Types
```
**Pflicht:**
- Alle Funktionsparameter annotieren
- Alle Rückgabewerte annotieren
- Moderne Syntax: `str | None` statt `Optional[str]`
- Container: `list[str]`, `dict[str, str]`, `tuple[int, int]`
### Naming Conventions
```python
# Klassen: PascalCase
class SaxonWorkerPool:
class TransformationJob:
# Funktionen/Methoden: snake_case
def transform_saxon(xml_file: Path) -> bool:
def calculate_hash(content: bytes) -> str:
# Private Methoden: _snake_case mit Unterstrich
def _create_tree_item(self, node: TreeNode):
def _load_project_data(self):
# Variablen: snake_case
xml_file_path = Path("test.xml")
diff_pdf_count = 0
self.current_zoom = 100
# Konstanten: UPPER_CASE
SAXON_WORKER_JAVA = """..."""
MAX_RETRY_COUNT = 3
```
### Formatierung & Linting
- **Zeilenlänge:** 120 Zeichen (via Ruff konfiguriert)
- **Strings:** Bevorzugt Double-Quotes `"..."`, aber konsistent im File
- **Trailing Commas:** Bei mehrzeiligen Strukturen verwenden
- **Ruff:** Alle Warnings beheben vor Commit
### Error Handling
**IMMER Logging statt print() verwenden:**
```python
import logging
logger = logging.getLogger(__name__)
def transform(xml_path: Path) -> tuple[bool, str]:
try:
# Operation durchführen
logger.info(f"Transformation gestartet: {xml_path}")
result = do_transform(xml_path)
logger.debug(f"Zwischenergebnis: {result}")
return True, "Erfolg"
except FileNotFoundError as e:
error_msg = f"XML-Datei nicht gefunden: {xml_path}"
logger.error(error_msg)
return False, error_msg
except Exception as e:
error_msg = f"Fehler bei Transformation: {str(e)}"
logger.exception(error_msg) # Mit Stack Trace
return False, error_msg
```
**Pattern:**
- `logger.debug()` für Debugging-Infos
- `logger.info()` für normale Operationen
- `logger.warning()` für Warnungen
- `logger.error()` für Fehler ohne Stack Trace
- `logger.exception()` für Fehler MIT Stack Trace
- Fehlermeldungen auf Deutsch
### Docstrings
**Google-Style auf Deutsch:**
```python
def transform_xml_to_pdf(xml_path: Path, xsl_path: Path, output_dir: Path) -> tuple[bool, str]:
"""
Transformiert eine XML-Datei mit XSL zu PDF.
Args:
xml_path: Pfad zur XML-Eingabedatei
xsl_path: Pfad zum XSL-Stylesheet
output_dir: Zielverzeichnis für PDF-Ausgabe
Returns:
tuple[bool, str]: (Erfolg, Fehlermeldung oder Info-Text)
Raises:
FileNotFoundError: Wenn XML- oder XSL-Datei nicht existiert
"""
```
**Modul-Docstrings am Dateianfang:**
```python
"""
Saxon Worker Pool - Persistente JVM-Prozesse für schnelle XSLT-Transformationen.
Eliminiert JVM-Startup-Overhead durch Vorinitialisierung von N Worker-Prozessen.
Verwendet multiprocessing.Queue für Thread-sichere Kommunikation.
"""
```
## Pydantic Models
### Definition
```python
from pydantic import BaseModel, Field
class Project(BaseModel):
id: int = Field(..., description="Eindeutige Projekt-ID", gt=0)
name: str = Field(..., description="Projekt-Name", min_length=1, max_length=255)
project_dir: Path = Field(..., description="Pfad zum Projekt-Verzeichnis")
# Helper-Methoden direkt im Model erlaubt
def getJavaVm(self) -> str:
global app_settings
value = [x.version for x in app_settings.java_vms if x.id == self.java_vm_id]
return value[0] if len(value) else ""
```
### Settings speichern
```python
from conf import app_settings, ProjectData
# Globale Einstellungen
app_settings.theme = "Fusion"
app_settings.save() # WICHTIG: Nicht vergessen!
# Projekteinstellungen
project_data = ProjectData.readSettings(project_dir)
project_data.nodes.append(new_node)
project_data.writeSettings(project_dir) # WICHTIG: Persistieren!
```
## PySide6 UI-Integration
### KRITISCH: UI-Dateien nicht manuell bearbeiten!
```
src/ui/
├── MainWindow.ui # Qt Designer Datei (editieren erlaubt)
├── MainWinddow_ui.py # AUTO-GENERIERT (NICHT BEARBEITEN!)
└── MainWindow.py # Implementierung (hier Code schreiben)
```
### UI-Import-Pattern
```python
# In src/ui/JavaVmConfigDialog.py
from PySide6.QtWidgets import QDialog
from ui.JavaVmConfigDialog_ui import Ui_JavaVmConfigDialog
class JavaVmConfigDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
# UI einrichten
self.ui = Ui_JavaVmConfigDialog()
self.ui.setupUi(self)
# Signale NACH setupUi() verbinden
self.ui.browseButton.clicked.connect(self._browse_file)
def _browse_file(self):
# Widgets über self.ui.widgetName zugreifen
current_path = self.ui.pathLineEdit.text()
...
```
**Wichtig:**
- UI-Klassen NIEMALS direkt erben, nur als `self.ui` Member
- Alle Widgets über `self.ui.widgetName` zugreifen
- Signal-Verbindungen immer NACH `setupUi()` aufrufen
## Projektstruktur-Änderungen
Beim Modifizieren der Baumstruktur (TreeNode, XslFile, XmlFile):
```python
# 1. ProjectData modifizieren
self.project_data.nodes.append(new_node)
# 2. SOFORT persistieren
self.project_data.writeSettings(self.project.project_dir)
# 3. UI neu laden
self._load_nodes_to_tree()
```
**Pattern:** Immer in dieser Reihenfolge: Modifizieren → Speichern → UI aktualisieren
## Wichtige Konventionen
### Pathlib IMMER verwenden
```python
# RICHTIG
from pathlib import Path
xml_path = Path("data/test.xml")
xml_path = Path.home() / ".config" / "app" / "config.json"
xml_path = Path(os.path.expandvars("$HOME/data")).expanduser()
if xml_path.exists():
content = xml_path.read_text(encoding="utf-8")
# FALSCH
xml_path = "data/test.xml" # String-Pfad
xml_path = os.path.join("data", "test.xml") # os.path statt pathlib
```
### Globale Singletons
```python
# In conf.py am Modulende
app_settings = AppSettings()
# In anderen Modulen
from conf import app_settings
# Verwendung
java_vm = [x for x in app_settings.java_vms if x.id == vm_id][0]
```
### Thread-basierte Operationen
```python
from PySide6.QtCore import QThread, Signal
class HashCalculatorThread(QThread):
# Signale für Thread-sichere Kommunikation
progress = Signal(int)
finished = Signal(dict)
def __init__(self, files: list[Path]):
super().__init__()
self.files = files
def run(self):
for i, file_path in enumerate(self.files):
hash_value = calculate_hash(file_path)
self.progress.emit(i + 1)
self.finished.emit(results)
# Verwendung
thread = HashCalculatorThread(xml_files)
thread.progress.connect(self._on_progress)
thread.finished.connect(self._on_finished)
thread.start() # NICHT run() direkt aufrufen!
```
## RAM-Optimierung
Da DocuMentor permanent läuft, sparsam mit RAM umgehen:
- Worker-Pools nach Verwendung herunterfahren
- Große Datenstrukturen frühzeitig freigeben
- Polars DataFrames statt Pandas (geringerer RAM-Verbrauch)
- Lazy Loading wo möglich
---
**Zusammenfassung:** Deutsch sprechen, pathlib verwenden, Typen annotieren, Ruff nutzen, UI-Dateien nicht anfassen!
+181 -15
View File
@@ -19,13 +19,11 @@ Die Basis bilden etwa 100 XSL-Dateien. Die meisten sind mittels `<xsl:import/>`
Diese Schritte können sich mehrfach wiederholen. Diese Schritte können sich mehrfach wiederholen.
Da der DocuMentor permanent im Hintergrund läuft, ist ein sparsamer Umgang mit RAM wichtig. Da der DocuMentor permanent im Hintergrund läuft, ist ein sparsamer Umgang mit RAM wichtig:
- Worker-Pools nach Verwendung herunterfahren
## PySide6-GUI - Große Datenstrukturen frühzeitig freigeben
- Beim Erstellen neuer Dialoge und Fenster sollte immer eine entsprechende UI-Datei erstellt werden - Polars DataFrames statt Pandas (geringerer RAM-Verbrauch)
- Der Entwickler sollte später in der Lage sein, den neuen Dialog bzw. Fenster über diese UI-Datei zu gestalten - Lazy Loading wo möglich
- Aus der UI-Datei wird in Visual Studio Code über eine Erweiterung automatisch eine .py-Datei erzeugt
- Die automatisch generierte .py-Datei muss in den Code eingebunden und verwendet werden
## Entwicklungskommandos ## Entwicklungskommandos
@@ -34,7 +32,6 @@ Dieses Projekt verwendet den `uv` Paketmanager (nicht pip oder poetry):
```bash ```bash
uv sync # Abhängigkeiten installieren uv sync # Abhängigkeiten installieren
uv run python src/main.py # Anwendung starten uv run python src/main.py # Anwendung starten
uv run python test_hash_implementation.py # Hash-Tests ausführen
``` ```
### Linting ### Linting
@@ -43,6 +40,130 @@ uv run ruff check # Code-Style prüfen (Zeilenlänge: 120)
uv run ruff format # Code formatieren uv run ruff format # Code formatieren
``` ```
### Tests
Dieses Projekt verwendet KEINE pytest/unittest-Frameworks. Tests sind standalone Python-Skripte:
```bash
uv run python test_hash_implementation.py # Hash-Tests
uv run python test_xml_hash_duplicate_detection.py # Duplikatserkennung
```
## Code-Style-Richtlinien
### Import-Organisation
Reihenfolge (keine Leerzeilen zwischen Gruppen):
```python
# 1. Standard Library
import os
import sys
import logging
from pathlib import Path
from typing import TYPE_CHECKING
# 2. Drittanbieter
from PySide6.QtCore import Qt, QThread, Signal
from PySide6.QtWidgets import QDialog, QMainWindow
from pydantic import BaseModel, Field
# 3. Lokale Imports (IMMER absolute Imports, KEINE relativen .imports)
from conf import app_settings, TreeNode, XslFile
from ui.MainWindow import MainWindow
```
- `TYPE_CHECKING` für zirkuläre Import-Vermeidung nutzen
- Keine relativen Imports (`.` oder `..`)
### Type Annotations
Moderne Union-Syntax verwenden:
```python
# RICHTIG
def transform(xml_path: Path, params: dict[str, str]) -> tuple[bool, str]:
result: str | None = None
files: list[Path] = []
# FALSCH
def transform(xml_path, params): # Keine Annotations
result: Optional[str] = None # Alte Union-Syntax
files: List[Path] = [] # Großgeschriebene Types
```
### Naming Conventions
```python
# Klassen: PascalCase
class SaxonWorkerPool:
# Funktionen/Methoden: snake_case
def transform_saxon(xml_file: Path) -> bool:
# Private Methoden: _snake_case mit Unterstrich
def _create_tree_item(self, node: TreeNode):
# Konstanten: UPPER_CASE
SAXON_WORKER_JAVA = """..."""
```
### Formatierung
- **Zeilenlänge:** 120 Zeichen (via Ruff konfiguriert)
- **Strings:** Bevorzugt Double-Quotes `"..."`, aber konsistent im File
- **Trailing Commas:** Bei mehrzeiligen Strukturen verwenden
### Error Handling
IMMER Logging statt `print()` verwenden:
```python
import logging
logger = logging.getLogger(__name__)
def transform(xml_path: Path) -> tuple[bool, str]:
try:
logger.info(f"Transformation gestartet: {xml_path}")
result = do_transform(xml_path)
return True, "Erfolg"
except FileNotFoundError as e:
error_msg = f"XML-Datei nicht gefunden: {xml_path}"
logger.error(error_msg)
return False, error_msg
except Exception as e:
error_msg = f"Fehler bei Transformation: {str(e)}"
logger.exception(error_msg) # Mit Stack Trace
return False, error_msg
```
- `logger.debug()` für Debugging-Infos
- `logger.info()` für normale Operationen
- `logger.warning()` für Warnungen
- `logger.error()` für Fehler ohne Stack Trace
- `logger.exception()` für Fehler MIT Stack Trace
- Fehlermeldungen auf Deutsch
### Docstrings
Google-Style auf Deutsch:
```python
def transform_xml_to_pdf(xml_path: Path, xsl_path: Path, output_dir: Path) -> tuple[bool, str]:
"""
Transformiert eine XML-Datei mit XSL zu PDF.
Args:
xml_path: Pfad zur XML-Eingabedatei
xsl_path: Pfad zum XSL-Stylesheet
output_dir: Zielverzeichnis für PDF-Ausgabe
Returns:
tuple[bool, str]: (Erfolg, Fehlermeldung oder Info-Text)
Raises:
FileNotFoundError: Wenn XML- oder XSL-Datei nicht existiert
"""
```
### Pfadbehandlung
- Immer `pathlib.Path`-Objekte verwenden, keine Strings
- `expanduser()` und `expandvars()` für Benutzer-/Umgebungspfade verwenden
- Projektrelative Pfade werden als relativ gespeichert, zur Laufzeit gegen `project_dir` aufgelöst
## Architektur ## Architektur
### Konfigurationssystem (src/conf.py) ### Konfigurationssystem (src/conf.py)
@@ -96,6 +217,24 @@ Beim Erstellen neuer Dialoge:
- Die UI-Datei wird automatisch als `.py`-Datei von einer VS Code Extension generiert - Die UI-Datei wird automatisch als `.py`-Datei von einer VS Code Extension generiert
- Die generierte UI-Klasse in der Implementierungsdatei importieren und verwenden - Die generierte UI-Klasse in der Implementierungsdatei importieren und verwenden
**UI-Import-Pattern:**
```python
from PySide6.QtWidgets import QDialog
from ui.JavaVmConfigDialog_ui import Ui_JavaVmConfigDialog
class JavaVmConfigDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_JavaVmConfigDialog()
self.ui.setupUi(self)
# Signale NACH setupUi() verbinden
self.ui.browseButton.clicked.connect(self._browse_file)
```
- UI-Klassen NIEMALS direkt erben, nur als `self.ui` Member
- Alle Widgets über `self.ui.widgetName` zugreifen
- Signal-Verbindungen immer NACH `setupUi()` aufrufen
### Hauptfenster (src/ui/MainWindow.py) ### Hauptfenster (src/ui/MainWindow.py)
Zentrale Schaltstelle der Anwendung mit mehreren wichtigen Verantwortlichkeiten: Zentrale Schaltstelle der Anwendung mit mehreren wichtigen Verantwortlichkeiten:
@@ -117,6 +256,13 @@ Zentrale Schaltstelle der Anwendung mit mehreren wichtigen Verantwortlichkeiten:
- `XmlHashCalculatorThread`: Hintergrund-blake2b-Hash-Berechnung für XML-Dateien - `XmlHashCalculatorThread`: Hintergrund-blake2b-Hash-Berechnung für XML-Dateien
- `DatabaseTestThread` (in PostgreSqlConfigDialog): Asynchrones Testen von Datenbankverbindungen - `DatabaseTestThread` (in PostgreSqlConfigDialog): Asynchrones Testen von Datenbankverbindungen
### XSL-Abhängigkeitsgraph (src/ui/XslDependencyDialog.py)
Interaktiver Dialog zur Visualisierung von `<xsl:import/>`- und `<xsl:include/>`-Abhängigkeiten zwischen XSL-Dateien:
- Sidebar mit Suchfilter zur Navigation
- Abhängigkeitsgraph-Darstellung via vis.js
- Parsing der XSL-Dateien mit lxml
### Hash-Berechnungssystem ### Hash-Berechnungssystem
Die Anwendung verwendet blake2b-Hashing zur Verfolgung von XML-Dateiänderungen: Die Anwendung verwendet blake2b-Hashing zur Verfolgung von XML-Dateiänderungen:
@@ -132,15 +278,40 @@ Die Anwendung verwendet blake2b-Hashing zur Verfolgung von XML-Dateiänderungen:
Die Anwendung unterstützt mehrere Qt-Themes: Die Anwendung unterstützt mehrere Qt-Themes:
- Theme-Auswahlmenü wird dynamisch aus `QStyleFactory.keys()` befüllt - Theme-Auswahlmenü wird dynamisch aus `QStyleFactory.keys()` befüllt
- Theme-Präferenz wird in `AppSettings.theme` gespeichert - Theme-Präferenz wird in `AppSettings.theme` gespeichert
- Dark-Theme-Unterstützung via `qdarktheme` Paket (aktuell in main.py auskommentiert)
### Datenbankintegration ### Datenbankintegration
PostgreSQL-Integration mit Polars und ConnectorX: PostgreSQL-Integration mit Polars und ConnectorX:
- Konfiguration wird im `PostgreSqlDb`-Modell mit SSL-Modus-Unterstützung gespeichert - Konfiguration wird im `PostgreSqlDb`-Modell mit SSL-Modus-Unterstützung gespeichert
- SQL-Abfragen werden via `_execute_sql_query()` im MainWindow ausgeführt - SQL-Abfragen werden asynchron via `DatabaseQueryThread` im `DatabaseMixin` ausgeführt
- Ergebnisse werden in Polars DataFrames geladen - Ergebnisse werden in Polars DataFrames geladen
### Thread-basierte Operationen
```python
from PySide6.QtCore import QThread, Signal
class HashCalculatorThread(QThread):
progress = Signal(int)
finished = Signal(dict)
def __init__(self, files: list[Path]):
super().__init__()
self.files = files
def run(self):
for i, file_path in enumerate(self.files):
hash_value = calculate_hash(file_path)
self.progress.emit(i + 1)
self.finished.emit(results)
# Verwendung
thread = HashCalculatorThread(xml_files)
thread.progress.connect(self._on_progress)
thread.finished.connect(self._on_finished)
thread.start() # NICHT run() direkt aufrufen!
```
## Wichtige Konventionen ## Wichtige Konventionen
### Deutsche Sprache ### Deutsche Sprache
@@ -150,11 +321,6 @@ Die Codebasis verwendet Deutsch für:
- Variablennamen wo kontextuell passend - Variablennamen wo kontextuell passend
- Log-Meldungen - Log-Meldungen
### Pfadbehandlung
- Immer `pathlib.Path`-Objekte verwenden, keine Strings
- `expanduser()` und `expandvars()` für Benutzer-/Umgebungspfade verwenden
- Projektrelative Pfade werden als relativ gespeichert, zur Laufzeit gegen `project_dir` aufgelöst
### ID-basierte Lookups ### ID-basierte Lookups
Konfigurationsentitäten (Tools, Datenbanken) werden in Projekten über ID referenziert. Die Hilfsmethoden des `Project`-Modells (`getXsl()`, `getJavaVm()`, etc.) verwenden, um IDs in Anzeigewerte aufzulösen. Konfigurationsentitäten (Tools, Datenbanken) werden in Projekten über ID referenziert. Die Hilfsmethoden des `Project`-Modells (`getXsl()`, `getJavaVm()`, etc.) verwenden, um IDs in Anzeigewerte aufzulösen.