Files
xsl-validator/AGENTS.md
T
info f4d2d4b944 Docs: Entwicklerrichtlinien für KI-Coding-Agenten hinzufügen
Fügt AGENTS.md mit umfassenden Richtlinien für Coding-Agenten hinzu:
- Sprachkonventionen (Deutsch)
- Code-Style & Type Annotations
- Build-/Test-Kommandos mit uv
- PySide6 UI-Integration
- Pydantic Models & Settings
- Import-Organisation & Error Handling
2026-01-18 13:40:15 +01:00

9.2 KiB

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!)

# 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

# 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):

# 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:

# 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

# 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:

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:

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:

"""
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

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

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

# 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):

# 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

# 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

# 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

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!