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
2c9a4475fc
97 files changed
+14627
No files matched your search
@@ -0,0 +1,125 @@
|
||||
"""Prueft die Aus-/wird-Bloecke einer Plan-Taskdatei gegen den Projektbaum.
|
||||
|
||||
Koordinator-Werkzeug im Scratchpad. Geltungsbereich (Fangnetz, kein Nachweis):
|
||||
- Sieht nur Codebloecke, deren letzte nicht leere Zeile vor dem Zaun genau "Aus:" oder
|
||||
"wird:" ist (Projektkonvention der Planer-Taskdateien seit Teil 5). Die Zieldatei ist der
|
||||
letzte Pfad in Backticks in einer Ueberschrift (##, ###, ####) oberhalb des Blocks, der
|
||||
auf eine im Projekt vorhandene oder unter "## Zieldateien" genannte Datei zeigt.
|
||||
- Modus vor: jeder Aus-Block steht genau einmal in seiner Zieldatei (0 = Befund, >1 = Befund
|
||||
"mehrdeutig"). Belegt, dass der Plan den heutigen Stand byte-genau zitiert.
|
||||
- Modus nach: jeder wird-Block steht in der Zieldatei; jeder Aus-Block steht nicht mehr darin,
|
||||
ausser er ist Teil seines eigenen wird-Blocks (Einfuegung).
|
||||
- Ganzdatei-Bloecke ("neu anlegen") sieht es nicht; dafuer ist .vault-tools/plan-diff/plan_diff.py da.
|
||||
- Vergleich nach Normalisierung CRLF -> LF. Eine fehlende Zieldatei ist im Modus vor fuer
|
||||
Aus-Bloecke ein Befund, im Modus nach fuer beide.
|
||||
Exit: 0 kein Befund, 2 Befunde, 3 kein einziger Aus-/wird-Block gefunden (nichts geprueft).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ZAUN = re.compile(r"^```")
|
||||
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 lies_bloecke(taskdatei: Path, projekt: Path) -> list[dict]:
|
||||
zeilen = normal(taskdatei.read_text(encoding="utf-8")).split("\n")
|
||||
bloecke: list[dict] = []
|
||||
ziel: str | None = None
|
||||
i = 0
|
||||
while i < len(zeilen):
|
||||
zeile = zeilen[i]
|
||||
if UEBERSCHRIFT.match(zeile):
|
||||
for treffer in PFAD.findall(zeile):
|
||||
ziel = treffer
|
||||
if ZAUN.match(zeile):
|
||||
start = i
|
||||
# Ein Zaun aus n Backticks schliesst erst mit mindestens n Backticks: Ein
|
||||
# Vier-Backtick-Block darf einen Drei-Backtick-Block enthalten (Doku-Tasks).
|
||||
laenge = len(zeile) - len(zeile.lstrip("`"))
|
||||
i += 1
|
||||
inhalt: list[str] = []
|
||||
while i < len(zeilen) and not (
|
||||
zeilen[i].startswith("`" * laenge) and not zeilen[i].strip("`").strip()
|
||||
):
|
||||
inhalt.append(zeilen[i])
|
||||
i += 1
|
||||
marke = ""
|
||||
k = start - 1
|
||||
while k >= 0 and not zeilen[k].strip():
|
||||
k -= 1
|
||||
if k >= 0:
|
||||
marke = zeilen[k].strip()
|
||||
if marke in ("Aus:", "wird:"):
|
||||
bloecke.append({"art": marke[:-1], "ziel": ziel, "zeile": start + 1, "text": "\n".join(inhalt)})
|
||||
i += 1
|
||||
return bloecke
|
||||
|
||||
|
||||
def main() -> int:
|
||||
zerleger = argparse.ArgumentParser()
|
||||
zerleger.add_argument("modus", choices=["vor", "nach"])
|
||||
zerleger.add_argument("taskdateien", nargs="+")
|
||||
zerleger.add_argument("--projekt", required=True)
|
||||
argumente = zerleger.parse_args()
|
||||
projekt = Path(argumente.projekt)
|
||||
|
||||
befunde = 0
|
||||
geprueft = 0
|
||||
for name in argumente.taskdateien:
|
||||
taskdatei = Path(name)
|
||||
bloecke = lies_bloecke(taskdatei, projekt)
|
||||
paare = 0
|
||||
for nummer, block in enumerate(bloecke):
|
||||
if block["ziel"] is None:
|
||||
print(f"BEFUND {taskdatei.name}:{block['zeile']}: {block['art']}-Block ohne Zieldatei in einer Ueberschrift")
|
||||
befunde += 1
|
||||
continue
|
||||
datei = projekt / block["ziel"]
|
||||
if not datei.exists():
|
||||
if argumente.modus == "nach" or block["art"] == "Aus":
|
||||
print(f"BEFUND {taskdatei.name}:{block['zeile']}: Zieldatei {block['ziel']} fehlt")
|
||||
befunde += 1
|
||||
continue
|
||||
inhalt = normal(datei.read_text(encoding="utf-8"))
|
||||
anzahl = inhalt.count(block["text"])
|
||||
geprueft += 1
|
||||
if argumente.modus == "vor":
|
||||
if block["art"] != "Aus":
|
||||
continue
|
||||
paare += 1
|
||||
if anzahl == 0:
|
||||
print(f"BEFUND {taskdatei.name}:{block['zeile']}: Aus-Block steht nicht in {block['ziel']}")
|
||||
befunde += 1
|
||||
elif anzahl > 1:
|
||||
print(f"BEFUND {taskdatei.name}:{block['zeile']}: Aus-Block steht {anzahl}-mal in {block['ziel']} (mehrdeutig)")
|
||||
befunde += 1
|
||||
else:
|
||||
if block["art"] == "wird":
|
||||
paare += 1
|
||||
if anzahl == 0:
|
||||
print(f"BEFUND {taskdatei.name}:{block['zeile']}: wird-Block steht nicht in {block['ziel']}")
|
||||
befunde += 1
|
||||
else:
|
||||
folge = bloecke[nummer + 1] if nummer + 1 < len(bloecke) else None
|
||||
einfuegung = folge is not None and folge["art"] == "wird" and block["text"] in folge["text"]
|
||||
if anzahl > 0 and not einfuegung:
|
||||
print(f"BEFUND {taskdatei.name}:{block['zeile']}: Aus-Block steht noch in {block['ziel']}")
|
||||
befunde += 1
|
||||
print(f"{taskdatei.name}: {len(bloecke)} Aus-/wird-Bloecke, im Modus {argumente.modus} geprueft: {paare}")
|
||||
if geprueft == 0:
|
||||
print("Nichts geprueft: kein Aus-/wird-Block gefunden.")
|
||||
return 3
|
||||
print(f"{befunde} Befund(e).")
|
||||
return 2 if befunde else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user