// Fälle für die Workflow-Skripte unter .claude/workflows/, gefahren mit attrappe.mjs. // Aufruf: node faelle_workflows.mjs [] // Ohne Argument gilt ../../.claude/workflows relativ zu dieser Datei. Je Workflow läuft zuerst // die Grundlinie; scheitert sie, messen die übrigen Fälle dieses Workflows nichts. // Exit 0, wenn alle Fälle bestehen, sonst 1. import { mkdtempSync, writeFileSync, rmSync, cpSync } from 'node:fs' import os from 'node:os' import path from 'node:path' import { fileURLToPath } from 'node:url' import { fuehreAus } from './attrappe.mjs' const hier = path.dirname(fileURLToPath(import.meta.url)) const ORDNER = path.resolve(process.argv[2] || path.join(hier, '..', '..', '.claude', 'workflows')) const WF = name => path.join(ORDNER, name + '.js') const ok = (wert, dauer = 5) => ({ wert, dauer }) const fenster = (fuenf, woche, extra = []) => [ { label: '5-hour limit', percentUsed: fuenf, resetsAt: '2026-09-24T18:30:00.000Z' }, { label: 'Weekly · all models', percentUsed: woche, resetsAt: '2026-09-28T00:00:00.000Z' }, ...extra, ] const ablesung = (fuenf, woche, extra) => ({ verfuegbar: true, status: 'ok', plan: 'Pro', fenster: fenster(fuenf, woche, extra), fehler: '' }) const OHNE = 'ohne' // Antworten für alle Agenten, die die Workflows starten. Unbekannte Labels werfen, damit ein // falsches Label im Protokoll auffällt. function antworten(k = {}) { let n = 0 return async a => { const label = a.label || '' if (label.startsWith('verbrauch:')) { const x = (k.ablesungen || [])[n++] if (x === OHNE) return undefined return ok(x === undefined ? ablesung(10, 10) : x) } if (label === 'werkzeug:planwerkzeuge') return ok({ laeufe: [{ befehl: 'planwerkzeuge', exit: 0, ausgabe: 'keine Befunde' }] }) if (label === 'werkzeug:kopie') return ok({ laeufe: [{ befehl: 'kopie', exit: 0, ausgabe: "KOPIE=C:/tmp/k/mat\n{'neu': 2}" }] }) if (label === 'werkzeug:vergleich') return ok({ laeufe: [{ befehl: 'vergleich', exit: 0, ausgabe: 'VERGLEICH-OK\nEXIT suite: 0' }] }) if (label === 'werkzeug:mutanten') { if (k.mutanten === OHNE) return undefined const ausgabe = k.mutantenAusgabe !== undefined ? k.mutantenAusgabe : 'wurzel: /tmp/x\nM1 OK a=GRUEN(x)\nergebnis: 1 Mutanten, 1 OK, 0 LUECKE, 0 UNKLAR, 0 ZEIT, 0 MUSTER\nEXIT mutanten_lauf: 0' return ok({ laeufe: [{ befehl: 'mutanten', exit: 0, ausgabe }] }) } if (label.startsWith('plankritik:')) { const linse = label.slice('plankritik:'.length) if (k.linse) { const x = k.linse(linse); if (x === OHNE) return undefined; if (x !== undefined) return ok(x, k.dauer || 30) } const basis = { arithmetik: 10, bezeichner: 20, abdeckung: 30 }[linse] || 40 return ok({ befunde: [0, 1].map(j => ({ schweregrad: 'wichtig', datei: 'task-01.md', zeile: basis + j, beschreibung: linse + ' ' + j, reproduktion: 'x' })), selbst_ausgefuehrt: [], nicht_pruefbar: [] }, k.dauer || 30) } if (label.startsWith('widerlegung:')) return ok({ urteil: 'bestaetigt', schweregrad: 'wichtig', bedingung: '', begruendung: 'nachgestellt', beleg: 'befehl' }, k.dauer || 30) if (label.startsWith('pruefung')) { if (k.pruefer === OHNE) return undefined return ok({ selbst_ausgefuehrt: [], eigener_lauf: [], befunde: [{ schweregrad: 'klein', datei: 'a.py', zeile: 1, beschreibung: 'x', reproduktion: 'y' }], plan_vs_umsetzung: [], geprueft_und_in_ordnung: [], nicht_pruefbar: [] }, 20) } if (label.startsWith('executer:')) { if (k.executer === OHNE) return undefined return ok({ erledigt: ['alles'], geaenderte_dateien: [], verifikation: [], abweichungen: ['keine'], annahmen: [], beobachtet: [] }, 10) } if (label.startsWith('planer:task-')) { const nr = Number(label.slice('planer:task-'.length)) const x = k.planer ? k.planer(nr) : undefined if (x === OHNE) return undefined const ziel = /Schreib genau eine Datei: (\S+\.md)/.exec(a.prompt) const datei = k.planerDatei ? k.planerDatei(nr, ziel && ziel[1]) : ziel && ziel[1] return ok({ datei, zieldateien: [], testdateien: [], abweichungen_von_uebersicht: [], offene_punkte: [] }, k.planerDauer ? k.planerDauer(nr) : 20 + nr * 3) } throw new Error('unerwarteter Agent ' + label) } } function pruefe() { const fehler = [] return { fehler, soll: (bedingung, text) => { if (!bedingung) fehler.push(text) } } } const mitLabel = (p, anfang) => p.agents.filter(x => (x.label || '').startsWith(anfang)) const spitze = (p, phase, modell) => p.spitzeJePhase[phase + '|' + modell] || 0 // Wie viele andere Agenten mit diesem Labelanfang liefen, als a startete. const laufendBeiStart = (p, a, anfang) => mitLabel(p, anfang).filter(b => b !== a && b.start < a.start && (b.ende === null || b.ende > a.start)).length function ohneVerstoss(p, soll) { soll(p.verstoesse.length === 0, 'Verstöße: ' + p.verstoesse.join('; ')) } const faelle = [] const fall = (workflow, name, lauf) => faelle.push({ workflow, name, lauf }) // ---------------------------------------------------------------- attrappe // Die Attrappe muss so streng sein wie das Ziel, sonst misst jeder grüne Fall nur sie selbst. const TMP = mkdtempSync(path.join(os.tmpdir(), 'attrappe-')) function skript(name, rumpf) { const datei = path.join(TMP, name + '.js') writeFileSync(datei, `export const meta = { name: '${name}', description: 'Selbsttest' }\n${rumpf}\n`, 'utf8') return datei } const A = (datei, k) => fuehreAus(datei, {}, antworten(k), { ordner: TMP }) for (const [i, [name, rumpf, muster]] of [ ['Date.now() wirft', 'return Date.now()', /Date\.now\(\)/], ['new Date() ohne Argument wirft', 'return new Date()', /new Date\(\)/], ['Math.random() wirft', 'return Math.random()', /Math\.random\(\)/], ['unbekannter Workflow wirft', "return await workflow('gibt-es-nicht', {})", /nicht lesbar/], ['Schema ohne properties wirft', "return await agent('x', { label: 'verbrauch:t', model: 'haiku', schema: { type: 'object' } })", /Schema braucht/], ].entries()) { fall('attrappe', name, async () => { const { fehler, soll } = pruefe() const { fehler: f } = await A(skript('wirft-' + i, rumpf)) soll(f && muster.test(f), 'Fehler: ' + f) return fehler }) } fall('attrappe', 'Grundlinie: Math.min und new Date(0) gehen, Globals sind da', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, fehler: f } = await A(skript('grund', "phase('P'); log('x'); return [Math.min(1, 2), new Date(0).getTime(), typeof parallel, typeof pipeline, args !== undefined]")) soll(f === null && JSON.stringify(e) === '[1,0,"function","function",true]', 'Fehler/Ergebnis: ' + f + ' ' + JSON.stringify(e)) return fehler }) fall('attrappe', 'workflow() im Kind wirft', async () => { const { fehler, soll } = pruefe() const enkel = skript('enkel', 'return 1') const kind = skript('kind', `return await workflow({ scriptPath: ${JSON.stringify(enkel)} }, {})`) const { fehler: f } = await A(skript('eltern', `return await workflow({ scriptPath: ${JSON.stringify(kind)} }, {})`)) soll(f && /nesting is limited to one level/.test(f), 'Fehler: ' + f) return fehler }) fall('attrappe', 'im Kind tragen Agenten die Phase ▸ , phase() wirkt dort nicht', async () => { const { fehler, soll } = pruefe() const kind = skript('kindphase', "phase('Eigen'); return await agent('x', { label: 'werkzeug:planwerkzeuge', model: 'sonnet', phase: 'Eigen' })") const { protokoll: p } = await A(skript('elternphase', `phase('Oben'); return await workflow({ scriptPath: ${JSON.stringify(kind)} }, {})`)) soll(p.agents.length === 1 && p.agents[0].phase === '▸ kindphase', 'Phase: ' + (p.agents[0] && p.agents[0].phase)) soll(JSON.stringify(p.phasen) === '["Oben"]', 'phase() im Kind hat gewirkt: ' + JSON.stringify(p.phasen)) return fehler }) fall('attrappe', 'dasselbe Kind zweimal: ▸ name und ▸ name #2', async () => { const { fehler, soll } = pruefe() const kind = skript('kindzwei', "return await agent('x', { label: 'werkzeug:planwerkzeuge', model: 'sonnet' })") const { protokoll: p } = await A(skript('elternzwei', `await workflow({ scriptPath: ${JSON.stringify(kind)} }, {}); return await workflow({ scriptPath: ${JSON.stringify(kind)} }, {})`)) soll(JSON.stringify(p.agents.map(a => a.phase)) === '["▸ kindzwei","▸ kindzwei #2"]', 'Phasen: ' + JSON.stringify(p.agents.map(a => a.phase))) return fehler }) fall('attrappe', 'agent() ohne model ist ein Verstoß', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await A(skript('ohne-modell', "return await agent('x', { label: 'verbrauch:t' })")) soll(p.verstoesse.some(v => /ohne model/.test(v)), 'Verstöße: ' + JSON.stringify(p.verstoesse)) return fehler }) fall('attrappe', 'Ergebnis ohne required-Feld wird null und ist ein Verstoß', async () => { const { fehler, soll } = pruefe() const schema = "{ type: 'object', properties: { a: { type: 'string' }, b: { type: 'string' } }, required: ['a', 'b'] }" const { ergebnis: e, protokoll: p } = await A(skript('required', `return await agent('x', { label: 'werkzeug:planwerkzeuge', model: 'sonnet', schema: ${schema} })`)) soll(e === null && p.verstoesse.some(v => /Ergebnis ohne/.test(v)), 'Ergebnis/Verstöße: ' + JSON.stringify([e, p.verstoesse])) return fehler }) fall('attrappe', 'Spitze zählt gleichzeitige Agenten je Phase und Modell', async () => { const { fehler, soll } = pruefe() const rumpf = "phase('P'); await parallel([1, 2, 3].map(() => () => agent('x', { label: 'widerlegung:t', model: 'opus', phase: 'P' }))); await agent('x', { label: 'widerlegung:t', model: 'opus', phase: 'P' }); return 1" const { protokoll: p } = await A(skript('spitze', rumpf)) soll(spitze(p, 'P', 'opus') === 3, 'Spitze ' + spitze(p, 'P', 'opus')) return fehler }) // ---------------------------------------------------------------- verbrauch const V =(a, k) => fuehreAus(WF('verbrauch'), a, antworten(k), { ordner: ORDNER }) fall('verbrauch', 'Grundlinie: Agent liest 10/10, wunsch 3 ergibt 3 Plätze', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, fehler: f, protokoll: p } = await V({ anlass: 'test', wunsch: 3 }, {}) soll(f === null, 'Fehler: ' + f) soll(e && e.status === 'ok' && e.plaetze === 3 && e.gebremst === false && e.quelle === 'agent', 'Ergebnis: ' + JSON.stringify(e)) soll(p.agents.length === 1 && p.agents[0].label === 'verbrauch:test' && p.agents[0].modell === 'haiku', 'Agent: ' + JSON.stringify(p.agents.map(x => [x.label, x.modell]))) ohneVerstoss(p, soll) return fehler }) fall('verbrauch', 'probe ersetzt den Agenten', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await V({ anlass: 't', wunsch: 2, probe: ablesung(10, 10) }) soll(p.agents.length === 0, 'Agent gestartet trotz probe') soll(e && e.quelle === 'probe' && e.plaetze === 2, JSON.stringify(e)) return fehler }) for (const [fuenf, woche, wunsch, plaetze] of [ [49.9, 10, 4, 4], [50, 10, 4, 2], [69.9, 10, 4, 2], [70, 10, 4, 1], [84.9, 10, 4, 1], [85, 10, 4, 0], [100, 10, 4, 0], [0, 0, 4, 4], [10, 69.9, 4, 4], [10, 70, 4, 2], [10, 84.9, 4, 2], [10, 85, 4, 1], [10, 94.9, 4, 1], [10, 95, 4, 0], [10, 100, 4, 0], [101, 10, 4, 0], [10, 150, 4, 0], ]) { fall('verbrauch', `Stufe 5 h ${fuenf} %, Woche ${woche} % ergibt ${plaetze}`, async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await V({ anlass: 't', wunsch, probe: ablesung(fuenf, woche) }) soll(e && e.plaetze === plaetze, `plaetze ${e && e.plaetze}, erwartet ${plaetze}`) soll(e && e.gebremst === (plaetze === 0), 'gebremst ' + (e && e.gebremst)) return fehler }) } fall('verbrauch', 'wunsch 0 bei 95 % misst nur und bremst nicht', async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await V({ anlass: 't', wunsch: 0, probe: ablesung(95, 10) }) soll(e && e.plaetze === 0 && e.gebremst === false && e.status === 'ok', JSON.stringify(e)) return fehler }) fall('verbrauch', 'wunsch 7 wird auf die harte Obergrenze 4 gesenkt', async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await V({ anlass: 't', wunsch: 7, probe: ablesung(10, 10) }) soll(e && e.plaetze === 4 && /harte Obergrenze/.test(e.grund), JSON.stringify(e)) return fehler }) for (const wunsch of [-1, 1.5, '3', null, undefined]) { fall('verbrauch', `wunsch ${JSON.stringify(wunsch)} wirft vor dem Agenten`, async () => { const { fehler, soll } = pruefe() const { fehler: f, protokoll: p } = await V({ anlass: 't', wunsch }) soll(f && /wunsch muss/.test(f), 'Fehler: ' + f) soll(p.agents.length === 0, 'Agent gestartet') return fehler }) } for (const anlass of [undefined, '', ' ', 3]) { fall('verbrauch', `anlass ${JSON.stringify(anlass)} wirft`, async () => { const { fehler, soll } = pruefe() const { fehler: f } = await V({ anlass, wunsch: 1 }) soll(f && /anlass fehlt/.test(f), 'Fehler: ' + f) return fehler }) } for (const probe of [null, [], 'x']) { fall('verbrauch', `probe ${JSON.stringify(probe)} wirft`, async () => { const { fehler, soll } = pruefe() const { fehler: f } = await V({ anlass: 't', wunsch: 1, probe }) soll(f && /probe muss/.test(f), 'Fehler: ' + f) return fehler }) } fall('verbrauch', 'Agent ohne Ergebnis: nicht_gelesen, zwei Plätze, bei wunsch 1 einer', async () => { const { fehler, soll } = pruefe() const a = await V({ anlass: 't', wunsch: 3 }, { ablesungen: [OHNE] }) soll(a.ergebnis && a.ergebnis.status === 'nicht_gelesen' && a.ergebnis.plaetze === 2 && a.ergebnis.gebremst === false, JSON.stringify(a.ergebnis)) const b = await V({ anlass: 't', wunsch: 1 }, { ablesungen: [OHNE] }) soll(b.ergebnis && b.ergebnis.plaetze === 1, JSON.stringify(b.ergebnis)) return fehler }) for (const [name, probe] of [ ['status unavailable', { verfuegbar: true, status: 'unavailable', plan: '', fenster: [], fehler: '' }], ['verfuegbar false', { verfuegbar: false, status: 'fehler', plan: '', fenster: [], fehler: 'Werkzeug fehlt' }], ['verfuegbar als Text', { verfuegbar: 'true', status: 'ok', plan: 'Pro', fenster: fenster(10, 10), fehler: '' }], ]) { fall('verbrauch', `${name}: nicht_gelesen`, async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await V({ anlass: 't', wunsch: 3, probe }) soll(e && e.status === 'nicht_gelesen' && e.plaetze === 2, JSON.stringify(e)) return fehler }) } const ok5 = { label: '5-hour limit', percentUsed: 10, resetsAt: 'x' } const okW = { label: 'Weekly · all models', percentUsed: 10, resetsAt: 'y' } for (const [name, liste] of [ ['fenster leer', []], ['fenster kein Array', { a: 1 }], ['nur Woche', [okW]], ['nur 5 Stunden', [ok5]], ['Eintrag null an erster Stelle', [null, ok5, okW]], ['Eintrag null an letzter Stelle', [ok5, okW, null]], ['label fehlt', [ok5, { percentUsed: 1, resetsAt: 'z' }, okW]], ['percentUsed null vorn', [{ ...ok5, percentUsed: null }, okW]], ['percentUsed null hinten', [ok5, { ...okW, percentUsed: null }]], ['percentUsed als Text', [{ ...ok5, percentUsed: '12' }, okW]], ['percentUsed NaN', [{ ...ok5, percentUsed: NaN }, okW]], ['percentUsed -1', [ok5, { ...okW, percentUsed: -1 }]], ['percentUsed Infinity', [{ ...ok5, percentUsed: Infinity }, okW]], ['Opus-Woche ohne Zahl', [ok5, okW, { label: 'Weekly · Opus', percentUsed: null, resetsAt: 'z' }]], ]) { fall('verbrauch', `${name}: unbekannte_fenster, alte Obergrenze`, async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await V({ anlass: 't', wunsch: 4, probe: { verfuegbar: true, status: 'ok', plan: 'Pro', fenster: liste, fehler: '' } }) soll(e && e.status === 'unbekannte_fenster' && e.plaetze === 2 && e.gebremst === false, JSON.stringify(e)) return fehler }) } for (const [name, liste] of [ ['doppeltes 5-Stunden-Fenster, hohes zuerst', [{ ...ok5, percentUsed: 90 }, ok5, okW]], ['doppeltes 5-Stunden-Fenster, hohes zuletzt', [ok5, okW, { ...ok5, percentUsed: 90 }]], ]) { fall('verbrauch', `${name}: das höhere zählt`, async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await V({ anlass: 't', wunsch: 4, probe: { verfuegbar: true, status: 'ok', plan: 'Pro', fenster: liste, fehler: '' } }) soll(e && e.plaetze === 0 && e.gebremst === true && e.fuenf_stunden.prozent === 90, JSON.stringify(e)) return fehler }) } fall('verbrauch', 'Opus-Woche 90 % zählt wie die Woche', async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await V({ anlass: 't', wunsch: 4, probe: ablesung(10, 10, [{ label: 'Weekly · Opus', percentUsed: 90, resetsAt: 'z' }]) }) soll(e && e.plaetze === 1 && e.woche_opus && e.woche_opus.prozent === 90, JSON.stringify(e)) return fehler }) fall('verbrauch', 'ein Label mit "hour" in der Mitte ist kein 5-Stunden-Fenster', async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await V({ anlass: 't', wunsch: 4, probe: ablesung(10, 10, [{ label: 'Weekly · Opus hours', percentUsed: 90, resetsAt: 'z' }]) }) soll(e && e.plaetze === 1 && e.fuenf_stunden.prozent === 10, JSON.stringify(e)) return fehler }) fall('verbrauch', 'Woche aller Modelle 90 % zählt auch neben einer niedrigen Opus-Woche', async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await V({ anlass: 't', wunsch: 4, probe: ablesung(10, 90, [{ label: 'Weekly · Opus', percentUsed: 10, resetsAt: 'z' }]) }) soll(e && e.plaetze === 1, JSON.stringify(e)) return fehler }) for (const [name, fremd] of [['Fable 99 %', 99], ['Fable ohne Zahl', null], ['Fable als Text', 'viel']]) { fall('verbrauch', `fremdes Fenster (${name}) wird übergangen`, async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await V({ anlass: 't', wunsch: 4, probe: ablesung(10, 10, [{ label: 'Weekly · Fable', percentUsed: fremd, resetsAt: 'z' }]) }) soll(e && e.status === 'ok' && e.plaetze === 4, JSON.stringify(e)) return fehler }) } for (const [a, b] of [['5-Hour Limit', 'Weekly · All Models'], ['5 hour limit', 'weekly all models'], ['5hour', 'WEEKLY - ALL MODELS']]) { fall('verbrauch', `Schreibweise "${a}" / "${b}" wird erkannt`, async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await V({ anlass: 't', wunsch: 4, probe: { verfuegbar: true, status: 'ok', plan: 'Pro', fenster: [{ label: a, percentUsed: 60, resetsAt: 'x' }, { label: b, percentUsed: 10, resetsAt: 'y' }], fehler: '' } }) soll(e && e.status === 'ok' && e.plaetze === 2, JSON.stringify(e)) return fehler }) } // ---------------------------------------------------------------- plankritik const PK_ARGS = { projekt: 'D:/x', planordner: 'D:/x/plan', kopie: 'C:/tmp/k/mat' } const PK = (a, k, o = {}) => fuehreAus(WF('plankritik'), a, antworten(k), { ordner: ORDNER, ...o }) fall('plankritik', 'Grundlinie: drei Linsen gleichzeitig, Widerlegung zu zweit, drei Ablesungen', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, fehler: f, protokoll: p } = await PK(PK_ARGS, {}) soll(f === null, 'Fehler: ' + f) soll(e && e.status === 'fertig', 'status ' + (e && e.status)) soll(spitze(p, 'Plankritik', 'opus') === 3, 'Linsen gleichzeitig: ' + spitze(p, 'Plankritik', 'opus')) soll(spitze(p, 'Widerlegung', 'opus') === 2, 'Widerlegung gleichzeitig: ' + spitze(p, 'Widerlegung', 'opus')) const ab = mitLabel(p, 'verbrauch:').map(x => x.label) soll(JSON.stringify(ab) === JSON.stringify(['verbrauch:plankritik:vor Linsen', 'verbrauch:plankritik:vor Widerlegung', 'verbrauch:plankritik:Ende']), 'Ablesungen: ' + JSON.stringify(ab)) soll(mitLabel(p, 'verbrauch:').every(x => x.modell === 'haiku'), 'Ablesung nicht auf haiku') soll(e && e.verbrauch.length === 3 && e.plaetze.linsen === 3 && e.plaetze.widerlegung === 2, 'verbrauch/plaetze: ' + JSON.stringify(e && e.plaetze)) soll(e && e.zaehlung.bestaetigt === 6 && e.zaehlung.nicht_geprueft === 0, 'zaehlung ' + JSON.stringify(e && e.zaehlung)) for (const k of ['zaehlung', 'kopie', 'temp_ordner', 'befunde', 'selbst_ausgefuehrt', 'nicht_pruefbar', 'vorbereitung', 'ohne_ergebnis']) soll(e && k in e, 'Feld fehlt: ' + k) ohneVerstoss(p, soll) return fehler }) fall('plankritik', '5 Stunden bei 60 %: Linsen zu zweit', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PK(PK_ARGS, { ablesungen: [ablesung(60, 10)] }) soll(spitze(p, 'Plankritik', 'opus') === 2 && e.plaetze.linsen === 2, 'Linsen gleichzeitig: ' + spitze(p, 'Plankritik', 'opus')) soll(e.status === 'fertig', 'status ' + e.status) return fehler }) fall('plankritik', 'gebremst vor den Linsen: keine Linse, keine Widerlegung', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, fehler: f, protokoll: p } = await PK(PK_ARGS, { ablesungen: [ablesung(90, 10)] }) soll(f === null, 'Fehler: ' + f) soll(e && e.status === 'gebremst' && e.gebremst_vor === 'Linsen', JSON.stringify(e && [e.status, e.gebremst_vor])) soll(mitLabel(p, 'plankritik:').length === 0 && mitLabel(p, 'widerlegung:').length === 0, 'Opus gestartet trotz Bremse') soll(e && e.verbrauch.length === 1 && e.ohne_ergebnis.length === 3, 'verbrauch/ohne_ergebnis ' + JSON.stringify(e && [e.verbrauch.length, e.ohne_ergebnis])) soll(e && e.zaehlung && e.zaehlung.bestaetigt === 0 && e.zaehlung.nicht_geprueft === 0, 'zaehlung ' + JSON.stringify(e && e.zaehlung)) soll(e && /neu starten/.test(e.naechster_schritt), 'naechster_schritt ' + (e && e.naechster_schritt)) return fehler }) fall('plankritik', 'gebremst vor der Widerlegung: nur die Befunde über der Schwelle heißen gebremst', async () => { const { fehler, soll } = pruefe() const zeile = { arithmetik: 11, bezeichner: 12, abdeckung: 13 } const mitKlein = linse => ({ befunde: [ { schweregrad: 'wichtig', datei: 'task-01.md', zeile: zeile[linse], beschreibung: 'w', reproduktion: 'x' }, { schweregrad: 'klein', datei: 'task-02.md', zeile: zeile[linse], beschreibung: 'k', reproduktion: 'x' }, ], selbst_ausgefuehrt: [], nicht_pruefbar: [] }) const { ergebnis: e, protokoll: p } = await PK(PK_ARGS, { ablesungen: [ablesung(10, 10), ablesung(90, 10)], linse: mitKlein }) soll(mitLabel(p, 'plankritik:').length === 3, 'Linsen: ' + mitLabel(p, 'plankritik:').length) soll(mitLabel(p, 'widerlegung:').length === 0, 'Widerlegung trotz Bremse') soll(e.status === 'gebremst' && e.gebremst_vor === 'Widerlegung', JSON.stringify([e.status, e.gebremst_vor])) const urteile = e.befunde.map(b => b.schweregrad + ':' + b.urteil).sort() soll(urteile.filter(u => u === 'wichtig:nicht geprüft (gebremst)').length === 3 && urteile.filter(u => u === 'klein:nicht geprüft').length === 3, 'Urteile: ' + JSON.stringify(urteile)) soll(e.zaehlung.nicht_geprueft === 6 && e.zaehlung.bestaetigt === 0, JSON.stringify(e.zaehlung)) soll(mitLabel(p, 'verbrauch:').length === 3, 'Ablesung am Ende fehlt') soll(/nachholen/.test(e.naechster_schritt), 'naechster_schritt ' + e.naechster_schritt) return fehler }) fall('plankritik', 'vor der Widerlegung 75 %: Widerlegung einzeln', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PK(PK_ARGS, { ablesungen: [ablesung(10, 10), ablesung(75, 10)] }) soll(spitze(p, 'Widerlegung', 'opus') === 1 && e.plaetze.widerlegung === 1, 'Widerlegung gleichzeitig: ' + spitze(p, 'Widerlegung', 'opus')) soll(mitLabel(p, 'widerlegung:').length === 6 && e.status === 'fertig', JSON.stringify([mitLabel(p, 'widerlegung:').length, e.status])) return fehler }) fall('plankritik', 'Ablesung ohne Ergebnis: Linsen zu zweit (alte Obergrenze)', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PK(PK_ARGS, { ablesungen: [OHNE] }) soll(spitze(p, 'Plankritik', 'opus') === 2, 'Linsen gleichzeitig: ' + spitze(p, 'Plankritik', 'opus')) soll(e.status === 'fertig' && e.verbrauch[0].status === 'nicht_gelesen', JSON.stringify(e.verbrauch[0])) return fehler }) fall('plankritik', 'als Kind: verbrauch.js wirft, Rückfall auf zwei', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, fehler: f, protokoll: p } = await PK(PK_ARGS, {}, { tiefe: 1 }) soll(f === null, 'Fehler: ' + f) soll(spitze(p, '▸ plankritik', 'opus') === 2, 'Linsen gleichzeitig: ' + spitze(p, '▸ plankritik', 'opus')) soll(mitLabel(p, 'verbrauch:').length === 0, 'Ableseagent trotz Kind') soll(e && e.verbrauch.every(v => v.status === 'nicht_gelesen' && /nicht ausführbar/.test(v.grund)), JSON.stringify(e && e.verbrauch)) soll(e && e.status === 'fertig', 'status ' + (e && e.status)) return fehler }) fall('plankritik', 'Aufträge nennen den Testort', async () => { const { fehler, soll } = pruefe() const mit = await PK(PK_ARGS, {}) const satz = 'Tests fährst du nur in der Kopie C:/tmp/k/mat, nie im echten Projektbaum' for (const a of [...mitLabel(mit.protokoll, 'plankritik:'), ...mitLabel(mit.protokoll, 'widerlegung:')]) { soll(a.prompt.includes(satz) && /eigenen Kopie dieser Kopie/.test(a.prompt), a.label + ' ohne Testort') } const ohne = await PK({ projekt: 'D:/x', planordner: 'D:/x/plan' }, {}) for (const a of mitLabel(ohne.protokoll, 'plankritik:')) { soll(/Tests fährst du in dieser Plankritik nicht/.test(a.prompt), a.label + ': ohne Kopie kein Testverbot') } return fehler }) fall('plankritik', 'plaetze 1 begrenzt die Linsen auf eine', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await PK({ ...PK_ARGS, plaetze: 1 }, {}) soll(spitze(p, 'Plankritik', 'opus') === 1, 'Linsen gleichzeitig: ' + spitze(p, 'Plankritik', 'opus')) return fehler }) for (const plaetze of [0, 5, 2.5, '3']) { fall('plankritik', `plaetze ${JSON.stringify(plaetze)} wirft vor dem ersten Agenten`, async () => { const { fehler, soll } = pruefe() const { fehler: f, protokoll: p } = await PK({ ...PK_ARGS, plaetze }, {}) soll(f && /plaetze muss/.test(f), 'Fehler: ' + f) soll(p.agents.length === 0, 'Agent gestartet') return fehler }) } fall('plankritik', 'doppelte Linse wirft vor dem ersten Agenten', async () => { const { fehler, soll } = pruefe() const { fehler: f, protokoll: p } = await PK({ ...PK_ARGS, linsen: ['arithmetik', 'bezeichner', 'arithmetik'] }, {}) soll(f && /doppelt in linsen/.test(f), 'Fehler: ' + f) soll(p.agents.length === 0, 'Agent gestartet') return fehler }) fall('plankritik', 'Linse ohne Ergebnis steht in ohne_ergebnis', async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await PK(PK_ARGS, { linse: l => (l === 'arithmetik' ? OHNE : undefined) }) soll(e.status === 'fertig' && JSON.stringify(e.ohne_ergebnis) === '["arithmetik"]', JSON.stringify(e.ohne_ergebnis)) return fehler }) fall('plankritik', 'verifizieren false: keine Ablesung vor der Widerlegung', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PK({ ...PK_ARGS, verifizieren: false }, {}) soll(mitLabel(p, 'widerlegung:').length === 0, 'Widerlegung trotz verifizieren false') soll(mitLabel(p, 'verbrauch:').length === 2, 'Ablesungen: ' + mitLabel(p, 'verbrauch:').length) soll(e.status === 'fertig' && e.zaehlung.nicht_geprueft === 6, JSON.stringify(e.zaehlung)) return fehler }) fall('plankritik', 'mit kopie, mutanten in Vorgabe: ein Agent werkzeug:mutanten, Rohausgabe geht an die Linse arithmetik', async () => { const { fehler, soll } = pruefe() const marke = 'MUTANT-MARKE-007' const { protokoll: p } = await PK(PK_ARGS, { mutantenAusgabe: `wurzel: /tmp/x\n${marke}\nEXIT mutanten_lauf: 0` }) const agenten = mitLabel(p, 'werkzeug:mutanten') soll(agenten.length === 1, 'Agenten werkzeug:mutanten: ' + agenten.length) soll(agenten[0] && /MSYS_NO_PATHCONV=1/.test(agenten[0].prompt), 'MSYS_NO_PATHCONV fehlt im Befehl: ' + (agenten[0] && agenten[0].prompt)) soll(agenten[0] && /cygpath -m -l/.test(agenten[0].prompt), 'cygpath -m -l fehlt im Befehl: ' + (agenten[0] && agenten[0].prompt)) const arith = mitLabel(p, 'plankritik:arithmetik')[0] soll(arith && arith.prompt.includes(marke) && //.test(arith.prompt), 'Rohausgabe fehlt im Auftrag der Linse arithmetik') return fehler }) fall('plankritik', 'mutanten "aus": kein Agent werkzeug:mutanten', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await PK({ ...PK_ARGS, mutanten: 'aus' }, {}) soll(mitLabel(p, 'werkzeug:mutanten').length === 0, 'Agent trotz mutanten aus') return fehler }) fall('plankritik', 'ohne kopie und ohne materialisieren: kein Agent werkzeug:mutanten', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await PK({ projekt: 'D:/x', planordner: 'D:/x/plan' }, {}) soll(mitLabel(p, 'werkzeug:mutanten').length === 0, 'Agent ohne Kopie gestartet') return fehler }) fall('plankritik', 'mutanten "halb" wirft vor dem ersten Agenten', async () => { const { fehler, soll } = pruefe() const { fehler: f, protokoll: p } = await PK({ ...PK_ARGS, mutanten: 'halb' }, {}) soll(f && /mutanten muss/.test(f), 'Fehler: ' + f) soll(p.agents.length === 0, 'Agent gestartet') return fehler }) fall('plankritik', 'Hilfsagent ohne EXIT mutanten_lauf und ohne KEINE_MUTANTEN_TOML: kein Block ', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await PK(PK_ARGS, { mutantenAusgabe: 'irgendeine unbrauchbare Ausgabe ohne Marker' }) const arith = mitLabel(p, 'plankritik:arithmetik')[0] soll(arith && !//.test(arith.prompt), 'Block trotz unbrauchbarer Ausgabe vorhanden') return fehler }) fall('plankritik', 'KEINE_MUTANTEN_TOML: Linse bekommt den Hinweissatz statt der Rohausgabe', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await PK(PK_ARGS, { mutantenAusgabe: 'KEINE_MUTANTEN_TOML' }) const arith = mitLabel(p, 'plankritik:arithmetik')[0] soll(arith && /keine mutanten\.toml/.test(arith.prompt) && /Schwere wichtig/.test(arith.prompt), 'Hinweissatz fehlt: ' + (arith && arith.prompt)) soll(arith && !//.test(arith.prompt), 'Block trotz KEINE_MUTANTEN_TOML vorhanden') return fehler }) fall('plankritik', 'ZEITGRENZE im Mutantenlauf: Linse erfährt es, Hilfsagent fährt mit zehn Minuten', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await PK(PK_ARGS, { mutantenAusgabe: 'M1 OK a=ROT(x)\nZEITGRENZE: Mutantenlauf nach 540 s abgebrochen\nEXIT mutanten_lauf: 124' }) const hilfe = mitLabel(p, 'werkzeug:mutanten')[0] soll(hilfe && /timeout auf 600000/.test(hilfe.prompt), 'Zeitvorgabe fehlt im Auftrag des Hilfsagenten') const arith = mitLabel(p, 'plankritik:arithmetik')[0] soll(arith && /Zeitgrenze erreicht/.test(arith.prompt) && //.test(arith.prompt), 'Hinweis auf die Zeitgrenze fehlt: ' + (arith && arith.prompt.slice(-400))) return fehler }) // Fälle für die Nachbesserung vom 2026-09-28 (N5, N7, N8, N10): fünf Mutanten aus // eigene_mutanten.py töten, dazu Modus muster, Exit 2/124 und mutanten_status/mutanten_modus. fall('plankritik', 'Modus muster wird als muster an den Wrapper übergeben, nicht als voll', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await PK({ ...PK_ARGS, mutanten: 'muster' }, {}) const hilfe = mitLabel(p, 'werkzeug:mutanten')[0] const zeile = (hilfe ? hilfe.prompt.split('\n').find(z => z.includes('mutanten_lauf_kopie.sh')) : '') || '' soll(/ muster$/.test(zeile), 'Befehl endet nicht auf muster: ' + zeile) return fehler }) fall('plankritik', 'Mutantenstufe läuft auch mit materialisierter Kopie ohne args.kopie', async () => { const { fehler, soll } = pruefe() const args = { projekt: 'D:/x', planordner: 'D:/x/plan', materialisieren: { ohne: ['config/secrets.toml'] } } const { protokoll: p } = await PK(args, {}) soll(mitLabel(p, 'werkzeug:mutanten').length === 1, 'Mutantenlauf lief nicht mit materialisierter Kopie') return fehler }) fall('plankritik', 'arithmetik bekommt mehr als die letzten fünf Zeilen des Mutantenlaufs', async () => { const { fehler, soll } = pruefe() const marke = 'MARKE-VOR-DEM-ENDE' const fuellzeilen = Array.from({ length: 50 }, (_, i) => 'zeile ' + i).join('\n') const ausgabe = `wurzel: /tmp/x\n${marke}\n${fuellzeilen}\nEXIT mutanten_lauf: 0` const { protokoll: p } = await PK(PK_ARGS, { mutantenAusgabe: ausgabe }) const arith = mitLabel(p, 'plankritik:arithmetik')[0] soll(arith && arith.prompt.includes(marke), 'Marke außerhalb der letzten fünf Zeilen fehlt im Auftrag') return fehler }) fall('plankritik', 'mutanten_lauf in der Rückgabe ist bei einem Lauf nicht null', async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await PK(PK_ARGS, {}) soll(e && e.mutanten_lauf !== null && /EXIT mutanten_lauf/.test(e.mutanten_lauf), 'mutanten_lauf: ' + JSON.stringify(e && e.mutanten_lauf)) return fehler }) fall('plankritik', 'Wurzel im Mutanten-Befehl ist ctx.kopie, nie args.projekt', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await PK({ ...PK_ARGS, projekt: 'D:/anderes-projekt' }, {}) const hilfe = mitLabel(p, 'werkzeug:mutanten')[0] soll(hilfe && hilfe.prompt.includes(PK_ARGS.kopie) && !hilfe.prompt.includes('anderes-projekt'), 'Befehl nennt args.projekt statt ctx.kopie: ' + (hilfe && hilfe.prompt)) return fehler }) fall('plankritik', 'modus muster: Linse erfährt, dass kein Mutant gefahren ist', async () => { const { fehler, soll } = pruefe() const ausgabe = 'wurzel: /tmp/x\nmodus: muster (nur Musterpruefung, kein Mutant gefahren)\nM1 OK Trefferzahl=1\nEXIT mutanten_lauf: 0' const { protokoll: p } = await PK({ ...PK_ARGS, mutanten: 'muster' }, { mutantenAusgabe: ausgabe }) const arith = mitLabel(p, 'plankritik:arithmetik')[0] soll(arith && /Kein Mutant ist gefahren/.test(arith.prompt), 'Hinweis auf Musterlauf fehlt') soll(arith && !/prüfe bei OK an der Fehlermeldung/.test(arith.prompt), 'Satz "prüfe bei OK" steht trotz Modus muster noch da') return fehler }) fall('plankritik', 'Exit 2 des Mutantenlaufs: Linse erfährt, dass er vor oder während der Mutanten gescheitert ist', async () => { const { fehler, soll } = pruefe() const ausgabe = 'wurzel: /tmp/x\nbefunde:\n - x\nEXIT mutanten_lauf: 2' const { protokoll: p } = await PK(PK_ARGS, { mutantenAusgabe: ausgabe }) const arith = mitLabel(p, 'plankritik:arithmetik')[0] soll(arith && /vor oder während der Mutanten gescheitert \(Exit 2\)/.test(arith.prompt) && /Schwere wichtig/.test(arith.prompt), 'Hinweis auf Exit 2 fehlt: ' + (arith && arith.prompt.slice(-400))) return fehler }) fall('plankritik', 'mutanten_status und mutanten_modus in allen Zuständen', async () => { const { fehler, soll } = pruefe() const { ergebnis: eAus } = await PK({ ...PK_ARGS, mutanten: 'aus' }, {}) soll(eAus && eAus.mutanten_status === 'aus' && eAus.mutanten_modus === 'aus', 'aus: ' + JSON.stringify(eAus && [eAus.mutanten_status, eAus.mutanten_modus])) const { ergebnis: eOhneKopie } = await PK({ projekt: 'D:/x', planordner: 'D:/x/plan' }, {}) soll(eOhneKopie && eOhneKopie.mutanten_status === 'ohne_kopie' && eOhneKopie.mutanten_modus === 'voll', 'ohne_kopie: ' + JSON.stringify(eOhneKopie && [eOhneKopie.mutanten_status, eOhneKopie.mutanten_modus])) const { ergebnis: eKeineToml } = await PK(PK_ARGS, { mutantenAusgabe: 'KEINE_MUTANTEN_TOML' }) soll(eKeineToml && eKeineToml.mutanten_status === 'keine_toml', 'keine_toml: ' + JSON.stringify(eKeineToml && eKeineToml.mutanten_status)) const { ergebnis: eOhneErgebnis } = await PK(PK_ARGS, { mutantenAusgabe: 'irgendeine unbrauchbare Ausgabe ohne Marker' }) soll(eOhneErgebnis && eOhneErgebnis.mutanten_status === 'ohne_ergebnis', 'ohne_ergebnis: ' + JSON.stringify(eOhneErgebnis && eOhneErgebnis.mutanten_status)) const { ergebnis: eExit2 } = await PK(PK_ARGS, { mutantenAusgabe: 'EXIT mutanten_lauf: 2' }) soll(eExit2 && eExit2.mutanten_status === 'exit_2', 'exit_2: ' + JSON.stringify(eExit2 && eExit2.mutanten_status)) const { ergebnis: eExitDefault } = await PK(PK_ARGS, {}) soll(eExitDefault && eExitDefault.mutanten_status === 'exit_0' && eExitDefault.mutanten_modus === 'voll', 'exit_0: ' + JSON.stringify(eExitDefault && [eExitDefault.mutanten_status, eExitDefault.mutanten_modus])) const { ergebnis: eMuster } = await PK({ ...PK_ARGS, mutanten: 'muster' }, {}) soll(eMuster && eMuster.mutanten_modus === 'muster', 'muster: ' + JSON.stringify(eMuster && eMuster.mutanten_modus)) return fehler }) // ---------------------------------------------------------------- pruefung const PR_ARGS = { projekt: 'D:/x', plan: 'D:/x/plan' } const PR = (a, k, o = {}) => fuehreAus(WF('pruefung'), a, antworten(k), { ordner: ORDNER, ...o }) fall('pruefung', 'Grundlinie: Ablesung vor und nach dem pruefer', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, fehler: f, protokoll: p } = await PR(PR_ARGS, {}) soll(f === null, 'Fehler: ' + f) const folge = p.agents.slice().sort((x, y) => x.start - y.start).map(x => x.label) soll(JSON.stringify(folge) === JSON.stringify(['verbrauch:pruefung:vor Prüfung', 'pruefung', 'verbrauch:pruefung:nach Prüfung']), 'Folge: ' + JSON.stringify(folge)) soll(e && e.verbrauch.length === 2 && e.verbrauch.every(v => v.status === 'ok' && v.plaetze === 0 && v.gebremst === false), JSON.stringify(e && e.verbrauch)) soll(e && e.effort === '(Frontmatter)', 'effort ' + (e && e.effort)) soll(mitLabel(p, 'pruefung')[0].modell === 'opus', 'pruefer nicht auf opus') ohneVerstoss(p, soll) return fehler }) fall('pruefung', 'kritisch hebt auf xhigh', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PR({ ...PR_ARGS, kritisch: true }, {}) soll(e.effort === 'xhigh' && mitLabel(p, 'pruefung:kritisch')[0].effort === 'xhigh', 'effort ' + e.effort) return fehler }) fall('pruefung', 'verbrauch false: keine Ablesung', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PR({ ...PR_ARGS, verbrauch: false }, {}) soll(mitLabel(p, 'verbrauch:').length === 0 && e.verbrauch.length === 0, 'Ablesung trotz verbrauch false') return fehler }) fall('pruefung', 'als Kind ohne verbrauch false: Ablesung scheitert, Prüfung läuft', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, fehler: f, protokoll: p } = await PR(PR_ARGS, {}, { tiefe: 1 }) soll(f === null, 'Fehler: ' + f) soll(mitLabel(p, 'pruefung').length === 1, 'pruefer nicht gelaufen') soll(e && e.verbrauch.length === 2 && e.verbrauch.every(v => v.status === 'nicht_gelesen'), JSON.stringify(e && e.verbrauch)) return fehler }) fall('pruefung', 'pruefer ohne Bericht: Fehler, beide Ablesungen gelaufen', async () => { const { fehler, soll } = pruefe() const { fehler: f, protokoll: p } = await PR(PR_ARGS, { pruefer: OHNE }) soll(f === 'pruefung: der pruefer hat keinen Bericht geliefert', 'Fehler: ' + f) soll(mitLabel(p, 'verbrauch:').length === 2, 'Ablesungen: ' + mitLabel(p, 'verbrauch:').length) return fehler }) for (const verbrauch of ['nein', 0, null]) { fall('pruefung', `verbrauch ${JSON.stringify(verbrauch)} wirft`, async () => { const { fehler, soll } = pruefe() const { fehler: f, protokoll: p } = await PR({ ...PR_ARGS, verbrauch }, {}) soll(f && /verbrauch muss/.test(f), 'Fehler: ' + f) soll(p.agents.length === 0, 'Agent gestartet') return fehler }) } // ---------------------------------------------------------------- welle-mit-pruefung const WM_ARGS = { projekt: 'D:/x', planordner: 'D:/x/plan', tasks: [{ datei: 'task-01.md', modell: 'haiku' }], kopie: 'C:/tmp/k/mat', suite: 'pytest -q' } const WM = (a, k) => fuehreAus(WF('welle-mit-pruefung'), a, antworten(k), { ordner: ORDNER }) fall('welle-mit-pruefung', 'Grundlinie: Welle, Vergleich, Ablesung um die Prüfung, Kind liest nicht selbst', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, fehler: f, protokoll: p } = await WM(WM_ARGS, {}) soll(f === null, 'Fehler: ' + f) soll(e && e.status === 'fertig', 'status ' + (e && e.status)) soll(mitLabel(p, 'executer:').length === 1 && mitLabel(p, 'werkzeug:vergleich').length === 1 && mitLabel(p, 'pruefung').length === 1, 'Agenten: ' + JSON.stringify(p.agents.map(x => x.label))) const ab = mitLabel(p, 'verbrauch:').map(x => x.label) soll(JSON.stringify(ab) === JSON.stringify(['verbrauch:welle-mit-pruefung:vor Prüfung', 'verbrauch:welle-mit-pruefung:nach Prüfung']), 'Ablesungen: ' + JSON.stringify(ab)) const kind = p.workflows.find(w => w.ref === 'pruefung') soll(kind && kind.args.verbrauch === false, 'Kind pruefung ohne verbrauch false') soll(e.pruefung.verbrauch.length === 0 && mitLabel(p, 'verbrauch:pruefung:').length === 0, 'Kind hat selbst abgelesen') soll(e.verbrauch.length === 2, 'verbrauch ' + e.verbrauch.length) ohneVerstoss(p, soll) return fehler }) fall('welle-mit-pruefung', 'bis vergleich: keine Prüfung, pruefung_args mit Rohbelegen', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, fehler: f, protokoll: p } = await WM({ ...WM_ARGS, bis: 'vergleich' }, {}) soll(f === null, 'Fehler: ' + f) soll(e && e.status === 'bis_vergleich', 'status ' + (e && e.status)) soll(mitLabel(p, 'pruefung').length === 0 && mitLabel(p, 'verbrauch:').length === 0, 'Prüfung oder Ablesung trotz bis') const pa = e && e.pruefung_args soll(pa && /VERGLEICH-OK/.test(pa.schwerpunkt) && pa.plan === 'D:/x/plan' && JSON.stringify(pa.tasks) === '["task-01.md"]', 'pruefung_args ' + JSON.stringify(pa)) soll(pa && !('verbrauch' in pa), 'pruefung_args trägt verbrauch') soll(e && /nur lesend/.test(e.naechster_schritt), 'naechster_schritt ohne Lesevorgabe') return fehler }) fall('welle-mit-pruefung', 'bis vergleich bei unvollständiger Welle: keine pruefung_args', async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await WM({ ...WM_ARGS, bis: 'vergleich' }, { executer: OHNE }) soll(e && e.status === 'bis_vergleich' && e.pruefung_args === null && /unvollständig/.test(e.pruefung_grund), JSON.stringify(e && [e.status, e.pruefung_args, e.pruefung_grund])) return fehler }) fall('welle-mit-pruefung', 'unvollständige Welle: Prüfung übersprungen, keine Ablesung', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await WM(WM_ARGS, { executer: OHNE }) soll(e.status === 'ohne_pruefung' && /unvollständig/.test(e.pruefung.uebersprungen), JSON.stringify([e.status, e.pruefung])) soll(mitLabel(p, 'verbrauch:').length === 0 && mitLabel(p, 'pruefung').length === 0, 'Ablesung oder Prüfung trotz Lücke') return fehler }) fall('welle-mit-pruefung', 'verbrauch.js fehlt: Ablesungen nicht_gelesen, Welle und Prüfung bleiben', async () => { const { fehler, soll } = pruefe() const ohneVerbrauch = mkdtempSync(path.join(os.tmpdir(), 'ohne-verbrauch-')) cpSync(ORDNER, ohneVerbrauch, { recursive: true }) rmSync(path.join(ohneVerbrauch, 'verbrauch.js')) const { ergebnis: e, fehler: f } = await fuehreAus(path.join(ohneVerbrauch, 'welle-mit-pruefung.js'), WM_ARGS, antworten({}), { ordner: ohneVerbrauch }) rmSync(ohneVerbrauch, { recursive: true, force: true }) soll(f === null && e.status === 'fertig' && e.welle && e.pruefung.zaehlung, 'Fehler/Ergebnis: ' + f + ' ' + JSON.stringify(e && e.status)) soll(e && e.verbrauch.length === 2 && e.verbrauch.every(v => v.status === 'nicht_gelesen'), JSON.stringify(e && e.verbrauch)) return fehler }) for (const bis of ['alles', '', 1]) { fall('welle-mit-pruefung', `bis ${JSON.stringify(bis)} wirft`, async () => { const { fehler, soll } = pruefe() const { fehler: f, protokoll: p } = await WM({ ...WM_ARGS, bis }, {}) soll(f && /bis kennt nur/.test(f), 'Fehler: ' + f) soll(p.agents.length === 0, 'Agent gestartet') return fehler }) } // ---------------------------------------------------------------- planung const T = (nr, voraussetzungen, welle) => ({ nr, thema: 'Thema ' + nr, datei: `task-${String(nr).padStart(2, '0')}-t.md`, modell: 'haiku', voraussetzungen, welle }) const DAG = [T(1, [], 1), T(2, [1], 2), T(3, [1], 2), T(4, [1], 2), T(5, [2, 3], 3), T(6, [4], 3), T(7, [], 1)] const PL_ARGS = { projekt: 'D:/x', planordner: 'D:/x/plan', gemeinsam: 'GEMEINSAM', taskauftrag: 'Task {NR} {THEMA} in {DATEI}, Modell {MODELL}, Welle {WELLE}, vor: {VORAUSSETZUNGEN}', tasks: DAG } const PL = (a, k, o = {}) => fuehreAus(WF('planung'), a, antworten(k), { ordner: ORDNER, ...o }) const planerVon = (p, nr) => p.agents.find(x => x.label === `planer:task-${String(nr).padStart(2, '0')}`) function voraussetzungenEingehalten(p, tasks, soll) { for (const t of tasks) { const a = planerVon(p, t.nr) if (!a) continue for (const n of t.voraussetzungen) { const v = planerVon(p, n) soll(v && v.ende !== null && v.ende < a.start, `Task ${t.nr} startete vor dem Ende von Task ${n}`) } } } fall('planung', 'Grundlinie: Start nach den Voraussetzungen, ohne Wunsch zwei gleichzeitig', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, fehler: f, protokoll: p } = await PL(PL_ARGS, {}) soll(f === null, 'Fehler: ' + f) soll(e && e.status === 'fertig' && e.geschrieben.length === 7, 'status ' + (e && e.status)) voraussetzungenEingehalten(p, DAG, soll) soll(spitze(p, 'Taskdateien', 'opus') === 2, 'gleichzeitig: ' + spitze(p, 'Taskdateien', 'opus')) const ab = mitLabel(p, 'verbrauch:').map(x => x.label) soll(ab[0] === 'verbrauch:planung:Anfang' && ab[ab.length - 1] === 'verbrauch:planung:Ende' && ab.includes('verbrauch:planung:nach 4 Tasks'), 'Ablesungen: ' + JSON.stringify(ab)) soll(e && e.plaetze_verlauf[0] === 2, 'plaetze_verlauf ' + JSON.stringify(e && e.plaetze_verlauf)) soll(p.agents.filter(x => x.label.startsWith('planer:')).every(x => x.modell === 'opus' && x.agentType === 'planer'), 'planer nicht auf opus/planer') ohneVerstoss(p, soll) return fehler }) fall('planung', 'Wunsch vier: drei bis vier gleichzeitig, Voraussetzungen halten', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PL({ ...PL_ARGS, plaetze: 4 }, {}) const s = spitze(p, 'Taskdateien', 'opus') soll(s >= 3 && s <= 4, 'gleichzeitig: ' + s) soll(e.status === 'fertig' && e.plaetze_verlauf[0] === 4, JSON.stringify([e.status, e.plaetze_verlauf])) voraussetzungenEingehalten(p, DAG, soll) return fehler }) const FREI = [1, 2, 3, 4, 5, 6, 7, 8].map(n => T(n, [], 1)) fall('planung', 'Nachlesen senkt die Plätze: danach startet keiner neben zwei anderen', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PL({ ...PL_ARGS, tasks: FREI, plaetze: 4, nachlesen_alle: 2 }, { ablesungen: [ablesung(10, 10), ablesung(60, 10), ablesung(60, 10), ablesung(60, 10), ablesung(60, 10)] }) const zweite = mitLabel(p, 'verbrauch:')[1] const danach = mitLabel(p, 'planer:').filter(a => a.start > zweite.start) soll(danach.length > 0, 'nach der zweiten Ablesung startete kein Planer') soll(danach.every(a => laufendBeiStart(p, a, 'planer:') <= 1), 'Start neben zwei oder mehr: ' + JSON.stringify(danach.map(a => [a.label, laufendBeiStart(p, a, 'planer:')]))) soll(e.status === 'fertig' && JSON.stringify(e.plaetze_verlauf.slice(0, 2)) === '[4,2]', JSON.stringify([e.status, e.plaetze_verlauf])) return fehler }) fall('planung', 'Nachlesen hebt die Plätze: danach laufen mehr als zwei', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PL({ ...PL_ARGS, tasks: FREI, plaetze: 4, nachlesen_alle: 2 }, { ablesungen: [ablesung(60, 10), ablesung(10, 10), ablesung(10, 10), ablesung(10, 10)] }) const zweite = mitLabel(p, 'verbrauch:')[1] const danach = mitLabel(p, 'planer:').filter(a => a.start > zweite.start) soll(danach.some(a => laufendBeiStart(p, a, 'planer:') >= 2), 'nach dem Heben nie mehr als zwei gleichzeitig') soll(e.status === 'fertig' && JSON.stringify(e.plaetze_verlauf.slice(0, 2)) === '[2,4]', JSON.stringify([e.status, e.plaetze_verlauf])) return fehler }) fall('planung', 'gebremst schon am Anfang: kein Planer, Status gebremst', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PL(PL_ARGS, { ablesungen: [ablesung(90, 10)] }) soll(mitLabel(p, 'planer:').length === 0, 'Planer trotz Bremse') soll(e.status === 'gebremst' && e.offen.length === 7, JSON.stringify([e.status, e.offen])) soll(/nicht mit resumeFromRunId/.test(e.naechster_schritt), 'naechster_schritt ' + e.naechster_schritt) return fehler }) fall('planung', 'Startreihenfolge folgt Welle und Nummer, nicht der Liste', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await PL({ ...PL_ARGS, tasks: [T(3, [], 2), T(2, [], 1), T(1, [], 1)], plaetze: 1 }, {}) const folge = mitLabel(p, 'planer:').sort((x, y) => x.start - y.start).map(a => a.label) soll(JSON.stringify(folge) === '["planer:task-01","planer:task-02","planer:task-03"]', 'Folge ' + JSON.stringify(folge)) return fehler }) fall('planung', 'kein Nachlesen, wenn kein Task mehr offen ist', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await PL({ ...PL_ARGS, tasks: FREI.slice(0, 3), plaetze: 4, nachlesen_alle: 1 }, {}) const ab = mitLabel(p, 'verbrauch:').map(x => x.label) soll(JSON.stringify(ab) === '["verbrauch:planung:Anfang","verbrauch:planung:Ende"]', 'Ablesungen: ' + JSON.stringify(ab)) return fehler }) fall('planung', 'Ergebnis mit fremder Datei gilt als gescheitert', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PL(PL_ARGS, { planerDatei: (nr, richtig) => (nr === 2 ? 'D:/x/plan/task-99-t.md' : richtig) }) soll(e.gescheitert.includes(2) && e.blockiert.includes(5) && !planerVon(p, 5), JSON.stringify([e.gescheitert, e.blockiert])) soll(p.logs.some(l => /statt task-02-t\.md/.test(l)), 'kein Hinweis auf die fremde Datei') return fehler }) fall('planung', 'Ergebnis mit ähnlichem Dateinamen gilt als gescheitert', async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await PL(PL_ARGS, { planerDatei: (nr, richtig) => (nr === 7 ? 'D:/x/plan/xtask-07-t.md' : richtig) }) soll(JSON.stringify(e.gescheitert) === '[7]', JSON.stringify(e.gescheitert)) return fehler }) fall('planung', 'Nachlesen im Takt von nachlesen_alle, solange Tasks offen sind', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await PL({ ...PL_ARGS, tasks: FREI, plaetze: 2, nachlesen_alle: 2 }, { planerDauer: () => 100 }) const ab = mitLabel(p, 'verbrauch:').map(x => x.label.slice('verbrauch:planung:'.length)) soll(JSON.stringify(ab) === '["Anfang","nach 2 Tasks","nach 4 Tasks","nach 6 Tasks","Ende"]', 'Ablesungen: ' + JSON.stringify(ab)) return fehler }) fall('planung', '5 Stunden bei 60 %: höchstens zwei gleichzeitig', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PL({ ...PL_ARGS, plaetze: 4 }, { ablesungen: [ablesung(60, 10), ablesung(60, 10)] }) soll(spitze(p, 'Taskdateien', 'opus') === 2, 'gleichzeitig: ' + spitze(p, 'Taskdateien', 'opus')) soll(e.status === 'fertig', 'status ' + e.status) voraussetzungenEingehalten(p, DAG, soll) return fehler }) fall('planung', 'Bremse beim Nachlesen: kein Start mehr, laufende enden', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PL({ ...PL_ARGS, nachlesen_alle: 2 }, { ablesungen: [ablesung(10, 10), ablesung(90, 10)] }) soll(e.status === 'gebremst' && e.offen.length > 0, JSON.stringify([e.status, e.offen])) const zweite = mitLabel(p, 'verbrauch:')[1] soll(zweite && p.agents.filter(x => x.label.startsWith('planer:') && x.start > zweite.start).length === 0, 'Planer nach der Bremse gestartet') soll(p.agents.filter(x => x.label.startsWith('planer:')).every(x => x.ende !== null), 'laufender Planer nicht beendet') return fehler }) fall('planung', 'erste Voraussetzung scheitert: abhängige blockiert, unabhängige laufen', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PL(PL_ARGS, { planer: nr => (nr === 1 ? OHNE : undefined) }) soll(e.status === 'unvollstaendig', 'status ' + e.status) soll(JSON.stringify(e.gescheitert) === '[1]' && JSON.stringify(e.blockiert) === '[2,3,4,5,6]', JSON.stringify([e.gescheitert, e.blockiert])) soll(e.geschrieben.map(g => g.nr).join() === '7', 'geschrieben ' + e.geschrieben.map(g => g.nr)) soll([2, 3, 4, 5, 6].every(n => !planerVon(p, n)), 'blockierter Task gestartet') return fehler }) fall('planung', 'letzte Tasks scheitern: nichts blockiert', async () => { const { fehler, soll } = pruefe() const { ergebnis: e } = await PL(PL_ARGS, { planer: nr => (nr === 6 || nr === 7 ? OHNE : undefined) }) soll(e.status === 'unvollstaendig' && JSON.stringify(e.gescheitert.slice().sort()) === '[6,7]' && e.blockiert.length === 0, JSON.stringify([e.status, e.gescheitert, e.blockiert])) soll(e.geschrieben.length === 5, 'geschrieben ' + e.geschrieben.length) return fehler }) fall('planung', 'erledigt: Task 1 startet nicht, die abhängigen schon', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PL({ ...PL_ARGS, erledigt: [1] }, {}) soll(!planerVon(p, 1), 'Task 1 gestartet') soll(e.status === 'fertig' && e.geschrieben.length === 6, JSON.stringify([e.status, e.geschrieben.length])) return fehler }) fall('planung', 'drei Planer ohne Ergebnis: Abbruch, kein weiterer Start', async () => { const { fehler, soll } = pruefe() const frei = [1, 2, 3, 4, 5, 6].map(n => T(n, [], 1)) const { ergebnis: e, protokoll: p } = await PL({ ...PL_ARGS, tasks: frei, plaetze: 2 }, { planer: nr => (nr <= 3 ? OHNE : undefined), planerDauer: nr => (nr <= 3 ? 5 : 60) }) soll(e.status === 'abgebrochen', 'status ' + e.status) soll(!planerVon(p, 5) && !planerVon(p, 6) && JSON.stringify(e.offen) === '[5,6]', 'offen ' + JSON.stringify(e.offen)) return fehler }) for (const [name, aendern, text] of [ ['Voraussetzung ins Leere', a => ({ ...a, tasks: [T(1, [], 1), T(2, [99], 2)] }), /Voraussetzung 99 gibt es nicht/], ['Zyklus', a => ({ ...a, tasks: [T(1, [2], 1), T(2, [1], 2)] }), /Zyklus/], ['Selbstbezug', a => ({ ...a, tasks: [T(1, [1], 1)] }), /Zyklus/], ['doppelte Nummer', a => ({ ...a, tasks: [T(1, [], 1), T(1, [], 1)] }), /doppelt in tasks/], ['doppelte Datei', a => ({ ...a, tasks: [T(1, [], 1), { ...T(2, [], 1), datei: T(1, [], 1).datei }] }), /bei zwei Tasks/], ['doppelte Voraussetzung', a => ({ ...a, tasks: [T(1, [], 1), T(2, [1, 1], 2)] }), /Voraussetzung steht doppelt/], ['modell unbekannt', a => ({ ...a, tasks: [{ ...T(1, [], 1), modell: 'gpt' }] }), /modell "gpt"/], ['erledigt ins Leere', a => ({ ...a, erledigt: [99] }), /erledigt muss/], ['plaetze 5', a => ({ ...a, plaetze: 5 }), /plaetze muss/], ['nachlesen_alle 0', a => ({ ...a, nachlesen_alle: 0 }), /nachlesen_alle muss/], ['tasks leer', a => ({ ...a, tasks: [] }), /nicht leere Liste/], ['nr als Text', a => ({ ...a, tasks: [{ ...T(1, [], 1), nr: '1' }] }), /nr muss/], ['voraussetzungen fehlen', a => ({ ...a, tasks: [{ ...T(1, [], 1), voraussetzungen: undefined }] }), /voraussetzungen muss/], ['gemeinsam fehlt', a => ({ ...a, gemeinsam: '' }), /args fehlen: gemeinsam/], ]) { fall('planung', `${name} wirft vor dem ersten Agenten`, async () => { const { fehler, soll } = pruefe() const { fehler: f, protokoll: p } = await PL(aendern(PL_ARGS), {}) soll(f && text.test(f), 'Fehler: ' + f) soll(p.agents.length === 0, 'Agent gestartet') return fehler }) } fall('planung', 'Auftrag nennt die Voraussetzungen und ersetzt alle Platzhalter', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await PL(PL_ARGS, {}) const t5 = planerVon(p, 5).prompt soll(t5.includes('GEMEINSAM') && t5.includes('task-02-t.md') && t5.includes('task-03-t.md') && t5.includes('D:/x/plan/task-05-t.md'), 'Auftrag Task 5 unvollständig') soll(!/\{(NR|THEMA|DATEI|MODELL|WELLE|VORAUSSETZUNGEN)\}/.test(t5), 'Platzhalter übrig') soll(/Lies sie, bevor du einen Namen, eine Signatur oder einen Import daraus übernimmst/.test(t5), 'Lesehinweis fehlt') soll(!/Lies sie/.test(planerVon(p, 1).prompt), 'Lesehinweis ohne Voraussetzungen') soll(/vor: \(keine\)/.test(planerVon(p, 1).prompt), 'Task 1 ohne "(keine)"') return fehler }) fall('planung', 'alles erledigt: kein Agent, keine Ablesung', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, protokoll: p } = await PL({ ...PL_ARGS, erledigt: DAG.map(t => t.nr) }, {}) soll(p.agents.length === 0 && e.status === 'fertig' && e.verbrauch.length === 0, JSON.stringify([p.agents.length, e.status])) return fehler }) fall('planung', 'Ablesung ohne Ergebnis: höchstens zwei gleichzeitig', async () => { const { fehler, soll } = pruefe() const { protokoll: p } = await PL({ ...PL_ARGS, plaetze: 4 }, { ablesungen: [OHNE, OHNE] }) soll(spitze(p, 'Taskdateien', 'opus') === 2, 'gleichzeitig: ' + spitze(p, 'Taskdateien', 'opus')) return fehler }) fall('planung', 'als Kind: Rückfall auf zwei, Planung läuft durch', async () => { const { fehler, soll } = pruefe() const { ergebnis: e, fehler: f, protokoll: p } = await PL({ ...PL_ARGS, plaetze: 4 }, {}, { tiefe: 1 }) soll(f === null && e.status === 'fertig', 'Fehler/status: ' + f + ' ' + (e && e.status)) soll(spitze(p, '▸ planung', 'opus') === 2, 'gleichzeitig: ' + spitze(p, '▸ planung', 'opus')) return fehler }) // ---------------------------------------------------------------- Lauf let gescheitert = 0 const grundlinieGescheitert = new Set() for (const f of faelle) { let fehler try { fehler = await f.lauf() } catch (e) { fehler = ['Absturz im Fall: ' + (e && e.stack || e)] } if (f.name.startsWith('Grundlinie') && fehler.length) grundlinieGescheitert.add(f.workflow) if (fehler.length) { gescheitert++ console.log(`FEHLER ${f.workflow}: ${f.name}\n ${fehler.join('\n ')}`) } else console.log(`OK ${f.workflow}: ${f.name}`) } for (const w of grundlinieGescheitert) console.log(`HINWEIS Grundlinie von ${w} gescheitert: Die übrigen Fälle dieses Workflows messen nichts.`) rmSync(TMP, { recursive: true, force: true }) console.log(`${faelle.length} Fälle, ${gescheitert} gescheitert (Ordner ${ORDNER})`) process.exit(gescheitert ? 1 : 0)