From 11ac46e56526af7ee5147c307c37d8b2bfbd27b3 Mon Sep 17 00:00:00 2001 From: dschlueter Date: Fri, 29 May 2026 17:51:54 +0200 Subject: [PATCH] feat: --interactive-Checkpoint, direktes SHIP bei PASS, default rounds 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /optimize --interactive pausiert nach erstem PASS; /continue setzt fort, /continue "Zusatz" hängt weiteren Auftrag an und wiederholt den Judge-Loop - Klares PASS → direkt SHIP ohne zweiten ShipIt-Inference-Call (1-3 min gespart) - PASS WITH CONCERNS → ShipIt-Runde weiterhin als finale Abwägung - Default --rounds 3→2 (~30 % schnellere Durchläufe für typische Tasks) - /continue-Command erkennt interactivePauseActive und leitet Signal weiter - Alle drei Interactive-Zustandsvariablen werden im finally-Block resettet - Dokumentation (README, BEDIENUNGSANLEITUNG, CLAUDE.md) vollständig aktualisiert Co-Authored-By: Claude Sonnet 4.6 --- BEDIENUNGSANLEITUNG.md | 94 +++++++++++++-- CLAUDE.md | 10 +- README.md | 7 +- pi-coder-judge-extension.ts | 222 +++++++++++++++++++++++++----------- 4 files changed, 251 insertions(+), 82 deletions(-) diff --git a/BEDIENUNGSANLEITUNG.md b/BEDIENUNGSANLEITUNG.md index 08e202a..20c52ab 100644 --- a/BEDIENUNGSANLEITUNG.md +++ b/BEDIENUNGSANLEITUNG.md @@ -13,7 +13,7 @@ einfache Slash-Kommandos in der pi-Agent-Oberfläche. 3. [Server starten und stoppen](#3-server-starten-und-stoppen) 4. [Neues Projekt anlegen](#4-neues-projekt-anlegen) 5. [Manueller Workflow: /coder → /judge → /fix → /shipit](#5-manueller-workflow) -6. [Automatischer Workflow: /optimize](#6-automatischer-workflow-optimize) +6. [Automatischer Workflow: /optimize](#6-automatischer-workflow-optimize) (inkl. [Interactive-Modus](#interactive-modus)) 7. [Kleine Änderungen: /patch und /quick_check](#7-kleine-änderungen-patch-und-quick_check) 8. [Dokumentation generieren: /update_doku](#8-dokumentation-generieren-update_doku) 9. [Versionsverwaltung: /version](#9-versionsverwaltung-version) @@ -278,14 +278,16 @@ Empfohlene Sofortmaßnahmen: keine ### Syntax ``` -/optimize [--rounds N] [--with-doku] [--continue] +/optimize [--rounds N] [--with-doku] [--continue] [--interactive] ``` -- `--rounds N` — maximale Anzahl Runden (Standard: 3) +- `--rounds N` — maximale Anzahl Runden (Standard: 2) - `--with-doku` — nach SHIP automatisch `/update_doku` ausführen - `--continue` — überspringt die Implementierungsphase und startet direkt mit dem Judge→Fix-Zyklus ab dem aktuellen Code-Stand. Nützlich wenn man bereits manuell `/coder`, `/judge` und `/fix` durchgeführt hat und den Rest automatisieren möchte. +- `--interactive` — pausiert nach erstem PASS für einen menschlichen Checkpoint. + Details: siehe [Interactive-Modus](#interactive-modus) weiter unten. ### Beispiel: einfacher Auftrag @@ -296,19 +298,21 @@ Empfohlene Sofortmaßnahmen: keine Was im Hintergrund passiert: ``` Phase 1: Coder implementiert... -Phase 2: Runde 1/3: Judge prüft... +Phase 2: Runde 1/2: Judge prüft... → Urteil: FAIL (2 Blocker) -Phase 3: Runde 1/3: Coder fixt... -Phase 4: Runde 2/3: Judge prüft... +Phase 3: Runde 1/2: Coder fixt... +Phase 4: Runde 2/2: Judge prüft... → Urteil: PASS WITH CONCERNS ✓ PASS WITH CONCERNS nach Runde 2 -Finale ShipIt-Prüfung... +Finale ShipIt-Prüfung... (nur bei PASS WITH CONCERNS) → SHIP [Dialog: Version → v0.1.0 (empfohlen)] ``` +Bei klarem `PASS` entfällt die ShipIt-Runde — es wird direkt SHIP ausgelöst. + Während des Ablaufs zeigt die Statuszeile immer die aktuelle Aktivität: -`Coder implementiert…` → `Editiere src/main.rs…` → `Git-Commit…` → `Judge reviewt (Runde 1/3)…` +`Coder implementiert…` → `Editiere src/main.rs…` → `Git-Commit…` → `Judge reviewt (Runde 1/2)…` ### Beispiel: mehr Runden @@ -359,10 +363,57 @@ In diesem Fall: `/judge` manuell ausführen, Blocker lesen, mit `/fix` manuell e ### Max. Runden ohne PASS ``` -⚠ 3 Runden durchlaufen ohne PASS. Bitte manuell prüfen. +⚠ 2 Runden durchlaufen ohne PASS. Bitte manuell prüfen. ``` Dann: `/judge` und `/fix` manuell für gezielte Eingriffe. +Mit `--rounds N` kann die Grenze hochgesetzt werden, z.B. `--rounds 5` für komplexe Aufgaben. + +### Interactive-Modus + +Mit `--interactive` pausiert `/optimize` nach dem ersten PASS und wartet auf menschliches +Feedback — bevor das abschließende SHIP ausgelöst wird. + +``` +/optimize Implementiere Feature X --interactive +``` + +Typischer Ablauf: +``` +Phase 1: Coder implementiert... +Phase 2: Judge prüft... + → Urteil: PASS +⏸ PASS erreicht. Weitere Features? /continue "Zusatzauftrag" — oder /continue zum Shippern. +``` + +Jetzt hast du drei Optionen: + +**Option A: Direkt shippern** +``` +/continue +``` +→ ShipIt wird gestartet, Version-Dialog erscheint. + +**Option B: Zusatzauftrag hinzufügen** +``` +/continue "Füge außerdem eine --verbose Option hinzu" +``` +→ Coder implementiert den Zusatz, dann läuft der Judge-Loop erneut an. +→ Nach erneutem PASS erscheint der Checkpoint wieder — du kannst beliebig viele + Iterationen anhängen, bevor du mit `/continue` zum SHIP gehst. + +**Option C: Abbrechen** +``` +/cancel +``` +→ Loop wird abgebrochen, kein SHIP. + +**Timeout:** Wenn du 30 Minuten lang nichts eingibst, bricht `/optimize` automatisch ab. + +**Wann ist `--interactive` sinnvoll?** +- Wenn der Auftrag aus mehreren voneinander abhängigen Features besteht +- Wenn du nach jeder fertigen Stufe entscheiden möchtest, ob du weitermachst +- Wenn du sicherstellen willst, dass nichts unbeabsichtigt committed wird --- @@ -678,6 +729,28 @@ Die Checkboxen werden automatisch abgehakt: /optimize Schreibe einen vollständigen Markdown-Parser mit AST in Python --rounds 5 ``` +### Schrittweise Features hinzufügen mit --interactive + +```bash +# Erst Grundgerüst implementieren und PASS abwarten: +/optimize Schreibe ein CLI-Tool 'filewatch' das Dateiänderungen überwacht --interactive + +# Nach PASS erscheint: ⏸ PASS – warte auf /continue… + +# Option A: Genug, direkt shippern: +/continue + +# Option B: Weiteres Feature anhängen: +/continue "Füge außerdem einen --filter GLOB-Parameter hinzu" +# → Coder implementiert, Judge prüft erneut, PASS → Checkpoint wieder aktiv + +# Nochmal erweitern: +/continue "Füge --output-log DATEI hinzu um Änderungen zu protokollieren" + +# Fertig → shippern: +/continue +``` + --- ## 12. Fehlermeldungen und Lösungen @@ -750,7 +823,7 @@ bereit, das GNU `patch -p1` mit Fuzzy-Matching nutzt. Lies die Datei neu ein und wende die Änderungen als unified diff mit apply_patch an. ``` -### "Drei Runden ohne PASS" / Loop-Erkennung schlägt an +### "N Runden ohne PASS" / Loop-Erkennung schlägt an ``` ⚠ Derselbe Blocker tritt erneut auf – Schleife abgebrochen. @@ -767,6 +840,7 @@ Dann den Blocker analysieren und entweder: - `/fix Ignoriere Blocker X, das ist nicht Teil dieser Aufgabe` - Den Code selbst anpassen und dann `/fix` aufrufen - Die Aufgabe in TASK.md präzisieren +- Bei komplexen Aufgaben mit mehr Runden wiederholen: `/optimize --continue --rounds 5` ### Server läuft, aber pi wechselt nicht das Modell diff --git a/CLAUDE.md b/CLAUDE.md index fa0cd13..d685c82 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,9 +41,12 @@ Beide nutzen dasselbe GGUF (`Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ4_XS.g **Zentraler Ablauf in `/optimize`:** 1. `writeTaskMd()` → TASK.md anlegen 2. Coder: `coderKickoff()` → implementiert + committet -3. Loop (max. N Runden): Judge → `parseVerdict()` → PASS? → ShipIt. FAIL? → `parseBlockers()` → Fix → nächste Runde -4. Loop-Erkennung: gleicher Blocker zweimal → Abbruch (`finalNotify()`) -5. Optional: `runUpdateDoku()` bei `--with-doku` +3. Äußere `while(keepGoing)`-Schleife (für `--interactive`-Zusatzaufträge) +4. Loop (max. N Runden, Standard 2): Judge → `parseVerdict()` → PASS? → break. FAIL? → `parseBlockers()` → Fix → nächste Runde +5. Bei PASS + `--interactive`: Polling auf `interactiveContinueRequested`. Kein Zusatzauftrag → ShipIt. Zusatzauftrag → `coderKickoff()` → `keepGoing = true` +6. SHIP-Schritt: klares `PASS` → direkt SHIP (kein ShipIt-Call). `PASS WITH CONCERNS` → `shipitPrompt()` → SHIP/NO-SHIP +7. Loop-Erkennung: gleicher Blocker zweimal → Abbruch (`finalNotify()`) +8. Optional: `runUpdateDoku()` bei `--with-doku` **`tool_call`-Hook (edit-Reordering):** Sortiert Multi-Edit-Aufrufe auf dieselbe Datei von hinten nach vorne. Verhindert den Fehler „edits[n] doesn't match" wenn mehrere Stellen einer Datei auf einmal geändert werden. @@ -60,6 +63,7 @@ Kritische Felder bei llama-cpp-Providern: `contextWindow` muss mit dem `-c`-Para ## Wichtige Invarianten - **`cancelRequested`** ist eine modulare Variable — sie wird von `/cancel` gesetzt und nach jedem Loop-Schritt in `/optimize` geprüft und zurückgesetzt. +- **`interactivePauseActive` / `interactiveContinueRequested` / `interactivePauseTask`** — drei modulare Variablen für den `--interactive`-Modus. `interactivePauseActive` wird vom `/continue`-Command geprüft, um zwischen Interactive-Pause-Signal und normalem Fortsetzen zu unterscheiden. Alle drei werden im `finally`-Block zurückgesetzt. - **`sendAndWait()`** wartet erst auf `idle`, dann `deliverAs: "followUp"` — verhindert „Agent is already processing". - **`tickTaskMdStatus()`** nutzt Python3 für den String-Ersatz in TASK.md (kein Shell-Escaping-Problem). - Beide Start-Skripte warten bis zu 90×2 s auf HTTP-Erreichbarkeit und führen dann einen Smoke-Test-Completion durch. diff --git a/README.md b/README.md index 7e88eed..5f5aed4 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,10 @@ Nutzer gibt Auftrag │ PASS? ▼ /shipit → qwen3.5-judge (:8002) → Finale Freigabe: SHIP / NO-SHIP + (nur bei "PASS WITH CONCERNS" — klares PASS → direkt SHIP) /optimize = Coder→Judge→Fix-Schleife automatisch (bis PASS oder max. N Runden) + --interactive: pausiert nach PASS für menschlichen Checkpoint + optionale Zusatzaufträge ``` Beide Modelle laufen als **separate llama.cpp-Docker-Container** und sprechen eine @@ -283,7 +285,7 @@ wenn pi agent Folgeanfragen schnell hintereinander schickt. | `/judge [fokus]` | Judge | Code-Review gegen TASK.md + letzten Commit | | `/fix [hinweis]` | Coder | Judge-Kritik beheben, committen | | `/shipit` | Judge | Finale Freigabeprüfung | -| `/optimize [--rounds N] [--with-doku] [--continue]` | beide | Vollautomatische Schleife bis PASS | +| `/optimize [--rounds N] [--with-doku] [--continue] [--interactive]` | beide | Vollautomatische Schleife bis PASS (Standard: 2 Runden) | | `/optimize ... [--test-cmd "cmd"] [--test-timeout N]` | beide | Externe Test-Suite im Loop ausführen | | `/patch <änderung>` | Coder | Gezielte Minimaländerung ohne Review | | `/quick_check [was]` | Judge | Schnelle Prüfung der letzten Änderung | @@ -307,8 +309,9 @@ Während der Ausführung zeigt pi_coder in der Statuszeile, was gerade passiert: | Coder implementiert | `Coder implementiert…` | | edit-Tool aktiv | `Editiere src/main.py…` | | git commit | `Git-Commit…` | -| Judge reviewt (Runde 2/3) | `Judge reviewt (Runde 2/3)…` | +| Judge reviewt (Runde 2/2) | `Judge reviewt (Runde 2/2)…` | | Tests laufen | `Tests laufen…` | | Fix-Phase | `Coder fixt Blocker…` | +| Interactive-Pause (--interactive) | `⏸ PASS – warte auf /continue…` | So ist jederzeit erkennbar, in welcher Phase sich der automatische Loop befindet. diff --git a/pi-coder-judge-extension.ts b/pi-coder-judge-extension.ts index bc0a698..f40ce23 100644 --- a/pi-coder-judge-extension.ts +++ b/pi-coder-judge-extension.ts @@ -827,6 +827,9 @@ function finalNotify( // ── Extension ──────────────────────────────────────────────────────────────── let cancelRequested = false; +let interactivePauseActive = false; +let interactiveContinueRequested = false; +let interactivePauseTask = ""; let currentActivity = ""; // Working-Message für den aktuellen Command-Kontext // Erzeugt eine knappe Statuszeile aus Tool-Name und Argumenten. @@ -1000,12 +1003,13 @@ export default function (pi: ExtensionAPI) { // ── Automatische Optimierungsschleife ──────────────────────────────────── pi.registerCommand("optimize", { - description: "Coder→Judge→Fix-Schleife bis PASS. Tests werden automatisch erkannt und parallel ausgeführt. /optimize [--rounds N] [--with-doku] [--continue] [--test-cmd \"override\"] [--test-timeout N]", + description: "Coder→Judge→Fix-Schleife bis PASS (default 2 Runden). Klares PASS → direkt SHIP; PASS WITH CONCERNS → ShipIt-Runde. --interactive pausiert nach PASS für Zusatzaufträge via /continue. /optimize [--rounds N] [--with-doku] [--continue] [--interactive] [--test-cmd \"override\"] [--test-timeout N]", 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 maxRounds = roundsMatch ? Math.max(1, parseInt(roundsMatch[1], 10)) : 2; const withDoku = /--with-doku/.test(args || ""); const continueMode = /--continue/.test(args || ""); + const interactive = /--interactive/.test(args || ""); const testCmdMatch = (args || "").match(/--test-cmd\s+"([^"]+)"|--test-cmd\s+'([^']+)'|--test-cmd\s+(\S+)/); const testCmd: string | null = testCmdMatch ? (testCmdMatch[1] ?? testCmdMatch[2] ?? testCmdMatch[3]) : null; const testTimeoutMatch = (args || "").match(/--test-timeout\s+(\d+)/); @@ -1015,6 +1019,7 @@ export default function (pi: ExtensionAPI) { .replace(/--test-timeout\s+\d+/, "") .replace(/--with-doku/, "") .replace(/--continue/, "") + .replace(/--interactive/, "") .replace(/--test-cmd\s+"[^"]*"/, "") .replace(/--test-cmd\s+\S+/, "") .trim(); @@ -1085,76 +1090,144 @@ export default function (pi: ExtensionAPI) { let lastBlockers = ""; let verdict = ""; + let keepGoing = true; - // Schleife: Judge → (PASS? fertig : Fix → nächste Runde) - for (let round = 1; round <= maxRounds; round++) { - const prog = "●".repeat(round - 1) + "◉" + "○".repeat(maxRounds - round); - if (!await switchModel(pi, ctx, "llama-cpp-judge", "qwen3.5-judge")) { - finalNotify(ctx, "⛔ Modell-Fehler", "Judge-Modell (llama-cpp-judge) nicht verfügbar"); - return; - } + // Äußere Schleife für --interactive: nach PASS pausieren, Zusatzaufträge ermöglichen. + while (keepGoing) { + keepGoing = false; + verdict = ""; + lastBlockers = ""; - if (autoTestCmds.length > 0) { - const label = autoTestCmds.length === 1 - ? autoTestCmds[0].split(" ")[0] - : `${autoTestCmds.length} Suiten parallel`; - ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Tests laufen (${label}, max. ${testTimeout}s)…`); - const testOutput = await runTestsParallel(pi, ctx, autoTestCmds, testTimeout); - ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Judge analysiert Test-Ergebnis…`); - currentActivity = `Judge reviewt (Runde ${round}/${maxRounds})…`; - await sendAndWait(pi, ctx, judgeWithTestsPrompt(testOutput, "")); - } else { - ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Judge — TASK.md + letzter Commit + Tests…`); - currentActivity = `Judge reviewt (Runde ${round}/${maxRounds})…`; - await sendAndWait(pi, ctx, judgePrompt("")); - } - if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", `Nach Judge Runde ${round}`); return; } - - const judgeText = getLastAssistantText(ctx); - verdict = parseVerdict(judgeText); - - if (verdict === "PASS" || verdict === "PASS WITH CONCERNS") { - await tickTaskMdStatus(pi, ctx, "Review bestanden (PASS)"); - ctx.ui.setStatus("optimize", `${"●".repeat(round)} ✓ ${verdict} nach Runde ${round}/${maxRounds} — ShipIt…`); - break; - } - - // Loop-Erkennung: gleicher Blocker zweimal → manuell eingreifen - const currentBlockers = parseBlockers(judgeText); - if (currentBlockers && currentBlockers === lastBlockers) { - ctx.ui.setStatus("optimize", `${prog} ⚠ Gleicher Blocker in Runde ${round} – manuelle Intervention nötig`); - finalNotify(ctx, "⚠ Schleife", "Gleicher Blocker zweimal – manuelle Intervention nötig"); - return; - } - lastBlockers = currentBlockers; - - if (round === maxRounds) { - ctx.ui.setStatus("optimize", `${"●".repeat(maxRounds)} ⚠ Max. ${maxRounds} Runden ohne PASS`); - if (verdict === "UNREADABLE") { - finalNotify(ctx, "⚠ Urteil unklar", `${maxRounds} Runden – Judge-Urteil nicht erkennbar, Antwort im Chat prüfen`); - } else { - finalNotify(ctx, "⚠ Kein PASS", `${maxRounds} Runden ohne PASS – bitte /judge und /fix manuell`); + // Schleife: Judge → (PASS? fertig : Fix → nächste Runde) + for (let round = 1; round <= maxRounds; round++) { + const prog = "●".repeat(round - 1) + "◉" + "○".repeat(maxRounds - round); + if (!await switchModel(pi, ctx, "llama-cpp-judge", "qwen3.5-judge")) { + finalNotify(ctx, "⛔ Modell-Fehler", "Judge-Modell (llama-cpp-judge) nicht verfügbar"); + return; } - return; + + if (autoTestCmds.length > 0) { + const label = autoTestCmds.length === 1 + ? autoTestCmds[0].split(" ")[0] + : `${autoTestCmds.length} Suiten parallel`; + ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Tests laufen (${label}, max. ${testTimeout}s)…`); + const testOutput = await runTestsParallel(pi, ctx, autoTestCmds, testTimeout); + ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Judge analysiert Test-Ergebnis…`); + currentActivity = `Judge reviewt (Runde ${round}/${maxRounds})…`; + await sendAndWait(pi, ctx, judgeWithTestsPrompt(testOutput, "")); + } else { + ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Judge — TASK.md + letzter Commit + Tests…`); + currentActivity = `Judge reviewt (Runde ${round}/${maxRounds})…`; + await sendAndWait(pi, ctx, judgePrompt("")); + } + if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", `Nach Judge Runde ${round}`); return; } + + const judgeText = getLastAssistantText(ctx); + verdict = parseVerdict(judgeText); + + if (verdict === "PASS" || verdict === "PASS WITH CONCERNS") { + await tickTaskMdStatus(pi, ctx, "Review bestanden (PASS)"); + const nextStep = interactive ? "warte auf /continue…" : "ShipIt…"; + ctx.ui.setStatus("optimize", `${"●".repeat(round)} ✓ ${verdict} nach Runde ${round}/${maxRounds} — ${nextStep}`); + break; + } + + // Loop-Erkennung: gleicher Blocker zweimal → manuell eingreifen + const currentBlockers = parseBlockers(judgeText); + if (currentBlockers && currentBlockers === lastBlockers) { + ctx.ui.setStatus("optimize", `${prog} ⚠ Gleicher Blocker in Runde ${round} – manuelle Intervention nötig`); + finalNotify(ctx, "⚠ Schleife", "Gleicher Blocker zweimal – manuelle Intervention nötig"); + return; + } + lastBlockers = currentBlockers; + + if (round === maxRounds) { + ctx.ui.setStatus("optimize", `${"●".repeat(maxRounds)} ⚠ Max. ${maxRounds} Runden ohne PASS`); + if (verdict === "UNREADABLE") { + finalNotify(ctx, "⚠ Urteil unklar", `${maxRounds} Runden – Judge-Urteil nicht erkennbar, Antwort im Chat prüfen`); + } else { + finalNotify(ctx, "⚠ Kein PASS", `${maxRounds} Runden ohne PASS – bitte /judge und /fix manuell`); + } + return; + } + + // Fix-Phase: Blocker-Preview aus Judge-Bericht anzeigen + const blockerHint = currentBlockers + ? (currentBlockers.length > 50 ? currentBlockers.slice(0, 47) + "…" : currentBlockers) + : "Kritikpunkte aus Judge-Bericht"; + ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Coder fixt — ${blockerHint}`); + if (!await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder")) { + finalNotify(ctx, "⛔ Modell-Fehler", "Coder-Modell (llama-cpp-coder) nicht verfügbar"); + return; + } + currentActivity = "Coder fixt Blocker…"; + await sendAndWait(pi, ctx, fixPrompt("")); + if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", `Nach Fix Runde ${round}`); return; } } - // Fix-Phase: Blocker-Preview aus Judge-Bericht anzeigen - const blockerHint = currentBlockers - ? (currentBlockers.length > 50 ? currentBlockers.slice(0, 47) + "…" : currentBlockers) - : "Kritikpunkte aus Judge-Bericht"; - ctx.ui.setStatus("optimize", `${prog} Runde ${round}/${maxRounds}: Coder fixt — ${blockerHint}`); - if (!await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder")) { - finalNotify(ctx, "⛔ Modell-Fehler", "Coder-Modell (llama-cpp-coder) nicht verfügbar"); - return; + // Interactive-Modus: nach PASS pausieren und auf /continue warten (max 30 min). + // /continue ohne Args → direkt ShipIt. /continue "Zusatz" → Coder implementiert, Loop nochmal. + if (interactive && (verdict === "PASS" || verdict === "PASS WITH CONCERNS")) { + interactivePauseActive = true; + interactiveContinueRequested = false; + interactivePauseTask = ""; + + ctx.ui.setStatus("optimize", `⏸ ${verdict} – warte auf /continue…`); + ctx.ui.notify( + `✅ ${verdict} erreicht. Weitere Features? /continue "Zusatzauftrag" — oder /continue zum Shippern.`, + "info" + ); + + const waitStart = Date.now(); + while (!interactiveContinueRequested && !cancelRequested) { + if (Date.now() - waitStart > 30 * 60 * 1000) { + interactivePauseActive = false; + finalNotify(ctx, "⚠ Timeout", "30 min ohne /continue — abgebrochen"); + return; + } + await new Promise(r => setTimeout(r, 500)); + } + interactivePauseActive = false; + + if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", "Im Interactive-Modus"); return; } + + if (interactivePauseTask) { + // Zusatzauftrag: Coder implementiert, dann Judge-Loop erneut + const addPreview = interactivePauseTask.length > 50 + ? interactivePauseTask.slice(0, 47) + "…" + : interactivePauseTask; + ctx.ui.setStatus("optimize", `◉ Coder implementiert Zusatzauftrag: ${addPreview}`); + await writeTaskMd(pi, ctx, interactivePauseTask); + if (!await switchModel(pi, ctx, "llama-cpp-coder", "qwen3.5-coder")) { + finalNotify(ctx, "⛔ Modell-Fehler", "Coder-Modell nicht verfügbar"); + return; + } + currentActivity = "Coder implementiert Zusatzauftrag…"; + await sendAndWait(pi, ctx, coderKickoff(interactivePauseTask)); + await tickTaskMdStatus(pi, ctx, "Implementierung"); + if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", "Nach Zusatz-Implementierung"); return; } + keepGoing = true; + continue; + } + // /continue ohne Args → direkt zu ShipIt (verdict bleibt PASS) } - currentActivity = "Coder fixt Blocker…"; - await sendAndWait(pi, ctx, fixPrompt("")); - if (cancelRequested) { finalNotify(ctx, "⛔ Abgebrochen", `Nach Fix Runde ${round}`); return; } } - // Finale ShipIt-Prüfung nur bei PASS - if (verdict === "PASS" || verdict === "PASS WITH CONCERNS") { - ctx.ui.setStatus("optimize", `${"●".repeat(maxRounds)}◉ ShipIt — SHIP oder NO-SHIP?…`); + // Finaler SHIP-Schritt: klares PASS → direkt SHIP ohne zweiten Inference-Aufruf. + // "PASS WITH CONCERNS" → ShipIt-Runde als finale Abwägung. + if (verdict === "PASS") { + ctx.ui.setStatus("optimize", "🚀 SHIP – produktionsreif"); + await autoCommitIfDirty(pi, ctx); + notifyShipSuccess(ctx); + finalNotify(ctx, "🚀 SHIP", "Programm ist produktionsreif"); + await runVersionBump(pi, ctx); + if (withDoku) { + await runUpdateDoku(pi, ctx); + } else { + ctx.ui.notify("Nächster Schritt: /update_doku für Code-Kommentare, README.md und BEDIENUNGSANLEITUNG.md", "info"); + } + } else if (verdict === "PASS WITH CONCERNS") { + ctx.ui.setStatus("optimize", `${"●".repeat(maxRounds)}◉ ShipIt — PASS WITH CONCERNS, finale Freigabe?…`); if (!await switchModel(pi, ctx, "llama-cpp-judge", "qwen3.5-judge")) { finalNotify(ctx, "⛔ Modell-Fehler", "Judge-Modell (llama-cpp-judge) nicht verfügbar"); return; @@ -1187,8 +1260,11 @@ export default function (pi: ExtensionAPI) { } catch (e: any) { finalNotify(ctx, "⛔ Fehler", String(e?.message ?? e)); } finally { - // Sicherstellen dass cancelRequested nie in einen späteren /optimize-Aufruf leckt + // Sicherstellen dass keine Zustandsvariable in späteren /optimize-Aufruf leckt cancelRequested = false; + interactivePauseActive = false; + interactiveContinueRequested = false; + interactivePauseTask = ""; } } }); @@ -1352,8 +1428,20 @@ export default function (pi: ExtensionAPI) { }); pi.registerCommand("continue", { - description: "Nimmt unterbrochenen Prozess wieder auf — liest TASK.md, PLAN.md, git log und entscheidet den nächsten Schritt.", - handler: async function (_args: string, ctx: ExtensionCommandContext) { + description: "Im --interactive-Modus: bestätigt PASS und geht zu ShipIt — oder /continue \"Zusatz\" für weiteren Auftrag. Sonst: nimmt unterbrochenen Prozess wieder auf.", + handler: async function (args: string, ctx: ExtensionCommandContext) { + // Interactive-Pause-Handler: Signal an laufenden /optimize-Loop + if (interactivePauseActive) { + interactivePauseTask = (args || "").trim(); + interactiveContinueRequested = true; + const msg = interactivePauseTask + ? `Zusatzauftrag eingetragen: "${interactivePauseTask}" — Coder startet` + : "Fortfahren — ShipIt wird gestartet"; + ctx.ui.notify(msg, "info"); + return; + } + + // Standard-Verhalten: unterbrochenen Prozess wieder aufnehmen if (!await waitUntilModelReady(pi, ctx, 8001, "qwen3.5-coder")) { ctx.ui.notify("Coder-Server nicht bereit (Port 8001) — start-coder.sh ausführen", "error"); return;