plan-kopie: welle_vergleich vergleicht gitignorierte Zieldateien byte-genau

Eine Zieldatei unter einem ignorierten Pfad (tests/.work/) stand nie in
git status und galt als unverändert gegenüber HEAD, ohne Byte-Vergleich.
Jetzt fragt das Werkzeug git check-ignore und vergleicht sie gegen die Kopie.
Treiber mit acht neuen Fällen (sechs auf der alten Fassung rot), fünf Mutanten
getötet; Paket R1 Welle 2: 12 Fehlalarme vorher, 0 Befunde danach.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Pascal MichelandClaude Opus 5.5 committed 2026-10-17 14:24:16 +02:00
1 parent 5cbe75c5e3
commit 7ba47d69ec
3 files changed
+102 -9

No files matched your search

+31 -5
View File
@@ -5,9 +5,15 @@ Koordinator-Werkzeug im Scratchpad. Fragen und Herkunft der Antwort:
2. Welche durfte sie aendern? Quelle: Abschnitt "## Zieldateien" der uebergebenen Taskdateien.
3. Ist jede Zieldatei byte-gleich mit dem Plan? Quelle: die Kopie `mat`, auf der plan_materialisiere.py
alle Codebloecke des Plans angewendet hat.
4. Welche Zieldateien blendet git aus? Quelle: `git check-ignore -z --stdin` im Projekt. Getrackte
Dateien zaehlen nicht, auch wenn ein Muster passt (git-check-ignore(1)).
Seit 2026-10-17: Eine ignorierte Zieldatei steht nie in `git status`. Frueher hiess das "unveraendert gegenueber
HEAD" und der Byte-Vergleich fiel aus (Paket R1 im Edge-Stack: 8 und 12 Fehlalarme fuer tests/.work/).
Jetzt wird sie wie jede andere gegen die Kopie verglichen; ob sie vor der Welle schon so aussah, sieht das
Werkzeug bei ihr nicht, und eine geloeschte gilt als geloescht, sobald sie im Baum fehlt.
Was es NICHT sieht (Fangnetz, kein Nachweis):
- Dateien, die .gitignore ausblendet (data/, artifacts/, node_modules/): Ein Executer, der dorthin
schreibt, bleibt unsichtbar.
- Dateien, die .gitignore ausblendet und die keine Zieldatei sind (data/, artifacts/, node_modules/): Ein
Executer, der dorthin schreibt, bleibt unsichtbar.
- Ob die Kopie selbst sauber ist. Eine verschmutzte Kopie erzeugt Fehlalarm, kein falsches Gruen.
- Prosa-Dateien (--prosa): nur Mengenpruefung, kein Byte-Vergleich; die liest der Koordinator selbst.
- Bereits committete Aenderungen: Verglichen wird der Arbeitsbaum gegen HEAD.
@@ -99,6 +105,25 @@ def geaendert(projekt: Path, befunde: list[str]) -> dict[str, str]:
return aus
def ignoriert(projekt: Path, pfade: list[str], befunde: list[str]) -> set[str]:
"""Die Zieldateien unter pfade, die git ignoriert. Exit 1 von check-ignore heisst: keine; alles ueber 1 ein Befund."""
if not pfade:
return set()
try:
ergebnis = subprocess.run(
["git", "-C", str(projekt), "check-ignore", "-z", "--stdin"],
input=("\0".join(pfade) + "\0").encode("utf-8"),
capture_output=True,
)
except OSError as fehler:
befunde.append(f"git check-ignore laesst sich nicht starten ({fehler})")
return set()
if ergebnis.returncode not in (0, 1):
befunde.append(f"git check-ignore endet mit {ergebnis.returncode}: {ergebnis.stderr.decode('utf-8', 'replace').strip()}")
return set()
return {p for p in ergebnis.stdout.decode("utf-8", "replace").split("\0") if p}
def erste_abweichung(echt: bytes, kopie: bytes) -> str:
if echt.replace(b"\r\n", b"\n") == kopie.replace(b"\r\n", b"\n"):
return "nur die Zeilenenden weichen ab (CRLF gegen LF)"
@@ -122,6 +147,7 @@ def pruefe(args: argparse.Namespace, befunde: list[str]) -> None:
for name in args.taskdateien:
ziele.update(zieldateien(Path(name), projekt, befunde))
status = geaendert(projekt, befunde)
ign = ignoriert(projekt, sorted(ziele), befunde)
for pfad in sorted(set(status) - set(ziele)):
befunde.append(f"{pfad}: geaendert ({status[pfad]!r}), liegt aber ausserhalb der Zieldateien dieser Welle")
@@ -136,10 +162,10 @@ def pruefe(args: argparse.Namespace, befunde: list[str]) -> None:
befunde.append(f"{pfad}: soll geloescht sein, steht aber noch im Baum")
if kopie.exists():
befunde.append(f"{pfad}: soll geloescht sein, steht aber noch in der Kopie")
if not echt.exists() and "D" not in status.get(pfad, ""):
if not echt.exists() and pfad not in ign and "D" not in status.get(pfad, ""):
befunde.append(f"{pfad}: fehlt im Baum, git meldet aber keine Loeschung ({status.get(pfad)!r})")
continue
if pfad not in status:
if pfad not in status and pfad not in ign:
befunde.append(f"{pfad}: Zieldatei ist unveraendert gegenueber HEAD")
continue
if not echt.is_file():
@@ -155,7 +181,7 @@ def pruefe(args: argparse.Namespace, befunde: list[str]) -> None:
gleich += 1
else:
befunde.append(f"{pfad}: weicht vom Plan ab -- {erste_abweichung(a, b)}")
print(f"{len(ziele)} Zieldateien, {len(status)} geaenderte Dateien laut git, {gleich} byte-gleich mit der Kopie, {len(prosa & set(ziele))} nur als Menge geprueft.")
print(f"{len(ziele)} Zieldateien, {len(status)} geaenderte Dateien laut git, {gleich} byte-gleich mit der Kopie, {len(prosa & set(ziele))} nur als Menge geprueft, {len(ign)} von git ignoriert.")
def main() -> int: