Compare commits
9 changed files with 85 additions and 293 deletions
|
|
@ -32,16 +32,11 @@ eine andere Persona annimmt:
|
|||
sie, führt Tests aus und erstellt Git-Commits.
|
||||
|
||||
**Judge-Persona**: Überprüft den Code mit dem Blick eines skeptischen Senior-Entwicklers.
|
||||
Prüft Korrektheit, Robustheit, Randfälle, Sicherheit und Produktionsreife. Maßstab für das Urteil
|
||||
sind `TASK.md` und, falls vorhanden, `SPEC.md`:
|
||||
- `PASS` — alle Anforderungen erfüllt, Tests grün, kein Blocker und kein Major
|
||||
- `PASS WITH CONCERNS` — alle Anforderungen erfüllt, aber mindestens ein Major: ein konkretes Risiko im
|
||||
geforderten Einsatz, mit Reproduktion (wird wie `FAIL` behandelt, außer du verwendest `--approve-concerns`)
|
||||
- `FAIL` — eine Anforderung fehlt, Tests schlagen fehl oder ein reproduzierbarer Fehler im geforderten Verhalten
|
||||
|
||||
Härtung über die Anforderungen hinaus, Skalierung, Stil und Wünsche schreibt der Judge unter „Hinweise“. Sie
|
||||
ändern das Urteil nicht, und der Coder bearbeitet sie bei `/fix` nicht. Ein Major ohne Reproduktion gilt als
|
||||
Hinweis. Ohne diese Regeln urteilte der Judge mit Strata am 15.10.2026 in keiner Aufgabe PASS.
|
||||
Prüft Korrektheit, Robustheit, Randfälle, Sicherheit und Produktionsreife. Gibt ein Urteil:
|
||||
- `PASS` — Code ist in Ordnung
|
||||
- `PASS WITH CONCERNS` — hat Anmerkungen, die behoben werden müssen (wird wie `FAIL` behandelt,
|
||||
außer du verwendest `--approve-concerns`)
|
||||
- `FAIL` — enthält Blocker, die behoben werden müssen
|
||||
|
||||
Die Umschaltung zwischen den Rollen erfolgt über unterschiedliche System-Prompts, die die
|
||||
Extension automatisch vor jeder Inference einsetzt. Es läuft kein zweiter Server.
|
||||
|
|
@ -149,21 +144,6 @@ Falls die GGUF-Datei an einem anderen Ort liegt:
|
|||
HF_HOME=/mnt/daten/huggingface ./start-coding-server.sh
|
||||
```
|
||||
|
||||
### Anderes Modell für die Schleife
|
||||
|
||||
Ohne weitere Angabe arbeiten Coder und Judge mit `llama-cpp-single/coding_model` (Port 8001). Ein anderes Modell aus
|
||||
`models.json` setzt die Umgebungsvariable `PI_CODER_MODEL` beim Start von pi, als `<provider>/<id>`:
|
||||
|
||||
```bash
|
||||
PI_CODER_MODEL=strata/qwen3.8-flash-next-iq3_s \
|
||||
pi --provider strata --model qwen3.8-flash-next-iq3_s
|
||||
```
|
||||
|
||||
`--provider`/`--model` machen das Modell auch für freie Eingaben zum Standard dieses Laufs; die globalen
|
||||
Einstellungen bleiben unverändert. Die Bereitschaftsprüfung fragt den Endpunkt aus `baseUrl` des Modells ab. Der
|
||||
Provider `strata` (lokaler Strata-Server auf `127.0.0.1:9090`) steht in `models.json`; den Server startet man außerhalb
|
||||
dieses Repos.
|
||||
|
||||
---
|
||||
|
||||
## 4. Neues Projekt anlegen
|
||||
|
|
@ -254,22 +234,21 @@ Urteil: PASS WITH CONCERNS
|
|||
Blocker: keine
|
||||
|
||||
Major:
|
||||
- Nicht-existente Datei: Traceback statt Meldung und Exit-Code 2 (TASK.md verlangt Exit 2).
|
||||
Reproduktion: python3 textcount.py fehlt.txt; echo $? → Traceback, Exit 1
|
||||
- Stoppwortliste ist hardcoded; große Projekte erwarten --stopwords-file Option
|
||||
|
||||
Minor:
|
||||
- Fehlermeldung nennt den Dateinamen nicht
|
||||
- Keine --version Flag
|
||||
- Fehlermeldung bei nicht-existenter Datei gibt keinen Exit-Code 1 zurück
|
||||
|
||||
Fehlende Tests:
|
||||
- Test für leere Datei fehlt
|
||||
- Test für Datei mit nur Leerzeichen fehlt
|
||||
|
||||
Hinweise (ändern das Urteil nicht):
|
||||
Produktionsrisiken:
|
||||
- Bei sehr großen Dateien (>1 GB) wird alles in den RAM geladen
|
||||
- Keine --version Flag
|
||||
|
||||
Konkrete Fix-Aufträge:
|
||||
1. FileNotFoundError abfangen, Meldung mit Dateiname, exit(2)
|
||||
1. exit(1) bei FileNotFoundError
|
||||
2. Test für leere Eingabedatei
|
||||
```
|
||||
|
||||
|
|
@ -436,15 +415,12 @@ Stand und der Fix-Zyklus läuft automatisch bis PASS oder max. N Runden.
|
|||
|
||||
### Loop-Erkennung
|
||||
|
||||
Wenn zweimal hintereinander dieselbe Beanstandung auftritt, bricht `/optimize` ab:
|
||||
Wenn zweimal hintereinander genau dieselben Blocker oder Concerns auftreten, bricht
|
||||
`/optimize` ab:
|
||||
```
|
||||
⚠ Schleife: Gleicher Blocker zweimal – manuelle Intervention nötig
|
||||
⚠ Derselbe Blocker tritt erneut auf – Schleife abgebrochen. Bitte manuell prüfen.
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### Max. Runden ohne PASS
|
||||
|
|
@ -866,13 +842,6 @@ Die Checkboxen werden automatisch abgehakt:
|
|||
|
||||
## 12. Fehlermeldungen und Lösungen
|
||||
|
||||
### "Urteil unklar: … Judge-Urteil nicht erkennbar"
|
||||
|
||||
**Ursache:** In der Judge-Antwort steht keine Zeile „Urteil: PASS | PASS WITH CONCERNS | FAIL“. Markdown dazwischen
|
||||
wird erkannt (`## Urteil: **PASS**`, `**Urteil:** FAIL`); fehlt das Wort ganz, gilt die Runde als nicht bestanden.
|
||||
|
||||
**Lösung:** Antwort im Chat lesen, dann `/judge` erneut oder `/fix` mit der Kritik.
|
||||
|
||||
### "Modell-Datei nicht gefunden"
|
||||
|
||||
```
|
||||
|
|
|
|||
17
README.md
17
README.md
|
|
@ -48,9 +48,6 @@ nur die Persona im System-Prompt ändert sich.
|
|||
Coder- und Judge-Rollen werden durch verschiedene System-Prompts realisiert —
|
||||
dasselbe Modell, derselbe Container, unterschiedliche Persona.
|
||||
|
||||
Ein anderes Modell für beide Rollen (z. B. den lokalen Strata-Server) setzt `PI_CODER_MODEL=<provider>/<id>`,
|
||||
siehe [BEDIENUNGSANLEITUNG.md](BEDIENUNGSANLEITUNG.md#anderes-modell-für-die-schleife).
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen
|
||||
|
|
@ -96,14 +93,9 @@ Das Install-Skript sichert eine vorhandene `~/.pi/agent/settings.json` als `.bak
|
|||
kopiert dann `settings.json` aus dem Repo. Damit ist der korrekte Default-Provider
|
||||
(`llama-cpp-single/coding_model`) automatisch konfiguriert.
|
||||
|
||||
Die Extension wird nicht kopiert, sondern verlinkt
|
||||
(`~/.pi/agent/extensions/pi-coding-extension.ts` → Repo). Änderungen an `pi-coding-extension.ts` gelten
|
||||
damit für jeden pi-Aufruf ab dem nächsten Start bzw. `/reload`, ohne erneutes Deploy. Das gilt auch für noch
|
||||
nicht committete Änderungen.
|
||||
|
||||
Nach späteren Änderungen an `models.json` oder `settings.json`:
|
||||
Nach späteren Änderungen an `pi-coding-extension.ts` oder `models.json`:
|
||||
```bash
|
||||
./deploy-pi-config.sh # verlinkt die Extension, kopiert models.json, settings.json (nur packages)
|
||||
./deploy-pi-config.sh # kopiert nach ~/.pi/agent/
|
||||
# dann /reload in pi agent
|
||||
```
|
||||
|
||||
|
|
@ -264,7 +256,7 @@ und Extension-Guards gegen Regressionen.
|
|||
| `start-coding-server.sh` | Single-Server starten (Port 8001, Coder + Judge) |
|
||||
| `stop-coding-server.sh` | Container stoppen |
|
||||
| `status-coding-server.sh` | Laufstatus anzeigen |
|
||||
| `deploy-pi-config.sh` | Extension nach `~/.pi/agent/extensions/` verlinken, models.json + settings.json kopieren |
|
||||
| `deploy-pi-config.sh` | Extension + models.json + settings.json nach `~/.pi/agent/` deployen |
|
||||
| `examples/` | Demo-Projekte (Python, Rust, Go, C, Bash, HTML/JS) für Live-Demos |
|
||||
| `examples/restore-all.sh` | Examples nach Demo-Lauf in Ausgangszustand zurücksetzen |
|
||||
|
||||
|
|
@ -300,9 +292,6 @@ einen weiteren Fix-Auftrag, genau wie bei `FAIL`. Erst sauberes `PASS` beendet d
|
|||
|
||||
Opt-out: `--approve-concerns` lässt `PASS WITH CONCERNS` als bestanden gelten.
|
||||
|
||||
Wann der Judge welches Urteil gibt (Urteilsregeln, Abschnitt „Hinweise“): siehe
|
||||
[BEDIENUNGSANLEITUNG.md](BEDIENUNGSANLEITUNG.md), Judge-Persona.
|
||||
|
||||
---
|
||||
|
||||
## Live-Aktivitätsstatus
|
||||
|
|
|
|||
|
|
@ -1,20 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
# Verlinkt die Extension und kopiert models.json/settings.json aus dem Repo nach ~/.pi/agent/.
|
||||
# Die Extension ist ein Symlink: Änderungen im Repo gelten ab dem nächsten pi-Start bzw. /reload.
|
||||
# Erneut ausführen nur nach Änderungen an models.json oder settings.json.
|
||||
# Kopiert die versionierten Dateien aus dem Repo nach ~/.pi/agent/.
|
||||
# Nach jeder Änderung im Repo ausführen, damit pi agent die neue Version lädt.
|
||||
set -euo pipefail
|
||||
REPO="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
mkdir -p ~/.pi/agent/extensions
|
||||
|
||||
# A previous, differing copy is kept as .bak before it is replaced by the symlink.
|
||||
EXT=~/.pi/agent/extensions/pi-coding-extension.ts
|
||||
if [ -f "$EXT" ] && [ ! -L "$EXT" ] && ! cmp -s "$EXT" "$REPO/pi-coding-extension.ts"; then
|
||||
cp "$EXT" "$EXT.bak"
|
||||
echo "Backup: pi-coding-extension.ts → pi-coding-extension.ts.bak"
|
||||
fi
|
||||
ln -sfn "$REPO/pi-coding-extension.ts" "$EXT"
|
||||
echo "Verlinkt: ~/.pi/agent/extensions/pi-coding-extension.ts → $REPO/pi-coding-extension.ts"
|
||||
cp "$REPO/pi-coding-extension.ts" ~/.pi/agent/extensions/pi-coding-extension.ts
|
||||
echo "Kopiert: pi-coding-extension.ts → ~/.pi/agent/extensions/"
|
||||
|
||||
cp "$REPO/models.json" ~/.pi/agent/models.json
|
||||
echo "Kopiert: models.json → ~/.pi/agent/"
|
||||
|
|
|
|||
22
models.json
22
models.json
|
|
@ -39,28 +39,6 @@
|
|||
]
|
||||
},
|
||||
|
||||
"strata": {
|
||||
"baseUrl": "http://127.0.0.1:9090/v1",
|
||||
"api": "openai-completions",
|
||||
"apiKey": "none",
|
||||
"compat": {
|
||||
"supportsDeveloperRole": false,
|
||||
"supportsReasoningEffort": true,
|
||||
"maxTokensField": "max_tokens"
|
||||
},
|
||||
"models": [
|
||||
{
|
||||
"id": "qwen3.8-flash-next-iq3_s",
|
||||
"name": "Strata Qwen3.8-Flash-Next IQ3_S (lokal :9090)",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 16384,
|
||||
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"openrouter": {
|
||||
"models": [
|
||||
{ "id": "qwen/qwen3-235b-a22b:free", "name": "Qwen3 235B (Free)" },
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// pi-coding-extension.ts
|
||||
// Automatisierter Coder-Judge-Fix-Workflow für AI-Coding-Assistenten
|
||||
// Ein Modell (Standard: coding_model auf Single-Server :8001, sonst PI_CODER_MODEL); Rollen Coder/Judge via System-Prompt
|
||||
// Ein Modell (coding_model) auf Single-Server (Port 8001); Rollen Coder/Judge via System-Prompt
|
||||
|
||||
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
||||
import { Type } from "typebox";
|
||||
|
|
@ -51,28 +51,6 @@ function coderKickoff(task: string): string {
|
|||
].join("\n");
|
||||
}
|
||||
|
||||
// Urteilsregeln und Ausgabeformat beider Judge-Prompts. Ohne feste Regeln fand der Judge (Strata, 15.10.) in jeder
|
||||
// Runde neue Härtungen über die Anforderungen hinaus und urteilte nie PASS, auch bei erfüllter SPEC.
|
||||
function judgeVerdictSection(): string[] {
|
||||
return [
|
||||
"Urteilsregeln (verbindlich; Maßstab ist TASK.md und, falls vorhanden, SPEC.md):",
|
||||
"- FAIL: eine Anforderung ist nicht erfüllt, Tests schlagen fehl, oder ein reproduzierbarer Fehler im geforderten Verhalten.",
|
||||
"- PASS WITH CONCERNS: alle Anforderungen erfüllt, aber mindestens ein Major: ein konkretes Risiko im geforderten Einsatz, mit Reproduktion (Befehl oder Eingabe → falsches Ergebnis).",
|
||||
"- PASS: alle Anforderungen erfüllt, Tests grün, kein Blocker und kein Major. Minor-Punkte und Hinweise ändern daran nichts.",
|
||||
"- Härtung über die Anforderungen hinaus, Skalierung, Stil, Wünsche und Risiken in nicht geforderten Umgebungen gehören unter „Hinweise“. Sie ändern das Urteil nicht und sind keine Fix-Aufträge.",
|
||||
"- Ein Major ohne Reproduktion ist ein Hinweis.",
|
||||
"",
|
||||
"Ausgabeformat:",
|
||||
"- Urteil: PASS | PASS WITH CONCERNS | FAIL",
|
||||
"- Blocker",
|
||||
"- Major",
|
||||
"- Minor",
|
||||
"- Fehlende Tests",
|
||||
"- Hinweise (ändern das Urteil nicht)",
|
||||
"- Konkrete Fix-Aufträge an den Coder (nur für Blocker, Major, Minor und fehlende Tests)",
|
||||
];
|
||||
}
|
||||
|
||||
function judgePrompt(extra: string): string {
|
||||
const suffix = extra?.trim() ? "\n\nZusätzlicher Fokus des Users:\n" + extra.trim() : "";
|
||||
return [
|
||||
|
|
@ -87,7 +65,14 @@ function judgePrompt(extra: string): string {
|
|||
"4. Bewerte Korrektheit, Robustheit, Fehlerbehandlung, Sicherheit, Logging, Wartbarkeit und Produktionsreife.",
|
||||
"5. Wenn etwas fehlt, sage es klar und direkt.",
|
||||
"",
|
||||
...judgeVerdictSection(),
|
||||
"Ausgabeformat:",
|
||||
"- Urteil: PASS | PASS WITH CONCERNS | FAIL",
|
||||
"- Blocker",
|
||||
"- Major",
|
||||
"- Minor",
|
||||
"- Fehlende Tests",
|
||||
"- Produktionsrisiken",
|
||||
"- Konkrete Fix-Aufträge an den Coder",
|
||||
"",
|
||||
"Wenn du etwas behauptest, nenne die Datei, den Befehl, den Test oder den Reproduktionshinweis."
|
||||
].join("\n") + suffix;
|
||||
|
|
@ -113,7 +98,14 @@ function judgeWithTestsPrompt(testOutput: string, extra: string): string {
|
|||
"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.",
|
||||
"",
|
||||
...judgeVerdictSection(),
|
||||
"Ausgabeformat:",
|
||||
"- Urteil: PASS | PASS WITH CONCERNS | FAIL",
|
||||
"- Blocker",
|
||||
"- Major",
|
||||
"- Minor",
|
||||
"- Fehlende Tests",
|
||||
"- Produktionsrisiken",
|
||||
"- Konkrete Fix-Aufträge an den Coder",
|
||||
].join("\n") + suffix;
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +115,7 @@ function fixPrompt(extra: string): string {
|
|||
"Wechsle in den Reparaturmodus.",
|
||||
"Lies TASK.md als Referenz — stelle sicher, dass nach den Fixes alle Anforderungen erfüllt bleiben.",
|
||||
"Nutze den letzten Judge-Bericht als verbindliche Aufgabenliste.",
|
||||
"Behebe zuerst Blocker, dann Major, dann Minor. Der Abschnitt „Hinweise“ ist kein Auftrag.",
|
||||
"Behebe zuerst Blocker, dann Major, dann Minor.",
|
||||
"Lies jede betroffene Datei unmittelbar vor dem Editieren erneut ein.",
|
||||
"Wenn du mehrere Stellen in derselben Datei änderst: Erzeuge einen unified diff und nutze das apply_patch-Tool — nicht mehrere edit-Aufrufe.",
|
||||
"Fallback: Datei komplett neu schreiben wenn apply_patch nicht möglich.",
|
||||
|
|
@ -440,30 +432,13 @@ async function tickTaskMdStatus(
|
|||
);
|
||||
}
|
||||
|
||||
// Single-Modell: genau ein Modell (Standard coding_model auf :8001, oder PI_CODER_MODEL) für beide Rollen.
|
||||
// Single-Server: genau ein Modell (coding_model auf :8001) für beide Rollen.
|
||||
// Der Rollenwechsel Coder/Judge erfolgt NICHT mehr über den Modellwechsel, sondern
|
||||
// über den System-Prompt (siehe before_agent_start-Hook + currentRole). Diese Funktion
|
||||
// stellt nur sicher, dass das eine Single-Modell aktiv ist; nach dem ersten Aufruf
|
||||
// ist der Wechsel via currentModelKey gecacht und damit ein No-op.
|
||||
// PI_CODER_MODEL="<provider>/<id>" setzt ein anderes Modell für beide Rollen (z. B. strata/qwen3.8-flash-next-iq3_s);
|
||||
// ohne die Variable bleibt es beim Single-Server auf :8001.
|
||||
const DEFAULT_SINGLE_MODEL = "llama-cpp-single/coding_model";
|
||||
const SINGLE_MODEL_SPEC = (process.env.PI_CODER_MODEL || DEFAULT_SINGLE_MODEL).trim();
|
||||
const SINGLE_PROVIDER = SINGLE_MODEL_SPEC.slice(0, SINGLE_MODEL_SPEC.indexOf("/"));
|
||||
const SINGLE_MODEL_ID = SINGLE_MODEL_SPEC.slice(SINGLE_MODEL_SPEC.indexOf("/") + 1);
|
||||
const SINGLE_LABEL = `${SINGLE_PROVIDER}/${SINGLE_MODEL_ID}`;
|
||||
|
||||
// Endpunkt des Single-Modells laut models.json, für Bereitschaftsprüfung und Meldungen.
|
||||
function singleEndpoint(ctx: ExtensionCommandContext): string {
|
||||
const model = ctx.modelRegistry.find(SINGLE_PROVIDER, SINGLE_MODEL_ID);
|
||||
return model?.baseUrl ? model.baseUrl.replace(/\/+$/, "") : "";
|
||||
}
|
||||
|
||||
function notReadyMessage(ctx: ExtensionCommandContext): string {
|
||||
const where = singleEndpoint(ctx) || SINGLE_LABEL;
|
||||
const hint = SINGLE_MODEL_SPEC === DEFAULT_SINGLE_MODEL ? "start-coding-server.sh ausführen" : "Server starten";
|
||||
return `Server nicht bereit (${where}) — ${hint}`;
|
||||
}
|
||||
const SINGLE_PROVIDER = "llama-cpp-single";
|
||||
const SINGLE_MODEL_ID = "coding_model";
|
||||
|
||||
async function ensureSingleModel(
|
||||
pi: ExtensionAPI,
|
||||
|
|
@ -543,20 +518,17 @@ async function sendAndWait(
|
|||
async function waitUntilModelReady(
|
||||
pi: ExtensionAPI,
|
||||
ctx: ExtensionCommandContext,
|
||||
port: number,
|
||||
modelAlias: string,
|
||||
maxWaitMs = 180_000
|
||||
): Promise<boolean> {
|
||||
const endpoint = singleEndpoint(ctx);
|
||||
if (!endpoint) {
|
||||
ctx.ui.notify(`Modell ${SINGLE_LABEL} nicht in models.json gefunden`, "error");
|
||||
return false;
|
||||
}
|
||||
const deadline = Date.now() + maxWaitMs;
|
||||
const body = JSON.stringify({
|
||||
model: SINGLE_MODEL_ID,
|
||||
model: modelAlias,
|
||||
messages: [{ role: "user", content: "ping" }],
|
||||
max_tokens: 1, temperature: 0.0, stream: false,
|
||||
});
|
||||
// Body als Datei — verhindert Shell-Injection wenn die Modell-ID Sonderzeichen enthält
|
||||
// Body als Datei — verhindert Shell-Injection wenn modelAlias Sonderzeichen enthält
|
||||
const tmpBody = `/tmp/pi_ready_${Date.now()}_${Math.random().toString(36).slice(2)}.json`;
|
||||
await pi.exec("bash", ["-c", `printf "%s" "$1" > "${tmpBody}"`, "_", body], { cwd: ctx.cwd });
|
||||
let notified = false;
|
||||
|
|
@ -564,13 +536,13 @@ async function waitUntilModelReady(
|
|||
while (Date.now() < deadline) {
|
||||
const r = await pi.exec("bash", ["-c",
|
||||
`curl -s -o /dev/null -w "%{http_code}" --max-time 5 ` +
|
||||
`-X POST "$1/chat/completions" ` +
|
||||
`-X POST http://localhost:${port}/v1/chat/completions ` +
|
||||
`-H "Content-Type: application/json" ` +
|
||||
`-d "@${tmpBody}"`, "_", endpoint
|
||||
`-d "@${tmpBody}"`
|
||||
], { cwd: ctx.cwd });
|
||||
if (r.stdout?.trim() === "200") return true;
|
||||
if (!notified) {
|
||||
ctx.ui.notify(`Modell-Server (${endpoint}) lädt noch — warte bis zu 3 min…`, "info");
|
||||
ctx.ui.notify(`Modell-Server (Port ${port}) lädt noch — warte bis zu 3 min…`, "info");
|
||||
notified = true;
|
||||
}
|
||||
await new Promise(res => setTimeout(res, 3000));
|
||||
|
|
@ -672,17 +644,10 @@ function normalizeForComparison(s: string): string {
|
|||
}
|
||||
|
||||
function parseVerdict(text: string): string {
|
||||
// Markdown zwischen „Urteil“ und dem Wort ist erlaubt: „## Urteil: **PASS**“, „**Urteil:** FAIL“.
|
||||
const m = text.match(/Urteil[\s*_`:]*(PASS WITH CONCERNS|PASS|FAIL)/i);
|
||||
const m = text.match(/Urteil:\s*(PASS WITH CONCERNS|PASS|FAIL)/i);
|
||||
return m ? m[1].toUpperCase() : "UNREADABLE";
|
||||
}
|
||||
|
||||
// SHIP-Urteil der Freigabe-Runde; Markdown wie bei parseVerdict erlaubt. "" wenn nicht erkennbar.
|
||||
function parseShipVerdict(text: string): string {
|
||||
const m = text.match(/Urteil[\s*_`:]*(NO-SHIP|SHIP)/i);
|
||||
return m ? m[1].toUpperCase() : "";
|
||||
}
|
||||
|
||||
// Extrahiert den Blocker-Abschnitt für die Loop-Erkennung.
|
||||
// Erkennt Bullet-Listen (- / – / *), Bold (**Blocker**) und Headings (## Blocker).
|
||||
function parseBlockers(text: string): string {
|
||||
|
|
@ -692,23 +657,6 @@ function parseBlockers(text: string): string {
|
|||
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.
|
||||
// null = Tag existiert nicht (erster Lauf) → alles verarbeiten
|
||||
// [] = nichts geändert → Phase überspringen
|
||||
|
|
@ -1180,15 +1128,15 @@ export default function (pi: ExtensionAPI) {
|
|||
// ── Manuelle Kommandos ───────────────────────────────────────────────────
|
||||
|
||||
pi.registerCommand("coder", {
|
||||
description: "Implementiert <auftrag> ohne Review-Loop → Coder-Rolle (Single-Modell).",
|
||||
description: "Implementiert <auftrag> ohne Review-Loop → Coder-Rolle (:8001).",
|
||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||
const task = (args || "").trim();
|
||||
if (!task) {
|
||||
ctx.ui.notify("Benutzung: /coder <auftrag>", "error");
|
||||
return;
|
||||
}
|
||||
if (!await waitUntilModelReady(pi, ctx)) {
|
||||
ctx.ui.notify(notReadyMessage(ctx), "error");
|
||||
if (!await waitUntilModelReady(pi, ctx, 8001, "coding_model")) {
|
||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-coding-server.sh ausführen", "error");
|
||||
return;
|
||||
}
|
||||
await ensureLocalGitRepo(pi, ctx);
|
||||
|
|
@ -1201,10 +1149,10 @@ export default function (pi: ExtensionAPI) {
|
|||
});
|
||||
|
||||
pi.registerCommand("judge", {
|
||||
description: "Review gegen TASK.md + git show HEAD → Judge-Rolle (Single-Modell).",
|
||||
description: "Review gegen TASK.md + git show HEAD → Judge-Rolle (:8001).",
|
||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||
if (!await waitUntilModelReady(pi, ctx)) {
|
||||
ctx.ui.notify(notReadyMessage(ctx), "error");
|
||||
if (!await waitUntilModelReady(pi, ctx, 8001, "coding_model")) {
|
||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-coding-server.sh ausführen", "error");
|
||||
return;
|
||||
}
|
||||
await selectRole(pi, ctx, "judge");
|
||||
|
|
@ -1215,10 +1163,10 @@ export default function (pi: ExtensionAPI) {
|
|||
});
|
||||
|
||||
pi.registerCommand("fix", {
|
||||
description: "Fixt Judge-Kritik, committet Ergebnis → Coder-Rolle (Single-Modell).",
|
||||
description: "Fixt Judge-Kritik, committet Ergebnis → Coder-Rolle (:8001).",
|
||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||
if (!await waitUntilModelReady(pi, ctx)) {
|
||||
ctx.ui.notify(notReadyMessage(ctx), "error");
|
||||
if (!await waitUntilModelReady(pi, ctx, 8001, "coding_model")) {
|
||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-coding-server.sh ausführen", "error");
|
||||
return;
|
||||
}
|
||||
await ensureLocalGitRepo(pi, ctx);
|
||||
|
|
@ -1230,10 +1178,10 @@ export default function (pi: ExtensionAPI) {
|
|||
});
|
||||
|
||||
pi.registerCommand("shipit", {
|
||||
description: "Finale Freigabe gegen TASK.md + git log → Judge-Rolle (Single-Modell).",
|
||||
description: "Finale Freigabe gegen TASK.md + git log → Judge-Rolle (:8001).",
|
||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||
if (!await waitUntilModelReady(pi, ctx)) {
|
||||
ctx.ui.notify(notReadyMessage(ctx), "error");
|
||||
if (!await waitUntilModelReady(pi, ctx, 8001, "coding_model")) {
|
||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-coding-server.sh ausführen", "error");
|
||||
return;
|
||||
}
|
||||
await selectRole(pi, ctx, "judge");
|
||||
|
|
@ -1241,7 +1189,7 @@ export default function (pi: ExtensionAPI) {
|
|||
currentActivity = "Judge: finale Freigabe…";
|
||||
await sendAndWait(pi, ctx, shipitPrompt(args || ""));
|
||||
const shipText = getLastAssistantText(ctx);
|
||||
const shipVerdict = parseShipVerdict(shipText);
|
||||
const shipVerdict = shipText.match(/Urteil:\s*(SHIP|NO-SHIP)/i)?.[1]?.toUpperCase() ?? "";
|
||||
if (shipVerdict === "SHIP") {
|
||||
await autoCommitIfDirty(pi, ctx);
|
||||
notifyShipSuccess(ctx);
|
||||
|
|
@ -1298,11 +1246,11 @@ export default function (pi: ExtensionAPI) {
|
|||
: `--continue: Überspringe Implementierung, starte direkt mit Judge-Prüfung.`;
|
||||
ctx.ui.notify(continueMsg, "info");
|
||||
|
||||
// Im --continue-Modus: Single-Modell prüfen (Coder + Judge in einem).
|
||||
ctx.ui.setStatus("optimize", `Single-Modell wird geprüft (${SINGLE_LABEL})…`);
|
||||
const serverReady = await waitUntilModelReady(pi, ctx);
|
||||
// Im --continue-Modus: Single-Server prüfen (Port 8001 — Coder + Judge in einem).
|
||||
ctx.ui.setStatus("optimize", "Single-Server wird geprüft (Port 8001)…");
|
||||
const serverReady = await waitUntilModelReady(pi, ctx, 8001, "coding_model");
|
||||
if (!serverReady) {
|
||||
finalNotify(ctx, "⛔ Server nicht erreichbar", `${notReadyMessage(ctx)} (kein HTTP 200 nach 3 min)`);
|
||||
finalNotify(ctx, "⛔ Server nicht erreichbar", "Port 8001 — kein HTTP 200 nach 3 min. start-coding-server.sh ausführen");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1312,7 +1260,7 @@ export default function (pi: ExtensionAPI) {
|
|||
const taskPreview = task.length > 55 ? task.slice(0, 52) + "…" : task;
|
||||
ctx.ui.setStatus("optimize", `◉ Coder liest Anforderungen + implementiert: ${taskPreview}`);
|
||||
if (!await selectRole(pi, ctx, "coder")) {
|
||||
finalNotify(ctx, "⛔ Modell-Fehler", `Modell (${SINGLE_LABEL}) nicht verfügbar`);
|
||||
finalNotify(ctx, "⛔ Modell-Fehler", "Modell (llama-cpp-single) nicht verfügbar");
|
||||
return;
|
||||
}
|
||||
currentActivity = "Coder implementiert…";
|
||||
|
|
@ -1326,8 +1274,8 @@ export default function (pi: ExtensionAPI) {
|
|||
// GPU-Ladevorgangs und ist kein verlässliches Signal. Nur HTTP 200 auf einen
|
||||
// echten Completion-Request bedeutet: Modell ist im VRAM und bereit.
|
||||
ctx.ui.setStatus("optimize", "Single-Server wird geprüft…");
|
||||
if (!await waitUntilModelReady(pi, ctx)) {
|
||||
finalNotify(ctx, "⛔ Server nicht erreichbar", `${notReadyMessage(ctx)} (kein HTTP 200 nach 3 min)`);
|
||||
if (!await waitUntilModelReady(pi, ctx, 8001, "coding_model")) {
|
||||
finalNotify(ctx, "⛔ Server nicht erreichbar", "Port 8001 — kein HTTP 200 nach 3 min. start-coding-server.sh ausführen");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1365,7 +1313,7 @@ export default function (pi: ExtensionAPI) {
|
|||
for (let round = 1; round <= maxRounds; round++) {
|
||||
const prog = "●".repeat(round - 1) + "◉" + "○".repeat(maxRounds - round);
|
||||
if (!await selectRole(pi, ctx, "judge")) {
|
||||
finalNotify(ctx, "⛔ Modell-Fehler", `Modell (${SINGLE_LABEL}) nicht verfügbar`);
|
||||
finalNotify(ctx, "⛔ Modell-Fehler", "Modell (llama-cpp-single) nicht verfügbar");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1404,9 +1352,10 @@ export default function (pi: ExtensionAPI) {
|
|||
}
|
||||
|
||||
// Loop-Erkennung: gleiche Beanstandung zweimal → manuell eingreifen.
|
||||
// Schlüssel: Blocker, bei „Blocker: Keine“ die Major-Punkte, sonst der ganze Judge-Text (loopKey).
|
||||
// Fallback auf den gesamten Judge-Text, wenn kein Blocker-Abschnitt vorhanden ist
|
||||
// (z.B. reine Minor-Concerns) — sonst würde die Erkennung dort nie greifen.
|
||||
// Normalisierung verhindert False-Negatives durch minimale Formulierungsunterschiede.
|
||||
const currentBlockers = loopKey(judgeText);
|
||||
const currentBlockers = parseBlockers(judgeText) || judgeText;
|
||||
if (currentBlockers && normalizeForComparison(currentBlockers) === normalizeForComparison(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");
|
||||
|
|
@ -1429,7 +1378,7 @@ export default function (pi: ExtensionAPI) {
|
|||
? (currentBlockers.length > 50 ? currentBlockers.slice(0, 47) + "…" : currentBlockers)
|
||||
: "Kritikpunkte aus Judge-Bericht";
|
||||
if (!await selectRole(pi, ctx, "coder")) {
|
||||
finalNotify(ctx, "⛔ Modell-Fehler", `Modell (${SINGLE_LABEL}) nicht verfügbar`);
|
||||
finalNotify(ctx, "⛔ Modell-Fehler", "Modell (llama-cpp-single) nicht verfügbar");
|
||||
return;
|
||||
}
|
||||
currentActivity = `Coder fixt: ${blockerHint}`;
|
||||
|
|
@ -1522,15 +1471,15 @@ export default function (pi: ExtensionAPI) {
|
|||
// ── Schlanke Kommandos für kleine Änderungen ─────────────────────────────
|
||||
|
||||
pi.registerCommand("patch", {
|
||||
description: "Gezielte Minimaländerung ohne Refactoring, committet → Coder-Rolle (Single-Modell).",
|
||||
description: "Gezielte Minimaländerung ohne Refactoring, committet → Coder-Rolle (:8001).",
|
||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||
const change = (args || "").trim();
|
||||
if (!change) {
|
||||
ctx.ui.notify("Benutzung: /patch <beschreibung der änderung>", "error");
|
||||
return;
|
||||
}
|
||||
if (!await waitUntilModelReady(pi, ctx)) {
|
||||
ctx.ui.notify(notReadyMessage(ctx), "error");
|
||||
if (!await waitUntilModelReady(pi, ctx, 8001, "coding_model")) {
|
||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-coding-server.sh ausführen", "error");
|
||||
return;
|
||||
}
|
||||
await ensureLocalGitRepo(pi, ctx);
|
||||
|
|
@ -1542,10 +1491,10 @@ export default function (pi: ExtensionAPI) {
|
|||
});
|
||||
|
||||
pi.registerCommand("quick_check", {
|
||||
description: "Schnelle OK/PROBLEM-Prüfung einer kleinen Codeänderung → Judge-Rolle (Single-Modell).",
|
||||
description: "Schnelle OK/PROBLEM-Prüfung einer kleinen Codeänderung → Judge-Rolle (:8001).",
|
||||
handler: async function (args: string, ctx: ExtensionCommandContext) {
|
||||
if (!await waitUntilModelReady(pi, ctx)) {
|
||||
ctx.ui.notify(notReadyMessage(ctx), "error");
|
||||
if (!await waitUntilModelReady(pi, ctx, 8001, "coding_model")) {
|
||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-coding-server.sh ausführen", "error");
|
||||
return;
|
||||
}
|
||||
await selectRole(pi, ctx, "judge");
|
||||
|
|
@ -1613,8 +1562,8 @@ export default function (pi: ExtensionAPI) {
|
|||
ctx.ui.notify("Benutzung: /plan <auftrag>", "error");
|
||||
return;
|
||||
}
|
||||
if (!await waitUntilModelReady(pi, ctx)) {
|
||||
ctx.ui.notify(notReadyMessage(ctx), "error");
|
||||
if (!await waitUntilModelReady(pi, ctx, 8001, "coding_model")) {
|
||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-coding-server.sh ausführen", "error");
|
||||
return;
|
||||
}
|
||||
await writeTaskMd(pi, ctx, task);
|
||||
|
|
@ -1696,8 +1645,8 @@ export default function (pi: ExtensionAPI) {
|
|||
}
|
||||
|
||||
// Standard-Verhalten: unterbrochenen Prozess wieder aufnehmen
|
||||
if (!await waitUntilModelReady(pi, ctx)) {
|
||||
ctx.ui.notify(notReadyMessage(ctx), "error");
|
||||
if (!await waitUntilModelReady(pi, ctx, 8001, "coding_model")) {
|
||||
ctx.ui.notify("Server nicht bereit (Port 8001) — start-coding-server.sh ausführen", "error");
|
||||
return;
|
||||
}
|
||||
await selectRole(pi, ctx, "coder");
|
||||
|
|
|
|||
|
|
@ -15,13 +15,6 @@ test("models.json: Provider llama-cpp-single hat genau ein Modell coding_model a
|
|||
assert.deepEqual(p.models.map((m) => m.id), ["coding_model"]);
|
||||
});
|
||||
|
||||
test("models.json: Provider strata zeigt auf den lokalen Strata-Server (:9090)", () => {
|
||||
const p = models.providers["strata"];
|
||||
assert.ok(p, "Provider strata fehlt");
|
||||
assert.match(p.baseUrl, /^http:\/\/127\.0\.0\.1:9090\/v1$/);
|
||||
assert.deepEqual(p.models.map((m) => m.id), ["qwen3.8-flash-next-iq3_s"]);
|
||||
});
|
||||
|
||||
test("models.json: tote Dual-/Alt-Provider sind entfernt", () => {
|
||||
for (const dead of ["llama-cpp", "llama-cpp-coder", "llama-cpp-judge"]) {
|
||||
assert.ok(!(dead in models.providers), `Toter Provider '${dead}' noch vorhanden`);
|
||||
|
|
|
|||
|
|
@ -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)", () => {
|
||||
assert.match(extSource, /const currentBlockers = loopKey\(judgeText\)/);
|
||||
assert.match(extSource, /parseBlockers\(judgeText\)\s*\|\|\s*judgeText/);
|
||||
});
|
||||
|
||||
test("currentRole wird nach jedem Kommando auf 'idle' zurückgesetzt (kein Persona-Leak in freien Chat)", () => {
|
||||
|
|
@ -61,14 +61,3 @@ test("LICENCE.md: deterministisch in der Doku-Phase, nur wenn nicht vorhanden, p
|
|||
assert.match(extSource, /git config user\.name/);
|
||||
assert.match(extSource, /new Date\(\)\.getFullYear\(\)/);
|
||||
});
|
||||
|
||||
test("Single-Modell: Standard bleibt llama-cpp-single/coding_model, PI_CODER_MODEL überschreibt", () => {
|
||||
assert.match(extSource, /DEFAULT_SINGLE_MODEL = "llama-cpp-single\/coding_model"/);
|
||||
assert.match(extSource, /process\.env\.PI_CODER_MODEL \|\| DEFAULT_SINGLE_MODEL/);
|
||||
});
|
||||
|
||||
test("Bereitschaftsprüfung: Endpunkt aus models.json statt fest Port 8001", () => {
|
||||
assert.doesNotMatch(extSource, /waitUntilModelReady\(pi, ctx, 8001/);
|
||||
assert.doesNotMatch(extSource, /localhost:\$\{port\}/);
|
||||
assert.match(extSource, /function singleEndpoint\b/);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ const parseVerdict = extractFn("parseVerdict");
|
|||
const parseBlockers = extractFn("parseBlockers");
|
||||
const normalizeForComparison = extractFn("normalizeForComparison");
|
||||
const licenceMd = extractFn("licenceMd");
|
||||
const loopKey = extractFn("loopKey");
|
||||
const parseShipVerdict = extractFn("parseShipVerdict");
|
||||
|
||||
test("parseVerdict: klares PASS", () => {
|
||||
assert.equal(parseVerdict("Review fertig.\nUrteil: PASS\nKeine Blocker."), "PASS");
|
||||
|
|
@ -80,63 +78,3 @@ test("licenceMd: proprietärer Default mit eingesetztem Jahr und Inhaber", () =>
|
|||
test("licenceMd: Platzhalter-Inhaber wird unverändert eingesetzt", () => {
|
||||
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-");
|
||||
});
|
||||
|
||||
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"), "");
|
||||
});
|
||||
|
||||
// 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/);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -44,9 +44,3 @@ test("install-Skript deployt settings.json mit vorherigem Backup", () => {
|
|||
assert.match(s, /cp "\$REPO\/models\.json"/);
|
||||
assert.match(s, /pi-coding-extension\.ts/);
|
||||
});
|
||||
|
||||
test("install-Skript verlinkt die Extension statt sie zu kopieren", () => {
|
||||
const s = readRepoFile("deploy-pi-config.sh");
|
||||
assert.match(s, /ln -sfn "\$REPO\/pi-coding-extension\.ts" "\$EXT"/);
|
||||
assert.doesNotMatch(s, /cp "\$REPO\/pi-coding-extension\.ts"/, "cp auf den Symlink bricht mit 'same file' ab");
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue