diff --git a/.gitignore b/.gitignore index 05e1df1f..2229a744 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,9 @@ logs/ # Generated diagrams build/diagrams/ + +# Python virtual environment +.venv/ +venv/ +*.pyc +__pycache__/ diff --git a/.junie/scripts/requirements.txt b/.junie/scripts/requirements.txt new file mode 100644 index 00000000..32819b43 --- /dev/null +++ b/.junie/scripts/requirements.txt @@ -0,0 +1,11 @@ +attrs==25.4.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 +idna==3.11 +jsonschema==4.25.1 +jsonschema-specifications==2025.9.1 +PyYAML==6.0.3 +referencing==0.37.0 +requests==2.32.5 +rpds-py==0.28.0 +urllib3==2.5.0 diff --git a/.junie/scripts/validate-frontmatter.py b/.junie/scripts/validate-frontmatter.py index 5f138024..3df316ec 100644 --- a/.junie/scripts/validate-frontmatter.py +++ b/.junie/scripts/validate-frontmatter.py @@ -1,36 +1,78 @@ -import os, re, yaml, json -from glob import glob +""" +Validiert YAML-Frontmatter in Markdown-Dateien unterhalb von docs/ gegen ein JSON-Schema. + +- Erwartet das Schema in docs/.frontmatter.schema.json +- Meldet fehlendes oder ungültiges Frontmatter pro Datei +- Exit-Code 0 bei Erfolg, 1 wenn mindestens ein Fehler auftritt +""" + +from __future__ import annotations + +import json +import re +import sys +from pathlib import Path +from typing import Any + +import yaml try: import jsonschema -except ImportError: - # GitHub Actions step will install this before running; provide friendlier message if missing - raise SystemExit("[FM] jsonschema package not installed. Please run: pip install jsonschema pyyaml") +except ImportError: # pragma: no cover + # GitHub Actions step installiert dies vor dem Lauf; freundlichere Fehlermeldung + raise SystemExit("[FM] jsonschema nicht installiert. Bitte ausführen: pip install jsonschema pyyaml") -SCHEMA_PATH = 'docs/.frontmatter.schema.json' -FM_REGEX = re.compile(r'^---\n(.*?)\n---', re.S) -with open(SCHEMA_PATH, encoding='utf-8') as f: - schema = json.load(f) +SCHEMA_PATH = Path("docs/.frontmatter.schema.json") +# Erlaubt LF und CRLF, verlangt Frontmatter am Datei-Anfang +FM_REGEX = re.compile(r"\A---\r?\n(.*?)\r?\n---(?:\r?\n|$)", re.S) -errors = 0 -for path in glob('docs/**/*.md', recursive=True): - # ADRs und ggf. generierte Inhalte vorerst ausnehmen (separater Rollout für FM) - if path.startswith('docs/architecture/adr/'): - continue - # Skip generated or non-content files if any (none by default) - with open(path, 'r', encoding='utf-8') as fh: - content = fh.read() - m = FM_REGEX.search(content) - if not m: - print(f"[FM] fehlt: {path}") - errors = 1 - continue + +def load_schema(path: Path) -> dict[str, Any]: try: - fm = yaml.safe_load(m.group(1)) or {} - jsonschema.validate(fm, schema) - except Exception as e: - print(f"[FM] invalid in {path}: {e}") - errors = 1 + with path.open(encoding="utf-8") as f: + return json.load(f) + except FileNotFoundError: + print(f"[FM] Schema-Datei fehlt: {path.as_posix()}") + sys.exit(1) + except json.JSONDecodeError as e: # pragma: no cover + print(f"[FM] Ungültiges JSON-Schema in {path.as_posix()}: {e}") + sys.exit(1) -exit(errors) + +def extract_frontmatter(text: str) -> str | None: + m = FM_REGEX.search(text) + return m.group(1) if m else None + + +def validate_file(path: Path, schema: dict[str, Any]) -> bool: + content = path.read_text(encoding="utf-8") + fm_text = extract_frontmatter(content) + if fm_text is None: + print(f"[FM] fehlt: {path.as_posix()}") + return False + try: + fm = yaml.safe_load(fm_text) or {} + jsonschema.validate(fm, schema) + return True + except Exception as e: # jsonschema.ValidationError u.a. + print(f"[FM] ungültig in {path.as_posix()}: {e}") + return False + + +def main() -> int: + schema = load_schema(SCHEMA_PATH) + + had_error = False + for path in Path("docs").rglob("*.md"): + # ADRs und ggf. generierte Inhalte vorerst ausnehmen (separater Rollout für FM) + if path.as_posix().startswith("docs/architecture/adr/"): + continue + if not validate_file(path, schema): + had_error = True + + return 1 if had_error else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.junie/scripts/youtrack-sync-kb.py b/.junie/scripts/youtrack-sync-kb.py index cd8f95fc..0f0245e0 100644 --- a/.junie/scripts/youtrack-sync-kb.py +++ b/.junie/scripts/youtrack-sync-kb.py @@ -68,7 +68,7 @@ def http(method: str, url: str, **kw): # *** KORRIGIERTE FUNKTION *** def get_project_by_short_name(name: str): """Sucht die interne ID des Projekts anhand des Kürzels (z.B. 'MP'). - Versucht zuerst /api/projects, fällt bei Fehler auf /api/admin/projects zurück. + Versucht zuerst /api/projects, fällt bei Fehlern auf /api/admin/projects zurück. """ # 1) Primärer, nicht-admin Endpunkt url1 = yt_url("/api/projects?fields=id,shortName") @@ -160,7 +160,7 @@ def find_article_in_kb_by_title(title: str, parent_id: str | None = None): return None # *** KORRIGIERTE FUNKTION *** -def create_article(title: str, markdown: str, parent_id: str = None): +def create_article(title: str, markdown: str, parent_id: str | None = None): """Erstellt einen neuen Artikel in der Knowledge Base des Projekts. Fallback: Wenn keine KB existiert (KB_ID is None), wird der Artikel dem Projekt zugeordnet. """