feat: --test-cmd für /optimize — Tests laufen in Extension, nicht im Judge
Neues Flag: /optimize <auftrag> --test-cmd "pytest -x" Die Extension führt den Test-Befehl vor jedem Judge-Call selbst aus (pi.exec). Judge bekommt den Output fertig übergeben und muss keine Tests mehr starten. Das entkoppelt Test-Laufzeit vom LLM-Call und spart Judge-Inferenz-Zeit. - judgeWithTestsPrompt(): wie judgePrompt, aber mit Test-Output im Prompt, explizites Verbot weitere Tests zu starten - runTests(): führt Shell-Befehl aus, kürzt Output auf 6000 Zeichen - Ohne --test-cmd: bisheriges Verhalten unverändert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0fb0ec9e5d
commit
6afcd6a271
1 changed files with 67 additions and 5 deletions
|
|
@ -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<string> {
|
||||
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 <auftrag> | /judge | /fix | /shipit",
|
||||
"Auto-Loop: /optimize <auftrag> [--rounds N] [--with-doku] [--continue]",
|
||||
"Auto-Loop: /optimize <auftrag> [--rounds N] [--with-doku] [--continue] [--test-cmd \"cmd\"]",
|
||||
"Planung: /plan <auftrag> → /coder | /optimize --continue | /discard",
|
||||
"Patch: /patch <änderung> → /quick_check [was]",
|
||||
"Doku: /update_doku | Neues Projekt: /new_project <pfad>",
|
||||
|
|
@ -607,20 +655,25 @@ export default function (pi: ExtensionAPI) {
|
|||
// ── Automatische Optimierungsschleife ────────────────────────────────────
|
||||
|
||||
pi.registerCommand("optimize", {
|
||||
description: "Coder→Judge→Fix-Schleife bis PASS + optional Doku. /optimize <auftrag> [--rounds N] [--with-doku] [--continue]",
|
||||
description: "Coder→Judge→Fix-Schleife bis PASS + optional Doku. /optimize <auftrag> [--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 <auftrag> [--rounds N] [--with-doku] [--continue]", "error");
|
||||
ctx.ui.notify("Benutzung: /optimize <auftrag> [--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");
|
||||
|
||||
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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue