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>
This commit is contained in:
co-authored by
Claude Fable 5
parent
70e81e2ff1
commit
92b448fe30
@@ -0,0 +1,133 @@
|
||||
#!/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 (7–40 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())
|
||||
@@ -0,0 +1,8 @@
|
||||
# SHA<TAB>Grund — kuratierte Ausnahmen für pruefe_prosa.py (a).
|
||||
dfe04c4a Gitea-Direktpush, vom Mirror force-überschrieben; Text dokumentiert das (deploy-uebergabe, cfgmon)
|
||||
dfe04c4 Kurzform desselben überschriebenen Commits (dfe04c4→6ffab68)
|
||||
2b715ca Gitea-Direktpush, überschrieben (2b715ca→0bd77e2); Text dokumentiert das
|
||||
7645a2b Commit im vendor/windows-Repo — außerhalb des Prüfbereichs (Windows-Build-VM, overmind)
|
||||
5bc25447 Image-Tag aus vendor/windows-CI — außerhalb des Prüfbereichs (overmind)
|
||||
2fafe38b Authentik-uid, kein Git-SHA (AAR 2026-08-11, MAS-subject)
|
||||
823a08cac6b03a47d7e2f661200a49ac6e09d38d neckbeard-v0.1.1-Referenz — externes Repo, nicht im Komponenten-Prüfbereich
|
||||
|
Reference in New Issue
Block a user