Stufe 4c (#764): Kommentare in C#, WebUI-Skripten, CSS und HTML auf Englisch

Rund 2.600 Kommentarblöcke in 310 C#-Dateien übersetzt, Begründungen vollständig
erhalten: blockweise gleiche Anzahl wie vorher, kein Block unter 60 % seiner
Länge, der Code außerhalb der Kommentare per Tokenizer byte-identisch geprüft.
Zitate deutscher Oberflächentexte und Testdaten bleiben deutsch.

WebUI: Kommentare in JS/CSS/HTML übersetzt, JSDoc-@param an die in 4b
umbenannten Parameter angeglichen.

Dabei behoben:
- 4b hatte "tag" (HTML-Tag) als deutsches "Tag" gelesen und zu "dayValue"
  gemacht (makeElement, svg, Tagesübersicht) - zurück auf tag/day.
- Aufgaben "InProgress" bekamen die Plakettenart "laufend", für die es nie eine
  CSS-Klasse gab - jetzt "success" wie laufende Instanzen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-10-04 12:45:03 +02:00
co-authored by Claude Opus 5.5
parent da4ceac7d5
commit 837a634a22
311 changed files with 7238 additions and 7252 deletions
@@ -8,10 +8,10 @@ using Shouldly;
namespace ClawdDotNet.Tools.Tests.FileRW;
/// <summary>
/// Die Nachschau nach einem Absturz (#555) und das atomare Schreiben (#553).
/// The follow-up check after a crash (#555) and atomic writing (#553).
///
/// Das Dateisystem wird auch hier nicht abstrahiert — es geht gerade darum, was auf der
/// Platte steht, nachdem ein Aufruf unterbrochen wurde.
/// The file system is not abstracted here either — this is precisely about what is on
/// disk after a call was interrupted.
/// </summary>
public sealed class NachschauTests : IDisposable
{
@@ -27,7 +27,7 @@ public sealed class NachschauTests : IDisposable
public void Dispose()
{
try { Directory.Delete(Path.GetDirectoryName(_root)!, recursive: true); }
catch { /* Aufräumen ist Nebensache */ }
catch { /* Cleaning up is secondary */ }
}
private AgentToolContext Context() => new(
@@ -44,7 +44,7 @@ public sealed class NachschauTests : IDisposable
private Task<ToolResult> Run(string json)
=> _tool.ExecuteAsync(JsonDocument.Parse(json).RootElement, Context(), CancellationToken.None);
// ═══════════════════ #553: atomar schreiben ═══════════════════
// ═══════════════════ #553: writing atomically ═══════════════════
[Fact]
public async Task Schreiben_hinterlaesst_keine_Nebendateien()
@@ -52,7 +52,7 @@ public sealed class NachschauTests : IDisposable
var result = await Run("""{"action":"write","path":"a.txt","content":"Inhalt"}""");
result.Success.ShouldBeTrue();
// Das atomare Schreiben geht über eine Nebendatei — die muss danach weg sein.
// Atomic writing goes via a side file — it must be gone afterwards.
Directory.GetFiles(_root).ShouldHaveSingleItem();
(await File.ReadAllTextAsync(Path.Combine(_root, "a.txt"))).ShouldBe("Inhalt");
}
@@ -63,11 +63,11 @@ public sealed class NachschauTests : IDisposable
await Run("""{"action":"write","path":"a.txt","content":"ein sehr langer alter Inhalt"}""");
await Run("""{"action":"write","path":"a.txt","content":"kurz"}""");
// Beim Ersetzen darf kein Rest der alten, längeren Fassung stehenbleiben.
// When replacing, no remnant of the old, longer version may remain.
(await File.ReadAllTextAsync(Path.Combine(_root, "a.txt"))).ShouldBe("kurz");
}
// ═══════════════════ #555: Nachschau bei write ═══════════════════
// ═══════════════════ #555: follow-up check for write ═══════════════════
[Fact]
public async Task Fehlende_Datei_heisst_nichts_geschehen()
@@ -92,13 +92,13 @@ public sealed class NachschauTests : IDisposable
{
await Run("""{"action":"write","path":"a.txt","content":"alt"}""");
// So sähe es aus, wenn der Absturz vor dem Ersetzen kam.
// This is what it would look like if the crash came before the replacement.
var befund = await Probe("""{"action":"write","path":"a.txt","content":"neu"}""");
befund.Outcome.ShouldBe(ProbeOutcome.NotStarted);
}
// ═══════════════════ #555: Nachschau bei append ═══════════════════
// ═══════════════════ #555: follow-up check for append ═══════════════════
[Fact]
public async Task Vollstaendig_angehaengter_Text_wird_erkannt()
@@ -114,8 +114,8 @@ public sealed class NachschauTests : IDisposable
[Fact]
public async Task Ein_halb_angehaengter_Text_wird_als_Riss_erkannt()
{
// Anhängen ist nicht atomar — genau dieser Zustand kann wirklich entstehen.
// Hier wird er von Hand hergestellt, weil ein echter Riss nicht reproduzierbar ist.
// Appending is not atomic — exactly this state can really arise.
// Here it is produced by hand, because a real tear cannot be reproduced.
var pfad = Path.Combine(_root, "log.txt");
await File.WriteAllTextAsync(pfad, "Zeile 1\nZeile 2 ist nur halb ang");
@@ -136,7 +136,7 @@ public sealed class NachschauTests : IDisposable
befund.Outcome.ShouldBe(ProbeOutcome.NotStarted);
}
// ═══════════════════ Die Grenzen der Nachschau ═══════════════════
// ═══════════════════ The limits of the follow-up check ═══════════════════
[Theory]
[InlineData("read")]
@@ -152,8 +152,8 @@ public sealed class NachschauTests : IDisposable
[Fact]
public async Task Ein_unbrauchbarer_Pfad_ergibt_Unbekannt_statt_einer_Ausnahme()
{
// Die Nachschau darf nie werfen: Ein Fehlschlag der Nachschau ist eine Aussage
// über die Nachschau, nicht über die Welt.
// The follow-up check must never throw: a failure of the follow-up check is a statement
// about the follow-up check, not about the world.
var befund = await Probe("""{"action":"write","path":"../../ausserhalb.txt","content":"x"}""");
befund.Outcome.ShouldBe(ProbeOutcome.Unknown);
@@ -8,11 +8,11 @@ using Shouldly;
namespace ClawdDotNet.Tools.Tests.FileRW;
/// <summary>
/// Sammel-Lesen (#663) und der Hinweis auf den anderen Workspace (#662).
/// Batch reading (#663) and the hint about the other workspace (#662).
///
/// Anlass aus dem Testbetrieb: Der Tagesabschluss las 24 Stundendateien einzeln und starb
/// an der Schrittgrenze, und derselbe Agent rief Dateien wiederholt im falschen Workspace
/// ab, weil die Fehlermeldung nicht sagte, wo sie liegen.
/// Occasion from test operation: the daily close read 24 hourly files individually and died
/// at the step limit, and the same agent repeatedly fetched files in the wrong workspace,
/// because the error message did not say where they lie.
/// </summary>
public sealed class SammelLesenTests : IDisposable
{
@@ -33,7 +33,7 @@ public sealed class SammelLesenTests : IDisposable
public void Dispose()
{
try { Directory.Delete(_basis, recursive: true); }
catch { /* Aufräumen ist Nebensache */ }
catch { /* Cleaning up is secondary */ }
}
private AgentToolContext Context(string sharedLevel = "ReadWrite") => new(
@@ -58,7 +58,7 @@ public sealed class SammelLesenTests : IDisposable
}
}
// ═══════════════════ #663: ein Aufruf statt 24 ═══════════════════
// ═══════════════════ #663: one call instead of 24 ═══════════════════
[Fact]
public async Task Ein_Verzeichnis_mit_Muster_kommt_in_einem_Aufruf()
@@ -160,7 +160,7 @@ public sealed class SammelLesenTests : IDisposable
result.Content.ShouldNotContain("skript.exe");
}
// ═══════════════════ #662: Hinweis auf den anderen Workspace ═══════════════════
// ═══════════════════ #662: hint about the other workspace ═══════════════════
[Fact]
public async Task Liegt_die_Datei_im_anderen_Workspace_sagt_die_Meldung_das()
@@ -186,7 +186,7 @@ public sealed class SammelLesenTests : IDisposable
[Fact]
public async Task Ohne_Recht_am_anderen_Workspace_gibt_es_keinen_Hinweis()
{
// Ein Hinweis auf etwas, das der Agent ohnehin nicht darf, schickt ihn ins Leere.
// A hint about something the agent may not do anyway sends it into nothing.
Stundendateien(1);
var result = await Run("""{"action":"read","path":"wetter/stunden/berlin_2026-09-20_00-00.txt"}""", sharedLevel: "Denied");
@@ -7,12 +7,12 @@ using Shouldly;
namespace ClawdDotNet.Tools.Tests.FileRW;
/// <summary>
/// Sortiertes, gedeckeltes Auflisten (#673).
/// Sorted, capped listing (#673).
///
/// Anlass aus dem Testbetrieb: Die Betriebskontrolle meldete zwei Stundendateien als
/// fehlend, die es gab. Das Verzeichnis lieferte 105 Eintraege in der Reihenfolge des
/// Dateisystems — darin zwei Namen zu finden ist Sucharbeit, und ein guenstiges Modell
/// erledigt sie falsch.
/// Occasion from test operation: the operations check reported two hourly files as
/// missing that existed. The directory delivered 105 entries in the order of the
/// file system — finding two names in that is search work, and a cheap model
/// does it wrong.
/// </summary>
public sealed class VerzeichnisListeTests : IDisposable
{
@@ -33,7 +33,7 @@ public sealed class VerzeichnisListeTests : IDisposable
public void Dispose()
{
try { Directory.Delete(_basis, recursive: true); }
catch { /* Aufräumen ist Nebensache */ }
catch { /* Cleaning up is secondary */ }
}
private AgentToolContext Context() => new(
@@ -48,7 +48,7 @@ public sealed class VerzeichnisListeTests : IDisposable
private Task<ToolResult> Run(string json)
=> _tool.ExecuteAsync(JsonDocument.Parse(json).RootElement, Context(), CancellationToken.None);
/// <summary>Stundendateien, absichtlich in gemischter Reihenfolge geschrieben.</summary>
/// <summary>Hourly files, deliberately written in mixed order.</summary>
private void Stundendateien(int anzahl, string tag = "2026-09-21")
{
var verzeichnis = Path.Combine(_shared, "wetter", "stunden");
@@ -67,7 +67,7 @@ public sealed class VerzeichnisListeTests : IDisposable
.Select(z => z.Split(" ", StringSplitOptions.RemoveEmptyEntries).Last())
.ToList();
// ═══════════════════ Reihenfolge ═══════════════════
// ═══════════════════ Order ═══════════════════
[Fact]
public async Task Die_juengste_Datei_steht_oben()
@@ -106,7 +106,7 @@ public sealed class VerzeichnisListeTests : IDisposable
Namen(result.Content).First().ShouldBe("berlin_2026-09-21_00-00.txt");
}
// ═══════════════════ Deckel und Gesamtzahl ═══════════════════
// ═══════════════════ Cap and total count ═══════════════════
[Fact]
public async Task Der_Kopf_nennt_immer_die_Gesamtzahl()
@@ -136,7 +136,7 @@ public sealed class VerzeichnisListeTests : IDisposable
Namen(zuviel.Content).Count.ShouldBe(10, "mehr als da ist gibt es nicht");
}
// ═══════════════════ Gezielt fragen ═══════════════════
// ═══════════════════ Asking specifically ═══════════════════
[Fact]
public async Task Ein_Muster_beantwortet_die_Frage_nach_einer_bestimmten_Datei()
@@ -177,15 +177,15 @@ public sealed class VerzeichnisListeTests : IDisposable
Namen(result.Content).Count.ShouldBe(4);
}
// ═══════════════════ Das Muster ist kein zweiter Pfad ═══════════════════
// ═══════════════════ The pattern is not a second path ═══════════════════
[Theory]
[InlineData("list")]
[InlineData("read_many")]
public async Task Ein_Muster_darf_nicht_aus_dem_Verzeichnis_herausfuehren(string action)
{
// Directory.GetFiles wertet Trennzeichen im Muster aus: "../*" listet das
// Elternverzeichnis, ohne dass die Pfadpruefung je gefragt wurde.
// Directory.GetFiles evaluates separators in the pattern: "../*" lists the
// parent directory without the path check ever being asked.
Stundendateien(2);
File.WriteAllText(Path.Combine(_shared, "wetter", "geheim.txt"), "nicht fuer den Agenten");
@@ -4,18 +4,18 @@ using Shouldly;
namespace ClawdDotNet.Tools.Tests.FileRW;
/// <summary>
/// S6 aus der Bestandsaufnahme: Die Pfadprüfung verglich nur Zeichenketten-Präfixe.
/// Ohne abschließenden Verzeichnistrenner erlaubte ein Root "…\Workspace" damit auch
/// S6 from the inventory: the path check only compared string prefixes.
/// Without a trailing directory separator a root "…\Workspace" thus also allowed
/// "…\Workspace-Backup\…".
///
/// Das Dateisystem wird hier bewusst NICHT abstrahiert — die Tests sollen die echte
/// Pfadsemantik der jeweiligen Plattform prüfen (.., UNC, Alternate Data Streams,
/// abschließende Punkte, unter Linux zusätzlich Groß-/Kleinschreibung und
/// Verknüpfungen). Eine Abstraktion würde genau die Fehlerklasse verstecken, um die es
/// geht.
/// The file system is deliberately NOT abstracted here — the tests are supposed to check the real
/// path semantics of the respective platform (.., UNC, alternate data streams,
/// trailing dots, under Linux additionally upper/lower case and
/// links). An abstraction would hide exactly the class of errors this is
/// about.
///
/// Die Verzeichnisgrenze selbst prüft <c>PathBoundaryTests</c> in Core.Tests; hier geht
/// es um das, was <see cref="WorkspacePath.Resolve"/> darüber hinaus abweist.
/// The directory boundary itself is checked by <c>PathBoundaryTests</c> in Core.Tests; here it
/// is about what <see cref="WorkspacePath.Resolve"/> rejects beyond that.
/// </summary>
public sealed class WorkspacePathTests : IDisposable
{
@@ -30,13 +30,13 @@ public sealed class WorkspacePathTests : IDisposable
public void Dispose()
{
try { Directory.Delete(Path.GetDirectoryName(_root)!, recursive: true); }
catch { /* Aufräumen ist Nebensache */ }
catch { /* Cleaning up is secondary */ }
}
private string Resolve(string? relative) => WorkspacePath.Resolve(_root, relative, "personal");
// ═══════════════════════════════════════════════════════════
// Ausbruchsversuche
// Breakout attempts
// ═══════════════════════════════════════════════════════════
[Theory]
@@ -51,17 +51,17 @@ public sealed class WorkspacePathTests : IDisposable
}
/// <summary>
/// Die Windows-Formen müssen auch unter Linux abgewiesen werden — dort greift
/// aber eine andere Regel: <c>Path.IsPathRooted(@"C:\temp\x")</c> ist unter Linux
/// <c>false</c>, weil das schlicht ein Dateiname mit Doppelpunkt ist. Gefangen
/// werden diese Fälle von der Zeichenregel: Doppelpunkt und Backslash sind im
/// Pfad unzulässig, getrennt wird mit <c>/</c>. Das Ergebnis ist auf beiden
/// Plattformen dasselbe, der Weg dorthin nicht.
/// The Windows forms must be rejected under Linux too — but there a different
/// rule applies: <c>Path.IsPathRooted(@"C:\temp\x")</c> is <c>false</c> under Linux,
/// because that is simply a file name with a colon. These cases are caught
/// by the character rule: colon and backslash are not permitted in the
/// path, separation is done with <c>/</c>. The result is the same on both
/// platforms, the way there is not.
///
/// Dieselbe Regel fängt unter Linux auch <c>@"..\..\evil.txt"</c> aus dem Test
/// darüber. Ein Ausbruch war das dort nie — der Backslash trennt unter Linux
/// nichts, die Datei wäre im Workspace gelandet —, aber der Test erwartete eine
/// Abweisung und bekam keine.
/// The same rule also catches <c>@"..\..\evil.txt"</c> from the test
/// above under Linux. That was never a breakout there — the backslash separates
/// nothing under Linux, the file would have landed in the workspace —, but the test expected a
/// rejection and got none.
/// </summary>
[Theory]
[InlineData(@"C:\Windows\System32\config\SAM")]
@@ -74,9 +74,9 @@ public sealed class WorkspacePathTests : IDisposable
}
/// <summary>
/// Die Linux-Entsprechungen. Unter Windows sind das gewöhnliche relative Pfade,
/// die entweder ins Leere zeigen oder von der <c>..</c>-Regel gefangen werden —
/// deshalb laufen sie dort mit, statt übersprungen zu werden.
/// The Linux equivalents. Under Windows these are ordinary relative paths
/// that either point nowhere or are caught by the <c>..</c> rule —
/// that's why they run along there instead of being skipped.
/// </summary>
[Theory]
[InlineData("/etc/passwd")]
@@ -92,12 +92,12 @@ public sealed class WorkspacePathTests : IDisposable
[InlineData("datei.txt:$DATA")]
public void Alternate_Data_Streams_werden_abgelehnt(string path)
{
// Ein ADS umgeht sonst die Endungsprüfung: "x.txt:evil.exe".
// An ADS would otherwise bypass the extension check: "x.txt:evil.exe".
Should.Throw<UnauthorizedAccessException>(() => Resolve(path));
}
/// <summary>
/// Der eigentliche Kern von S6: ein Nachbarverzeichnis mit gleichem Präfix.
/// The actual core of S6: a neighboring directory with the same prefix.
/// </summary>
[Fact]
public void Ein_Nachbarverzeichnis_mit_gleichem_Praefix_gilt_als_ausserhalb()
@@ -117,7 +117,7 @@ public sealed class WorkspacePathTests : IDisposable
}
// ═══════════════════════════════════════════════════════════
// Gegenproben — normale Nutzung muss funktionieren
// Counter-checks — normal use must work
// ═══════════════════════════════════════════════════════════
[Theory]
@@ -155,7 +155,7 @@ public sealed class WorkspacePathTests : IDisposable
[Fact]
public void Ein_Unterverzeichnis_mit_aehnlichem_Namen_bleibt_innerhalb()
{
// Gegenprobe zur Präfix-Regel: Innerhalb des Roots ist alles erlaubt.
// Counter-check to the prefix rule: within the root everything is allowed.
var inner = Path.Combine(_root, "Workspace-Backup", "x.txt");
WorkspacePath.IsInside(inner, _root).ShouldBeTrue();