mirror of
https://github.com/Klotzkette/claude-fuer-deutsches-recht
synced 2026-06-09 10:03:19 +00:00
fix(readmes): doppelten TESTAKTEN-SECTION-Block aus 84 Plugin-READMEs entfernt
Der frueher autogenerierte '<!-- BEGIN TESTAKTEN-SECTION (auto-generated) -->'- Block ist in 84 Plugin-READMEs wieder aufgetaucht und stand redundant direkt neben dem aktuellen 'plugin-sofort-download-section'-Block. Beispiel: normenkontrolle-bauleitplanung/README.md, wo der alte Block zusaetzlich nur 1 von 2 vorhandenen Testakten zeigte. Die Plugins selbst sind technisch unveraendert (plugin.json + skills/ in Ordnung); Aenderung ist reine README-Kosmetik. Kein Versions-Bump noetig, Release-ZIPs sind nicht betroffen. scripts/inject-testakten-section.py: komplett auf 'Entferner' umgebaut. Es injiziert nichts mehr in Plugin-READMEs, sondern bereinigt nur noch zurueckkehrende Altblocke idempotent. Plugin-READMEs erhalten ihre Testakten-Auflistung ausschliesslich aus inject-plugin-sofort-download-section.
This commit is contained in:
@@ -1,366 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fuegt in jeder Plugin-README, fuer die Testakten existieren, eine
|
||||
Sektion '## Testakte(n)' direkt unter dem Direkt-Download-Block ein.
|
||||
Bestehende, manuell formulierte Testakten-Sektionen werden NICHT
|
||||
geloescht; stattdessen wird sie nach oben verschoben, indem ein
|
||||
ein-/ausgegrenzter automatischer Block ergaenzt wird.
|
||||
Entfernt den frueher autogenerierten '<!-- BEGIN TESTAKTEN-SECTION (auto-generated) -->'-
|
||||
Block aus allen Plugin-READMEs. Plugin-READMEs erhalten ihre Testakten-Auflistung
|
||||
inzwischen ausschliesslich aus 'scripts/inject-plugin-sofort-download-section.py'.
|
||||
|
||||
Idempotent: HTML-Kommentar-Marker BEGIN/END TESTAKTEN.
|
||||
Dieses Skript dient nur noch der Bereinigung: Wenn ein alter Block irgendwo
|
||||
zurueckkehrt (z. B. durch Wiedereinspielen alter Skripte), entfernt der Aufruf
|
||||
ihn idempotent. Es wird NICHTS injiziert.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
BEGIN = "<!-- BEGIN TESTAKTEN-SECTION (auto-generated) -->"
|
||||
END = "<!-- END TESTAKTEN-SECTION (auto-generated) -->"
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
|
||||
# Mapping Plugin -> Liste der Testakten-Ordnernamen
|
||||
PLUGIN_TESTAKTEN: dict[str, list[str]] = {
|
||||
"arbeitszeugnis-analyse": ["arbeitszeugnis-analyse-bluehendes-leben"],
|
||||
"aussenwirtschaft-zoll-sanktionen": ["aussenwirtschaft-zoll-sanktionen-globalmaschinen"],
|
||||
"bav-strategie-konzern": ["bav-strategie-konzern-meissner-rheinwerk-ag"],
|
||||
"beamtenrecht": [
|
||||
"beamtenrecht-auslandseinsatz-mexiko-wasserbau-besoldung",
|
||||
"beamtenrecht-befoerderungskaskade-landesamt-weserbruecke",
|
||||
"beamtenrecht-dienstunfall-burnout-wiedereingliederung-hafenpolizei",
|
||||
"beamtenrecht-richterlaufbahn-besoldung-mondsee",
|
||||
"beamtenrecht-schulleitung-hannover-konkurrentenstreit",
|
||||
],
|
||||
"betaeubungsmittelrecht": ["betaeubungsmittelrecht-apotheke-substitution-festival"],
|
||||
"betreuungsrecht": [
|
||||
"betreuung-hildegard-sauer",
|
||||
"betreuung-schmalfeld-kontodaten-vertraege",
|
||||
],
|
||||
"bgb-at-pruefer": ["bgb-at-altfraenkische-werkstatt"],
|
||||
"common-law-kompass": ["common-law-kompass-crossborder-contract"],
|
||||
"datenschutzrecht": ["datenschutz-us-transfer-cloudsuite-rheinmain"],
|
||||
"deutsche-rechtsgeschichte": [
|
||||
"deutsche-rechtsgeschichte-restitution-bgb-ddr-kontinuitaet"
|
||||
],
|
||||
"designrecht-geschmacksmusterrecht": [
|
||||
"designrecht-geschmacksmuster-lichtbogen-stuhl-copycat"
|
||||
],
|
||||
"dsa-dma-digitalregulierung": ["dsa-dma-bayrische-baustube-meissner"],
|
||||
"einigungsvertrag-vermoegensrecht": [
|
||||
"einigungsvertrag-treuhand-mauergrundstueck-lindenau"
|
||||
],
|
||||
"einfache-leichte-sprache-jura": ["einfache-leichte-sprache-jura-mandantenbrief"],
|
||||
"energierecht": ["energierecht-stadtwerke-quartier"],
|
||||
"europarecht-kompass": ["europarecht-kompass-beihilfe-richtlinie"],
|
||||
"fachanwalt-verwaltungsrecht": ["bvg-widerspruchsstelle-abschleppen-mobg"],
|
||||
"festlandchina-wirtschaftsverkehr": [
|
||||
"festlandchina-wirtschaftsverkehr-fabrik-import-investition"
|
||||
],
|
||||
"fluggastrechte": ["fluggastrechte-familie-braeutigam"],
|
||||
"forschungszulage-antragstellung": ["forschungszulage-sensorik-startup-taunus"],
|
||||
"forderungsmanagement-klagewerkstatt": ["inkasso-zahlungsklage-modefuchs"],
|
||||
"geldwaeschepraevention-aml-kyc": ["geldwaesche-aml-kyc-musterholding"],
|
||||
"gesellschaftsgruender": ["gesellschaftsgruender-streit-roeschen-tech"],
|
||||
"gesellschaftsrecht-legal-english": ["gesellschaftsrecht-legal-english-frankfurt-startup"],
|
||||
"gebrauchsmusterrecht": ["gebrauchsmusterrecht-schnellverschluss-sensorhalter"],
|
||||
"haushaltsrecht-bho-bund-laender": [
|
||||
"haushaltsrecht-bho-szenario-buergergeld-verteidigung"
|
||||
],
|
||||
"hoai-leistungsphasen-praxis": ["hoai-leistungsphasen-kita-muehlenhof-lichtenrade-2026"],
|
||||
"informationsfreiheit-presseauskunft": [
|
||||
"informationsfreiheit-presseauskunft-klinikdaten-hafenstadt"
|
||||
],
|
||||
"insolvenzforderungsanmeldungspruefung": [
|
||||
"insolvenzforderungsanmeldungspruefung-phoenix-solar"
|
||||
],
|
||||
"insolvenzplan-starug-planwerkstatt": [
|
||||
"insolvenzplan-starug-planwerkstatt-metallbau-hansa"
|
||||
],
|
||||
"insolvenzverwaltung": [
|
||||
"insolvenzverwaltung-moebelwerk-havelberg-regelverfahren",
|
||||
"insolvenzverwaltung-nordlicht-handels-kiel",
|
||||
],
|
||||
"internationales-handelsrecht-lex-mercatoria": [
|
||||
"internationales-handelsrecht-cisg-incoterms-schiedsfall"
|
||||
],
|
||||
"jveg-kostenpruefer": ["jveg-zeugin-berger-lg-tuebingen"],
|
||||
"kanzlei-allgemein": ["kanzlei-allgemein-alltag"],
|
||||
"kanzlei-management": ["kanzlei-management-falkenried-partnerkreis-q2-2026"],
|
||||
"ki-richtlinie-kanzleien": ["ki-richtlinie-musterkanzlei"],
|
||||
"ki-vo-ai-act-pruefer": ["ki-vo-konformitaetsbescheinigung-bewerberpilot"],
|
||||
"kommunalrecht-laender": [
|
||||
"kommunalrecht-rathaus-morgenfurt-buergerentscheid-haushalt"
|
||||
],
|
||||
"krisenfrueherkennung-starug": ["krisenfrueherkennung-starug-vier-varianten"],
|
||||
"legistik-werkstatt": ["legistik-pflichtpostfach"],
|
||||
"lobbyregister-bundestag": [
|
||||
"lobbyregister-buergerinitiative-waldmoor",
|
||||
"lobbyregister-dublin-bank-frankfurt-branch",
|
||||
"lobbyregister-public-affairs-agentur-wasserstoff",
|
||||
],
|
||||
"luftrecht-flughafenrecht": [
|
||||
"luftrecht-airline-insolvenz-flugzeugpfand-flughafen"
|
||||
],
|
||||
"markenrecht-fashion-luxus": [
|
||||
"markenrecht-fashion-klotzzkette-vs-brezelmann-donauzon"
|
||||
],
|
||||
"meinungspruefer": ["meinungspruefer-grenzfaelle-alltag"],
|
||||
"nachbarschaftsstreit-pruefer": ["nachbarschaftsstreit-horrorfall-rosengarten"],
|
||||
"normenkontrolle-bauleitplanung": ["bebauungsplan-augsburg-bahnhofsareal"],
|
||||
"oeffentliches-wirtschaftsrecht": [
|
||||
"oeffentliches-wirtschaftsrecht-oepp-schulcampus-havelstadt"
|
||||
],
|
||||
"ordnungswidrigkeitenrecht": [
|
||||
"ordnungswidrigkeitenrecht-bussgeldmix-gewerbe-datenschutz-tier"
|
||||
],
|
||||
"patentrecht": ["patentrecht-erfindungsakten-ravenstein-moll"],
|
||||
"phishing-vorfall-pruefer": ["phishing-vorfall-mayer-sparkasse-berlin"],
|
||||
"arbeitsrecht": [
|
||||
"befristungskontrollklage-vogt-stadtwerke",
|
||||
"kuendigungsschutzklage-weber-techlogix",
|
||||
],
|
||||
"schriftform-und-textform-bgb": [
|
||||
"schriftform-maklervertrag-muenchen-eheleute-haspelbeck",
|
||||
"schriftform-mietkuendigung-bielefeld-online-pferdedrescher",
|
||||
],
|
||||
"roemisches-recht": ["roemisches-recht-kauf-besitz-erbschaft-pergamentfall"],
|
||||
"selbstvertreter-amtsgericht": ["selbstvertreter-amtsgericht-kuechentisch-kaufpreis"],
|
||||
"selbstvertreter-sozialgericht": ["selbstvertreter-sozialgericht-heizkosten-eilantrag"],
|
||||
"solo-selbststaendige-praxis": ["solo-selbststaendige-designstudio-luise-falkensee-2026"],
|
||||
"fachanwalt-sozialrecht": ["sozialrecht-rollstuhl-tannenberg"],
|
||||
"fashion-law-moderecht": ["fashion-law-moderecht-nachtfalter-kollektion-2026"],
|
||||
"seerecht-schifffahrtsrecht": ["seerecht-schiffshypothek-werft-wrack-bermuda"],
|
||||
"steuerrecht-anwalt-und-berater": [
|
||||
"beispielakte-edelholz-berlin",
|
||||
"fortbestehensprognose-paragrafix-gmbh",
|
||||
"grosskanzlei-corporate-ma-datenraum",
|
||||
"grunderwerbsteuer-sharedeal-closing-waldkrone",
|
||||
"grundsteuer-rosenwinkel-bescheidkette",
|
||||
],
|
||||
"strafbefehl-verteidiger": ["strafbefehl-ladendiebstahl-fahrerflucht"],
|
||||
"strassenrecht-infrastruktur": ["strassenrecht-ortsdurchfahrt-bruecke-auenfeld"],
|
||||
"strassenverkehrsrecht-stvo": [
|
||||
"strassenverkehrsrecht-stvo-schulstrasse-lieferzone"
|
||||
],
|
||||
"tierschutzrecht": ["tierschutzrecht-veterinaeramt-pferdehof-auenwiese"],
|
||||
"umweltrecht": ["umweltrecht-industrieanlage-genehmigung"],
|
||||
"umweltschutzverband-verbandsklage": [
|
||||
"umweltschutzverband-windpark-moorbach-umwrg"
|
||||
],
|
||||
"urteilsbauer-relationsmacher": [
|
||||
"lumen-studios-insolvenz-strafverfahren",
|
||||
"solis-vision-x-smartglasses",
|
||||
],
|
||||
"verkehr-infrastrukturrecht": ["verkehr-infrastrukturrecht-strassenbahn-ladezonen"],
|
||||
"verkehrsowi-verteidiger": ["verkehrsowi-rotlicht-tempo"],
|
||||
"venture-capital-geber": ["venture-capital-geber-nebelstern-portfolio-berlin"],
|
||||
"verbraucherschutzrecht-pruefer": [
|
||||
"verbraucherschutzrecht-smartmeter-abo-plattform"
|
||||
],
|
||||
"verbraucherschutzverband-durchsetzung": [
|
||||
"verbraucherschutzverband-abo-falle-sammelklage"
|
||||
],
|
||||
"wahlkampfrecht-praxis": ["wahlkampfrecht-landtagswahl-morgenstadt-2026"],
|
||||
"verlagsredaktion": ["verlagsredaktion-morgenlage-fachverlag"],
|
||||
"vertragsausfueller": ["vertragsausfueller-bsag-kiosk-huckelriede"],
|
||||
"vertragsrecht": ["sachverstaendigengutachten-ki-vorwurf-lg-regensburg-sieglinger"],
|
||||
"wandeldarlehen-lebenszyklus": ["wandeldarlehen-beispielcase"],
|
||||
"weg-hausverwaltung": ["weg-hausverwaltung-hohenzollernhof"],
|
||||
"zwangsverwaltung-zvg": [
|
||||
"zwangsverwaltung-friedrichshoefe-berlin",
|
||||
"zwangsverwaltung-zvg-mietshaus-parkstrasse",
|
||||
"zwangsverwaltung-zvg-versteigerung-eppendorf-altbau",
|
||||
],
|
||||
"handelsregister-praxis": ["handelsregister-registergericht-rabenhof-gmbh-2026"],
|
||||
"grundbuchamt-praxis": ["grundbuchamt-briefgrundschuld-wegerecht-altenau-2026"],
|
||||
"erbbaurecht-praxis": ["erbbaurecht-erbbauzins-heimfall-lindenwerder-2026"],
|
||||
"zwangsvollstreckung": ["vollstreckungsmappe-mueller-sparkasse-niederrhein"],
|
||||
}
|
||||
|
||||
|
||||
def get_testakte_title(akte_dir: Path) -> str:
|
||||
readme = akte_dir / "README.md"
|
||||
if not readme.is_file():
|
||||
return akte_dir.name
|
||||
first = readme.read_text(encoding="utf-8").splitlines()[0]
|
||||
# Strip leading '# ' and 'Akte:' / 'Akte ' Prefix
|
||||
title = re.sub(r"^#+\s*", "", first).strip()
|
||||
title = re.sub(r"^Akte\s*:?\s*", "", title, flags=re.IGNORECASE).strip()
|
||||
return title or akte_dir.name
|
||||
|
||||
|
||||
def build_testakten_section(plugin: str, akten: list[str]) -> str:
|
||||
if not akten:
|
||||
return ""
|
||||
lines = [
|
||||
BEGIN,
|
||||
"",
|
||||
"## Testakte" + ("n" if len(akten) > 1 else ""),
|
||||
"",
|
||||
]
|
||||
if len(akten) == 1:
|
||||
akte = akten[0]
|
||||
title = get_testakte_title(REPO / "testakten" / akte)
|
||||
lines.append(
|
||||
f"Zu diesem Plugin existiert eine vollständige Beispielakte: "
|
||||
f"**{title}** ([`testakten/{akte}/`](../testakten/{akte}/))."
|
||||
)
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"Direkt-Download als ZIP: "
|
||||
f"[testakte-{akte}.zip]("
|
||||
f"https://github.com/Klotzkette/claude-fuer-deutsches-recht/releases/latest/download/testakte-{akte}.zip)"
|
||||
)
|
||||
else:
|
||||
lines.append(
|
||||
f"Zu diesem Plugin existieren {len(akten)} vollständige Beispielakten:"
|
||||
)
|
||||
lines.append("")
|
||||
lines.append("| Akte | Quelle | Direkt-Download |")
|
||||
lines.append("|---|---|---|")
|
||||
for a in akten:
|
||||
title = get_testakte_title(REPO / "testakten" / a)
|
||||
lines.append(
|
||||
f"| {title} | [`testakten/{a}/`](../testakten/{a}/) | "
|
||||
f"[testakte-{a}.zip](https://github.com/Klotzkette/claude-fuer-deutsches-recht/releases/latest/download/testakte-{a}.zip) |"
|
||||
)
|
||||
lines.append("")
|
||||
lines.append("Die Akte ist absichtlich unordentlich, widersprüchlich und ungefiltert. Sie eignet sich für End-to-End-Tests, Demos und zum Üben.")
|
||||
lines.append("")
|
||||
lines.append(END)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def find_insert_position(text: str) -> int:
|
||||
"""Finde die Position direkt nach dem Direkt-Download-Block.
|
||||
|
||||
Heuristik: Suche '## Direkt-Download', danach die naechste '## ' Section.
|
||||
Falls nicht gefunden, suche nach Plugin-Description (vor erstem '## Start' o.ae.).
|
||||
Falls auch das fehlt: nach H1-Titel + leerer Zeile + erstem Absatz.
|
||||
"""
|
||||
# Neuere READMEs haben oben einen markierten Sofort-Download-Block. Die
|
||||
# Testakten-Sektion darf nicht in diesen Marker hineingeschoben werden.
|
||||
sofort_end = "<!-- END plugin-sofort-download-section (autogen) -->"
|
||||
idx = text.find(sofort_end)
|
||||
if idx != -1:
|
||||
return idx + len(sofort_end)
|
||||
|
||||
# Variant 1: nach Direkt-Download-Sektion. Heading darf beliebig dekoriert sein,
|
||||
# z.B. '## Direkt-Download', '## ⬇️ Direkt-Download (einzelnes ZIP)',
|
||||
# '## Arbeitsakte (Direkt-Download)', '## Direkt-Download (je ein ZIP pro Akte)'.
|
||||
m = re.search(
|
||||
r"^##[^\n]*Direkt-Download[^\n]*\n.*?(?=^## |\Z)",
|
||||
text,
|
||||
re.MULTILINE | re.DOTALL,
|
||||
)
|
||||
if m:
|
||||
return m.end()
|
||||
# Variant 2: vor erster '##'-Section
|
||||
m = re.search(r"^## ", text, re.MULTILINE)
|
||||
if m:
|
||||
return m.start()
|
||||
# Variant 3: ans Ende
|
||||
return len(text)
|
||||
|
||||
|
||||
OLD_HEADERS = (
|
||||
"## Testakte",
|
||||
"## Testakten",
|
||||
"## Beispielakte",
|
||||
"## Beispielakten",
|
||||
"## Mit-Testakte",
|
||||
"## Mit Testakte",
|
||||
PATTERN = re.compile(
|
||||
r"\n*" + re.escape(BEGIN) + r".*?" + re.escape(END) + r"\n*",
|
||||
re.DOTALL,
|
||||
)
|
||||
|
||||
|
||||
def remove_old_manual_sections(text: str) -> str:
|
||||
"""Entfernt manuell platzierte H2-Sektionen mit Testakten-Titel,
|
||||
AUSSER sie liegen innerhalb des Auto-Blocks BEGIN/END."""
|
||||
# Maskiere zuerst den Auto-Block, damit wir ihn nicht anfassen.
|
||||
auto_match = re.search(
|
||||
re.escape(BEGIN) + r".*?" + re.escape(END),
|
||||
text,
|
||||
re.DOTALL,
|
||||
)
|
||||
placeholder = "<<<AUTO_BLOCK_PLACEHOLDER>>>"
|
||||
if auto_match:
|
||||
auto_content = auto_match.group(0)
|
||||
text = text.replace(auto_content, placeholder, 1)
|
||||
else:
|
||||
auto_content = None
|
||||
|
||||
# Entferne alle alten Testakten-H2-Sektionen (bis zur naechsten H2).
|
||||
for header in OLD_HEADERS:
|
||||
pattern = (
|
||||
r"^" + re.escape(header) + r"\b.*?(?=^## |\Z)"
|
||||
)
|
||||
text = re.sub(pattern, "", text, flags=re.MULTILINE | re.DOTALL)
|
||||
|
||||
if auto_content is not None:
|
||||
text = text.replace(placeholder, auto_content, 1)
|
||||
|
||||
# Mehrfach-Leerzeilen auf max. 2 reduzieren
|
||||
text = re.sub(r"\n{3,}", "\n\n", text)
|
||||
return text
|
||||
|
||||
|
||||
def update_readme(readme: Path, plugin: str, akten: list[str]) -> bool:
|
||||
if not readme.is_file():
|
||||
def clean_readme(readme: Path) -> bool:
|
||||
txt = readme.read_text(encoding="utf-8")
|
||||
if BEGIN not in txt:
|
||||
return False
|
||||
original = readme.read_text(encoding="utf-8")
|
||||
block = build_testakten_section(plugin, akten)
|
||||
if not block:
|
||||
return False
|
||||
|
||||
new = original
|
||||
# 1. Falls vorhandener Auto-Block existiert: entfernen
|
||||
if BEGIN in new and END in new:
|
||||
new = re.sub(
|
||||
re.escape(BEGIN) + r".*?" + re.escape(END) + r"\n*",
|
||||
"",
|
||||
new,
|
||||
count=1,
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
|
||||
# 2. Alte manuell platzierte Testakten-Sektionen entfernen
|
||||
new = remove_old_manual_sections(new)
|
||||
|
||||
# 3. Block oben einfuegen (nach Direkt-Download)
|
||||
pos = find_insert_position(new)
|
||||
before = new[:pos].rstrip("\n") + "\n\n"
|
||||
after = new[pos:].lstrip("\n")
|
||||
new = before + block + "\n\n" + after
|
||||
|
||||
if new == original:
|
||||
new = PATTERN.sub("\n\n", txt)
|
||||
new = re.sub(r"\n{3,}", "\n\n", new)
|
||||
if new == txt:
|
||||
return False
|
||||
readme.write_text(new, encoding="utf-8")
|
||||
return True
|
||||
|
||||
|
||||
def main() -> int:
|
||||
os.chdir(REPO)
|
||||
changed = 0
|
||||
skipped = 0
|
||||
for plugin, akten in sorted(PLUGIN_TESTAKTEN.items()):
|
||||
readme = REPO / plugin / "README.md"
|
||||
if not readme.is_file():
|
||||
print(f" SKIP {plugin}: keine README.md")
|
||||
skipped += 1
|
||||
def main() -> None:
|
||||
removed = 0
|
||||
for readme in sorted(REPO.glob("*/README.md")):
|
||||
# testakten/-Ordner ausschliessen
|
||||
if readme.parts[-2] == "testakten":
|
||||
continue
|
||||
# Pruefe, ob alle Testakten-Ordner existieren
|
||||
existing = [a for a in akten if (REPO / "testakten" / a).is_dir()]
|
||||
if not existing:
|
||||
print(f" SKIP {plugin}: keine Testakten-Ordner gefunden")
|
||||
skipped += 1
|
||||
continue
|
||||
if update_readme(readme, plugin, existing):
|
||||
changed += 1
|
||||
print(f" UPD {plugin}: {len(existing)} Akte(n)")
|
||||
print(f"\nFertig: {changed} READMEs aktualisiert, {skipped} uebersprungen.")
|
||||
return 0
|
||||
if clean_readme(readme):
|
||||
removed += 1
|
||||
print(f" RM {readme.relative_to(REPO)}")
|
||||
print(f"\nFertig: {removed} Plugin-READMEs bereinigt.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user