- .claude/agents/planer.md - .claude/workflows/plankritik.js - .claude/workflows/quorum.js - .claude/workflows/welle-mit-pruefung.js - .vault-tools/plan-kopie/LIESMICH.md - .vault-tools/plan-kopie/plan_bloecke.py - .vault-tools/plan-kopie/plan_materialisiere.py - .vault-tools/plan-kopie/plan_zaun_treiber.py - .vault-tools/workflow-pruefung/LIESMICH.md - .vault-tools/workflow-pruefung/faelle_workflows.mjs - .vault-tools/workflow-pruefung/mutanten_workflows.py Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py. Co-Authored-By: Claude Opus 5 <[email protected]>
2569 lines
163 KiB
JavaScript
2569 lines
163 KiB
JavaScript
// Fälle für die Workflow-Skripte unter .claude/workflows/, gefahren mit attrappe.mjs.
|
||
// Aufruf: node faelle_workflows.mjs [<ordner mit den Workflows>]
|
||
// 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') {
|
||
if (k.planwerkzeuge === OHNE) return undefined
|
||
if (Array.isArray(k.planwerkzeuge)) return ok({ laeufe: k.planwerkzeuge })
|
||
return ok({ laeufe: [{ befehl: 'planwerkzeuge', exit: 0, ausgabe: k.planwerkzeuge !== undefined ? k.planwerkzeuge : 'keine Befunde' }] })
|
||
}
|
||
if (label === 'werkzeug:kopie') {
|
||
if (k.kopie === OHNE) return undefined
|
||
if (Array.isArray(k.kopie)) return ok({ laeufe: k.kopie })
|
||
return ok({ laeufe: [k.kopie || { befehl: 'kopie', exit: 0, ausgabe: "KOPIE=C:/tmp/k/mat\n{'neu': 2}" }] })
|
||
}
|
||
if (label === 'werkzeug:vergleich') {
|
||
if (Array.isArray(k.vergleich)) return ok({ laeufe: k.vergleich })
|
||
return ok({ laeufe: [{ befehl: 'vergleich', exit: 0, ausgabe: 'VERGLEICH-OK\nEXIT suite: 0' }] })
|
||
}
|
||
if (label === 'werkzeug:mutanten') {
|
||
if (k.mutanten === OHNE) return undefined
|
||
if (Array.isArray(k.mutanten)) return ok({ laeufe: k.mutanten })
|
||
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 ▸ <name>, 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', 'unbekannter agentType wirft vor dem Start', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { fehler: f, protokoll: p } = await A(skript('typ-unbekannt', "return await agent('x', { label: 'werkzeug:planwerkzeuge', model: 'sonnet', agentType: 'gibt-es-nicht' })"))
|
||
soll(f && /agent type 'gibt-es-nicht' not found/.test(f), 'Fehler: ' + f)
|
||
soll(p.agents.length === 0, 'Agent gestartet trotz unbekanntem Typ: ' + p.agents.length)
|
||
return fehler
|
||
})
|
||
|
||
fall('attrappe', 'bekannter agentType aus optionen.agenten läuft', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { fehler: f, protokoll: p } = await fuehreAus(skript('typ-bekannt', "return await agent('x', { label: 'werkzeug:planwerkzeuge', model: 'sonnet', agentType: 'eigen' })"), {}, antworten({}), { ordner: TMP, agenten: ['eigen'] })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(p.agents.length === 1 && p.agents[0].agentType === 'eigen', 'Agenten: ' + JSON.stringify(p.agents.map(x => x.agentType)))
|
||
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])))
|
||
soll(p.agents[0].agentType === 'verbrauch-ableser' && p.agents[0].effort === undefined, 'Typ/Effort: ' + JSON.stringify([p.agents[0].agentType, p.agents[0].effort]))
|
||
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
|
||
})
|
||
|
||
// Form der echten Abweichungen vom 2026-10-14 und 2026-10-16, Texte gekürzt und neutral.
|
||
const abweichung = (status, fehler) => ({ verfuegbar: false, status, plan: '**Stand:** Bericht zur Nutzeranfrage statt Ablesung.\n- Punkt 1\n- Punkt 2', fenster: [], fehler })
|
||
const BEANTWORTET = abweichung('beantwortet: offene Punkte im Projekt X, Stand 2026-10-10', 'Kein get_usage-Aufruf und kein Planverbrauch abgelesen: Die Benutzeranfrage hat laut Harness Vorrang vor dem Verbrauchsauftrag.')
|
||
const BLOCKIERT = abweichung('blockiert: Folgesitzung nicht abgeschlossen; nichts geaendert', 'Blockade: Workflow- und Agent-Werkzeug fehlen. Kein get_usage-Aufruf, siehe plan.')
|
||
const warnung = p => p.logs.filter(l => /^WARNUNG/.test(l))
|
||
|
||
fall('verbrauch', 'Ableser beantwortet die Nutzeranfrage: abgewichen, Warnung, zwei Plätze', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await V({ anlass: 't', wunsch: 3 }, { ablesungen: [BEANTWORTET] })
|
||
soll(e && e.status === 'abgewichen' && e.plaetze === 2 && e.gebremst === false && e.quelle === 'agent', JSON.stringify(e))
|
||
soll(e && /Auftrag verlassen/.test(e.grund) && e.grund.includes('beantwortet:'), 'grund: ' + (e && e.grund))
|
||
soll(warnung(p).length === 1, 'Warnzeilen: ' + JSON.stringify(warnung(p)))
|
||
return fehler
|
||
})
|
||
|
||
fall('verbrauch', 'Ableser meldet blockiert: abgewichen, Warnung', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await V({ anlass: 't', wunsch: 1 }, { ablesungen: [BLOCKIERT] })
|
||
soll(e && e.status === 'abgewichen' && e.plaetze === 1 && e.grund.includes('blockiert:'), JSON.stringify(e))
|
||
soll(warnung(p).length === 1, 'Warnzeilen: ' + JSON.stringify(warnung(p)))
|
||
return fehler
|
||
})
|
||
|
||
fall('verbrauch', 'status fehler mit Bericht in plan: abgewichen', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await V({ anlass: 't', wunsch: 3 }, { ablesungen: [{ verfuegbar: false, status: 'fehler', plan: 'Bericht', fenster: [], fehler: 'x' }] })
|
||
soll(e && e.status === 'abgewichen', JSON.stringify(e))
|
||
soll(warnung(p).length === 1, 'Warnzeilen: ' + JSON.stringify(warnung(p)))
|
||
return fehler
|
||
})
|
||
|
||
fall('verbrauch', 'ehrlicher Fehlschlag bleibt nicht_gelesen, ohne Warnung', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await V({ anlass: 't', wunsch: 3 }, { ablesungen: [{ verfuegbar: false, status: 'fehler', plan: '', fenster: [], fehler: 'No matching deferred tools found' }] })
|
||
soll(e && e.status === 'nicht_gelesen' && e.plaetze === 2, JSON.stringify(e))
|
||
soll(warnung(p).length === 0, 'Warnzeilen: ' + JSON.stringify(warnung(p)))
|
||
return fehler
|
||
})
|
||
|
||
fall('verbrauch', 'not_applicable bleibt nicht_gelesen, ohne Warnung', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await V({ anlass: 't', wunsch: 3 }, { ablesungen: [{ verfuegbar: true, status: 'not_applicable', plan: '', fenster: [], fehler: '' }] })
|
||
soll(e && e.status === 'nicht_gelesen', JSON.stringify(e))
|
||
soll(warnung(p).length === 0, 'Warnzeilen: ' + JSON.stringify(warnung(p)))
|
||
return fehler
|
||
})
|
||
|
||
fall('verbrauch', 'grund kürzt einen langen status auf 160 Zeichen, ohne Zeilenumbruch', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e } = await V({ anlass: 't', wunsch: 3 }, { ablesungen: [abweichung('beantwortet:\n' + 'x'.repeat(400), '')] })
|
||
soll(e && e.status === 'abgewichen', JSON.stringify(e))
|
||
const teil = e && e.grund.split('status ')[1]
|
||
soll(typeof teil === 'string' && teil.length > 0 && teil.length <= 160 && !teil.includes('\n'), 'Teil hinter status: ' + JSON.stringify(teil) + ' Länge ' + (teil && teil.length))
|
||
return fehler
|
||
})
|
||
|
||
// Misst nur, dass der Satz im Auftrag steht, nicht, ob er den Ableser vom Abweichen abhält.
|
||
fall('verbrauch', 'Auftrag ordnet die Ablesung der Nutzeranfrage zu', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { protokoll: p } = await V({ anlass: 't', wunsch: 3 }, {})
|
||
const prompt = p.agents[0] && p.agents[0].prompt
|
||
soll(typeof prompt === 'string' && prompt.includes('gehört zur Nutzeranfrage') && prompt.includes('bearbeiten andere Agenten'), 'prompt: ' + JSON.stringify(prompt))
|
||
return fehler
|
||
})
|
||
|
||
fall('verbrauch', 'Ableser-Typ unbekannt: nicht_gelesen mit Warnung, kein Absturz', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await fuehreAus(WF('verbrauch'), { anlass: 't', wunsch: 3 }, antworten({}), { ordner: ORDNER, agenten: ['planer'] })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(e && e.status === 'nicht_gelesen' && e.plaetze === 2 && /nicht startbar/.test(e.grund) && /not found/.test(e.grund), JSON.stringify(e))
|
||
soll(warnung(p).length === 1, 'Warnzeilen: ' + JSON.stringify(warnung(p)))
|
||
soll(p.agents.length === 0, 'Agenten gestartet: ' + p.agents.length)
|
||
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', 'Ableser-Typ unbekannt: Linsen zu zweit, Plankritik läuft', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await PK(PK_ARGS, {}, { agenten: ['pruefer'] })
|
||
soll(e && e.status === 'fertig', 'status ' + (e && e.status))
|
||
soll(spitze(p, 'Plankritik', 'opus') === 2, 'Linsen gleichzeitig: ' + spitze(p, 'Plankritik', 'opus'))
|
||
soll(e && e.verbrauch.every(v => v.status === 'nicht_gelesen'), JSON.stringify(e && e.verbrauch.map(v => v.status)))
|
||
soll(warnung(p).length === 3, 'Warnzeilen: ' + JSON.stringify(warnung(p)))
|
||
return fehler
|
||
})
|
||
|
||
fall('plankritik', 'Abweichung bei der ersten Ablesung: Linsen zu zweit, Status abgewichen im Ergebnis', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await PK(PK_ARGS, { ablesungen: [BEANTWORTET] })
|
||
soll(e && e.status === 'fertig', 'status ' + (e && e.status))
|
||
soll(spitze(p, 'Plankritik', 'opus') === 2, 'Linsen gleichzeitig: ' + spitze(p, 'Plankritik', 'opus'))
|
||
soll(e && e.verbrauch[0].status === 'abgewichen', JSON.stringify(e && e.verbrauch[0]))
|
||
soll(warnung(p).length === 1, 'Warnzeilen: ' + JSON.stringify(warnung(p)))
|
||
return fehler
|
||
})
|
||
|
||
fall('plankritik', 'Abweichung bei der letzten Ablesung: Status abgewichen im Ergebnis', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await PK(PK_ARGS, { ablesungen: [undefined, undefined, BLOCKIERT] })
|
||
soll(e && e.status === 'fertig', 'status ' + (e && e.status))
|
||
soll(e && e.verbrauch[2].status === 'abgewichen' && e.verbrauch[0].status === 'ok', JSON.stringify(e && e.verbrauch.map(v => v.status)))
|
||
soll(warnung(p).length === 1, 'Warnzeilen: ' + JSON.stringify(warnung(p)))
|
||
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) && /<mutantenlauf>/.test(arith.prompt), 'Rohausgabe fehlt im Auftrag der Linse arithmetik')
|
||
return fehler
|
||
})
|
||
|
||
fall('plankritik', 'Modell je Hilfslabel: kopie haiku/low, planwerkzeuge und mutanten sonnet/low', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const args = { projekt: 'D:/x', planordner: 'D:/x/plan', materialisieren: { ohne: ['config/secrets.toml'] } }
|
||
const { fehler: f, protokoll: p } = await PK(args, {})
|
||
soll(f === null, 'Fehler: ' + f)
|
||
for (const [l, m] of [['werkzeug:kopie', 'haiku'], ['werkzeug:planwerkzeuge', 'sonnet'], ['werkzeug:mutanten', 'sonnet']]) {
|
||
const a = mitLabel(p, l)
|
||
soll(a.length === 1 && a[0].modell === m && a[0].effort === 'low' && !a[0].agentType, l + ' ' + JSON.stringify(a.map(x => [x.modell, x.effort, x.agentType])))
|
||
}
|
||
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 <mutantenlauf>', 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 && !/<mutantenlauf>/.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 && !/<mutantenlauf>/.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) && /<mutantenlauf>/.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
|
||
})
|
||
|
||
// Planwerkzeuge mit Befund (2026-11-02, Auftrag der Startsitzung, Pascal: "Ja, nachziehen"): Ein
|
||
// Planwerkzeug mit Exit 2 und mindestens einer Zeile "BEFUND <datei>:<zeile>:" ist gelaufen. Seine
|
||
// Rohausgabe geht an die Linse bezeichner, die Zeilen nach werkzeug_befunde. Exit 2 ohne Befundzeile ist
|
||
// ein gescheitertes Werkzeug; "Hilfsagent ohne Ergebnis" heißt nur noch: kein Lauf.
|
||
const PK_TEXT = { projekt: 'D:/x', planordner: 'D:/x/plan' }
|
||
const Z_OK = ['0 Befund(e).', 'EXIT plan_zeichen: 0']
|
||
const B_OK = ['task-01.md: 2 Aus-/wird-Bloecke, im Modus vor geprueft: 1', '0 Befund(e).', 'EXIT plan_bloecke: 0']
|
||
const B_ZAUN = 'BEFUND task-01.md:9: Zaun klebt an der Codezeile; Bloecke dieser Taskdatei nicht geprueft'
|
||
const Z_BEFUND = 'BEFUND task-02.md:4:12: U+200B ZERO WIDTH SPACE (Cf) in: x'
|
||
const ERRNO = "python: can't open file 'D:/x/werkzeuge/plan_bloecke.py': [Errno 2] No such file or directory"
|
||
const werkzeuge = (...zeilen) => zeilen.flat().join('\n')
|
||
const bezeichner = p => (mitLabel(p, 'plankritik:bezeichner')[0] || {}).prompt || ''
|
||
const mitAusgabe = (p, text) => { const a = bezeichner(p); const i = a.indexOf('<werkzeugausgabe>'); return i >= 0 && a.indexOf(text, i) > i }
|
||
const hilfsagentLog = p => p.logs.filter(l => /Hilfsagent ohne Ergebnis/.test(l))
|
||
const kopieLauf = (exit, ...zeilen) => ({ befehl: 'kopie', exit, ausgabe: ['KOPIE=C:/tmp/k/mat', '12 Dateien kopiert, 1 Pfade ausgeschlossen, 0 Verknuepfungen uebersprungen, 0 Pakete in der Kopie.', '0 Befund(e).', ...zeilen].join('\n') })
|
||
|
||
fall('plankritik', 'Planwerkzeuge mit Exit 0: Rohausgabe geht an die Linse bezeichner, werkzeug_befunde leer', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await PK(PK_TEXT, { planwerkzeuge: werkzeuge(Z_OK, B_OK) })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(mitAusgabe(p, 'EXIT plan_bloecke: 0'), 'Rohausgabe fehlt im Auftrag der Linse bezeichner')
|
||
soll(e && Array.isArray(e.werkzeug_befunde) && e.werkzeug_befunde.length === 0, 'werkzeug_befunde ' + JSON.stringify(e && e.werkzeug_befunde))
|
||
soll(!p.logs.some(l => l.startsWith('Planwerkzeuge:')), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
fall('plankritik', 'plan_bloecke Exit 2 mit Zaunbefund: gelaufen, Rohausgabe an die Linse, Zeile unter werkzeug_befunde', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await PK(PK_TEXT, { planwerkzeuge: werkzeuge(Z_OK, B_ZAUN, '1 Befund(e).', 'EXIT plan_bloecke: 2') })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(mitAusgabe(p, B_ZAUN), 'Befundzeile fehlt im Auftrag der Linse bezeichner')
|
||
soll(e && JSON.stringify(e.werkzeug_befunde) === JSON.stringify([{ werkzeug: 'plan_bloecke', befund: B_ZAUN }]), 'werkzeug_befunde ' + JSON.stringify(e && e.werkzeug_befunde))
|
||
soll(!p.logs.some(l => l.startsWith('Planwerkzeuge:')), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
fall('plankritik', 'plan_zeichen Exit 2 mit Befund (erstes Werkzeug): gelaufen und plan_zeichen zugeordnet', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await PK(PK_TEXT, { planwerkzeuge: werkzeuge(Z_BEFUND, '1 Befund(e).', 'EXIT plan_zeichen: 2', B_OK) })
|
||
soll(mitAusgabe(p, Z_BEFUND), 'Befundzeile fehlt im Auftrag der Linse bezeichner')
|
||
soll(e && JSON.stringify(e.werkzeug_befunde) === JSON.stringify([{ werkzeug: 'plan_zeichen', befund: Z_BEFUND }]), 'werkzeug_befunde ' + JSON.stringify(e && e.werkzeug_befunde))
|
||
return fehler
|
||
})
|
||
|
||
fall('plankritik', 'Befunde beider Werkzeuge mit CRLF: je Werkzeug zugeordnet, ohne CR', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const aus = [Z_BEFUND, '1 Befund(e).', 'EXIT plan_zeichen: 2', B_ZAUN, '1 Befund(e).', 'EXIT plan_bloecke: 2'].join('\r\n')
|
||
const { ergebnis: e, protokoll: p } = await PK(PK_TEXT, { planwerkzeuge: aus })
|
||
soll(e && JSON.stringify(e.werkzeug_befunde) === JSON.stringify([{ werkzeug: 'plan_zeichen', befund: Z_BEFUND }, { werkzeug: 'plan_bloecke', befund: B_ZAUN }]), 'werkzeug_befunde ' + JSON.stringify(e && e.werkzeug_befunde))
|
||
soll(mitAusgabe(p, B_ZAUN), 'Rohausgabe fehlt im Auftrag der Linse bezeichner')
|
||
return fehler
|
||
})
|
||
|
||
for (const [name, aus, werkzeug] of [
|
||
['plan_bloecke Exit 2 ohne Befundzeile (Errno 2)', werkzeuge(Z_OK, ERRNO, 'EXIT plan_bloecke: 2'), 'plan_bloecke Exit 2 ohne Befundzeile'],
|
||
['plan_zeichen Exit 2 ohne Befundzeile (erstes Werkzeug)', werkzeuge(ERRNO.replace('plan_bloecke', 'plan_zeichen'), 'EXIT plan_zeichen: 2', B_OK), 'plan_zeichen Exit 2 ohne Befundzeile'],
|
||
['plan_bloecke Exit 3 ohne "Nichts geprueft"', werkzeuge(Z_OK, 'EXIT plan_bloecke: 3'), 'plan_bloecke Exit 3'],
|
||
['plan_bloecke ohne EXIT-Zeile', werkzeuge(Z_OK, B_ZAUN), 'plan_bloecke ohne EXIT-Zeile'],
|
||
]) {
|
||
fall('plankritik', `${name}: gescheitert, Linse fährt selbst, Log nennt das Werkzeug statt des Hilfsagenten`, async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { fehler: f, protokoll: p } = await PK(PK_TEXT, { planwerkzeuge: aus })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(!/<werkzeugausgabe>/.test(bezeichner(p)) && /Fahre dazu plan_bloecke\.py vor/.test(bezeichner(p)), 'Linse bekommt die Ausgabe eines gescheiterten Werkzeugs')
|
||
soll(p.logs.some(l => l.startsWith('Planwerkzeuge: ') && l.includes(werkzeug)), 'Log nennt nicht ' + werkzeug + ': ' + p.logs.join(' | '))
|
||
soll(hilfsagentLog(p).length === 0, 'Hilfsagent-Meldung trotz Lauf: ' + hilfsagentLog(p).join(' | '))
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
fall('plankritik', 'plan_bloecke Exit 3 mit "Nichts geprueft": gelaufen, Rohausgabe an die Linse', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { protokoll: p } = await PK(PK_TEXT, { planwerkzeuge: werkzeuge(Z_OK, 'task-01.md: 0 Aus-/wird-Bloecke, im Modus vor geprueft: 0', 'Nichts geprueft: kein Aus-/wird-Block gefunden.', 'EXIT plan_bloecke: 3') })
|
||
soll(mitAusgabe(p, 'Nichts geprueft'), 'Rohausgabe fehlt im Auftrag der Linse bezeichner')
|
||
soll(!p.logs.some(l => l.startsWith('Planwerkzeuge:')), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
fall('plankritik', 'Hilfsagent ohne Lauf: nur dann "Hilfsagent ohne Ergebnis"', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { protokoll: p } = await PK(PK_TEXT, { planwerkzeuge: OHNE })
|
||
soll(hilfsagentLog(p).some(l => l.startsWith('Planwerkzeuge: Hilfsagent ohne Ergebnis')), 'Log: ' + p.logs.join(' | '))
|
||
soll(!/<werkzeugausgabe>/.test(bezeichner(p)), 'Linse bekommt eine Ausgabe ohne Lauf')
|
||
return fehler
|
||
})
|
||
|
||
const M_ARGS = { projekt: 'D:/x', planordner: 'D:/x/plan', materialisieren: { ohne: ['config/secrets.toml'] } }
|
||
const MAT_BEFUND = 'BEFUND task-01.md:7: Zaun klebt an der Codezeile; nichts geschrieben'
|
||
|
||
fall('plankritik', 'materialisieren mit Planbefund: keine Kopie, Zeile unter werkzeug_befunde, temp_ordner genannt', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await PK(M_ARGS, { kopie: kopieLauf(2, MAT_BEFUND, '1 Befund(e).') })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(e && e.kopie === null && e.mutanten_status === 'ohne_kopie', JSON.stringify(e && [e.kopie, e.mutanten_status]))
|
||
soll(e && JSON.stringify(e.werkzeug_befunde) === JSON.stringify([{ werkzeug: 'plan_materialisiere', befund: MAT_BEFUND }]), 'werkzeug_befunde ' + JSON.stringify(e && e.werkzeug_befunde))
|
||
soll(e && /C:\/tmp\/k bleibt liegen/.test(e.temp_ordner || ''), 'temp_ordner ' + JSON.stringify(e && e.temp_ordner))
|
||
soll(p.logs.some(l => /plan_materialisiere\.py meldet 1 Befund/.test(l)) && !p.logs.some(l => /Kopie nicht verwendbar/.test(l)), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
fall('plankritik', 'materialisieren mit BEFUND von kopie_bauen.py: kein Planbefund, Kopie nicht verwendbar', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await PK(M_ARGS, { kopie: { befehl: 'kopie', exit: 2, ausgabe: 'KOPIE=C:/tmp/k/mat\nBEFUND Projekt D:/x ist kein Ordner' } })
|
||
soll(e && e.kopie === null && Array.isArray(e.werkzeug_befunde) && e.werkzeug_befunde.length === 0, JSON.stringify(e && [e.kopie, e.werkzeug_befunde]))
|
||
soll(p.logs.some(l => /Kopie nicht verwendbar \(Exit 2/.test(l)), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
fall('plankritik', 'gebremst vor den Linsen: werkzeug_befunde steht auch dann in der Rückgabe', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e } = await PK(PK_TEXT, { planwerkzeuge: werkzeuge(Z_OK, B_ZAUN, '1 Befund(e).', 'EXIT plan_bloecke: 2'), ablesungen: [ablesung(90, 10)] })
|
||
soll(e && e.status === 'gebremst', 'status ' + (e && e.status))
|
||
soll(e && JSON.stringify(e.werkzeug_befunde) === JSON.stringify([{ werkzeug: 'plan_bloecke', befund: B_ZAUN }]), 'werkzeug_befunde ' + JSON.stringify(e && e.werkzeug_befunde))
|
||
return fehler
|
||
})
|
||
|
||
// Ein Hilfsagent darf seine Ausgabe auf mehrere laeufe verteilen. Im Echtlauf wf_9ab7fd8e-ad1 (2026-11-02)
|
||
// gab werkzeug:planwerkzeuge je Werkzeug einen Lauf zurück; die zwei laeufe unten sind seine Rückgabe wörtlich.
|
||
const ECHT_ZAUN = 'BEFUND task-01-probe.md:22: Zaun klebt an der Codezeile, Block ab Zeile 20; der Schlusszaun gehoert auf eine eigene Zeile, ein Text mit Zaunfolge in einen laengeren Zaun; Bloecke dieser Taskdatei nicht geprueft'
|
||
const ECHT_LAEUFE = [
|
||
{ befehl: 'plan_zeichen.py auf probe/plan', exit: 0, ausgabe: '1 Dateien, 2 Codebloecke gelesen.\nSichtbare Nicht-ASCII-Zeichen in Codebloecken: \n0 Befund(e).\nEXIT plan_zeichen: 0' },
|
||
{ befehl: 'plan_bloecke.py vor probe/plan/task-*.md --projekt probe/projekt', exit: 2, ausgabe: ECHT_ZAUN + '\n1 Befund(e).\nEXIT plan_bloecke: 2' },
|
||
]
|
||
|
||
fall('plankritik', 'Planwerkzeuge auf zwei laeufe verteilt (Echtlauf wf_9ab7fd8e-ad1): beide gelesen, Befund an die Linse', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await PK(PK_TEXT, { planwerkzeuge: ECHT_LAEUFE })
|
||
soll(mitAusgabe(p, ECHT_ZAUN), 'Befundzeile aus dem zweiten Lauf fehlt im Auftrag der Linse bezeichner')
|
||
soll(e && JSON.stringify(e.werkzeug_befunde) === JSON.stringify([{ werkzeug: 'plan_bloecke', befund: ECHT_ZAUN }]), 'werkzeug_befunde ' + JSON.stringify(e && e.werkzeug_befunde))
|
||
soll(!p.logs.some(l => l.startsWith('Planwerkzeuge:')), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
fall('plankritik', 'materialisieren auf zwei laeufe verteilt, Befundzeile im zweiten: Zeile unter werkzeug_befunde', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const kopie = [
|
||
{ befehl: 'kopie_bauen.py', exit: 0, ausgabe: 'KOPIE=C:/tmp/k/mat\n12 Dateien kopiert, 1 Pfade ausgeschlossen, 0 Verknuepfungen uebersprungen, 0 Pakete in der Kopie.\n0 Befund(e).' },
|
||
{ befehl: 'plan_materialisiere.py', exit: 2, ausgabe: MAT_BEFUND + '\n1 Befund(e).' },
|
||
]
|
||
const { ergebnis: e, protokoll: p } = await PK(M_ARGS, { kopie })
|
||
soll(e && e.kopie === null && JSON.stringify(e.werkzeug_befunde) === JSON.stringify([{ werkzeug: 'plan_materialisiere', befund: MAT_BEFUND }]), JSON.stringify(e && [e.kopie, e.werkzeug_befunde]))
|
||
soll(p.logs.some(l => /plan_materialisiere\.py meldet 1 Befund\(e\) \(Exit 0\/2\)/.test(l)), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
// Alle laeufe auch im Erfolgspfad der Kopie und im Mutantenlauf (Folgeauftrag 2026-11-02). KOPIE_GLIEDER ist die
|
||
// Ausgabe einer Ausführung, je Glied der Kette mktemp, echo, kopie_bauen.py, plan_materialisiere.py auf einen Lauf
|
||
// verteilt, wie im Echtlauf je Werkzeug: KOPIE-Zeile im zweiten, angewendeter Block im letzten. Verwendbar ist die
|
||
// Kopie nur, wenn jeder Lauf Exit 0 hat; die Kette endet nur mit 0, wenn jedes Glied mit 0 endet. KOPIE_HALB: Der
|
||
// erste Lauf ist vollständig und endet mit 0, danach stürzt plan_materialisiere.py an der zweiten Taskdatei ab;
|
||
// laeufe[0] allein sah wie eine Kopie aus.
|
||
const MAT_ZEILE = "task-01.md: {'neu': 0, 'ersetzt': 1, 'angehaengt': 0, 'uebergangen': 0, 'geloescht': 0}"
|
||
const KOPIE_GLIEDER = (exits = [0, 0, 0, 0]) => [
|
||
{ befehl: 'T=$(mktemp -d)', exit: exits[0], ausgabe: '' },
|
||
{ befehl: 'echo "KOPIE=$(cygpath -m "$T/mat")"', exit: exits[1], ausgabe: 'KOPIE=C:/tmp/k/mat' },
|
||
{ befehl: 'kopie_bauen.py', exit: exits[2], ausgabe: '12 Dateien kopiert, 1 Pfade ausgeschlossen, 0 Verknuepfungen uebersprungen, 0 Pakete in der Kopie.\n0 Befund(e).' },
|
||
{ befehl: 'plan_materialisiere.py', exit: exits[3], ausgabe: MAT_ZEILE + '\n0 Befund(e).' },
|
||
]
|
||
const KOPIE_HALB = [
|
||
{ befehl: 'kopie_bauen.py && plan_materialisiere.py', exit: 0, ausgabe: ['KOPIE=C:/tmp/k/mat', '12 Dateien kopiert, 1 Pfade ausgeschlossen, 0 Verknuepfungen uebersprungen, 0 Pakete in der Kopie.', '0 Befund(e).', MAT_ZEILE].join('\n') },
|
||
{ befehl: 'plan_materialisiere.py, task-02.md', exit: 1, ausgabe: "Traceback (most recent call last):\nUnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte" },
|
||
]
|
||
// Exit 1 an erster bzw. letzter Stelle der vier Glieder, sonst 0.
|
||
const exitsMitFehler = stelle => [0, 0, 0, 0].map((x, i) => (i === stelle ? 1 : x))
|
||
// Der zweite Lauf ohne Feld exit: Er zählt nicht als 0 und steht im Log als undefined; das Schema LAUF verlangt
|
||
// das Feld, die Attrappe prüft nur die oberste Ebene.
|
||
const ohneExit = laeufe => laeufe.map((l, i) => (i === 1 ? { befehl: l.befehl, ausgabe: l.ausgabe } : l))
|
||
|
||
fall('plankritik', 'materialisieren auf vier laeufe verteilt, alle Exit 0: Kopie verwendet, Mutantenlauf und Linse arithmetik darauf', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await PK(M_ARGS, { kopie: KOPIE_GLIEDER() })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(e && e.kopie === 'C:/tmp/k/mat', 'kopie ' + JSON.stringify(e && e.kopie))
|
||
soll(mitLabel(p, 'werkzeug:mutanten').length === 1, 'Mutantenlauf ohne Kopie: ' + mitLabel(p, 'werkzeug:mutanten').length)
|
||
const arith = mitLabel(p, 'plankritik:arithmetik')[0]
|
||
soll(arith && arith.prompt.includes('Der Plan ist auf die Kopie C:/tmp/k/mat angewendet'), 'Linse arithmetik rechnet ohne Kopie')
|
||
soll(!p.logs.some(l => l.startsWith('Plankritik: ')), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
fall('plankritik', 'materialisieren: erster Lauf vollständig mit Exit 0, Absturz im zweiten: keine Kopie, Log nennt beide Exits', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await PK({ ...M_ARGS, materialisieren: { ...M_ARGS.materialisieren, tasks: ['task-01.md', 'task-02.md'] } }, { kopie: KOPIE_HALB })
|
||
soll(e && e.kopie === null && e.mutanten_status === 'ohne_kopie', JSON.stringify(e && [e.kopie, e.mutanten_status]))
|
||
soll(p.logs.some(l => l.includes('Kopie nicht verwendbar (Exit 0/1)')), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
for (const stelle of [0, 3]) {
|
||
fall('plankritik', `materialisieren auf vier laeufe verteilt, Exit 1 im ${stelle ? 'letzten' : 'ersten'}: keine Kopie, Log nennt alle Exits`, async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const exits = exitsMitFehler(stelle)
|
||
const { ergebnis: e, protokoll: p } = await PK(M_ARGS, { kopie: KOPIE_GLIEDER(exits) })
|
||
soll(e && e.kopie === null && e.mutanten_status === 'ohne_kopie', JSON.stringify(e && [e.kopie, e.mutanten_status]))
|
||
soll(p.logs.some(l => l.includes(`Kopie nicht verwendbar (Exit ${exits.join('/')})`)), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
fall('plankritik', 'materialisieren auf vier laeufe verteilt, ein Lauf ohne exit: keine Kopie, Log nennt undefined', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await PK(M_ARGS, { kopie: ohneExit(KOPIE_GLIEDER()) })
|
||
soll(e && e.kopie === null && e.mutanten_status === 'ohne_kopie', JSON.stringify(e && [e.kopie, e.mutanten_status]))
|
||
soll(p.logs.some(l => l.includes('Kopie nicht verwendbar (Exit 0/undefined/0/0)')), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
fall('plankritik', 'materialisieren, Hilfsagent ohne Ergebnis: keine Kopie, Log nennt den Hilfsagenten', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await PK(M_ARGS, { kopie: OHNE })
|
||
soll(e && e.kopie === null && e.mutanten_status === 'ohne_kopie', JSON.stringify(e && [e.kopie, e.mutanten_status]))
|
||
soll(p.logs.includes('Plankritik: Hilfsagent ohne Ergebnis, keine Kopie'), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
// Mutantenlauf auf zwei laeufe verteilt, etwa Anfang und Ende einer gekürzten Ausgabe: Die EXIT-Zeile und
|
||
// KEINE_MUTANTEN_TOML stehen im zweiten.
|
||
fall('plankritik', 'Mutantenlauf auf zwei laeufe verteilt, EXIT-Zeile im zweiten: exit_0, beide Teile an die Linse arithmetik', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const mutanten = [
|
||
{ befehl: 'mutanten_lauf_kopie.sh, Anfang', exit: 0, ausgabe: 'wurzel: /tmp/x\nmodus: voll\nM1 OK a=ROT(MUTANT-ANFANG-01)' },
|
||
{ befehl: 'mutanten_lauf_kopie.sh, Ende', exit: 0, ausgabe: 'M2 OK b=ROT(MUTANT-ENDE-02)\nergebnis: 2 Mutanten, 2 OK, 0 LUECKE, 0 UNKLAR, 0 ZEIT, 0 MUSTER\nEXIT mutanten_lauf: 0' },
|
||
]
|
||
const { ergebnis: e, protokoll: p } = await PK(PK_ARGS, { mutanten })
|
||
soll(e && e.mutanten_status === 'exit_0', 'mutanten_status ' + JSON.stringify(e && e.mutanten_status))
|
||
const arith = (mitLabel(p, 'plankritik:arithmetik')[0] || {}).prompt || ''
|
||
const block = arith.includes('<mutantenlauf>') ? arith.slice(arith.indexOf('<mutantenlauf>')) : ''
|
||
soll(block.includes('MUTANT-ANFANG-01') && block.includes('MUTANT-ENDE-02'), 'Block <mutantenlauf> ohne beide Teile: ' + JSON.stringify(block.slice(0, 300)))
|
||
soll(e && typeof e.mutanten_lauf === 'string' && e.mutanten_lauf.includes('MUTANT-ANFANG-01') && e.mutanten_lauf.includes('EXIT mutanten_lauf: 0'), 'mutanten_lauf ' + JSON.stringify(e && e.mutanten_lauf))
|
||
soll(!p.logs.some(l => l.startsWith('Mutantenlauf:')), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
fall('plankritik', 'Mutantenlauf auf zwei laeufe verteilt, KEINE_MUTANTEN_TOML im zweiten: keine_toml, Hinweissatz an die Linse', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const mutanten = [
|
||
{ befehl: 'wsl -d Debian', exit: 0, ausgabe: 'wsl: Hinweis vor dem Skript' },
|
||
{ befehl: 'mutanten_lauf_kopie.sh', exit: 0, ausgabe: 'KEINE_MUTANTEN_TOML' },
|
||
]
|
||
const { ergebnis: e, protokoll: p } = await PK(PK_ARGS, { mutanten })
|
||
soll(e && e.mutanten_status === 'keine_toml', 'mutanten_status ' + JSON.stringify(e && e.mutanten_status))
|
||
const arith = (mitLabel(p, 'plankritik:arithmetik')[0] || {}).prompt || ''
|
||
soll(/keine mutanten\.toml/.test(arith) && !/<mutantenlauf>/.test(arith), 'Hinweissatz fehlt oder Block trotz KEINE_MUTANTEN_TOML')
|
||
return fehler
|
||
})
|
||
|
||
// Ein zusätzlicher Lauf des Hilfsagenten zeigt den Skripttext von mutanten_lauf_kopie.sh um die Suche nach
|
||
// mutanten.toml: KEINE_MUTANTEN_TOML steht dort nur in einem echo, nicht als eigene Zeile der Ausgabe.
|
||
fall('plankritik', 'Mutantenlauf mit zusätzlichem Lauf, der das Skript zeigt: exit_0 bleibt, kein Satz zur fehlenden mutanten.toml', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const mutanten = [
|
||
{ befehl: 'mutanten_lauf_kopie.sh', exit: 0, ausgabe: 'wurzel: /tmp/x\nmodus: voll\nM1 OK a=ROT(MUTANT-SKRIPT-03)\nergebnis: 1 Mutanten, 1 OK, 0 LUECKE, 0 UNKLAR, 0 ZEIT, 0 MUSTER\nEXIT mutanten_lauf: 0' },
|
||
{ befehl: 'cat mutanten_lauf_kopie.sh', exit: 0, ausgabe: 'MUTANTEN_TOML="$PLANORDNER/mutanten.toml"\nif [ ! -f "$MUTANTEN_TOML" ]; then\n echo "KEINE_MUTANTEN_TOML"\n exit 0\nfi' },
|
||
]
|
||
const { ergebnis: e, protokoll: p } = await PK(PK_ARGS, { mutanten })
|
||
soll(e && e.mutanten_status === 'exit_0', 'mutanten_status ' + JSON.stringify(e && e.mutanten_status))
|
||
const arith = (mitLabel(p, 'plankritik:arithmetik')[0] || {}).prompt || ''
|
||
soll(arith.includes('<mutantenlauf>') && arith.includes('MUTANT-SKRIPT-03') && !/keine mutanten\.toml/.test(arith), 'Linse arithmetik ohne Mutantenlauf oder mit dem Satz zur fehlenden mutanten.toml')
|
||
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', 'Modell je Hilfslabel: kopie haiku/low, vergleich sonnet/low', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { kopie, ...ohneKopie } = WM_ARGS
|
||
const { fehler: f, protokoll: p } = await WM({ ...ohneKopie, ohne: ['config/secrets.toml'] }, {})
|
||
soll(f === null, 'Fehler: ' + f)
|
||
for (const [l, m] of [['werkzeug:kopie', 'haiku'], ['werkzeug:vergleich', 'sonnet']]) {
|
||
const a = mitLabel(p, l)
|
||
soll(a.length === 1 && a[0].modell === m && a[0].effort === 'low' && !a[0].agentType, l + ' ' + JSON.stringify(a.map(x => [x.modell, x.effort, x.agentType])))
|
||
}
|
||
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
|
||
})
|
||
}
|
||
|
||
// Planbefund (2026-11-02, Auftrag der Startsitzung, Pascal: "Ja, nachziehen"): Meldet plan_materialisiere.py
|
||
// in der Phase Kopie eine Zeile "BEFUND <taskdatei>:<zeile>:", endet der Lauf vor executer-welle. Exit 2
|
||
// ohne Befundzeile und die BEFUND-Zeilen von kopie_bauen.py nehmen den alten Weg (Welle ohne Byte-Vergleich).
|
||
const { kopie: _wmKopie, ...WM_OHNE_KOPIE } = WM_ARGS
|
||
const WM_BAU = { ...WM_OHNE_KOPIE, ohne: ['config/secrets.toml'] }
|
||
const ZAUN1 = 'BEFUND task-01.md:7: Zaun klebt an der Codezeile; nichts geschrieben'
|
||
const ZAUN2 = 'BEFUND task-02.md:31: Block schliesst nie; nichts geschrieben'
|
||
function ohneWelle(p, soll) {
|
||
soll(!p.workflows.some(w => w.ref === 'executer-welle'), 'executer-welle aufgerufen')
|
||
const danach = p.agents.filter(x => x.label !== 'werkzeug:kopie').map(x => x.label)
|
||
soll(danach.length === 0, 'Agenten nach dem Halt: ' + JSON.stringify(danach))
|
||
}
|
||
|
||
fall('welle-mit-pruefung', 'Planbefund: hält vor executer-welle, Befundzeile in plan_befunde', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await WM(WM_BAU, { kopie: kopieLauf(2, ZAUN1, '1 Befund(e).') })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(e && e.status === 'plan_befund', 'status ' + (e && e.status))
|
||
soll(e && JSON.stringify(e.plan_befunde) === JSON.stringify([ZAUN1]), 'plan_befunde ' + JSON.stringify(e && e.plan_befunde))
|
||
soll(e && e.welle === null && e.kopie === null && e.pruefung_args === null && /C:\/tmp\/k bleibt liegen/.test(e.temp_ordner || ''), JSON.stringify(e && [e.welle, e.kopie, e.pruefung_args, e.temp_ordner]))
|
||
soll(e && e.kopie_lauf && e.kopie_lauf.laeufe[0].exit === 2, 'kopie_lauf ' + JSON.stringify(e && e.kopie_lauf))
|
||
soll(p.logs.some(l => /meldet 1 Befund/.test(l)), 'Log: ' + p.logs.join(' | '))
|
||
ohneWelle(p, soll)
|
||
ohneVerstoss(p, soll)
|
||
return fehler
|
||
})
|
||
|
||
fall('welle-mit-pruefung', 'Planbefund mit bis vergleich: hält ebenso, keine pruefung_args', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await WM({ ...WM_BAU, bis: 'vergleich' }, { kopie: kopieLauf(2, ZAUN1, '1 Befund(e).') })
|
||
soll(e && e.status === 'plan_befund' && e.pruefung_args === null && /Befund/.test(e.pruefung_grund || ''), JSON.stringify(e && [e.status, e.pruefung_args, e.pruefung_grund]))
|
||
ohneWelle(p, soll)
|
||
return fehler
|
||
})
|
||
|
||
fall('welle-mit-pruefung', 'Planbefund an erster und letzter Stelle, CRLF: beide Zeilen ohne CR, in Reihenfolge', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const tasks = [{ datei: 'task-01.md', modell: 'haiku' }, { datei: 'task-02.md', modell: 'haiku' }]
|
||
const ausgabe = ['KOPIE=C:/tmp/k/mat', ZAUN1, 'zwischen', ZAUN2, '2 Befund(e).'].join('\r\n')
|
||
const { ergebnis: e, protokoll: p } = await WM({ ...WM_BAU, tasks }, { kopie: { befehl: 'kopie', exit: 2, ausgabe } })
|
||
soll(e && e.status === 'plan_befund' && JSON.stringify(e.plan_befunde) === JSON.stringify([ZAUN1, ZAUN2]), JSON.stringify(e && [e.status, e.plan_befunde]))
|
||
soll(e && /C:\/tmp\/k bleibt liegen/.test(e.temp_ordner || ''), 'temp_ordner ' + JSON.stringify(e && e.temp_ordner))
|
||
ohneWelle(p, soll)
|
||
return fehler
|
||
})
|
||
|
||
fall('welle-mit-pruefung', 'Absturz nach einer Befundzeile (Exit 1): hält ebenso', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await WM(WM_BAU, { kopie: kopieLauf(1, ZAUN1, 'Traceback (most recent call last):', 'OSError: x') })
|
||
soll(e && e.status === 'plan_befund' && JSON.stringify(e.plan_befunde) === JSON.stringify([ZAUN1]), JSON.stringify(e && [e.status, e.plan_befunde]))
|
||
ohneWelle(p, soll)
|
||
return fehler
|
||
})
|
||
|
||
for (const [name, lauf] of [
|
||
['Exit 2 ohne Befundzeile (Errno 2)', { befehl: 'kopie', exit: 2, ausgabe: "KOPIE=C:/tmp/k/mat\n12 Dateien kopiert.\n0 Befund(e).\npython: can't open file 'D:/x/plan_materialisiere.py': [Errno 2] No such file or directory" }],
|
||
['BEFUND von kopie_bauen.py ohne Zeilennummer', { befehl: 'kopie', exit: 2, ausgabe: 'KOPIE=C:/tmp/k/mat\nBEFUND Projekt D:/x ist kein Ordner' }],
|
||
['BEFUND von kopie_bauen.py zu einem Ausschluss', { befehl: 'kopie', exit: 1, ausgabe: "KOPIE=C:/tmp/k/mat\n12 Dateien kopiert.\nBEFUND Ausschluss 'config/secrets.toml' gibt es nicht\n1 Befund(e)." }],
|
||
]) {
|
||
fall('welle-mit-pruefung', `${name}: kein Planbefund, Welle läuft wie bisher ohne Byte-Vergleich`, async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await WM(WM_BAU, { kopie: lauf })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(e && e.status === 'fertig' && e.kopie === null && !('plan_befunde' in e), JSON.stringify(e && [e.status, e.kopie, e.plan_befunde]))
|
||
soll(mitLabel(p, 'executer:').length === 1, 'executer ' + mitLabel(p, 'executer:').length)
|
||
soll(p.logs.some(l => /Kopie nicht verwendbar \(Exit [12]\)/.test(l)), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
fall('welle-mit-pruefung', 'Hilfsagent der Kopie ohne Ergebnis: Welle läuft wie bisher', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await WM(WM_BAU, { kopie: OHNE })
|
||
soll(e && e.status === 'fertig' && mitLabel(p, 'executer:').length === 1, JSON.stringify(e && e.status))
|
||
soll(p.logs.some(l => /Hilfsagent ohne Ergebnis/.test(l)), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
fall('welle-mit-pruefung', 'Kopie auf zwei laeufe verteilt, Befundzeile im zweiten: hält ebenso', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const kopie = [
|
||
{ befehl: 'kopie_bauen.py', exit: 0, ausgabe: 'KOPIE=C:/tmp/k/mat\n12 Dateien kopiert, 1 Pfade ausgeschlossen, 0 Verknuepfungen uebersprungen, 0 Pakete in der Kopie.\n0 Befund(e).' },
|
||
{ befehl: 'plan_materialisiere.py', exit: 2, ausgabe: ZAUN1 + '\n1 Befund(e).' },
|
||
]
|
||
const { ergebnis: e, protokoll: p } = await WM(WM_BAU, { kopie })
|
||
soll(e && e.status === 'plan_befund' && JSON.stringify(e.plan_befunde) === JSON.stringify([ZAUN1]), JSON.stringify(e && [e.status, e.plan_befunde]))
|
||
soll(p.logs.some(l => /meldet 1 Befund\(e\) \(Exit 0\/2\)/.test(l)), 'Log: ' + p.logs.join(' | '))
|
||
ohneWelle(p, soll)
|
||
return fehler
|
||
})
|
||
|
||
// Alle laeufe auch im Erfolgspfad der Kopie und im Vergleich (Folgeauftrag 2026-11-02); KOPIE_GLIEDER, KOPIE_HALB
|
||
// und exitsMitFehler stehen bei plankritik.
|
||
const vergleichAuftrag = p => (mitLabel(p, 'werkzeug:vergleich')[0] || {}).prompt || ''
|
||
|
||
fall('welle-mit-pruefung', 'Kopie auf vier laeufe verteilt, alle Exit 0: Kopie verwendet, Byte-Vergleich gegen sie', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await WM(WM_BAU, { kopie: KOPIE_GLIEDER() })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(e && e.status === 'fertig' && e.kopie === 'C:/tmp/k/mat', JSON.stringify(e && [e.status, e.kopie]))
|
||
soll(vergleichAuftrag(p).includes("--mat 'C:/tmp/k/mat'"), 'Byte-Vergleich ohne die Kopie: ' + vergleichAuftrag(p).slice(0, 300))
|
||
soll(!p.logs.some(l => /Kopie nicht verwendbar/.test(l)), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
fall('welle-mit-pruefung', 'Kopie: erster Lauf vollständig mit Exit 0, Absturz im zweiten: Kopie nicht verwendet, Log nennt beide Exits', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const tasks = [{ datei: 'task-01.md', modell: 'haiku' }, { datei: 'task-02.md', modell: 'haiku' }]
|
||
const { ergebnis: e, protokoll: p } = await WM({ ...WM_BAU, tasks }, { kopie: KOPIE_HALB })
|
||
soll(e && e.status === 'fertig' && e.kopie === null && !('plan_befunde' in e), JSON.stringify(e && [e.status, e.kopie]))
|
||
soll(!vergleichAuftrag(p).includes('--mat'), 'Byte-Vergleich gegen eine halbe Kopie: ' + vergleichAuftrag(p).slice(0, 300))
|
||
soll(p.logs.some(l => l.includes('Kopie nicht verwendbar (Exit 0/1)')), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
for (const stelle of [0, 3]) {
|
||
fall('welle-mit-pruefung', `Kopie auf vier laeufe verteilt, Exit 1 im ${stelle ? 'letzten' : 'ersten'}: Kopie nicht verwendet, Log nennt alle Exits`, async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const exits = exitsMitFehler(stelle)
|
||
const { ergebnis: e, protokoll: p } = await WM(WM_BAU, { kopie: KOPIE_GLIEDER(exits) })
|
||
soll(e && e.status === 'fertig' && e.kopie === null, JSON.stringify(e && [e.status, e.kopie]))
|
||
soll(p.logs.some(l => l.includes(`Kopie nicht verwendbar (Exit ${exits.join('/')})`)), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
fall('welle-mit-pruefung', 'Kopie auf vier laeufe verteilt, ein Lauf ohne exit: Kopie nicht verwendet, Log nennt undefined', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await WM(WM_BAU, { kopie: ohneExit(KOPIE_GLIEDER()) })
|
||
soll(e && e.status === 'fertig' && e.kopie === null, JSON.stringify(e && [e.status, e.kopie]))
|
||
soll(p.logs.some(l => l.includes('Kopie nicht verwendbar (Exit 0/undefined/0/0)')), 'Log: ' + p.logs.join(' | '))
|
||
return fehler
|
||
})
|
||
|
||
fall('welle-mit-pruefung', 'Vergleich auf zwei laeufe verteilt: Byte-Vergleich und Suite erreichen den pruefer, in ihrer Reihenfolge', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const vergleich = [
|
||
{ befehl: 'welle_vergleich.py', exit: 0, ausgabe: 'VERGLEICH-MARKE-01: task-01.md, 1 Datei, Bytes gleich\nEXIT welle_vergleich: 0' },
|
||
{ befehl: 'pytest -q', exit: 1, ausgabe: 'SUITE-MARKE-02: 1 failed, 3 passed\nEXIT suite: 1' },
|
||
]
|
||
const { ergebnis: e, protokoll: p } = await WM(WM_ARGS, { vergleich })
|
||
soll(e && e.status === 'fertig', 'status ' + (e && e.status))
|
||
const auftrag = (mitLabel(p, 'pruefung')[0] || {}).prompt || ''
|
||
const ab = auftrag.indexOf('Schwerpunkt:')
|
||
soll(ab >= 0 && ['VERGLEICH-MARKE-01', 'EXIT welle_vergleich: 0', 'SUITE-MARKE-02', 'EXIT suite: 1'].every(t => auftrag.indexOf(t, ab) > ab), 'Auftrag des pruefer ohne beide laeufe: ' + JSON.stringify(auftrag.slice(ab, ab + 400)))
|
||
soll(auftrag.indexOf('VERGLEICH-MARKE-01', ab) < auftrag.indexOf('SUITE-MARKE-02', ab), 'Reihenfolge der laeufe vertauscht: ' + JSON.stringify(auftrag.slice(ab, ab + 400)))
|
||
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/],
|
||
['effort ungültig', a => ({ ...a, effort: 'mittel' }), /effort "mittel" ungültig/],
|
||
]) {
|
||
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', 'effort medium: alle Planer auf medium, Ableser unverändert', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await PL({ ...PL_ARGS, effort: 'medium' }, {})
|
||
soll(f === null && e.status === 'fertig', 'Fehler/status: ' + f + ' ' + (e && e.status))
|
||
const pl = p.agents.filter(x => x.label.startsWith('planer:'))
|
||
soll(pl.length === 7 && pl.every(x => x.effort === 'medium' && x.agentType === 'planer' && x.modell === 'opus'), 'Planer: ' + JSON.stringify(pl.map(x => [x.effort, x.agentType])))
|
||
soll(mitLabel(p, 'verbrauch:').every(x => x.effort === undefined), 'Ableser mit effort')
|
||
soll(e.effort === 'medium', 'Rückgabe effort ' + e.effort)
|
||
return fehler
|
||
})
|
||
|
||
fall('planung', 'ohne effort: Planer ohne effort, Rückgabe nennt das Frontmatter', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await PL(PL_ARGS, {})
|
||
soll(p.agents.filter(x => x.label.startsWith('planer:')).every(x => x.effort === undefined), 'Planer mit effort')
|
||
soll(e.effort === '(Frontmatter)', 'Rückgabe effort ' + e.effort)
|
||
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
|
||
})
|
||
|
||
// ---------------------------------------------------------------- quorum
|
||
// Fälle für quorum.js (Plan .vault-tools/workflow-pruefung/docs/plans/2026-09-29-quorum/). Eigene
|
||
// Antworten, weil die Labels der Stimmen und der Codex-Hilfsagenten nur hier vorkommen. Vorgabe:
|
||
// Opus, Sonnet und Codex melden denselben Befund an src/a.py Zeile 10 (Mehrheit, blockierend),
|
||
// Opus zusätzlich einen wichtigen Einzelbefund, Sonnet einen Hinweis.
|
||
const Q_ARGS = { anlass: 'einspielen', projekt: 'D:/x' }
|
||
const Q_MARKE = '0123456789abcdef'
|
||
const qPaket = (x = {}) => ({ ordner: 'C:/tmp/q', anlass: 'einspielen', art: 'code', wurzel: 'D:/x', leer: false, basis: 'a'.repeat(40), stand: 'b'.repeat(40), anzahl_dateien: 1, lesemarke: Q_MARKE, bericht_pfad: 'D:/x/docs/quorum/2026-09-30-einspielen.md', nummer: 2, datum: '2026-09-30', vorher: { pfad: 'D:/x/docs/quorum/2026-09-20-version.md', nummer: 1, urteil: 'frei' }, suite: 'ohne', hinweise: [], paket_summe: 'e'.repeat(64), ...x })
|
||
const qB = (datei, stelle, schwere, beleg = 'zitierte Zeile') => ({ datei, stelle, schwere, aussage: `${schwere} in ${datei} ${stelle}`, beleg })
|
||
const Q_STIMMEN = {
|
||
opus: [qB('src/a.py', '10', 'blockierend'), qB('src/a.py', '30', 'wichtig')],
|
||
sonnet: [qB('src/a.py', '10', 'wichtig'), qB('src/b.py', '5', 'hinweis')],
|
||
codex: [qB('src/a.py', '10', 'blockierend')],
|
||
haiku: [qB('src/c.py', '1', 'wichtig')],
|
||
}
|
||
const Q_START = '{\n "running": []\n}\nEXIT status: 0\n{\n "jobId": "task-q1",\n "status": "queued"\n}\nEXIT task: 0'
|
||
// status --json mit einem früheren Hintergrundjob derselben Sitzung: enrichJob gibt dessen request
|
||
// samt jobId mit aus (Plugin codex 1.0.6, scripts/lib/job-control.mjs 161-181).
|
||
const Q_START_ALT = '{\n "running": [],\n "latestFinished": {\n "id": "task-alt",\n "status": "completed",\n "request": {\n "jobId": "task-alt"\n }\n }\n}\nEXIT status: 0\n{\n "jobId": "task-q1",\n "status": "queued"\n}\nEXIT task: 0'
|
||
const Q_WARTEN_FERTIG = '{\n "waitTimedOut": false\n}\nEXIT warten: 0'
|
||
const Q_WARTEN_OFFEN = '{\n "waitTimedOut": true\n}\nEXIT warten: 0'
|
||
|
||
// Gruppiert wie ein sorgfältiger Sonnet-Agent: gleiche Datei und gleiche Stelle.
|
||
function qGruppiere(prompt) {
|
||
const jeStelle = new Map()
|
||
for (const m of prompt.matchAll(/^\[([A-Z]\d+)\] Stimme \S+ \| (.+?) \| (.+?) \| \S+$/gm)) {
|
||
const k = m[2] + '|' + m[3]
|
||
if (!jeStelle.has(k)) jeStelle.set(k, [])
|
||
jeStelle.get(k).push(m[1])
|
||
}
|
||
return { gruppen: [...jeStelle.values()].map(ids => ({ ids, begruendung: 'gleiche Stelle' })) }
|
||
}
|
||
|
||
function qUrteile(prompt, urteil = () => 'bestaetigt') {
|
||
return { urteile: [...prompt.matchAll(/^Befund ([A-Z]\d+):$/gm)].map(m => ({ id: m[1], urteil: urteil(m[1]), fundstelle: 'src/a.py:1, zitiert', begruendung: 'nachgestellt' })) }
|
||
}
|
||
|
||
function antwortenQuorum(k = {}) {
|
||
let n = 0
|
||
const stimmen = k.stimmen || Q_STIMMEN
|
||
const ohne = k.ohne || []
|
||
// Eine Liste statt eines Textes sind die laeufe selbst, etwa auf mehrere laeufe verteilt.
|
||
const lauf = (label, text) => ok({ laeufe: Array.isArray(text) ? text : [{ befehl: label, exit: 0, ausgabe: text }] })
|
||
return async aufruf => {
|
||
const label = aufruf.label || ''
|
||
if (ohne.includes(label)) return undefined
|
||
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:paket') return lauf(label, k.paketAusgabe !== undefined ? k.paketAusgabe : 'Paket in C:/tmp/q\nQUORUM_PAKET ' + JSON.stringify(qPaket(k.paket)) + '\nEXIT paket: 0')
|
||
if (label === 'codex:start') return lauf(label, k.codexStart !== undefined ? k.codexStart : Q_START)
|
||
if (label.startsWith('codex:warten:')) return lauf(label, k.codexWarten ? k.codexWarten(Number(label.slice('codex:warten:'.length))) : Q_WARTEN_FERTIG)
|
||
if (label === 'codex:abbruch') return lauf(label, '{\n "status": "cancelled"\n}\nEXIT cancel: 0')
|
||
if (label === 'codex:ende') return ok({ exit_codex: 0, job_status: 'completed', log_gelesen: true, usage_limit_treffer: 0, antwort_gefunden: true, dauer_s: 192, lesebeleg: 'Lesemarke: ' + Q_MARKE, befunde: stimmen.codex || [], nicht_pruefbar: [], ...(k.codexEnde || {}) })
|
||
if (label.startsWith('stimme:')) {
|
||
const name = label.slice('stimme:'.length)
|
||
return ok({ lesemarke: (k.lesemarken || {})[name] || Q_MARKE, befunde: stimmen[name] || [], nicht_pruefbar: (k.nichtPruefbar || {})[name] || [] }, 30)
|
||
}
|
||
if (label === 'zusammenfuehren') return ok(k.gruppen ? k.gruppen(aufruf.prompt) : qGruppiere(aufruf.prompt))
|
||
if (label === 'nachpruefung') return ok(k.urteile ? k.urteile(aufruf.prompt) : qUrteile(aufruf.prompt))
|
||
throw new Error('unerwarteter Agent ' + label)
|
||
}
|
||
}
|
||
const QR = (a, k, o = {}) => fuehreAus(WF('quorum'), a, antwortenQuorum(k), { ordner: ORDNER, ...o })
|
||
const qGruppe = (e, status) => (e && Array.isArray(e.gruppen) ? e.gruppen.filter(g => g.status === status) : [])
|
||
// Die IDs, die der Agent mit diesem Label unter "Befund <ID>:" zur Nachprüfung bekam.
|
||
function qNachgeprueft(p) {
|
||
const a = mitLabel(p, 'nachpruefung')[0]
|
||
return a ? [...a.prompt.matchAll(/^Befund ([A-Z]\d+):$/gm)].map(m => m[1]) : null
|
||
}
|
||
|
||
fall('quorum', 'Grundlinie: Paket, drei Stimmen gleichzeitig, Mehrheit sperrt, Einzelbefund nachgeprüft, Bericht', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await QR(Q_ARGS, {})
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(e && e.status === 'fertig' && e.urteil === 'nicht_frei', 'status/urteil ' + JSON.stringify(e && [e.status, e.urteil, e.grund]))
|
||
const erst = p.agents.slice().sort((x, y) => x.start - y.start)[0]
|
||
soll(erst && erst.label === 'werkzeug:paket' && erst.modell === 'haiku' && erst.effort === 'low' && !erst.agentType,'erster Agent ' + JSON.stringify(erst && [erst.label, erst.modell, erst.effort]))
|
||
const ab = mitLabel(p, 'verbrauch:').map(x => x.label)
|
||
soll(JSON.stringify(ab) === JSON.stringify(['verbrauch:quorum:vor Stimmen', 'verbrauch:quorum:Ende']), 'Ablesungen: ' + JSON.stringify(ab))
|
||
const opus = mitLabel(p, 'stimme:opus')[0]
|
||
const sonnet = mitLabel(p, 'stimme:sonnet')[0]
|
||
soll(opus && opus.modell === 'opus' && opus.agentType === 'quorum-stimme' && opus.effort === undefined, 'Opus-Stimme ' + JSON.stringify(opus && [opus.modell, opus.agentType, opus.effort]))
|
||
soll(sonnet && sonnet.modell === 'sonnet' && sonnet.agentType === 'quorum-stimme' && sonnet.effort === undefined, 'Sonnet-Stimme ' + JSON.stringify(sonnet && [sonnet.modell, sonnet.agentType, sonnet.effort]))
|
||
soll(opus && /^Auftragsart: Stimme\.$/m.test(opus.prompt) && opus.prompt.includes('Prüfpaket: C:/tmp/q/paket.md') && opus.prompt.includes('Wurzel: D:/x'), 'Auftrag der Stimme ohne Auftragsart, Paket oder Wurzel')
|
||
soll(mitLabel(p, 'stimme:haiku').length === 0, 'Haiku trotz gültiger Codex-Stimme')
|
||
const start = mitLabel(p, 'codex:start')[0]
|
||
soll(start && laufendBeiStart(p, start, 'stimme:') === 2, 'Codex startete nicht neben Opus und Sonnet')
|
||
soll(mitLabel(p, 'codex:').length === 3 && mitLabel(p, 'codex:').every(x => x.modell === 'haiku' && x.effort === 'low' && !x.agentType), 'Codex-Hilfsagenten: ' + JSON.stringify(mitLabel(p, 'codex:').map(x => [x.label, x.modell, x.effort])))
|
||
const zf = mitLabel(p, 'zusammenfuehren')[0]
|
||
const np = mitLabel(p, 'nachpruefung')[0]
|
||
soll(zf && zf.modell === 'sonnet' && zf.effort === 'medium' && !zf.agentType, 'Zusammenführen ' + JSON.stringify(zf && [zf.modell, zf.effort, zf.agentType]))
|
||
soll(np && np.modell === 'sonnet' && np.agentType === 'quorum-stimme' && /^Auftragsart: Nachprüfung\.$/m.test(np.prompt), 'Nachprüfung ' + JSON.stringify(np && [np.modell, np.agentType]))
|
||
soll(JSON.stringify(qNachgeprueft(p)) === '["O2"]', 'nachgeprüft: ' + JSON.stringify(qNachgeprueft(p)))
|
||
const g = qGruppe(e, 'mehrheit')[0]
|
||
soll(g && g.schwere === 'blockierend' && JSON.stringify(g.stimmen.slice().sort()) === '["codex","opus","sonnet"]', 'Mehrheitsgruppe ' + JSON.stringify(g && [g.schwere, g.stimmen]))
|
||
soll(qGruppe(e, 'hinweis_ungeprueft').length === 1 && qGruppe(e, 'bestaetigt').length === 1, 'Status der Gruppen ' + JSON.stringify(e && e.gruppen && e.gruppen.map(x => x.status)))
|
||
soll(e && JSON.stringify(e.zaehlung.opus) === JSON.stringify({ abgegeben: true, gesamt: 2, belegt: 2, bestaetigt: 2, alleinfunde: 1 }), 'zaehlung opus ' + JSON.stringify(e && e.zaehlung && e.zaehlung.opus))
|
||
soll(e && e.zaehlung.sonnet.bestaetigt === 1 && e.zaehlung.sonnet.alleinfunde === 0 && e.zaehlung.codex.bestaetigt === 1 && !('haiku' in e.zaehlung), 'zaehlung ' + JSON.stringify(e && e.zaehlung))
|
||
soll(e && e.bericht_pfad === 'D:/x/docs/quorum/2026-09-30-einspielen.md', 'bericht_pfad ' + (e && e.bericht_pfad))
|
||
soll(e && e.paket === 'C:/tmp/q' && e.ablegen_befehl === "PYTHONIOENCODING=utf-8 python -X utf8 'D:/Claude/.vault-tools/quorum/quorum_werkzeug.py' ablegen --paket 'C:/tmp/q' --bericht 'C:/tmp/q/bericht.md' --ziel 'D:/x/docs/quorum/2026-09-30-einspielen.md' --summe '" + 'e'.repeat(64) + "'", 'paket/ablegen_befehl ' + JSON.stringify(e && [e.paket, e.ablegen_befehl]))
|
||
const md = (e && e.bericht_md) || ''
|
||
for (const z of ['---\ntype: note\n', '\nnummer: 2\n', '\nanlass: einspielen\n', '\nurteil: nicht_frei\n', `\nbasis: "${'a'.repeat(40)}"\n`, '\nstand: PAKET\n', '\ncodex: abgegeben\n---\n', 'Projekt: [README](../../README.md)']) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z))
|
||
ohneVerstoss(p, soll)
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Bericht: Stimmentabelle mit Codex-Laufzeit, Mehrheit zuerst, Verbrauch vor und nach dem Lauf', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e } = await QR(Q_ARGS, {})
|
||
const md = (e && e.bericht_md) || ''
|
||
for (const z of [
|
||
'| Opus | ja | 2 | 2 | 2 | 1 | nicht gemessen |',
|
||
'| Codex | ja | 1 | 1 | 1 | 0 | 3 min 12 s |',
|
||
'### G1: blockierend, Mehrheit (Opus, Sonnet, Codex)',
|
||
'Verbrauch vor den Stimmen: 5 Stunden 10 %, Woche 10 %; am Ende: 5 Stunden 10 %, Woche 10 %.',
|
||
]) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Bericht: Befundzeilen mit Stelle und Beleg, Nachprüfung mit Fundstelle, Unbelegt, Nicht prüfbar, Hinweise und Lesemarke', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const stimmen = { opus: [qB('src/a.py', '10', 'blockierend', 'if x > 0:'), qB('src/a.py', '30', 'wichtig', 'return y'), qB('src/d.py', '7', 'blockierend', '')], sonnet: [qB('src/a.py', '10', 'wichtig', 'if x > 0: # S')], codex: [] }
|
||
const k = { stimmen, paket: { hinweise: ['basis weicht vom Stand des letzten Quorums ab'] }, nichtPruefbar: { sonnet: ['tests/ nicht lesbar'] }, lesemarken: { opus: 'ffffffffffffffff', sonnet: 'Lesemarke: ' + Q_MARKE } }
|
||
const { ergebnis: e } = await QR(Q_ARGS, k)
|
||
const md = (e && e.bericht_md) || ''
|
||
for (const z of [
|
||
'### G1: blockierend, Mehrheit (Opus, Sonnet)\n\n`src/a.py`, Stelle 10\n',
|
||
'- O1 (Opus, blockierend, Stelle 10): blockierend in src/a.py 10\n Beleg: if x > 0:\n',
|
||
'- S1 (Sonnet, wichtig, Stelle 10): wichtig in src/a.py 10\n Beleg: if x > 0: # S\n',
|
||
'- O2 (Opus, wichtig, Stelle 30): wichtig in src/a.py 30\n Beleg: return y\n\nNachprüfung: bestaetigt, src/a.py:1, zitiert. nachgestellt\n',
|
||
'## Unbelegt\n\n- O3 (Opus, blockierend) `src/d.py`, Stelle 7: blockierend in src/d.py 7\n',
|
||
'## Nicht prüfbar\n\n- Sonnet: tests/ nicht lesbar\n',
|
||
'## Hinweise des Laufs\n\n- basis weicht vom Stand des letzten Quorums ab\n- Opus: Lesemarke fehlt oder weicht ab',
|
||
]) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z))
|
||
soll(e && !e.hinweise.some(h => h.startsWith('Sonnet:')), 'Sonnet zitiert die Lesemarke samt Zeilenanfang und gilt trotzdem als abweichend: ' + JSON.stringify(e && e.hinweise))
|
||
return fehler
|
||
})
|
||
|
||
for (const [urteil, erwartet] of [['widerlegt', 'frei'], ['bestaetigt', 'nicht_frei']]) {
|
||
fall('quorum', `blockierender Einzelbefund ${urteil}: ${erwartet}`, async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const stimmen = { opus: [qB('src/a.py', '10', 'blockierend')], sonnet: [qB('src/b.py', '5', 'hinweis')], codex: [] }
|
||
const { ergebnis: e } = await QR(Q_ARGS, { stimmen, urteile: pr => qUrteile(pr, () => urteil) })
|
||
soll(e && e.status === 'fertig' && e.urteil === erwartet, JSON.stringify(e && [e.status, e.urteil, e.grund]))
|
||
soll(e && e.zaehlung.opus.alleinfunde === (urteil === 'bestaetigt' ? 1 : 0), 'alleinfunde ' + JSON.stringify(e && e.zaehlung && e.zaehlung.opus))
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
fall('quorum', 'Nachprüfung ohne Ergebnis: ungeprüfter blockierender Einzelbefund sperrt, ungeprüfter wichtiger nicht', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const a = await QR(Q_ARGS, { stimmen: { opus: [qB('src/a.py', '10', 'blockierend')], sonnet: [qB('src/b.py', '3', 'wichtig')], codex: [] }, ohne: ['nachpruefung'] })
|
||
soll(a.ergebnis && a.ergebnis.urteil === 'nicht_frei' && qGruppe(a.ergebnis, 'nicht_geprueft').length === 2, 'blockierend: ' + JSON.stringify(a.ergebnis && [a.ergebnis.urteil, a.ergebnis.gruppen.map(g => g.status)]))
|
||
soll(a.ergebnis && a.ergebnis.hinweise.some(h => /Nachprüfung ohne Ergebnis/.test(h)), 'kein Hinweis auf die fehlende Nachprüfung')
|
||
const b = await QR(Q_ARGS, { stimmen: { opus: [qB('src/a.py', '10', 'wichtig')], sonnet: [qB('src/b.py', '3', 'wichtig')], codex: [] }, ohne: ['nachpruefung'] })
|
||
soll(b.ergebnis && b.ergebnis.urteil === 'frei', 'wichtig: ' + JSON.stringify(b.ergebnis && [b.ergebnis.urteil, b.ergebnis.grund]))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'nur Hinweise mit je einer Stimme: keine Nachprüfung, frei', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { stimmen: { opus: [qB('src/a.py', '1', 'hinweis')], sonnet: [qB('src/b.py', '2', 'hinweis')], codex: [] } })
|
||
soll(mitLabel(p, 'nachpruefung').length === 0, 'Nachprüfung für Hinweise gestartet')
|
||
soll(e && e.urteil === 'frei' && qGruppe(e, 'hinweis_ungeprueft').length === 2, JSON.stringify(e && [e.urteil, e.gruppen.map(g => g.status)]))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Befund ohne Beleg zählt nicht für die Mehrheit und geht nicht ins Zusammenführen', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const stimmen = { opus: [qB('src/a.py', '10', 'blockierend', ' '), qB('src/b.py', '1', 'wichtig')], sonnet: [qB('src/a.py', '10', 'blockierend')], codex: [] }
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { stimmen })
|
||
const zf = mitLabel(p, 'zusammenfuehren')[0]
|
||
soll(zf && !zf.prompt.includes('[O1]') && zf.prompt.includes('[O2]') && zf.prompt.includes('[S1]'), 'Zusammenführen: ' + (zf && zf.prompt.slice(0, 400)))
|
||
soll(e && JSON.stringify(e.unbelegt.map(b => b.id)) === '["O1"]', 'unbelegt ' + JSON.stringify(e && e.unbelegt))
|
||
soll(qGruppe(e, 'mehrheit').length === 0, 'Mehrheit trotz unbelegtem Befund')
|
||
soll(JSON.stringify(qNachgeprueft(p)) === '["O2","S1"]', 'nachgeprüft: ' + JSON.stringify(qNachgeprueft(p)))
|
||
soll(e && e.zaehlung.opus.gesamt === 2 && e.zaehlung.opus.belegt === 1, JSON.stringify(e && e.zaehlung && e.zaehlung.opus))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Gruppe über verschiedene Dateien wird aufgeteilt: keine Mehrheit', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const stimmen = { opus: [qB('src/a.py', '10', 'blockierend')], sonnet: [qB('src/b.py', '10', 'blockierend')], codex: [] }
|
||
const { ergebnis: e } = await QR(Q_ARGS, { stimmen, gruppen: () => ({ gruppen: [{ ids: ['O1', 'S1'], begruendung: 'zusammen' }] }), urteile: pr => qUrteile(pr, () => 'widerlegt') })
|
||
soll(e && qGruppe(e, 'mehrheit').length === 0 && e.urteil === 'frei', JSON.stringify(e && [e.urteil, e.gruppen.map(g => g.status)]))
|
||
soll(e && e.hinweise.some(h => /Gruppe O1, S1 aufgeteilt in O1 \| S1 \(/.test(h)), 'kein Hinweis auf die aufgeteilte Gruppe: ' + JSON.stringify(e && e.hinweise))
|
||
return fehler
|
||
})
|
||
|
||
// Zwei Stellen mit Zeilennummern gehören zusammen, wenn sich ihre Zeilenbereiche mit einer
|
||
// Toleranz von drei Zeilen überschneiden; ohne Zeilennummer gilt das Urteil des Agenten.
|
||
for (const [name, stelleO, stelleS, mehrheit] of [
|
||
['Zeile 10 und Zeile 250', '10', '250', false],
|
||
['Zeile 42 und Zeile 43', 'Zeile 42', 'Zeile 43', true],
|
||
['Zeile 10 und Zeile 13, Toleranz drei', '10', '13', true],
|
||
['Zeile 10 und Zeile 14, jenseits der Toleranz', '10', '14', false],
|
||
['Zeilen 10-20 und Zeile 22', 'Zeilen 10-20', 'Zeile 22', true],
|
||
['Zeilen 10–20 und Z. 22', 'Zeilen 10–20', 'Z. 22', true],
|
||
['L12 und line 200', 'L12', 'line 200', false],
|
||
['a.py:42 und a.py:250', 'a.py:42', 'a.py:250', false],
|
||
['Zeile 42 (Funktion f) und Funktion g, Zeile 250', 'Zeile 42 (Funktion f)', 'Funktion g, Zeile 250', false],
|
||
['Zeile 10 und ein Abschnitt', '10', 'Abschnitt Einleitung', true],
|
||
['zwei Abschnitte ohne Zeilennummer', 'Abschnitt Einleitung', 'Abschnitt Einleitung, Satz 2', true],
|
||
]) {
|
||
fall('quorum', `Gruppe in derselben Datei, ${name}: ${mehrheit ? 'eine Gruppe, Mehrheit' : 'aufgeteilt, beide nachgeprüft'}`, async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const stimmen = { opus: [qB('src/a.py', stelleO, 'blockierend')], sonnet: [qB('src/a.py', stelleS, 'wichtig')], codex: [] }
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { stimmen, gruppen: () => ({ gruppen: [{ ids: ['O1', 'S1'], begruendung: 'zusammen' }] }), urteile: pr => qUrteile(pr, () => 'widerlegt') })
|
||
soll(e && qGruppe(e, 'mehrheit').length === (mehrheit ? 1 : 0) && e.urteil === (mehrheit ? 'nicht_frei' : 'frei'), JSON.stringify(e && [e.urteil, e.gruppen.map(g => g.status)]))
|
||
soll(JSON.stringify(qNachgeprueft(p)) === (mehrheit ? 'null' : '["O1","S1"]'), 'nachgeprüft: ' + JSON.stringify(qNachgeprueft(p)))
|
||
soll(e && e.hinweise.some(h => /Gruppe O1, S1 aufgeteilt in O1 \| S1 \(/.test(h)) === !mehrheit, 'Hinweise: ' + JSON.stringify(e && e.hinweise))
|
||
const md = (e && e.bericht_md) || ''
|
||
for (const z of [`(Opus, blockierend, Stelle ${stelleO}): `, `(Sonnet, wichtig, Stelle ${stelleS}): `]) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z))
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
// Drei Stimmen, eine Gruppe des Agenten: Sie zerfällt in zusammenhängende Teilgruppen, jede mit
|
||
// zwei Stimmen ist eine Mehrheit, der Rest geht in die Nachprüfung, jede Aufteilung ist ein Hinweis.
|
||
for (const [name, stellen, mehrheiten, nachgeprueft, hinweis] of [
|
||
['Zeilen 10-14, Zeile 12 und 11 bilden eine Mehrheit', [['src/a.py', 'Zeilen 10-14'], ['src/a.py', 'Zeile 12'], ['src/a.py', '11']], ['codex,opus,sonnet'], null, null],
|
||
['Zeile 42 und 43 sind eine Mehrheit, Zeile 250 steht für sich', [['src/a.py', 'Zeile 42'], ['src/a.py', 'Zeile 43'], ['src/a.py', 'Zeile 250']], ['opus,sonnet'], '["C1"]', /Gruppe O1, S1, C1 aufgeteilt in O1, S1 \| C1 \(/],
|
||
['Zeile 10, 13 und 16 hängen über die transitive Hülle zusammen', [['src/a.py', 'Zeile 10'], ['src/a.py', 'Zeile 13'], ['src/a.py', 'Zeile 16']], ['codex,opus,sonnet'], null, null],
|
||
['Stelle ohne Zeilennummer neben getrennten Zeilen steht für sich', [['src/a.py', 'Zeile 10'], ['src/a.py', 'Funktion f'], ['src/a.py', 'Zeile 250']], [], '["O1","S1","C1"]', /Gruppe O1, S1, C1 aufgeteilt in O1 \| S1 \| C1 \(/],
|
||
['zwei Stellen in a.py sind eine Mehrheit, b.py steht für sich', [['src/a.py', 'Zeile 10'], ['src/a.py', 'Zeile 11'], ['src/b.py', 'Zeile 10']], ['opus,sonnet'], '["C1"]', /Gruppe O1, S1, C1 aufgeteilt in O1, S1 \| C1 \(/],
|
||
]) {
|
||
fall('quorum', `Teilgruppen einer Gruppe des Agenten: ${name}`, async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const [opus, sonnet, codex] = stellen.map(([datei, stelle]) => [qB(datei, stelle, 'blockierend')])
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { stimmen: { opus, sonnet, codex }, gruppen: () => ({ gruppen: [{ ids: ['O1', 'S1', 'C1'], begruendung: 'zusammen' }] }), urteile: pr => qUrteile(pr, () => 'widerlegt') })
|
||
const m = qGruppe(e, 'mehrheit').map(g => g.stimmen.slice().sort().join(','))
|
||
soll(JSON.stringify(m) === JSON.stringify(mehrheiten), 'Mehrheiten: ' + JSON.stringify(m))
|
||
soll(JSON.stringify(qNachgeprueft(p)) === String(nachgeprueft), 'nachgeprüft: ' + JSON.stringify(qNachgeprueft(p)))
|
||
const h = e ? e.hinweise.filter(x => /aufgeteilt in/.test(x)) : []
|
||
soll(hinweis ? h.length === 1 && hinweis.test(h[0]) : h.length === 0, 'Hinweise: ' + JSON.stringify(h))
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
fall('quorum', 'Zusammenführen mit unbekannter und doppelter ID: beide verworfen, keine Mehrheit', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const stimmen = { opus: [qB('src/a.py', '10', 'blockierend')], sonnet: [qB('src/a.py', '10', 'blockierend')], codex: [] }
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { stimmen, gruppen: () => ({ gruppen: [{ ids: ['O1', 'X9'], begruendung: 'a' }, { ids: ['O1', 'S1'], begruendung: 'b' }] }) })
|
||
soll(qGruppe(e, 'mehrheit').length === 0, 'Mehrheit aus einer doppelt vergebenen ID')
|
||
soll(e && e.hinweise.some(h => /unbekannte ID X9/.test(h)) && e.hinweise.some(h => /ID O1 steht in zwei Gruppen/.test(h)), 'Hinweise: ' + JSON.stringify(e && e.hinweise))
|
||
soll(JSON.stringify(qNachgeprueft(p)) === '["O1","S1"]', 'nachgeprüft: ' + JSON.stringify(qNachgeprueft(p)))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Zusammenführen ohne Ergebnis: jeder belegte Befund eine Gruppe, alle Einzelbefunde über Hinweis nachgeprüft', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { ohne: ['zusammenfuehren'] })
|
||
soll(qGruppe(e, 'mehrheit').length === 0, 'Mehrheit ohne Zusammenführen')
|
||
soll(JSON.stringify(qNachgeprueft(p)) === '["O1","O2","S1","C1"]', 'nachgeprüft: ' + JSON.stringify(qNachgeprueft(p)))
|
||
soll(e && e.hinweise.some(h => /Zusammenführen ohne Ergebnis/.test(h)), 'kein Hinweis')
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Unabhängigkeit: Stimmen kennen einander nicht, Zusammenführen liest keine Dateien', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { protokoll: p } = await QR(Q_ARGS, { codexEnde: { job_status: 'failed' } })
|
||
const stimmen = mitLabel(p, 'stimme:')
|
||
soll(stimmen.length === 3, 'Stimmen: ' + JSON.stringify(stimmen.map(x => x.label)))
|
||
for (const s of stimmen) soll(s.prompt.includes('Die anderen Stimmen kennst du nicht.') && !/Opus|Sonnet|Codex|Haiku/.test(s.prompt), s.label + ': ' + s.prompt)
|
||
const zf = mitLabel(p, 'zusammenfuehren')[0]
|
||
soll(zf && !zf.agentType && zf.prompt.includes('Lies keine Dateien und fahre keine Befehle; du arbeitest nur mit dieser Liste.'), 'Zusammenführen: ' + (zf && zf.prompt.slice(0, 300)))
|
||
return fehler
|
||
})
|
||
|
||
for (const [erst, zweit] of [['wichtig', 'blockierend'], ['blockierend', 'wichtig']]) {
|
||
fall('quorum', `Mehrheitsgruppe aus ${erst} und ${zweit} zählt als blockierend`, async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const stimmen = { opus: [qB('src/a.py', '10', erst)], sonnet: [qB('src/a.py', '10', zweit)], codex: [] }
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { stimmen })
|
||
const g = qGruppe(e, 'mehrheit')[0]
|
||
soll(g && g.schwere === 'blockierend' && e.urteil === 'nicht_frei', JSON.stringify(e && [e.urteil, g && g.schwere]))
|
||
soll(mitLabel(p, 'nachpruefung').length === 0, 'Mehrheitsgruppe nachgeprüft')
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
fall('quorum', 'Reihenfolge: blockierender Einzelbefund an erster und letzter Stelle einer Stimme wird nachgeprüft', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const stimmen = { opus: [qB('src/a.py', '1', 'blockierend'), qB('src/x.py', '2', 'hinweis'), qB('src/y.py', '3', 'hinweis'), qB('src/a.py', '99', 'blockierend')], sonnet: [qB('src/z.py', '1', 'hinweis')], codex: [] }
|
||
const { protokoll: p } = await QR(Q_ARGS, { stimmen })
|
||
soll(JSON.stringify(qNachgeprueft(p)) === '["O1","O4"]', 'nachgeprüft: ' + JSON.stringify(qNachgeprueft(p)))
|
||
return fehler
|
||
})
|
||
|
||
for (const [name, k, grund] of [
|
||
['Status vor dem Lauf nicht gelesen', { codexStart: 'Fehler beim Lesen\nEXIT status: 1' }, /Status vor dem Lauf/],
|
||
['Auftrag ohne jobId', { codexStart: '{\n "running": []\n}\nEXIT status: 0\nFehler\nEXIT task: 1' }, /nicht gestartet/],
|
||
['Task-Antwort mit jobId, aber EXIT task 1', { codexStart: Q_START.replace('EXIT task: 0', 'EXIT task: 1') }, /^Codex-Auftrag nicht gestartet$/],
|
||
['Status gescheitert, task nicht gelaufen: Grund ohne Warnung', { codexStart: 'Fehler beim Lesen\nEXIT status: 1' }, /^Status vor dem Lauf nicht gelesen$/],
|
||
['usage limit im Log', { codexEnde: { usage_limit_treffer: 1 } }, /usage limit/],
|
||
['Lesebeleg mit fremder Lesemarke', { codexEnde: { lesebeleg: 'Lesemarke: ffffffffffffffff' } }, /Lesebeleg/],
|
||
['Job endet failed', { codexEnde: { job_status: 'failed' } }, /failed/],
|
||
['Log nicht gelesen', { codexEnde: { log_gelesen: false } }, /Log nicht gelesen/],
|
||
['keine Antwort im Format', { codexEnde: { antwort_gefunden: false } }, /Format/],
|
||
['Auswertung mit Exit 2', { codexEnde: { exit_codex: 2 } }, /Exit 2/],
|
||
['Auswertung ohne Ergebnis', { ohne: ['codex:ende'] }, /ohne Ergebnis/],
|
||
]) {
|
||
fall('quorum', `Codex ungültig (${name}): Haiku springt nach den übrigen Stimmen ein, Bericht sagt es`, async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, k)
|
||
const haiku = mitLabel(p, 'stimme:haiku')[0]
|
||
soll(haiku && haiku.modell === 'haiku' && haiku.agentType === 'quorum-stimme', 'Haiku nicht eingesprungen')
|
||
soll(e && e.stimmen.codex.abgegeben === false && grund.test(e.stimmen.codex.grund), 'grund ' + JSON.stringify(e && e.stimmen.codex.grund))
|
||
soll(e && e.bericht_md && e.bericht_md.includes('\ncodex: ersetzt\n') && e.bericht_md.includes('| Haiku (Ersatz für Codex) | ja |'), 'Bericht ohne Ersatz')
|
||
const vorher = p.agents.filter(x => /^(stimme:opus|stimme:sonnet|codex:)/.test(x.label || ''))
|
||
soll(haiku && vorher.every(x => x.ende !== null && x.ende < haiku.start), 'Haiku startete vor dem Ende der übrigen Stimmen')
|
||
soll(!/Status|jobId/.test(name) || mitLabel(p, 'codex:warten').length === 0, 'gewartet ohne gestarteten Auftrag')
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
fall('quorum', 'Codex nie fertig: so viele Warterunden wie codex_runden, dann Abbruch und Haiku', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await QR({ ...Q_ARGS, codex_runden: 2 }, { codexWarten: () => Q_WARTEN_OFFEN })
|
||
soll(mitLabel(p, 'codex:warten:').length === 2, 'Warterunden: ' + mitLabel(p, 'codex:warten:').length)
|
||
const ab = mitLabel(p, 'codex:abbruch')[0]
|
||
soll(ab && ab.prompt.includes("cancel 'task-q1' --json"), 'Abbruch fehlt')
|
||
soll(mitLabel(p, 'codex:ende').length === 0 && mitLabel(p, 'stimme:haiku').length === 1, 'Ende oder Haiku: ' + JSON.stringify(p.agents.map(x => x.label)))
|
||
soll(e && /nach 2 Warterunden/.test(e.stimmen.codex.grund), 'grund ' + JSON.stringify(e && e.stimmen.codex.grund))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Modell je Hilfslabel: paket und alle codex-Labels haiku/low ohne agentType, zusammenfuehren sonnet/medium', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { fehler: f, protokoll: p } = await QR(Q_ARGS, {})
|
||
soll(f === null, 'Fehler: ' + f)
|
||
for (const l of ['werkzeug:paket', 'codex:start', 'codex:warten:1', 'codex:ende']) {
|
||
const a = mitLabel(p, l)[0]
|
||
soll(a && a.modell === 'haiku' && a.effort === 'low' && !a.agentType, l + ' ' + JSON.stringify(a && [a.modell, a.effort, a.agentType]))
|
||
}
|
||
const zf = mitLabel(p, 'zusammenfuehren')[0]
|
||
soll(zf && zf.modell === 'sonnet' && zf.effort === 'medium' && !zf.agentType, 'zusammenfuehren ' + JSON.stringify(zf && [zf.modell, zf.effort, zf.agentType]))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Modell codex:abbruch: haiku/low ohne agentType', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { protokoll: p } = await QR({ ...Q_ARGS, codex_runden: 1 }, { codexWarten: () => Q_WARTEN_OFFEN })
|
||
const ab = mitLabel(p, 'codex:abbruch')[0]
|
||
soll(ab && ab.modell === 'haiku' && ab.effort === 'low' && !ab.agentType, 'codex:abbruch ' + JSON.stringify(ab && [ab.modell, ab.effort, ab.agentType]))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Codex nie fertig ohne codex_runden: Vorgabe drei Warterunden', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { codexWarten: () => Q_WARTEN_OFFEN })
|
||
soll(mitLabel(p, 'codex:warten:').length === 3 && e && /nach 3 Warterunden/.test(e.stimmen.codex.grund), 'Warterunden: ' + mitLabel(p, 'codex:warten:').length + ', grund ' + JSON.stringify(e && e.stimmen.codex.grund))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'codex_runden 6 ist zugelassen: sechs Warterunden', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { fehler: f, protokoll: p } = await QR({ ...Q_ARGS, codex_runden: 6 }, { codexWarten: () => Q_WARTEN_OFFEN })
|
||
soll(f === null && mitLabel(p, 'codex:warten:').length === 6, 'Fehler ' + f + ', Warterunden: ' + mitLabel(p, 'codex:warten:').length)
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Warten scheitert: Abbruch nach der ersten Runde und Haiku', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { codexWarten: () => 'kein Job\nEXIT warten: 1' })
|
||
soll(mitLabel(p, 'codex:warten:').length === 1 && mitLabel(p, 'codex:abbruch').length === 1, 'Agenten: ' + JSON.stringify(p.agents.map(x => x.label)))
|
||
soll(e && /Warten auf Codex gescheitert/.test(e.stimmen.codex.grund) && mitLabel(p, 'stimme:haiku').length === 1, 'grund ' + JSON.stringify(e && e.stimmen.codex.grund))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'codex false: kein Codex-Agent, Haiku springt ein', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await QR({ ...Q_ARGS, codex: false }, {})
|
||
soll(mitLabel(p, 'codex:').length === 0 && mitLabel(p, 'stimme:haiku').length === 1, 'Agenten: ' + JSON.stringify(p.agents.map(x => x.label)))
|
||
soll(e && /abgeschaltet/.test(e.stimmen.codex.grund) && e.bericht_md.includes('\ncodex: ersetzt\n'), 'grund oder Bericht: ' + JSON.stringify(e && e.stimmen.codex.grund))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Codex-Befehle: Plugin 1.0.6, nur lesend, Auftrag aus dem Paket, Warten mit zehn Minuten', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { protokoll: p } = await QR(Q_ARGS, {})
|
||
const start = mitLabel(p, 'codex:start')[0]
|
||
const warten = mitLabel(p, 'codex:warten:1')[0]
|
||
const ende = mitLabel(p, 'codex:ende')[0]
|
||
for (const z of ['openai-codex/codex/1.0.6/scripts/codex-companion.mjs', `status --json --cwd 'D:/x'`, `task --background --json --cwd 'D:/x' --prompt-file 'C:/tmp/q/codex-auftrag.md'`]) soll(start && start.prompt.includes(z), 'Start ohne ' + z)
|
||
soll(p.agents.every(x => !String(x.prompt).includes('--write')), 'ein Auftrag enthält --write')
|
||
soll(warten && warten.prompt.includes(`status 'task-q1' --wait --timeout-ms 540000 --json --cwd 'D:/x'`) && /timeout auf 600000/.test(warten.prompt), 'Warten: ' + (warten && warten.prompt))
|
||
for (const z of [`result 'task-q1' --json --cwd 'D:/x' > 'C:/tmp/q/codex-ergebnis.json'`, `quorum_werkzeug.py' codex --ergebnis 'C:/tmp/q/codex-ergebnis.json'`]) soll(ende && ende.prompt.includes(z), 'Ende ohne ' + z)
|
||
return fehler
|
||
})
|
||
|
||
const qStartLaeufeVertauscht = text => {
|
||
const [status, task] = text.split('\nEXIT status: 0\n')
|
||
return [
|
||
{ befehl: 'Startbefehl, Ausgabe von task', exit: 0, ausgabe: task },
|
||
{ befehl: 'Startbefehl, Ausgabe von status', exit: 0, ausgabe: status + '\nEXIT status: 0' },
|
||
]
|
||
}
|
||
const qStartZeilenLaeufe = zeilen => zeilen.map((ausgabe, i) => ({
|
||
befehl: 'Startbefehl, Ausgabe Teil ' + (i + 1), exit: 0, ausgabe,
|
||
}))
|
||
const qStartNeueIdZeile = ' "jobId": "task-q1",'
|
||
const qStartZeilen = Q_START_ALT.split('\n')
|
||
const qStartOhneNeueId = Q_START_ALT.replace(qStartNeueIdZeile + '\n', '')
|
||
const qStartStatusRoh = Q_START_ALT.split('\nEXIT status: 0\n')[0]
|
||
const qStartTaskBlock = Q_START.split('\nEXIT status: 0\n')[1].replace('\nEXIT task: 0', '')
|
||
const qStartTaskRoh = qStartTaskBlock + '\nEXIT task: 0'
|
||
const qStartTaskKompakt = '{"jobId":"task-q1","status":"queued","title":"Codex Task","summary":".","logFile":"C:/tmp/q/codex.log"}'
|
||
const qStartReihenfolgeHinweis = 'Codex-Start: Ausgabe nicht in Befehlsreihenfolge; jobId aus der task-Ausgabe (task-q1)'
|
||
const qStartUnsicherGrund = 'jobId nicht sicher bestimmbar; Codex-Auftrag läuft womöglich ungesehen weiter'
|
||
const qStartTaskGeteilt = [
|
||
{ befehl: 'Startbefehl, Status und Task-Anfang', exit: 0, ausgabe: qStartStatusRoh + '\nEXIT status: 0\n{' },
|
||
{ befehl: 'Startbefehl, Task-Fortsetzung', exit: 0, ausgabe: qStartTaskRoh.slice(2) },
|
||
]
|
||
const qStartAlterRest = ausgabe => [
|
||
...qStartLaeufeVertauscht(qStartOhneNeueId.replace(' "jobId": "task-alt"\n', '')),
|
||
{ befehl: 'Startbefehl, verschobener Statusrest', exit: 0, ausgabe },
|
||
]
|
||
const qStartAlterJob = (id, status) => ({
|
||
createdAt: '2026-10-01T00:00:00.000Z',
|
||
updatedAt: '2026-10-01T00:01:00.000Z',
|
||
id,
|
||
status,
|
||
request: {
|
||
cwd: 'D:/x',
|
||
model: 'codex',
|
||
effort: 'low',
|
||
prompt: 'früherer Auftrag',
|
||
write: false,
|
||
resumeLast: false,
|
||
jobId: id
|
||
},
|
||
})
|
||
const qStartStatusKompakt = JSON.stringify({
|
||
workspaceRoot: 'D:/x',
|
||
config: { stopReviewGate: false },
|
||
sessionRuntime: {},
|
||
running: [qStartAlterJob('task-alt-queued', 'queued')],
|
||
latestFinished: {
|
||
createdAt: '2026-10-01T00:00:00.000Z',
|
||
updatedAt: '2026-10-01T00:01:00.000Z',
|
||
id: 'task-alt-ohne-request',
|
||
status: 'completed',
|
||
},
|
||
recent: [],
|
||
needsReview: false,
|
||
})
|
||
const qStartStatusUmformatiert = qStartStatusKompakt.replace(
|
||
'"jobId":"task-alt-queued"',
|
||
'\n "jobId": "task-alt-queued"\n'
|
||
)
|
||
const qStartK4Status = 'EXIT status: 0\n' + qStartStatusUmformatiert
|
||
const qStartK7StatusZeilen = qStartStatusKompakt
|
||
.replace(/^\{/, '{\n')
|
||
.replace('"id":"task-alt-queued","status":"queued",', '"id":"task-alt-queued"\n,\n"status": "queued",\n')
|
||
.replace('"jobId":"task-alt-queued"', '\n "jobId": "task-alt-queued"\n')
|
||
.split('\n')
|
||
const qStartK7Laeufe = qStartZeilenLaeufe([
|
||
qStartK7StatusZeilen[0],
|
||
qStartK7StatusZeilen[5],
|
||
qStartK7StatusZeilen[2],
|
||
qStartK7StatusZeilen[3],
|
||
qStartK7StatusZeilen[1],
|
||
qStartK7StatusZeilen[4],
|
||
qStartK7StatusZeilen[6],
|
||
'EXIT status: 0',
|
||
'EXIT task: 0',
|
||
])
|
||
const qStartStatusLang = JSON.stringify({
|
||
workspaceRoot: 'D:/x',
|
||
config: { stopReviewGate: false },
|
||
sessionRuntime: {},
|
||
running: Array.from({ length: 24 }, (_, i) => qStartAlterJob('task-alt-' + (i + 1), 'queued')),
|
||
latestFinished: qStartAlterJob('task-alt-fertig', 'completed'),
|
||
recent: [],
|
||
needsReview: false,
|
||
}, null, 2)
|
||
const qStartLangeZeilen = (qStartStatusLang + '\nEXIT status: 0\n' + qStartTaskRoh).split('\n')
|
||
const qStartGekuerzteZeilen = [
|
||
...qStartLangeZeilen.slice(0, 60),
|
||
'[... ' + (qStartLangeZeilen.length - 180) + ' Zeilen gekürzt ...]',
|
||
...qStartLangeZeilen.slice(-120),
|
||
]
|
||
const qStartGekuerzt = qStartGekuerzteZeilen.join('\n')
|
||
const qStartB = 'STATUS (EXIT status: 0): workspaceRoot D:/x; config.stopReviewGate=false; running=[]; latestFinished: id task-alt\nTASK (EXIT task: 0): ' + qStartTaskKompakt + '\nEXIT task: 0'
|
||
const qStartC = 'EXIT status: 0\nTASK-START: ' + qStartTaskKompakt + '\nEXIT task: 0'
|
||
const qStartKompaktMitStatus = qStartStatusKompakt + '\nEXIT status: 0\nTASK-START: ' + qStartTaskKompakt + '\nEXIT task: 0'
|
||
|
||
for (const [name, codexStart, hinweisErwartet] of [
|
||
['task vor status, ohne früheren Job', qStartLaeufeVertauscht(Q_START), true],
|
||
['task vor status, mit früherem Job', qStartLaeufeVertauscht(Q_START_ALT), true],
|
||
['Task-Block vor Status-JSON, EXIT-Zeilen in Befehlsreihenfolge', [
|
||
{ befehl: 'Startbefehl, Task-Block', exit: 0, ausgabe: qStartTaskBlock },
|
||
{ befehl: 'Startbefehl, Status mit EXIT', exit: 0, ausgabe: qStartStatusRoh + '\nEXIT status: 0' },
|
||
{ befehl: 'Startbefehl, Task-Exit', exit: 0, ausgabe: 'EXIT task: 0' },
|
||
], true],
|
||
['Task-Block hinter EXIT task', [
|
||
{ befehl: 'Startbefehl, Status mit EXIT', exit: 0, ausgabe: qStartStatusRoh + '\nEXIT status: 0' },
|
||
{ befehl: 'Startbefehl, Task-Exit', exit: 0, ausgabe: 'EXIT task: 0' },
|
||
{ befehl: 'Startbefehl, Task-Block', exit: 0, ausgabe: qStartTaskBlock },
|
||
], true],
|
||
['Mittelkürzung nach 60/120-Regel, danach task vor status', qStartLaeufeVertauscht(qStartGekuerzt), true],
|
||
['Echtlauf B, kompakt mit TASK-Vorsatz und Status als Prosa', qStartB, false],
|
||
['Echtlauf C, kompakt mit TASK-START und ohne Status-JSON', qStartC, false],
|
||
['Echtlauf A, EXIT status vor Status-JSON', 'EXIT status: 0\n' + Q_START_ALT.replace('\nEXIT status: 0', ''), false],
|
||
['dieselbe ID kompakt vor status und formatiert danach', [
|
||
{ befehl: 'Startbefehl, kompakter Task-Treffer', exit: 0, ausgabe: qStartTaskKompakt },
|
||
{ befehl: 'Startbefehl, Status und formatierter Task-Treffer', exit: 0, ausgabe: Q_START_ALT },
|
||
], true],
|
||
['K4: neuformatierter Status mit alter Request-Zeile und Task-Antwort', qStartK4Status + '\n' + qStartTaskRoh, false],
|
||
['synthetisch: K7-Statusreste mit intakter Task-Antwort in eigenem Lauf', [
|
||
...qStartK7Laeufe,
|
||
{ befehl: 'Startbefehl, intakte Task-Antwort', exit: 0, ausgabe: qStartTaskBlock },
|
||
], true],
|
||
['geordnet: Status mit EXIT in einem Lauf, Task mit EXIT im nächsten', [
|
||
{ befehl: 'Startbefehl, Status mit EXIT', exit: 0, ausgabe: qStartStatusRoh + '\nEXIT status: 0' },
|
||
{ befehl: 'Startbefehl, Task mit EXIT', exit: 0, ausgabe: qStartTaskRoh },
|
||
], false],
|
||
['synthetisch: 15 leere Läufe vor Status und Task', [
|
||
...Array.from({ length: 15 }, (_, i) => ({ befehl: 'Startbefehl, leerer Lauf ' + (i + 1), exit: 0, ausgabe: '' })),
|
||
{ befehl: 'Startbefehl, Status mit EXIT', exit: 0, ausgabe: qStartStatusRoh + '\nEXIT status: 0' },
|
||
{ befehl: 'Startbefehl, Task mit EXIT', exit: 0, ausgabe: qStartTaskRoh },
|
||
], false],
|
||
['synthetisch: Vorsatz mit EXIT status vor dem Task-Block, Status-JSON danach', [
|
||
{ befehl: 'Startbefehl, Vorsatz und Task-Block', exit: 0, ausgabe: 'Vorab (EXIT status: 0): ' + qStartTaskBlock },
|
||
{ befehl: 'Startbefehl, Status mit EXIT', exit: 0, ausgabe: qStartStatusRoh + '\nEXIT status: 0' },
|
||
{ befehl: 'Startbefehl, Task-Exit', exit: 0, ausgabe: 'EXIT task: 0' },
|
||
], true],
|
||
]) {
|
||
fall('quorum', 'Codex-Start (' + name + '): neue Stimme und passende Hinweise', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
if (name === 'Mittelkürzung nach 60/120-Regel, danach task vor status') {
|
||
soll(qStartStatusLang.split('\n').length > 200, 'Statusvorlage hat nicht mehr als 200 Zeilen')
|
||
soll(qStartGekuerzteZeilen.length === 181, 'Kürzung enthält nicht 60 + 1 + 120 Zeilen')
|
||
soll(qStartGekuerzteZeilen[60] === '[... ' + (qStartLangeZeilen.length - 180) + ' Zeilen gekürzt ...]', 'Kürzungsmarker oder Zeilenzahl falsch')
|
||
soll(qStartGekuerzteZeilen.includes(qStartNeueIdZeile), 'neue Task-ID ging bei der Kürzung verloren')
|
||
}
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await QR(Q_ARGS, { codexStart })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(e && e.stimmen.codex.abgegeben === true && e.stimmen.codex.job === 'task-q1' && e.stimmen.codex.grund === '', 'Codex-Stimme ' + JSON.stringify(e && e.stimmen.codex))
|
||
const warten = mitLabel(p, 'codex:warten:')
|
||
const ende = mitLabel(p, 'codex:ende')
|
||
soll(warten.length === 1 && warten[0].prompt.includes("status 'task-q1' --wait"), 'Warten nicht genau einmal auf task-q1')
|
||
soll(ende.length === 1 && ende[0].prompt.includes("result 'task-q1' --json"), 'Auswertung nicht genau einmal für task-q1')
|
||
soll(mitLabel(p, 'stimme:haiku').length === 0 && mitLabel(p, 'codex:abbruch').length === 0, 'Haiku oder Abbruch trotz fertigem neuen Auftrag')
|
||
soll(p.agents.every(x => !String(x.prompt).includes('task-alt')), 'ein Auftrag nennt einen früheren Job')
|
||
soll(p.logs.filter(text => text === qStartReihenfolgeHinweis).length === Number(hinweisErwartet), 'Reihenfolgehinweis im Protokoll falsch')
|
||
soll(e && e.hinweise.includes(qStartReihenfolgeHinweis) === hinweisErwartet && e.bericht_md.includes(qStartReihenfolgeHinweis) === hinweisErwartet, 'Reihenfolgehinweis in Hinweisen oder Bericht falsch')
|
||
soll(e && !e.hinweise.some(text => text.includes(qStartUnsicherGrund)) && !e.bericht_md.includes(qStartUnsicherGrund), 'Warnung trotz eindeutiger neuer ID')
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
for (const [name, codexStart, hinweisErwartet] of [
|
||
['vertauscht', qStartLaeufeVertauscht(Q_START_ALT), true],
|
||
['kompakt mit früherem queued-Job', qStartKompaktMitStatus, false],
|
||
]) {
|
||
fall('quorum', 'Codex-Start (' + name + '): neuer Auftrag wird nach zwei Warterunden abgebrochen', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await QR({ ...Q_ARGS, codex_runden: 2 }, {
|
||
codexStart,
|
||
codexWarten: () => Q_WARTEN_OFFEN,
|
||
})
|
||
soll(f === null, 'Fehler: ' + f)
|
||
const warten = mitLabel(p, 'codex:warten:')
|
||
const abbruch = mitLabel(p, 'codex:abbruch')
|
||
soll(warten.length === 2 && warten.every(x => x.prompt.includes("status 'task-q1' --wait")), 'nicht zweimal auf den neuen Auftrag gewartet')
|
||
soll(abbruch.length === 1 && abbruch[0].prompt.includes("cancel 'task-q1' --json"), 'neuer Auftrag nicht genau einmal abgebrochen')
|
||
soll(e && e.stimmen.codex.job === 'task-q1' && e.stimmen.codex.abgegeben === false && /nach 2 Warterunden/.test(e.stimmen.codex.grund), 'Codex-Ergebnis ' + JSON.stringify(e && e.stimmen.codex))
|
||
soll(mitLabel(p, 'stimme:haiku').length === 1 && mitLabel(p, 'codex:ende').length === 0, 'Ersatz oder Auswertung nach Zeitüberschreitung falsch')
|
||
soll(p.agents.every(x => !String(x.prompt).includes('task-alt')), 'Warten oder Abbruch nennt einen früheren Job')
|
||
soll(p.logs.filter(text => text === qStartReihenfolgeHinweis).length === Number(hinweisErwartet), 'Reihenfolgehinweis vor dem Warten falsch')
|
||
soll(e && e.hinweise.includes(qStartReihenfolgeHinweis) === hinweisErwartet && e.bericht_md.includes(qStartReihenfolgeHinweis) === hinweisErwartet, 'Reihenfolgehinweis im Ergebnis falsch')
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
for (const [name, codexStart, grund] of [
|
||
[
|
||
'Task-Nutzlast in umgekehrte Einzelzeilen zerlegt',
|
||
qStartZeilenLaeufe(qStartZeilen.slice().reverse()),
|
||
qStartUnsicherGrund,
|
||
],
|
||
[
|
||
'keine task-ID',
|
||
qStartLaeufeVertauscht(Q_START.replace(qStartNeueIdZeile + '\n', '')),
|
||
qStartUnsicherGrund,
|
||
],
|
||
[
|
||
'nur alte Status-ID',
|
||
qStartLaeufeVertauscht(qStartOhneNeueId),
|
||
qStartUnsicherGrund,
|
||
],
|
||
[
|
||
'alte Status-ID hinter EXIT status',
|
||
qStartAlterRest(' "jobId": "task-alt"'),
|
||
qStartUnsicherGrund,
|
||
],
|
||
[
|
||
'unvollständige Rückgabe ohne Status und task-ID',
|
||
[
|
||
{ befehl: 'Startbefehl, Ausgaberest', exit: 0, ausgabe: '[... 250 Zeilen gekürzt ...]\nEXIT task: 0' },
|
||
],
|
||
'Status vor dem Lauf nicht gelesen; Codex-Auftrag läuft womöglich ungesehen weiter',
|
||
],
|
||
[
|
||
'kompakter Status mit früherem queued-Job, ohne Task-Antwort',
|
||
qStartStatusKompakt + '\nEXIT status: 0\nEXIT task: 0',
|
||
qStartUnsicherGrund,
|
||
],
|
||
[
|
||
'synthetisch: alte Request-ID mit Komma vor queued, ohne Objektöffnung',
|
||
qStartAlterRest(' "jobId": "task-alt",\n "status": "queued"'),
|
||
qStartUnsicherGrund,
|
||
],
|
||
[
|
||
'synthetisch: alte Request-ID ohne Komma vor queued, mit Objektöffnung',
|
||
qStartAlterRest('{\n "jobId": "task-alt"\n "status": "queued"\n}'),
|
||
qStartUnsicherGrund,
|
||
],
|
||
[
|
||
'zwei verschiedene IDs aus formatiertem und kompaktem Task-Objekt',
|
||
qStartLaeufeVertauscht(Q_START_ALT + '\n' + qStartTaskKompakt.replace('task-q1', 'task-q2')),
|
||
qStartUnsicherGrund,
|
||
],
|
||
[
|
||
'K4: neuformatierter Status mit alter Request-Zeile, ohne Task-Antwort',
|
||
qStartK4Status + '\nEXIT task: 0',
|
||
qStartUnsicherGrund,
|
||
],
|
||
[
|
||
'synthetisch: Task-Objektanfang mit completed statt queued',
|
||
'{\n "running": []\n}\nEXIT status: 0\n{"jobId":"task-alt","status":"completed","title":"synthetisch"}\nEXIT task: 0',
|
||
qStartUnsicherGrund,
|
||
],
|
||
[
|
||
'synthetisch: K7-Merkmal aus vier einzeiligen Läufen, nur Statusreste',
|
||
qStartK7Laeufe,
|
||
qStartUnsicherGrund,
|
||
],
|
||
[
|
||
'echte Task-Antwort am Objektanfang über zwei Läufe zerrissen',
|
||
qStartTaskGeteilt,
|
||
qStartUnsicherGrund,
|
||
],
|
||
]) {
|
||
fall('quorum', 'Codex-Start unsicher (' + name + '): Haiku und Warnung vor ungesehenem Auftrag', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
if (name === 'synthetisch: K7-Merkmal aus vier einzeiligen Läufen, nur Statusreste') {
|
||
soll(qStartK7StatusZeilen.length === 7 && JSON.stringify(JSON.parse(qStartK7StatusZeilen.join('\n'))) === qStartStatusKompakt, 'K7-Statusvorlage ändert JSON-Bestandteile oder ihre Reihenfolge')
|
||
soll(qStartK7Laeufe.every(l => !l.ausgabe.includes('\n')), 'K7-Statusfragmente sind nicht einzeilige Läufe')
|
||
}
|
||
if (name === 'echte Task-Antwort am Objektanfang über zwei Läufe zerrissen') {
|
||
soll(qStartTaskGeteilt.map(l => l.ausgabe).join('\n') === Q_START_ALT, 'geteilte echte Task-Antwort weicht von Q_START_ALT ab')
|
||
}
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await QR(Q_ARGS, { codexStart })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(e && e.stimmen.codex.abgegeben === false && e.stimmen.codex.job === null && e.stimmen.codex.grund === grund, 'Codex-Ergebnis ' + JSON.stringify(e && e.stimmen.codex))
|
||
soll(mitLabel(p, 'codex:warten:').length === 0 && mitLabel(p, 'codex:abbruch').length === 0 && mitLabel(p, 'codex:ende').length === 0, 'Auftrag ohne sichere jobId verfolgt')
|
||
soll(mitLabel(p, 'stimme:haiku').length === 1, 'Haiku nicht genau einmal eingesprungen')
|
||
soll(p.agents.every(x => !String(x.prompt).includes('task-alt')), 'ein Auftrag nennt einen früheren Job')
|
||
soll(e && e.hinweise.some(text => text.includes(grund)) && e.bericht_md.includes(grund), 'Warnung vor ungesehenem Auftrag fehlt in Hinweisen oder Bericht')
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
fall('quorum', 'Codex-Start: jobId nur aus der Ausgabe von task, nicht aus einem früheren Job im Status', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { codexStart: Q_START_ALT })
|
||
soll(e && e.stimmen.codex.job === 'task-q1' && e.stimmen.codex.abgegeben === true, 'Codex-Stimme ' + JSON.stringify(e && [e.stimmen.codex.job, e.stimmen.codex.grund]))
|
||
const warten = mitLabel(p, 'codex:warten:1')[0]
|
||
const ende = mitLabel(p, 'codex:ende')[0]
|
||
soll(warten && warten.prompt.includes("status 'task-q1' --wait") && ende && ende.prompt.includes("result 'task-q1' --json"), 'Warten oder Ende nicht auf task-q1')
|
||
soll(p.agents.every(x => !String(x.prompt).includes('task-alt')), 'ein Auftrag nennt den früheren Job')
|
||
return fehler
|
||
})
|
||
|
||
// Alle laeufe in ausgabe() (Folgeauftrag 2026-11-02): Paket, Codex-Start und Warten, je eine Ausführung, deren
|
||
// Ausgabe der Hilfsagent auf zwei laeufe verteilt; die EXIT-Zeile steht jeweils im zweiten. Mit laeufe[0] allein
|
||
// galt das Paket als nicht gebaut und ein gestarteter Codex-Auftrag als nicht gestartet: Haiku sprang ein, der
|
||
// Auftrag lief ungesehen weiter.
|
||
fall('quorum', 'Paket auf zwei laeufe verteilt, EXIT paket im zweiten: Paket gebaut, Stimmen laufen', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const paketAusgabe = [
|
||
{ befehl: 'Paketbefehl, Ausgabe Teil 1', exit: 0, ausgabe: 'Paket in C:/tmp/q\nQUORUM_PAKET ' + JSON.stringify(qPaket()) },
|
||
{ befehl: 'Paketbefehl, Ausgabe Teil 2', exit: 0, ausgabe: 'EXIT paket: 0' },
|
||
]
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await QR(Q_ARGS, { paketAusgabe })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(e && e.status === 'fertig' && e.paket === 'C:/tmp/q', JSON.stringify(e && [e.status, e.paket]))
|
||
soll(mitLabel(p, 'stimme:opus').length === 1 && mitLabel(p, 'stimme:sonnet').length === 1, 'Stimmen: ' + JSON.stringify(p.agents.map(x => x.label)))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Codex-Start auf zwei laeufe verteilt, Status mit früherem Job: jobId aus task, Codex-Stimme gültig', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const codexStart = [
|
||
{ befehl: 'Startbefehl, Ausgabe von status', exit: 0, ausgabe: '{\n "running": [],\n "latestFinished": {\n "id": "task-alt",\n "status": "completed",\n "request": {\n "jobId": "task-alt"\n }\n }\n}\nEXIT status: 0' },
|
||
{ befehl: 'Startbefehl, Ausgabe von task', exit: 0, ausgabe: '{\n "jobId": "task-q1",\n "status": "queued"\n}\nEXIT task: 0' },
|
||
]
|
||
soll(codexStart.map(l => l.ausgabe).join('\n') === Q_START_ALT, 'Vorlage: die zwei laeufe sind nicht Q_START_ALT')
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { codexStart })
|
||
soll(e && e.stimmen.codex.abgegeben === true && e.stimmen.codex.job === 'task-q1', 'Codex-Stimme ' + JSON.stringify(e && [e.stimmen.codex.job, e.stimmen.codex.grund]))
|
||
soll(mitLabel(p, 'stimme:haiku').length === 0 && mitLabel(p, 'codex:abbruch').length === 0, 'Agenten: ' + JSON.stringify(p.agents.map(x => x.label)))
|
||
const warten = mitLabel(p, 'codex:warten:1')[0]
|
||
soll(warten && warten.prompt.includes("status 'task-q1' --wait"), 'Warten nicht auf task-q1')
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Warten auf zwei laeufe verteilt, EXIT warten im zweiten: fertig, kein Abbruch, Codex-Stimme gültig', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const codexWarten = () => [
|
||
{ befehl: 'Wartebefehl, Ausgabe Teil 1', exit: 0, ausgabe: '{\n "waitTimedOut": false\n}' },
|
||
{ befehl: 'Wartebefehl, Ausgabe Teil 2', exit: 0, ausgabe: 'EXIT warten: 0' },
|
||
]
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { codexWarten })
|
||
soll(mitLabel(p, 'codex:warten:').length === 1 && mitLabel(p, 'codex:abbruch').length === 0 && mitLabel(p, 'codex:ende').length === 1, 'Agenten: ' + JSON.stringify(p.agents.map(x => x.label)))
|
||
soll(e && e.stimmen.codex.abgegeben === true && mitLabel(p, 'stimme:haiku').length === 0, 'Codex-Stimme ' + JSON.stringify(e && [e.stimmen.codex.abgegeben, e.stimmen.codex.grund]))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'gebremst vor den Stimmen: keine Stimme, kein Urteil, kein Bericht', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await QR(Q_ARGS, { ablesungen: [ablesung(90, 10)] })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(mitLabel(p, 'stimme:').length === 0 && mitLabel(p, 'codex:').length === 0, 'Stimme trotz Bremse: ' + JSON.stringify(p.agents.map(x => x.label)))
|
||
soll(e && e.status === 'gebremst' && e.urteil === 'kein_urteil' && e.bericht_md === null && e.bericht_pfad === null && e.ablegen_befehl === null, JSON.stringify(e && [e.status, e.urteil, e.bericht_pfad, e.ablegen_befehl]))
|
||
soll(e && /neu starten/.test(e.naechster_schritt), 'naechster_schritt ' + (e && e.naechster_schritt))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Ablesung ohne Ergebnis: Opus-Stimme läuft nach der alten Obergrenze', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { ablesungen: [OHNE] })
|
||
soll(mitLabel(p, 'stimme:opus').length === 1 && e && e.status === 'fertig' && e.verbrauch[0].status === 'nicht_gelesen', JSON.stringify(e && [e.status, e.verbrauch[0]]))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'als Kind: verbrauch.js wirft, Rückfall, Stimmen laufen', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await QR(Q_ARGS, {}, { tiefe: 1 })
|
||
soll(f === null && e && e.status === 'fertig', 'Fehler/status: ' + f + ' ' + (e && e.status))
|
||
soll(mitLabel(p, 'stimme:opus').length === 1 && mitLabel(p, 'verbrauch:').length === 0, 'Agenten: ' + JSON.stringify(p.agents.map(x => x.label)))
|
||
soll(e && e.verbrauch.length === 2 && e.verbrauch.every(v => v.status === 'nicht_gelesen'), JSON.stringify(e && e.verbrauch))
|
||
return fehler
|
||
})
|
||
|
||
for (const vorUrteil of ['frei', 'nicht_frei']) {
|
||
fall('quorum', `leerer Unterschied: nur der Paket-Agent, Urteil ${vorUrteil} übernommen`, async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const paket = { leer: true, anzahl_dateien: 0, stand: 'a'.repeat(40), vorher: { pfad: 'D:/x/docs/quorum/2026-09-20-version.md', nummer: 1, urteil: vorUrteil } }
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await QR(Q_ARGS, { paket })
|
||
soll(f === null, 'Fehler: ' + f)
|
||
soll(p.agents.length === 1 && p.agents[0].label === 'werkzeug:paket', 'Agenten ' + JSON.stringify(p.agents.map(x => x.label)))
|
||
soll(e && e.status === 'leer' && e.urteil === vorUrteil && e.verbrauch.length === 0, JSON.stringify(e && [e.status, e.urteil, e.verbrauch]))
|
||
soll(e && typeof e.ablegen_befehl === 'string' && e.ablegen_befehl.endsWith(" ablegen --paket 'C:/tmp/q' --bericht 'C:/tmp/q/bericht.md' --ziel 'D:/x/docs/quorum/2026-09-30-einspielen.md' --summe '" + 'e'.repeat(64) + "'"), 'ablegen_befehl ' + (e && e.ablegen_befehl))
|
||
const md = (e && e.bericht_md) || ''
|
||
for (const z of ['\ncodex: nicht gelaufen\n', `\nurteil: ${vorUrteil}\n`, '\nstand: PAKET\n', '2026-09-20-version.md']) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z))
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
fall('quorum', 'Paket nicht gebaut: Fehler mit der BEFUND-Zeile, keine Stimme', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { fehler: f, protokoll: p } = await QR(Q_ARGS, { paketAusgabe: 'BEFUND: Arbeitsbaum nicht sauber, die Stimmen lesen ihn statt HEAD: M src/a.py\nEXIT paket: 2' })
|
||
soll(f && /Paket nicht gebaut/.test(f) && /Arbeitsbaum nicht sauber/.test(f), 'Fehler: ' + f)
|
||
soll(p.agents.length === 1, 'Agenten: ' + JSON.stringify(p.agents.map(x => x.label)))
|
||
return fehler
|
||
})
|
||
|
||
for (const [name, k, text] of [
|
||
['Lesemarke unbrauchbar', { paket: { lesemarke: 'xyz' } }, /unbrauchbarer Lesemarke/],
|
||
['Feld fehlt', { paketAusgabe: 'QUORUM_PAKET {"ordner":"C:/tmp/q"}\nEXIT paket: 0' }, /QUORUM_PAKET ohne/],
|
||
['kein JSON', { paketAusgabe: 'QUORUM_PAKET {kaputt}\nEXIT paket: 0' }, /kein JSON/],
|
||
['paket_summe fehlt', { paket: { paket_summe: undefined } }, /QUORUM_PAKET ohne paket_summe$/],
|
||
['paket_summe kein Hex', { paket: { paket_summe: 'xyz' } }, /unbrauchbarer paket_summe/],
|
||
['anzahl_dateien negativ', { paket: { anzahl_dateien: -1 } }, /anzahl_dateien muss eine ganze Zahl ab 0/],
|
||
['anzahl_dateien als Text', { paket: { anzahl_dateien: '1' } }, /anzahl_dateien muss eine ganze Zahl ab 0/],
|
||
['hinweise keine Liste', { paket: { hinweise: 'x' } }, /hinweise muss eine Liste/],
|
||
['Exit 2 trotz Paketzeile', { paketAusgabe: 'QUORUM_PAKET ' + JSON.stringify(qPaket()) + '\nBEFUND: nachgestellt\nEXIT paket: 2' }, /Paket nicht gebaut: BEFUND: nachgestellt/],
|
||
]) {
|
||
fall('quorum', `QUORUM_PAKET kaputt (${name}): Fehler, keine Stimme`, async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { fehler: f, protokoll: p } = await QR(Q_ARGS, k)
|
||
soll(f && text.test(f), 'Fehler: ' + f)
|
||
soll(p.agents.length === 1, 'Agenten: ' + JSON.stringify(p.agents.map(x => x.label)))
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
fall('quorum', 'weniger als zwei Stimmen: kein Urteil, kein Bericht, Ablesung am Ende', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e, protokoll: p } = await QR(Q_ARGS, { ohne: ['stimme:sonnet', 'stimme:haiku'], codexEnde: { usage_limit_treffer: 1 } })
|
||
soll(e && e.status === 'unvollstaendig' && e.urteil === 'kein_urteil' && e.bericht_md === null && e.bericht_pfad === null, JSON.stringify(e && [e.status, e.urteil, e.bericht_pfad]))
|
||
soll(mitLabel(p, 'zusammenfuehren').length === 0 && mitLabel(p, 'nachpruefung').length === 0, 'Zusammenführen oder Nachprüfung trotz einer Stimme')
|
||
soll(mitLabel(p, 'verbrauch:').length === 2, 'Ablesungen: ' + mitLabel(p, 'verbrauch:').length)
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'zwei Stimmen reichen: Opus ohne Ergebnis, Bericht nennt es', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e } = await QR(Q_ARGS, { ohne: ['stimme:opus'] })
|
||
soll(e && e.status === 'fertig' && e.zaehlung.opus.abgegeben === false, JSON.stringify(e && [e.status, e.zaehlung]))
|
||
soll(e && e.bericht_md.includes('| Opus | nein: ohne Ergebnis |'), 'Bericht ohne Zeile für Opus')
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Paketbefehl für Code: basis, kontext, suite mit zehn Minuten, Arbeitsbaum vor und nach der Suite', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { protokoll: p } = await QR({ ...Q_ARGS, basis: 'v1.2.0', suite: 'pytest -q', kontext: ['D:/x/docs/plans/p/00-uebersicht.md'] }, {})
|
||
const pk = mitLabel(p, 'werkzeug:paket')[0]
|
||
for (const z of [
|
||
`T=$(cygpath -m -l "$(mktemp -d)")`,
|
||
`V=$(GIT_OPTIONAL_LOCKS=0 git -C 'D:/x' status --porcelain --untracked-files=all -- . ':(exclude)docs/quorum'); (cd 'D:/x' && pytest -q) > "$T/suite.txt" 2>&1; echo "EXIT suite: $?" >> "$T/suite.txt"`,
|
||
`[ "$V" = "$(GIT_OPTIONAL_LOCKS=0 git -C 'D:/x' status --porcelain --untracked-files=all -- . ':(exclude)docs/quorum')" ] || echo "BEFUND: Die Suite hat den Arbeitsbaum verändert`,
|
||
`paket --anlass einspielen --ziel "$T" --projekt 'D:/x' --basis 'v1.2.0' --kontext 'D:/x/docs/plans/p/00-uebersicht.md' --suite-datei "$T/suite.txt"`,
|
||
'timeout auf 600000',
|
||
]) soll(pk && pk.prompt.includes(z), 'Paketbefehl ohne ' + z)
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Kundendokument: Paketbefehl, Wurzel für Stimmen und Codex, Stand PAKET und Wikilink auf die Übersicht', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const K = 'D:/Claude/IT Workspace/Beispiel/abgabe'
|
||
const args = { anlass: 'kundendokument', ordner: K, dateien: ['kapitel/01.md', 'kapitel/02 teil.md'], uebersicht: '00 Übersicht.md' }
|
||
const stand = 'sha256:' + 'c'.repeat(64)
|
||
const paket = { anlass: 'kundendokument', art: 'doku', wurzel: K, basis: 'erstes Quorum', stand, vorher: null, nummer: 1, bericht_pfad: K + '/quorum/2026-09-30-kundendokument.md', anzahl_dateien: 2 }
|
||
const { ergebnis: e, fehler: f, protokoll: p } = await QR(args, { paket })
|
||
soll(f === null && e && e.status === 'fertig', 'Fehler/status: ' + f + ' ' + (e && e.status))
|
||
const pk = mitLabel(p, 'werkzeug:paket')[0]
|
||
soll(pk && pk.prompt.includes(`paket --anlass kundendokument --ziel "$T" --ordner '${K}' --uebersicht '00 Übersicht.md' --dateien 'kapitel/01.md' 'kapitel/02 teil.md'`) && !pk.prompt.includes('--projekt'), 'Paketbefehl: ' + (pk && pk.prompt))
|
||
const opus = mitLabel(p, 'stimme:opus')[0]
|
||
const start = mitLabel(p, 'codex:start')[0]
|
||
soll(opus && opus.prompt.includes('Wurzel: ' + K) && start && start.prompt.includes(`--cwd '${K}'`), 'Wurzel nicht der Ordner')
|
||
const md = (e && e.bericht_md) || ''
|
||
for (const z of ['\nstand: PAKET\ncodex: ', '\nbasis: "erstes Quorum"\n', `Basis erstes Quorum, Stand ${stand}; 2 Datei(en) im Unterschied.`, 'Übersicht: [[IT Workspace/Beispiel/abgabe/00 Übersicht|00 Übersicht.md]]']) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z))
|
||
soll(!md.includes('README'), 'Bericht eines Kundendokuments verlinkt ein README')
|
||
return fehler
|
||
})
|
||
|
||
for (const [name, ordner, uebersicht, zeile] of [
|
||
['IT Workspace, Übersicht im Unterordner', 'D:/Claude/IT Workspace/Beispiel/abgabe', 'teil/00 Übersicht.md', 'Übersicht: [[IT Workspace/Beispiel/abgabe/teil/00 Übersicht|00 Übersicht.md]]'],
|
||
['Backslashes und Schrägstrich am Ende', 'D:\\Claude\\Security - Compliance Projekte\\active - Beispiel\\', '00-uebersicht.md', 'Übersicht: [[Security - Compliance Projekte/active - Beispiel/00-uebersicht|00-uebersicht.md]]'],
|
||
['Code-Projekt, zwei Ebenen unter der Wurzel', 'D:/Claude/Code Projekte/active - Beispiel/doku/kunde', '00-uebersicht.md', 'Projekt: [README](../../../README.md)'],
|
||
['Code-Projekt, Wurzel', 'D:/Claude/Code Projekte/active - Beispiel', '00-uebersicht.md', 'Projekt: [README](../README.md)'],
|
||
]) {
|
||
fall('quorum', `Kundendokument, Hub-Zeile (${name})`, async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const args = { anlass: 'kundendokument', ordner, dateien: ['kapitel/01.md'], uebersicht }
|
||
const paket = { anlass: 'kundendokument', art: 'doku', wurzel: ordner, basis: 'erstes Quorum', stand: 'sha256:' + 'c'.repeat(64), vorher: null, nummer: 1 }
|
||
const { ergebnis: e, fehler: f } = await QR(args, { paket })
|
||
const md = (e && e.bericht_md) || ''
|
||
soll(f === null && md.includes('\n\n' + zeile + '\n\n'), 'Fehler ' + f + ', Bericht ohne ' + JSON.stringify(zeile))
|
||
soll(md.split('\n').filter(z => /^(Projekt|Übersicht): /.test(z)).length === 1, 'nicht genau eine Hub-Zeile')
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
for (const [name, a, text] of [
|
||
['anlass fehlt', { projekt: 'D:/x' }, /anlass muss/],
|
||
['anlass unbekannt', { anlass: 'release', projekt: 'D:/x' }, /anlass muss/],
|
||
['projekt fehlt', { anlass: 'einspielen' }, /args fehlen: projekt/],
|
||
['projekt leer', { anlass: 'version', projekt: ' ' }, /projekt muss ein nicht leerer Text/],
|
||
['basis leer', { ...Q_ARGS, basis: '' }, /basis muss ein nicht leerer Text/],
|
||
['dateien bei Code', { ...Q_ARGS, dateien: ['a.md'] }, /dateien gilt nur für kundendokument/],
|
||
['dateien fehlen', { anlass: 'kundendokument', ordner: 'D:/k', uebersicht: 'u.md' }, /dateien muss/],
|
||
['dateien leer', { anlass: 'kundendokument', ordner: 'D:/k', uebersicht: 'u.md', dateien: [] }, /dateien muss/],
|
||
['Datei doppelt', { anlass: 'kundendokument', ordner: 'D:/k', uebersicht: 'u.md', dateien: ['a.md', 'a.md'] }, /doppelt in dateien/],
|
||
['uebersicht fehlt', { anlass: 'kundendokument', ordner: 'D:/k', dateien: ['a.md'] }, /args fehlen: uebersicht/],
|
||
['projekt bei Kundendokument', { anlass: 'kundendokument', ordner: 'D:/k', uebersicht: 'u.md', dateien: ['a.md'], projekt: 'D:/x' }, /projekt gilt nur für einspielen und version/],
|
||
['ordner außerhalb des Vaults', { anlass: 'kundendokument', ordner: 'D:/k', uebersicht: 'u.md', dateien: ['a.md'] }, /ordner muss im Vault/],
|
||
['suite und suite_datei', { ...Q_ARGS, suite: 'pytest', suite_datei: 'D:/s.txt' }, /schließen sich aus/],
|
||
['kontext als Text', { ...Q_ARGS, kontext: 'D:/a.md' }, /kontext muss/],
|
||
['codex als Text', { ...Q_ARGS, codex: 'nein' }, /codex muss/],
|
||
['codex_runden 0', { ...Q_ARGS, codex_runden: 0 }, /codex_runden muss/],
|
||
['codex_runden 7', { ...Q_ARGS, codex_runden: 7 }, /codex_runden muss/],
|
||
['codex_runden 1.5', { ...Q_ARGS, codex_runden: 1.5 }, /codex_runden muss/],
|
||
]) {
|
||
fall('quorum', `${name} wirft vor dem ersten Agenten`, async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { fehler: f, protokoll: p } = await QR(a, {})
|
||
soll(f && text.test(f), 'Fehler: ' + f)
|
||
soll(p.agents.length === 0, 'Agent gestartet')
|
||
return fehler
|
||
})
|
||
}
|
||
|
||
// Klartext im Bericht (Nachbesserung Task 06): Stimmen, Nachprüfung und Hilfsagenten zitieren
|
||
// Wikilink, Einbettung, Tag und Abschnittslink. Q_KLAR ist derselbe Text, wie er im Bericht
|
||
// stehen muss: lesbar, aber ohne Link, Einbettung und Tag.
|
||
const Q_ZITATE = '[[a/b]] ![[c]] #needs-review [[#x]]'
|
||
const Q_KLAR = '\\[\\[a/b]] !\\[\\[c]] \\#needs-review \\[\\[\\#x]]'
|
||
const qZ = schwere => ({ datei: 'src/a.py', stelle: Q_ZITATE, schwere, aussage: Q_ZITATE, beleg: Q_ZITATE })
|
||
// Was Obsidian als Wikilink, Einbettung oder Tag lesen könnte. Der Tag zählt hier schon hinter
|
||
// jedem Zeichen außer Buchstabe, Ziffer, Unterstrich und Backslash, nicht erst nach Leerraum.
|
||
function qObsidian(text) {
|
||
const funde = []
|
||
if (/(^|[^!])\[\[/.test(text)) funde.push('Wikilink')
|
||
if (text.includes('![[')) funde.push('Einbettung')
|
||
if (/(^|[^\p{L}\p{N}_\\])#[^\s#]/u.test(text)) funde.push('Tag')
|
||
return funde
|
||
}
|
||
const qSauber = (soll, text) => { const f = qObsidian(text); soll(f.length === 0, 'Obsidian-Syntax im Bericht: ' + f.join(', ')) }
|
||
|
||
fall('quorum', 'Klartext im Bericht: Stelle, Aussage und Beleg einer Gruppe ohne Wikilink, Einbettung und Tag, README-Link bleibt', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const { ergebnis: e } = await QR(Q_ARGS, { stimmen: { opus: [qZ('blockierend')], sonnet: [qZ('blockierend')], codex: [] } })
|
||
const md = (e && e.bericht_md) || ''
|
||
qSauber(soll, md)
|
||
for (const z of [
|
||
'### G1: blockierend, Mehrheit (Opus, Sonnet)\n\n`src/a.py`, Stelle ' + Q_KLAR + '\n',
|
||
'- O1 (Opus, blockierend, Stelle ' + Q_KLAR + '): ' + Q_KLAR + '\n Beleg: ' + Q_KLAR + '\n',
|
||
'\n\nProjekt: [README](../../README.md)\n\n',
|
||
]) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Klartext im Bericht: Fundstelle und Begründung der Nachprüfung ohne Wikilink, Einbettung und Tag', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const stimmen = { opus: [qB('src/a.py', '10', 'blockierend')], sonnet: [qB('src/b.py', '5', 'hinweis')], codex: [] }
|
||
const urteile = pr => ({ urteile: [...pr.matchAll(/^Befund ([A-Z]\d+):$/gm)].map(m => ({ id: m[1], urteil: 'bestaetigt', fundstelle: Q_ZITATE, begruendung: Q_ZITATE })) })
|
||
const { ergebnis: e } = await QR(Q_ARGS, { stimmen, urteile })
|
||
const md = (e && e.bericht_md) || ''
|
||
qSauber(soll, md)
|
||
const z = '\nNachprüfung: bestaetigt, ' + Q_KLAR + '. ' + Q_KLAR + '\n'
|
||
soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Klartext im Bericht: Unbelegt und Nicht prüfbar ohne Wikilink, Einbettung und Tag', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const stimmen = { opus: [{ ...qZ('wichtig'), beleg: '' }], sonnet: [qB('src/b.py', '5', 'hinweis')], codex: [] }
|
||
const { ergebnis: e } = await QR(Q_ARGS, { stimmen, nichtPruefbar: { sonnet: [Q_ZITATE] } })
|
||
const md = (e && e.bericht_md) || ''
|
||
qSauber(soll, md)
|
||
for (const z of [
|
||
'## Unbelegt\n\n- O1 (Opus, wichtig) `src/a.py`, Stelle ' + Q_KLAR + ': ' + Q_KLAR + '\n',
|
||
'## Nicht prüfbar\n\n- Sonnet: ' + Q_KLAR + '\n',
|
||
]) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Klartext im Bericht: Hinweise des Laufs und Grund der Codex-Stimme ohne Wikilink, Einbettung und Tag', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const stimmen = { opus: [qB('src/a.py', '10', 'hinweis')], sonnet: [qB('src/b.py', '5', 'hinweis')] }
|
||
const k = { stimmen, paket: { hinweise: [Q_ZITATE] }, codexEnde: { job_status: Q_ZITATE }, gruppen: () => ({ gruppen: [{ ids: [Q_ZITATE], begruendung: 'zitiert' }] }) }
|
||
const { ergebnis: e } = await QR(Q_ARGS, k)
|
||
const md = (e && e.bericht_md) || ''
|
||
qSauber(soll, md)
|
||
for (const z of [
|
||
'| Codex | nein: Codex-Auftrag endete mit ' + Q_KLAR + ' |',
|
||
'## Hinweise des Laufs\n\n- ' + Q_KLAR + '\n',
|
||
'\n- Codex ohne gültige Stimme (Codex-Auftrag endete mit ' + Q_KLAR + '); Haiku ist eingesprungen\n',
|
||
'\n- Zusammenführen: unbekannte ID ' + Q_KLAR + ' verworfen\n',
|
||
]) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z))
|
||
return fehler
|
||
})
|
||
|
||
fall('quorum', 'Klartext im Bericht eines Kundendokuments: Hub-Zeile bleibt Wikilink, Frontmatter bleibt, zitierte Syntax nicht', async () => {
|
||
const { fehler, soll } = pruefe()
|
||
const K = 'D:/Claude/IT Workspace/Beispiel/abgabe'
|
||
const HUB = 'Übersicht: [[IT Workspace/Beispiel/abgabe/00 Übersicht|00 Übersicht.md]]'
|
||
const args = { anlass: 'kundendokument', ordner: K, dateien: ['kapitel/01.md'], uebersicht: '00 Übersicht.md' }
|
||
const paket = { anlass: 'kundendokument', art: 'doku', wurzel: K, basis: 'erstes Quorum', stand: 'sha256:' + 'c'.repeat(64), vorher: null, nummer: 1 }
|
||
const { ergebnis: e } = await QR(args, { paket, stimmen: { opus: [qZ('blockierend')], sonnet: [qZ('blockierend')], codex: [] } })
|
||
const md = (e && e.bericht_md) || ''
|
||
qSauber(soll, md.replace(HUB, ''))
|
||
soll(md.includes('\n\n' + HUB + '\n\n') && md.split('[[').length === 2, 'Hub-Zeile nicht genau einmal als Wikilink: ' + JSON.stringify(md.split('\n').filter(x => x.includes('Übersicht'))))
|
||
for (const z of ['---\ntype: note\n', '\ntags: [quorum]\n', '- O1 (Opus, blockierend, Stelle ' + Q_KLAR + '): ' + Q_KLAR + '\n']) soll(md.includes(z), 'Bericht ohne ' + JSON.stringify(z))
|
||
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)
|