Regelwerk, Agenten, Skills und Werkzeuge veröffentlichen
Erster Stand des öffentlichen Ausschnitts aus dem Vault. Die .gitignore arbeitet als Positivliste: ausgeschlossen ist alles, eingeschlossen nur, was ausdrücklich wieder aufgenommen wird. Notizen, Projekte, Finanzen, das Sitzungsgedächtnis und settings.local.json bleiben damit außen vor. Der Kundenname in AI/Regelwerk/seo.md ist durch eine neutrale Bezeichnung ersetzt; die Regeln selbst sind unverändert. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
commit
c064c729ed
82 files changed
+13428
No files matched your search
@@ -0,0 +1,153 @@
|
||||
"""Wendet die Codebloecke der Taskdateien eines Plans in Wellenreihenfolge auf eine Kopie des Projekts an.
|
||||
|
||||
Koordinator-Werkzeug (entstanden 2026-09-17, Trading Plattform Teil 3b). Es tut etwas und beurteilt
|
||||
nichts; das Urteil faellt danach mit Suite, Mutanten und welle_vergleich.py.
|
||||
Geltungsbereich:
|
||||
- Zieldatei ist der letzte Pfad in Backticks in einer Ueberschrift (## bis ####) oberhalb des Blocks.
|
||||
- Dateien, die "## Zieldateien" als "(gelöscht)" fuehrt, werden in der Kopie geloescht.
|
||||
- Bloecke mit Einrueckung (Befehle in Listen) und Prosa-Vorgaben ("wird: ein neuer Abschnitt ...")
|
||||
werden uebergangen: Ein Doku-Task mit frei zu formulierendem Text laesst sich nicht anwenden.
|
||||
- Marke ist die letzte nicht leere Zeile vor dem Zaun:
|
||||
"Aus:" -> merkt den Block als alten Text
|
||||
"wird:" -> ersetzt den gemerkten Text (muss genau einmal vorkommen)
|
||||
enthaelt "Die ganze Datei ist neu" -> schreibt die Datei
|
||||
beginnt mit "Häng diesen Block ans Ende" -> haengt an
|
||||
Alles andere (Befehle, erwartete Ausgaben, Prosa-"wird: ...") wird uebergangen und gezaehlt.
|
||||
- Zaeune aus n Backticks schliessen erst mit n Backticks.
|
||||
- Geschrieben wird immer mit LF.
|
||||
Aufruf: python plan_materialisiere.py <planordner> <kopie> <taskdatei> [<taskdatei> ...]
|
||||
Exit 0 ohne Befund, 2 mit Befund.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
UEBERSCHRIFT = re.compile(r"^#{2,4} ")
|
||||
PFAD = re.compile(r"`([^`\s]+/[^`\s]+\.[A-Za-z0-9]+|[^`\s/]+\.(?:py|ts|tsx|toml|md|css|json))`")
|
||||
|
||||
|
||||
def normal(text: str) -> str:
|
||||
return text.replace("\r\n", "\n").replace("\r", "\n")
|
||||
|
||||
|
||||
def bloecke(taskdatei: Path):
|
||||
zeilen = normal(taskdatei.read_text(encoding="utf-8")).split("\n")
|
||||
ziel = None
|
||||
i = 0
|
||||
while i < len(zeilen):
|
||||
zeile = zeilen[i]
|
||||
if UEBERSCHRIFT.match(zeile):
|
||||
treffer = PFAD.findall(zeile)
|
||||
if treffer:
|
||||
ziel = treffer[-1]
|
||||
if zeile.lstrip().startswith("```"):
|
||||
einzug = len(zeile) - len(zeile.lstrip())
|
||||
kern = zeile.lstrip()
|
||||
laenge = len(kern) - len(kern.lstrip("`"))
|
||||
start = i
|
||||
i += 1
|
||||
inhalt = []
|
||||
while i < len(zeilen):
|
||||
kandidat = zeilen[i].lstrip()
|
||||
if kandidat.startswith("`" * laenge) and not kandidat.strip("`").strip():
|
||||
break
|
||||
inhalt.append(zeilen[i][einzug:] if zeilen[i][:einzug].strip() == "" else zeilen[i])
|
||||
i += 1
|
||||
k = start - 1
|
||||
while k >= 0 and not zeilen[k].strip():
|
||||
k -= 1
|
||||
marke = zeilen[k].strip() if k >= 0 else ""
|
||||
yield {"ziel": ziel, "marke": marke, "zeile": start + 1, "text": "\n".join(inhalt), "einzug": einzug}
|
||||
i += 1
|
||||
|
||||
|
||||
LOESCHZIEL = re.compile(r"^- `(.+?)`\s*\((.*)\)\s*$")
|
||||
|
||||
|
||||
def loesche(taskdatei: Path, kopie: Path) -> int:
|
||||
"""Loescht, was der Abschnitt "## Zieldateien" als "(gelöscht)" fuehrt.
|
||||
|
||||
Der Pfad dort ist absolut und zeigt ins echte Projekt. Geloescht wird in der Kopie die
|
||||
Datei am laengsten Pfadende, das dort existiert -- nie etwas ausserhalb der Kopie.
|
||||
"""
|
||||
drin = False
|
||||
anzahl = 0
|
||||
for zeile in normal(taskdatei.read_text(encoding="utf-8")).split("\n"):
|
||||
if zeile.startswith("## "):
|
||||
drin = zeile.startswith("## Zieldateien")
|
||||
continue
|
||||
treffer = LOESCHZIEL.match(zeile) if drin else None
|
||||
if not treffer or "gelöscht" not in treffer.group(2):
|
||||
continue
|
||||
teile = treffer.group(1).replace("\\", "/").split("/")
|
||||
for anfang in range(len(teile)):
|
||||
kandidat = kopie.joinpath(*teile[anfang:])
|
||||
if ":" not in teile[anfang] and kandidat.is_file():
|
||||
kandidat.unlink()
|
||||
anzahl += 1
|
||||
break
|
||||
return anzahl
|
||||
|
||||
|
||||
def main() -> int:
|
||||
plan, kopie = Path(sys.argv[1]), Path(sys.argv[2])
|
||||
befunde = 0
|
||||
for name in sys.argv[3:]:
|
||||
taskdatei = plan / name
|
||||
alt = None
|
||||
zaehler = {"neu": 0, "ersetzt": 0, "angehaengt": 0, "uebergangen": 0}
|
||||
for block in bloecke(taskdatei):
|
||||
marke, ziel = block["marke"], block["ziel"]
|
||||
if block["einzug"] > 0:
|
||||
zaehler["uebergangen"] += 1
|
||||
continue
|
||||
if marke == "Aus:" or marke.startswith("Aus, Zeile"):
|
||||
alt = block
|
||||
continue
|
||||
if marke == "wird:":
|
||||
if alt is None or ziel is None:
|
||||
print(f"BEFUND {name}:{block['zeile']}: wird-Block ohne Aus-Block oder ohne Zieldatei")
|
||||
befunde += 1
|
||||
continue
|
||||
pfad = kopie / ziel
|
||||
if not pfad.exists():
|
||||
print(f"BEFUND {name}:{block['zeile']}: Zieldatei {ziel} fehlt in der Kopie")
|
||||
befunde += 1
|
||||
alt = None
|
||||
continue
|
||||
text = normal(pfad.read_text(encoding="utf-8"))
|
||||
anzahl = text.count(alt["text"])
|
||||
if anzahl != 1:
|
||||
print(f"BEFUND {name}:{alt['zeile']}: Aus-Block steht {anzahl}-mal in {ziel}")
|
||||
befunde += 1
|
||||
else:
|
||||
with open(pfad, "w", encoding="utf-8", newline="\n") as strom:
|
||||
strom.write(text.replace(alt["text"], block["text"]))
|
||||
zaehler["ersetzt"] += 1
|
||||
alt = None
|
||||
continue
|
||||
alt = None
|
||||
if ("Die ganze Datei ist neu" in marke or "vollständig ersetzt" in marke) and ziel is not None:
|
||||
pfad = kopie / ziel
|
||||
pfad.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(pfad, "w", encoding="utf-8", newline="\n") as strom:
|
||||
strom.write(block["text"].rstrip("\n") + "\n")
|
||||
zaehler["neu"] += 1
|
||||
continue
|
||||
if marke.startswith("Häng diesen Block ans Ende") and ziel is not None:
|
||||
pfad = kopie / ziel
|
||||
text = normal(pfad.read_text(encoding="utf-8"))
|
||||
with open(pfad, "w", encoding="utf-8", newline="\n") as strom:
|
||||
strom.write(text.rstrip("\n") + "\n" + block["text"].rstrip("\n") + "\n")
|
||||
zaehler["angehaengt"] += 1
|
||||
continue
|
||||
zaehler["uebergangen"] += 1
|
||||
zaehler["geloescht"] = loesche(taskdatei, kopie)
|
||||
print(f"{name}: {zaehler}")
|
||||
print(f"{befunde} Befund(e).")
|
||||
return 2 if befunde else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user