diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8c5c268 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,365 @@ +# 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!