fix: Loop-Erkennung meldet bei „Blocker: Keine“ keinen falschen Schleifenabbruch
Zwei Judge-Runden mit „Blocker: Keine“ und verschiedenen Major-Punkten galten als „gleicher Blocker zweimal“. loopKey() vergleicht jetzt die Blocker, bei leerem Blocker-Abschnitt die Major-Punkte, sonst den ganzen Judge-Text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a608f1dda8
commit
2acfc85482
4 changed files with 50 additions and 7 deletions
|
|
@ -430,12 +430,15 @@ Stand und der Fix-Zyklus läuft automatisch bis PASS oder max. N Runden.
|
||||||
|
|
||||||
### Loop-Erkennung
|
### Loop-Erkennung
|
||||||
|
|
||||||
Wenn zweimal hintereinander genau dieselben Blocker oder Concerns auftreten, bricht
|
Wenn zweimal hintereinander dieselbe Beanstandung auftritt, bricht `/optimize` ab:
|
||||||
`/optimize` ab:
|
|
||||||
```
|
```
|
||||||
⚠ Derselbe Blocker tritt erneut auf – Schleife abgebrochen. Bitte manuell prüfen.
|
⚠ Schleife: Gleicher Blocker zweimal – manuelle Intervention nötig
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Verglichen wird der Blocker-Abschnitt des Judges. Steht dort nur „Keine“, werden stattdessen die Major-Punkte
|
||||||
|
verglichen; fehlen beide Abschnitte, der ganze Judge-Text. Zwei Runden mit „Blocker: Keine“, aber verschiedenen
|
||||||
|
Major-Punkten sind also keine Schleife.
|
||||||
|
|
||||||
In diesem Fall: `/judge` manuell ausführen, Blocker lesen, mit `/fix` manuell eingreifen.
|
In diesem Fall: `/judge` manuell ausführen, Blocker lesen, mit `/fix` manuell eingreifen.
|
||||||
|
|
||||||
### Max. Runden ohne PASS
|
### Max. Runden ohne PASS
|
||||||
|
|
|
||||||
|
|
@ -677,6 +677,23 @@ function parseBlockers(text: string): string {
|
||||||
return m ? m[1].trim() : "";
|
return m ? m[1].trim() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Schlüssel für die Loop-Erkennung im /optimize-Loop: der Blocker-Abschnitt; steht dort nur „Keine“ o. ä.,
|
||||||
|
// der Major-Abschnitt; fehlen beide, der ganze Judge-Text. Ohne diese Unterscheidung galten zwei Runden mit
|
||||||
|
// „Blocker: Keine“ und unterschiedlichen Major-Punkten als „gleicher Blocker“ (Fehlalarm).
|
||||||
|
function loopKey(text: string): string {
|
||||||
|
const stop = "(?:\\n(?:\\*\\*(?:Blocker|Major|Minor)\\*\\*|##\\s*(?:Blocker|Major|Minor)|[-–*]\\s*(?:Blocker|Major|Minor))|$)";
|
||||||
|
const section = (name) => {
|
||||||
|
const m = text.match(new RegExp(`(?:\\*\\*${name}\\*\\*|##\\s*${name}|[-–*]\\s*${name})[:\\n]([\\s\\S]*?)${stop}`, "i"));
|
||||||
|
return m ? m[1].trim() : "";
|
||||||
|
};
|
||||||
|
const isNone = (s) => /^[\s\-–*•:]*(keine?r?|none|n\/a|nichts|—|-)?[\s.!]*$/i.test(s);
|
||||||
|
const blockers = section("Blocker");
|
||||||
|
if (blockers && !isNone(blockers)) return blockers;
|
||||||
|
const majors = section("Major");
|
||||||
|
if (majors && !isNone(majors)) return "MAJOR: " + majors;
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
// Gibt geänderte Quelldateien seit einem Git-Tag zurück.
|
// Gibt geänderte Quelldateien seit einem Git-Tag zurück.
|
||||||
// null = Tag existiert nicht (erster Lauf) → alles verarbeiten
|
// null = Tag existiert nicht (erster Lauf) → alles verarbeiten
|
||||||
// [] = nichts geändert → Phase überspringen
|
// [] = nichts geändert → Phase überspringen
|
||||||
|
|
@ -1372,10 +1389,9 @@ export default function (pi: ExtensionAPI) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop-Erkennung: gleiche Beanstandung zweimal → manuell eingreifen.
|
// Loop-Erkennung: gleiche Beanstandung zweimal → manuell eingreifen.
|
||||||
// Fallback auf den gesamten Judge-Text, wenn kein Blocker-Abschnitt vorhanden ist
|
// Schlüssel: Blocker, bei „Blocker: Keine“ die Major-Punkte, sonst der ganze Judge-Text (loopKey).
|
||||||
// (z.B. reine Minor-Concerns) — sonst würde die Erkennung dort nie greifen.
|
|
||||||
// Normalisierung verhindert False-Negatives durch minimale Formulierungsunterschiede.
|
// Normalisierung verhindert False-Negatives durch minimale Formulierungsunterschiede.
|
||||||
const currentBlockers = parseBlockers(judgeText) || judgeText;
|
const currentBlockers = loopKey(judgeText);
|
||||||
if (currentBlockers && normalizeForComparison(currentBlockers) === normalizeForComparison(lastBlockers)) {
|
if (currentBlockers && normalizeForComparison(currentBlockers) === normalizeForComparison(lastBlockers)) {
|
||||||
ctx.ui.setStatus("optimize", `${prog} ⚠ Gleicher Blocker in Runde ${round} – manuelle Intervention nötig`);
|
ctx.ui.setStatus("optimize", `${prog} ⚠ Gleicher Blocker in Runde ${round} – manuelle Intervention nötig`);
|
||||||
finalNotify(ctx, "⚠ Schleife", "Gleicher Blocker zweimal – manuelle Intervention nötig");
|
finalNotify(ctx, "⚠ Schleife", "Gleicher Blocker zweimal – manuelle Intervention nötig");
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ test("Fix-Auslösung: PASS WITH CONCERNS gilt nur mit --approve-concerns als bes
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Loop-Erkennung greift auch ohne Blocker-Abschnitt (Fallback auf Gesamttext)", () => {
|
test("Loop-Erkennung greift auch ohne Blocker-Abschnitt (Fallback auf Gesamttext)", () => {
|
||||||
assert.match(extSource, /parseBlockers\(judgeText\)\s*\|\|\s*judgeText/);
|
assert.match(extSource, /const currentBlockers = loopKey\(judgeText\)/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("currentRole wird nach jedem Kommando auf 'idle' zurückgesetzt (kein Persona-Leak in freien Chat)", () => {
|
test("currentRole wird nach jedem Kommando auf 'idle' zurückgesetzt (kein Persona-Leak in freien Chat)", () => {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ const parseVerdict = extractFn("parseVerdict");
|
||||||
const parseBlockers = extractFn("parseBlockers");
|
const parseBlockers = extractFn("parseBlockers");
|
||||||
const normalizeForComparison = extractFn("normalizeForComparison");
|
const normalizeForComparison = extractFn("normalizeForComparison");
|
||||||
const licenceMd = extractFn("licenceMd");
|
const licenceMd = extractFn("licenceMd");
|
||||||
|
const loopKey = extractFn("loopKey");
|
||||||
|
|
||||||
test("parseVerdict: klares PASS", () => {
|
test("parseVerdict: klares PASS", () => {
|
||||||
assert.equal(parseVerdict("Review fertig.\nUrteil: PASS\nKeine Blocker."), "PASS");
|
assert.equal(parseVerdict("Review fertig.\nUrteil: PASS\nKeine Blocker."), "PASS");
|
||||||
|
|
@ -78,3 +79,26 @@ test("licenceMd: proprietärer Default mit eingesetztem Jahr und Inhaber", () =>
|
||||||
test("licenceMd: Platzhalter-Inhaber wird unverändert eingesetzt", () => {
|
test("licenceMd: Platzhalter-Inhaber wird unverändert eingesetzt", () => {
|
||||||
assert.match(licenceMd("2030", "[Rechteinhaber eintragen]"), /Copyright \(c\) 2030 \[Rechteinhaber eintragen\]/);
|
assert.match(licenceMd("2030", "[Rechteinhaber eintragen]"), /Copyright \(c\) 2030 \[Rechteinhaber eintragen\]/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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-");
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue