2026-06-15 02:58:21 +02:00
|
|
|
// Testet die reine Urteils-/Blocker-Parselogik des /optimize-Loops.
|
|
|
|
|
// Diese Funktionen entscheiden, ob der Coder einen Fix bekommt — Regressionen hier sind kritisch.
|
|
|
|
|
|
|
|
|
|
import { test } from "node:test";
|
|
|
|
|
import assert from "node:assert/strict";
|
|
|
|
|
import { extractFn } from "./helpers.mjs";
|
|
|
|
|
|
|
|
|
|
const parseVerdict = extractFn("parseVerdict");
|
|
|
|
|
const parseBlockers = extractFn("parseBlockers");
|
|
|
|
|
const normalizeForComparison = extractFn("normalizeForComparison");
|
docs: LICENCE.md-Feature, .gitignore, Single-GPU-Doku
- pi-coder-judge-extension.ts: licenceMd() + writeLicenceMd() — erzeugt
proprietäre LICENCE.md in der Doku-Phase (nur wenn noch nicht vorhanden)
- tests: Guards und pure-logic-Tests für LICENCE.md ergänzt (38 Tests, alle grün)
- .gitignore: Node, TypeScript-Artefakte, Backups, Editor-Dateien, OS-Metadaten
- README.md + BEDIENUNGSANLEITUNG.md: vollständig auf Single-GPU-Architektur
aktualisiert (ein Server, System-Prompt-Rollen, kein Quick-Judge,
PASS WITH CONCERNS-Verhalten, LICENCE.md in /update_doku, start-single.sh)
- SINGLE_SERVER_PLAN.md: entfernt (umgesetzt, kein Mehrwert mehr)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 05:03:50 +02:00
|
|
|
const licenceMd = extractFn("licenceMd");
|
2026-10-15 02:42:30 +02:00
|
|
|
const loopKey = extractFn("loopKey");
|
2026-10-15 03:16:30 +02:00
|
|
|
const parseShipVerdict = extractFn("parseShipVerdict");
|
2026-06-15 02:58:21 +02:00
|
|
|
|
|
|
|
|
test("parseVerdict: klares PASS", () => {
|
|
|
|
|
assert.equal(parseVerdict("Review fertig.\nUrteil: PASS\nKeine Blocker."), "PASS");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("parseVerdict: FAIL", () => {
|
|
|
|
|
assert.equal(parseVerdict("Urteil: FAIL"), "FAIL");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("parseVerdict: PASS WITH CONCERNS wird NICHT als PASS verkürzt (kritisch für Fix-Auslösung)", () => {
|
|
|
|
|
// Würde die Regex hier 'PASS' zuerst matchen, käme kein Coder-Fix zustande.
|
|
|
|
|
assert.equal(parseVerdict("Urteil: PASS WITH CONCERNS"), "PASS WITH CONCERNS");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("parseVerdict: case-insensitiv und mit Bullet-Präfix", () => {
|
|
|
|
|
assert.equal(parseVerdict("- urteil: pass with concerns"), "PASS WITH CONCERNS");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("parseVerdict: ohne erkennbares Urteil → UNREADABLE", () => {
|
|
|
|
|
assert.equal(parseVerdict("Sieht solide aus, keine Anmerkungen."), "UNREADABLE");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("parseBlockers: extrahiert Bullet-Blocker und stoppt vor Major", () => {
|
|
|
|
|
const txt = "- Blocker: B1 Argument-Handling unklar\n- Major: M1 chmod fehlt";
|
|
|
|
|
const blockers = parseBlockers(txt);
|
|
|
|
|
assert.match(blockers, /B1 Argument-Handling/);
|
|
|
|
|
assert.doesNotMatch(blockers, /M1/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("parseBlockers: erkennt **Blocker**-Überschrift und stoppt vor Minor", () => {
|
|
|
|
|
const txt = "**Blocker**\nKaputter Import in main.py\n**Minor**\nTippfehler";
|
|
|
|
|
const blockers = parseBlockers(txt);
|
|
|
|
|
assert.match(blockers, /Kaputter Import/);
|
|
|
|
|
assert.doesNotMatch(blockers, /Tippfehler/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("parseBlockers: leer, wenn kein Blocker-Abschnitt vorhanden", () => {
|
|
|
|
|
assert.equal(parseBlockers("Urteil: PASS\nAlles in Ordnung."), "");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("normalizeForComparison: Whitespace-Kollaps, Kleinschreibung, trailing-Satzzeichen", () => {
|
|
|
|
|
assert.equal(
|
|
|
|
|
normalizeForComparison(" Blocker A. "),
|
|
|
|
|
normalizeForComparison("blocker a")
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("normalizeForComparison: gleiche Blocker trotz Formatierungsunterschied → Loop-Erkennung greift", () => {
|
|
|
|
|
const a = "- Race Condition in waitForIdle!";
|
|
|
|
|
const b = "Race condition in waitForIdle";
|
|
|
|
|
assert.notEqual(normalizeForComparison(a), ""); // sanity
|
|
|
|
|
assert.equal(
|
|
|
|
|
normalizeForComparison(a.replace(/^- /, "")),
|
|
|
|
|
normalizeForComparison(b)
|
|
|
|
|
);
|
|
|
|
|
});
|
docs: LICENCE.md-Feature, .gitignore, Single-GPU-Doku
- pi-coder-judge-extension.ts: licenceMd() + writeLicenceMd() — erzeugt
proprietäre LICENCE.md in der Doku-Phase (nur wenn noch nicht vorhanden)
- tests: Guards und pure-logic-Tests für LICENCE.md ergänzt (38 Tests, alle grün)
- .gitignore: Node, TypeScript-Artefakte, Backups, Editor-Dateien, OS-Metadaten
- README.md + BEDIENUNGSANLEITUNG.md: vollständig auf Single-GPU-Architektur
aktualisiert (ein Server, System-Prompt-Rollen, kein Quick-Judge,
PASS WITH CONCERNS-Verhalten, LICENCE.md in /update_doku, start-single.sh)
- SINGLE_SERVER_PLAN.md: entfernt (umgesetzt, kein Mehrwert mehr)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 05:03:50 +02:00
|
|
|
|
|
|
|
|
test("licenceMd: proprietärer Default mit eingesetztem Jahr und Inhaber", () => {
|
|
|
|
|
const txt = licenceMd("2026", "Dieter Schlüter");
|
|
|
|
|
assert.match(txt, /^# Lizenz/);
|
|
|
|
|
assert.match(txt, /Copyright \(c\) 2026 Dieter Schlüter/);
|
|
|
|
|
assert.match(txt, /Alle Rechte vorbehalten\./);
|
|
|
|
|
assert.match(txt, /ohne vorherige ausdrückliche schriftliche/);
|
|
|
|
|
assert.match(txt, /OHNE MÄNGELGEWÄHR/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("licenceMd: Platzhalter-Inhaber wird unverändert eingesetzt", () => {
|
|
|
|
|
assert.match(licenceMd("2030", "[Rechteinhaber eintragen]"), /Copyright \(c\) 2030 \[Rechteinhaber eintragen\]/);
|
|
|
|
|
});
|
2026-10-15 02:42:30 +02:00
|
|
|
|
|
|
|
|
test("loopKey: echte Blocker sind der Schlüssel", () => {
|
|
|
|
|
const txt = "## Blocker\n- B1 Absturz bei leerer Eingabe\n\n## Major\n- M1 x";
|
|
|
|
|
assert.match(loopKey(txt), /B1 Absturz/);
|
|
|
|
|
assert.doesNotMatch(loopKey(txt), /M1/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("loopKey: 'Blocker: Keine' → Major-Punkte statt Fehlalarm (Strata-Probe 15.10.)", () => {
|
|
|
|
|
const r1 = "## Urteil: **PASS WITH CONCERNS**\n\n## Blocker\n- Keine.\n\n## Major\n- M1 Flags als Name\n\n## Minor\n- N1";
|
|
|
|
|
const r2 = "## Urteil: **PASS WITH CONCERNS**\n\n## Blocker\n- Keine.\n\n## Major\n- M1 UTF-8-Absturz\n\n## Minor\n- N1";
|
|
|
|
|
assert.notEqual(loopKey(r1), loopKey(r2));
|
|
|
|
|
assert.match(loopKey(r1), /^MAJOR: - M1 Flags/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("loopKey: gleiche Major-Punkte zweimal → gleicher Schlüssel (Loop wird erkannt)", () => {
|
|
|
|
|
const r = "**Blocker**\nkeine\n**Major**\n- M1 gleich\n**Minor**\n- n";
|
|
|
|
|
assert.equal(loopKey(r), loopKey(r + "\n"));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("loopKey: weder Blocker noch Major → ganzer Text", () => {
|
|
|
|
|
assert.equal(loopKey("Urteil: FAIL\nirgendwas"), "Urteil: FAIL\nirgendwas");
|
|
|
|
|
assert.equal(loopKey("## Blocker\nNone\n## Major\n-"), "## Blocker\nNone\n## Major\n-");
|
|
|
|
|
});
|
2026-10-15 03:16:30 +02:00
|
|
|
|
|
|
|
|
test("parseVerdict: Urteil in Markdown-Fett (Strata-Judge 15.10.)", () => {
|
|
|
|
|
assert.equal(parseVerdict("## Urteil: **PASS WITH CONCERNS**\n### Blocker"), "PASS WITH CONCERNS");
|
|
|
|
|
assert.equal(parseVerdict("**Urteil:** FAIL"), "FAIL");
|
|
|
|
|
assert.equal(parseVerdict("## Urteil: **PASS**"), "PASS");
|
|
|
|
|
assert.equal(parseVerdict("Urteil: `PASS`"), "PASS");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("parseShipVerdict: SHIP, NO-SHIP, Markdown, unklar", () => {
|
|
|
|
|
assert.equal(parseShipVerdict("Urteil: SHIP"), "SHIP");
|
|
|
|
|
assert.equal(parseShipVerdict("## Urteil: **NO-SHIP**"), "NO-SHIP");
|
|
|
|
|
assert.equal(parseShipVerdict("**Urteil:** ship"), "SHIP");
|
|
|
|
|
assert.equal(parseShipVerdict("kein Urteil"), "");
|
|
|
|
|
});
|
2026-10-15 06:02:38 +02:00
|
|
|
|
|
|
|
|
// Judge-Prompts rufen judgeVerdictSection() auf; extractFn wertet jede Funktion einzeln aus, freie Namen landen
|
|
|
|
|
// im globalen Scope.
|
|
|
|
|
globalThis.judgeVerdictSection = extractFn("judgeVerdictSection");
|
|
|
|
|
const judgePrompt = extractFn("judgePrompt");
|
|
|
|
|
const judgeWithTestsPrompt = extractFn("judgeWithTestsPrompt");
|
|
|
|
|
const fixPrompt = extractFn("fixPrompt");
|
|
|
|
|
|
|
|
|
|
test("Judge-Prompts: Urteilsregeln und Hinweise in beiden Varianten (Strata-Judge 15.10.)", () => {
|
|
|
|
|
for (const p of [judgePrompt(""), judgeWithTestsPrompt("1 passed", "")]) {
|
|
|
|
|
assert.match(p, /Urteilsregeln \(verbindlich/);
|
|
|
|
|
assert.match(p, /- PASS: alle Anforderungen erfüllt, Tests grün, kein Blocker und kein Major/);
|
|
|
|
|
assert.match(p, /„Hinweise“\. Sie ändern das Urteil nicht/);
|
|
|
|
|
assert.match(p, /- Hinweise \(ändern das Urteil nicht\)/);
|
|
|
|
|
assert.match(p, /- Urteil: PASS \| PASS WITH CONCERNS \| FAIL/);
|
|
|
|
|
assert.doesNotMatch(p, /Produktionsrisiken/);
|
|
|
|
|
}
|
|
|
|
|
assert.match(judgeWithTestsPrompt("3 passed", ""), /3 passed/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("fixPrompt: Hinweise sind kein Auftrag", () => {
|
|
|
|
|
assert.match(fixPrompt(""), /„Hinweise“ ist kein Auftrag/);
|
|
|
|
|
});
|