Files
management/scripts/pruefe_prosa.py
T
Thore CimbalandClaude Fable 5 92b448fe30 feat: slice 3 - wiki, sources and AARs in their neckbeard homes
Gate 4, slice 3: verfahren/, hosts/, vision/ and shared/ moved via git
mv - six AARs to docs/aar/ (four harvested by the 2026-08-09 retro,
two open), procedures and host knowledge to docs/wiki/ (admin,
deployment, architecture, new area vision), the retro protocol and the
commit mapping table to docs/sources/ (protokolle/, migration/). New:
the wiki index linking every page, and the mirror-topology page
carrying the why-two-places reasoning verbatim from the old CLAUDE.md
(F-013 preserved). All moved-path references retargeted; the link
checker drove the sweep to zero.

pruefe_prosa.py added (pattern C+D): SHA citations resolve via repo,
mapping table, optional component clones or a curated exemption list
(documented dead Gitea-force-push commits, a vendor-repo tag, an
Authentik uid that is hex but no git SHA, the external neckbeard
reference); wiki task prose without an issue reference errors, with a
visible pragma for deliberate checklists; the dead-tracker denylist
now covers every mirrored repo's retired Gitea tracker (F-005) - two
links re-verified against live GitLab titles and retargeted, five
defused into honest historical citations.

Verified: validate 0/0, gen_status --check current, drift 0. Demo on
the pre-migration state fires 6 findings (3 orphaned SHAs, 3 task
blocks); on the current tree exactly the 3 F-004 task blocks remain -
they turn green in slice 4 when the issues exist, which is why
pruefe_prosa joins CI only then.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 12:00:00 +00:00

134 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""pruefe_prosa.py — deterministische Prosa-Prüfungen (offline).
Drei Prüfungen, jede aus einem realen Feldtest-Befund (Design
2026-08-11, Muster C und D; Prüfungen nur aus realen Fällen):
(a) SHA-Zitate auflösen (F-012: sechs verwaiste Zitate, niemand konnte
sie prüfen). Kandidaten sind Hex-Wörter (740 Zeichen, mindestens
je ein Buchstabe und eine Ziffer) in Wurzel-*.md und docs/**, ohne
docs/sources/ (die Zuordnungstabelle IST das Mapping). Auflösung:
1. Objekt existiert in diesem Repo (`git cat-file -e`),
2. SHA steht in der Zuordnungstabelle (alt→neu, ADR-0009),
3. Objekt existiert in einem Klon unter $NB_KOMPONENTEN (optional).
Unauflösbar MIT gesetztem NB_KOMPONENTEN → FEHLER; ohne → als
UNGEPRÜFT gelistet (sichtbar, kein stiller Skip).
(b) Aufgabenmarker im Wiki (F-004: fünf Arbeitspunkte lebten nur in
hosts/-Prosa, unsichtbar für das Board). Ein Absatz in docs/wiki/**
mit Marker (Nächster Schritt / Offen: / TODO / offene Checkbox)
muss einen Issue-Verweis tragen (#N, docs/issues/, /issues/-URL),
sonst FEHLER. Bewusste Nicht-Aufgaben (z. B. Checklisten eines
Verfahrens) tragen im selben Absatz das sichtbare Pragma
`<!-- pruefe-prosa:ok (Grund) -->`.
(c) Sperrliste stillgelegter Ziele (F-005: lebendes Dokument routete
auf den toten Gitea-Tracker). Treffer → FEHLER.
Usage: python scripts/pruefe_prosa.py [repo-root]
"""
from __future__ import annotations
import os
import re
import subprocess
import sys
from pathlib import Path
SHA_RE = re.compile(r"\b[0-9a-f]{7,40}\b")
MARKER_RE = re.compile(r"Nächste[r]? Schritt|(?:^|\*\*)Offen:|TODO\b|^\s*[-*] \[ \]",
re.M)
ISSUE_REF_RE = re.compile(r"#\d+|docs/issues/|/issues/\d+")
# F-005/ADR-0002: die Gitea-Tracker ALLER gespiegelten Repos sind seit dem
# Umzug tot; verbindlich sind die git.lab-Issues (Zuordnung: Migrations-
# Fußtext im jeweiligen GitLab-Issue).
SPERRLISTE = [re.compile(r"rohana\.axion1337\.de/sorb/[^)\s]*/issues/")]
ZUORDNUNG = "docs/sources/migration/commit-zuordnung-2026-08-07.md"
# Kuratierte Ausnahmen: SHA<TAB>Grund — nur für Zitate, deren Ziel
# nachweislich und dokumentiert nicht mehr existiert (oder außerhalb
# des Prüfbereichs liegt). Neue Einträge brauchen einen Grund.
AUSNAHMEN = "scripts/sha_ausnahmen.tsv"
def kandidaten(root: Path):
for pattern in ("*.md", "docs/**/*.md"):
for p in sorted(root.glob(pattern)):
rel = p.relative_to(root).as_posix()
if rel.startswith("docs/sources/") or p.name == "template.md":
continue
yield p, rel
def obj_existiert(repo: Path, sha: str) -> bool:
r = subprocess.run(["git", "-C", str(repo), "cat-file", "-e",
f"{sha}^{{object}}"], capture_output=True)
return r.returncode == 0
def main() -> int:
root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.cwd()
fehler: list[str] = []
ungeprueft: list[str] = []
zuordnung = ""
zp = root / ZUORDNUNG
if zp.is_file():
zuordnung = zp.read_text(encoding="utf-8")
ausnahmen: set[str] = set()
ap = root / AUSNAHMEN
if ap.is_file():
for zeile in ap.read_text(encoding="utf-8").splitlines():
if zeile.strip() and not zeile.startswith("#"):
ausnahmen.add(zeile.split("\t")[0].strip())
klone_dir = os.environ.get("NB_KOMPONENTEN", "")
klone = [d for d in Path(klone_dir).iterdir()
if (d / ".git").exists()] if klone_dir else []
for p, rel in kandidaten(root):
text = p.read_text(encoding="utf-8")
# (c) Sperrliste
for muster in SPERRLISTE:
for treffer in set(muster.findall(text)):
fehler.append(f"{rel}: Verweis auf stillgelegtes Ziel "
f"({treffer})")
# (a) SHA-Zitate
for sha in set(SHA_RE.findall(text)):
if sha.isdigit() or not any(c.isdigit() for c in sha) \
or not any(c in "abcdef" for c in sha):
continue
if sha in ausnahmen:
continue
if obj_existiert(root, sha) or sha in zuordnung:
continue
if any(obj_existiert(k, sha) for k in klone):
continue
if klone:
fehler.append(f"{rel}: SHA-Zitat unauflösbar: {sha}")
else:
ungeprueft.append(f"{rel}: {sha} (kein NB_KOMPONENTEN)")
# (b) Aufgabenmarker, nur Wiki
if rel.startswith("docs/wiki/"):
for absatz in re.split(r"\n\s*\n", text):
if "pruefe-prosa:ok" in absatz:
continue
if MARKER_RE.search(absatz) and not ISSUE_REF_RE.search(absatz):
zeile = absatz.strip().splitlines()[0][:70]
fehler.append(f"{rel}: Aufgabenprosa ohne "
f"Issue-Verweis: „{zeile}…“")
for f in fehler:
print(f"FEHLER {f}")
for u in ungeprueft:
print(f"UNGEPRÜFT {u}")
print(f"pruefe_prosa: {len(fehler)} Fehler, "
f"{len(ungeprueft)} ungeprüfte SHA-Zitate")
return 1 if fehler else 0
if __name__ == "__main__":
sys.exit(main())