diff --git a/pi-coder-judge-extension.ts b/pi-coder-judge-extension.ts index 1211c1b..98a00e7 100644 --- a/pi-coder-judge-extension.ts +++ b/pi-coder-judge-extension.ts @@ -57,6 +57,38 @@ function judgePrompt(extra: string): string { ].join("\n") + suffix; } +// Wie judgePrompt, aber Tests werden NICHT vom Judge ausgeführt — +// die Extension hat sie bereits extern gestartet und übergibt den Output. +function judgeWithTestsPrompt(testOutput: string, extra: string): string { + const suffix = extra?.trim() ? "\n\nZusätzlicher Fokus des Users:\n" + extra.trim() : ""; + return [ + "Du bist ein pingeliger, skeptischer Senior-Reviewer und QA-Ingenieur.", + "Deine Aufgabe ist NICHT, nett zu sein, sondern Fehler, Risiken, Randfälle und Produktionsprobleme zu finden.", + "", + "Die Test-Suite wurde bereits extern ausgeführt. Das Ergebnis steht unten.", + "Führe KEINE weiteren Tests aus — weder dieselben noch andere.", + "", + "Pflichten:", + "0. Lies TASK.md und prüfe, ob alle dort beschriebenen Anforderungen vollständig umgesetzt sind.", + "1. Sieh dir den letzten Commit an: 'git log -1 --stat' und 'git show HEAD'.", + "2. Analysiere das folgende Test-Ergebnis und leite daraus Blocker/Major/Minor ab:", + "```", + testOutput, + "```", + "3. Versuche weitere Fehler im Code aktiv zu finden (Randfälle, Sicherheit, Robustheit).", + "4. Wenn du etwas behauptest, nenne die Datei, die Zeile oder den Reproduktionshinweis.", + "", + "Ausgabeformat:", + "- Urteil: PASS | PASS WITH CONCERNS | FAIL", + "- Blocker", + "- Major", + "- Minor", + "- Fehlende Tests", + "- Produktionsrisiken", + "- Konkrete Fix-Aufträge an den Coder", + ].join("\n") + suffix; +} + function fixPrompt(extra: string): string { const suffix = extra?.trim() ? "\n\nZusätzlicher User-Hinweis:\n" + extra.trim() : ""; return [ @@ -369,6 +401,22 @@ async function sendAndWait( await ctx.waitForIdle(); } +// Führt einen Shell-Befehl aus und gibt stdout+stderr zurück (max. 6000 Zeichen). +// Wird von /optimize --test-cmd genutzt, damit Judge keine Tests selbst starten muss. +async function runTests( + pi: ExtensionAPI, + ctx: ExtensionCommandContext, + cmd: string +): Promise { + const result = await pi.exec("bash", ["-c", cmd], { cwd: ctx.cwd }); + const output = (result.stdout + (result.stderr ? "\n" + result.stderr : "")).trim(); + const MAX = 6000; + if (output.length > MAX) { + return output.slice(0, MAX) + `\n\n[… Ausgabe gekürzt, ${output.length} Zeichen gesamt]`; + } + return output || "(kein Output)"; +} + // Liest den Text der letzten Assistenten-Antwort aus dem Session-Branch. function getLastAssistantText(ctx: ExtensionCommandContext): string { const entries = ctx.sessionManager.getBranch(); @@ -520,7 +568,7 @@ export default function (pi: ExtensionAPI) { pi.on("session_start", async function (_event, ctx) { ctx.ui.setWidget("coder-judge", [ "Workflow: /coder | /judge | /fix | /shipit", - "Auto-Loop: /optimize [--rounds N] [--with-doku] [--continue]", + "Auto-Loop: /optimize [--rounds N] [--with-doku] [--continue] [--test-cmd \"cmd\"]", "Planung: /plan → /coder | /optimize --continue | /discard", "Patch: /patch <änderung> → /quick_check [was]", "Doku: /update_doku | Neues Projekt: /new_project ", @@ -607,20 +655,25 @@ export default function (pi: ExtensionAPI) { // ── Automatische Optimierungsschleife ──────────────────────────────────── pi.registerCommand("optimize", { - description: "Coder→Judge→Fix-Schleife bis PASS + optional Doku. /optimize [--rounds N] [--with-doku] [--continue]", + description: "Coder→Judge→Fix-Schleife bis PASS + optional Doku. /optimize [--rounds N] [--with-doku] [--continue] [--test-cmd \"befehl\"]", handler: async function (args: string, ctx: ExtensionCommandContext) { const roundsMatch = (args || "").match(/--rounds\s+(\d+)/); const maxRounds = roundsMatch ? Math.max(1, parseInt(roundsMatch[1], 10)) : 3; const withDoku = /--with-doku/.test(args || ""); const continueMode = /--continue/.test(args || ""); + // --test-cmd "befehl" oder --test-cmd befehl (ohne Leerzeichen im Befehl) + const testCmdMatch = (args || "").match(/--test-cmd\s+"([^"]+)"|--test-cmd\s+(\S+)/); + const testCmd: string | null = testCmdMatch ? (testCmdMatch[1] ?? testCmdMatch[2]) : null; const task = (args || "") .replace(/--rounds\s+\d+/, "") .replace(/--with-doku/, "") .replace(/--continue/, "") + .replace(/--test-cmd\s+"[^"]*"/, "") + .replace(/--test-cmd\s+\S+/, "") .trim(); if (!continueMode && !task) { - ctx.ui.notify("Benutzung: /optimize [--rounds N] [--with-doku] [--continue]", "error"); + ctx.ui.notify("Benutzung: /optimize [--rounds N] [--with-doku] [--continue] [--test-cmd \"befehl\"]", "error"); return; } @@ -651,9 +704,18 @@ export default function (pi: ExtensionAPI) { // Schleife: Judge → (PASS? fertig : Fix → nächste Runde) for (let round = 1; round <= maxRounds; round++) { const prog = "●".repeat(round - 1) + "◉" + "○".repeat(maxRounds - round); - ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Judge — TASK.md + letzter Commit + Tests…`); await switchModel(pi, ctx, "llama-cpp-judge", "qwen3.5-judge"); - await sendAndWait(pi, ctx, judgePrompt("")); + + if (testCmd) { + // Tests laufen in der Extension — Judge bekommt den Output fertig geliefert + ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Tests laufen (${testCmd})…`); + const testOutput = await runTests(pi, ctx, testCmd); + ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Judge analysiert Test-Ergebnis…`); + await sendAndWait(pi, ctx, judgeWithTestsPrompt(testOutput, "")); + } else { + ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Judge — TASK.md + letzter Commit + Tests…`); + await sendAndWait(pi, ctx, judgePrompt("")); + } if (cancelRequested) { cancelRequested = false; finalNotify(ctx, "⛔ Abgebrochen", `Nach Judge Runde ${round}`); return; } const judgeText = getLastAssistantText(ctx);