Erste Fassung: Claude-Werkzeuge aus dem Vault
.vault-tools (ohne trading-plattform-pruefung und watermarks-remover), .claude/agents, .claude/skills, .claude/workflows, .claude/settings.json. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
fa1c26b407
137 files changed
+43926
No files matched your search
@@ -0,0 +1,393 @@
|
||||
export const meta = {
|
||||
name: 'plankritik',
|
||||
description: 'Plankritik: pruefer-Linsen nach Verbrauch, doppelte Befunde zusammengefasst, wichtige und blockierende von einem zweiten pruefer widerlegt',
|
||||
whenToUse: 'Schritt 2 der Standardschleife vor dem ersten Executer, Pflicht ab etwa drei Tasks (AI/Regelwerk/codeworkflow.md). Args: {projekt, planordner, spec?, kopie?, materialisieren?: {ohne: [relpfade], tasks?: [taskdateien]}, suite?, linsen?, mutanten?: "voll"|"muster"|"aus" (Vorgabe voll), plaetze?: 1..4, verifizieren?: true, verifizieren_ab?: "wichtig"|"blockierend", effort?, werkzeuge?: Ordner der Planwerkzeuge, zusatz?}. Einzelheiten im Kommentar unter meta.',
|
||||
phases: [
|
||||
{ title: 'Vorbereitung', detail: 'Hilfsagent: Kopie bauen, Plan anwenden, Planwerkzeuge fahren' },
|
||||
{ title: 'Plankritik', detail: 'je Linse ein pruefer, gleichzeitig nach Verbrauch' },
|
||||
{ title: 'Widerlegung', detail: 'je wichtiger Befund ein zweiter pruefer, höchstens zwei gleichzeitig' },
|
||||
],
|
||||
}
|
||||
|
||||
// Diese Datei traegt LF. Mit CRLF im Arbeitsbaum (seit einer Nachbesserung am 2026-09-24) lehnte
|
||||
// die Rechtepruefung sie per Name wie per scriptPath mit "script contains control characters" ab:
|
||||
// Das CR ist das Steuerzeichen. Belegt am 2026-09-26 (Erinnerung
|
||||
// gespeicherter-workflow-per-name-scheitert). Die Einzelheiten zu den Args stehen hier, damit meta
|
||||
// kurz bleibt:
|
||||
// - werkzeuge ist ein Pfad, keine Ausgabe; Vorgabe D:/Claude/.vault-tools/plan-kopie.
|
||||
// - Ohne kopie baut materialisieren sie selbst (kopie_bauen.py plus plan_materialisiere.py);
|
||||
// ohne() muss Geheimnisdateien und große Datenordner nennen.
|
||||
// - mutanten: "voll" (Vorgabe), "muster" oder "aus". Die Stufe greift, sobald ctx.kopie steht -
|
||||
// also auch mit materialisieren, nicht nur mit args.kopie direkt gesetzt. Sie läuft auch ohne
|
||||
// die Linse arithmetik in args.linsen; die Rohausgabe steht dann nur in der Rückgabe
|
||||
// (mutanten_lauf, mutanten_status, mutanten_modus). Steuert den Hilfsagenten werkzeug:mutanten,
|
||||
// der mutanten_lauf_kopie.sh fährt (siehe Kommentar dort unten).
|
||||
// - plaetze begrenzt die gleichzeitigen Linsen; verbrauch.js senkt sie nach dem 5-Stunden-Fenster
|
||||
// und der Woche.
|
||||
// - Rückgabe mit status fertig oder gebremst; ein gebremster Lauf wird neu gestartet, nicht
|
||||
// fortgesetzt.
|
||||
//
|
||||
// Rollenparameter: model wird immer gesetzt (Stamm). Der Effort der pruefer kommt aus dem
|
||||
// Frontmatter von .claude/agents/pruefer.md; args.effort überschreibt ihn nur bewusst.
|
||||
// Der Hilfsagent hat keinen agentType und bekommt deshalb seinen Effort mit (low: gemessen
|
||||
// gleich treu wie high bei mechanischer Arbeit, codeworkflow.md "Effort je Rolle").
|
||||
// Opus-Plätze: Vor den Linsen und vor der Widerlegung liest verbrauch.js den Planverbrauch ab.
|
||||
// Die Linsen laufen mit so vielen Plätzen, wie verbrauch.js erlaubt (höchstens so viele wie
|
||||
// Linsen, höchstens LINSEN_HOECHSTENS), die Widerlegung mit höchstens zwei. Gebremst heißt: Die
|
||||
// Phase startet nicht, und der Lauf kommt mit status gebremst zurück.
|
||||
const EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max']
|
||||
const LINSEN_HOECHSTENS = 4
|
||||
const WIDERLEGUNG_HOECHSTENS = 2
|
||||
const OHNE_ABLESUNG = 2
|
||||
const VERBRAUCH = 'D:/Claude/.claude/workflows/verbrauch.js'
|
||||
const RANG = { blockierend: 0, wichtig: 1, klein: 2 }
|
||||
// Einfache Anführungszeichen: In Bash bleiben darin $, Backtick und Backslash wörtlich.
|
||||
const q = s => "'" + String(s).replace(/'/g, "'\\''") + "'"
|
||||
|
||||
// Text für die Linse arithmetik, wenn der Mutantenlauf (werkzeug:mutanten) etwas geliefert hat.
|
||||
// Seit der Nachbesserung 2026-09-28 (N5, N7): modus: muster in der Rohausgabe heißt, dass kein
|
||||
// Mutant gefahren ist (der Satz "prüfe bei OK an der Fehlermeldung" entfällt dann); Exit 2 oder 3
|
||||
// heißt, der Lauf ist vor oder während der Mutanten gescheitert.
|
||||
function mutantenText(m) {
|
||||
if (!m) return ''
|
||||
if (m.keineToml) return ' Der Plan hat keine mutanten.toml. Das ist ein Befund der Schwere wichtig, sobald der Plan Mutanten in "Fertig heißt" nennt.'
|
||||
const zeilen = m.rohausgabe.split('\n')
|
||||
const letzte = zeilen.slice(-200).join('\n')
|
||||
const musterModus = /^modus: muster/m.test(m.rohausgabe)
|
||||
const exitMatch = /EXIT mutanten_lauf: (\d+)/.exec(m.rohausgabe)
|
||||
const exitCode = exitMatch ? Number(exitMatch[1]) : null
|
||||
let hinweis = ''
|
||||
if (/ZEITGRENZE/.test(m.rohausgabe)) hinweis = ' Der Lauf hat seine Zeitgrenze erreicht: Mutanten ohne eigene Zeile sind nicht gefahren; setze für sie selbst höchstens die aus, die dir am riskantesten erscheinen, in einer eigenen Kopie.'
|
||||
else if (exitCode === 2 || exitCode === 3) hinweis = ` Der Mutantenlauf ist vor oder während der Mutanten gescheitert (Exit ${exitCode}); welche Mutanten ohne eigene Zeile sind, sind nicht gefahren; das ist ein Befund der Schwere wichtig.`
|
||||
const bewertung = musterModus
|
||||
? ' Kein Mutant ist gefahren; OK heißt hier nur, dass das Muster genau einmal in der Datei steht; ob ein Test fällt, ist unbelegt.'
|
||||
: ' Bewerte jeden Mutanten mit LUECKE, UNKLAR, ZEIT oder MUSTER als Befund und prüfe bei OK an der Fehlermeldung, ob der Test an der gemeinten Stelle fällt; setze Mutanten nicht erneut von Hand.'
|
||||
return `${hinweis}${bewertung}\n<mutantenlauf>\n${letzte}\n</mutantenlauf>`
|
||||
}
|
||||
|
||||
const LINSEN = {
|
||||
arithmetik: (a, ctx) => `Linse: Fixture-Arithmetik und Rot-Nachweise. Rechne jede Mindestzahl, jede Grenze und jeden Schwellenwert im Produktivcode des Plans gegen jede Fixture der Tests nach. Prüfe jede im Plan erwartete Fehlermeldung und jede vorhergesagte Testausgabe wörtlich: Würde der Test vor der Umsetzung genau so rot, und danach grün? pytest kürzt FAILED-Zeilen ohne Terminal auf 80 Zeichen; vergleiche Fehlerzeilen deshalb mit COLUMNS=300 vor dem Befehl.${ctx.kopie ? ` Der Plan ist auf die Kopie ${ctx.kopie} angewendet. Fahre dort die Suite (${a.suite || 'Testbefehl aus dem Plan'}) und vergleiche jede Vorhersage des Plans mit der echten Ausgabe. In der Kopie änderst du nichts außer dem, was die Testläufe selbst anlegen.` : ' Eine Kopie mit angewendetem Plan gibt es nicht; du rechnest am Text.'}${mutantenText(ctx.mutantenLauf)}`,
|
||||
bezeichner: (a, ctx) => `Linse: Bezeichner, Signaturen und Byte-Treue. Prüfe jeden Namen, jede Signatur und jeden Import über Taskgrenzen hinweg. Jeder Aus-Block muss genau einmal im echten Baum stehen, jeder Codeblock muss sich parsen lassen, und Codeblöcke dürfen keine Zeilen- und Absatztrenner, Format- oder Steuerzeichen enthalten. Prüfbefehle im Plan dürfen nur Code treffen, nie ein Wort, das auch in einem Kommentar steht.${ctx.werkzeuge ? `\n\nDie Planwerkzeuge sind schon gelaufen, hier ihre Rohausgabe. Bewerte sie, statt sie erneut zu fahren; nur bei Zweifeln selbst nachfahren.\n<werkzeugausgabe>\n${ctx.werkzeuge}\n</werkzeugausgabe>` : ` Fahre dazu plan_bloecke.py vor und plan_zeichen.py aus ${a.werkzeuge || 'D:/Claude/.vault-tools/plan-kopie'}.`}`,
|
||||
abdeckung: (a) => `Linse: Spec-Abdeckung in beide Richtungen. Jede Anforderung${a.spec ? ` aus ${a.spec}` : ' der Übersicht'} braucht einen Task und einen Test, und jeder Task braucht eine Anforderung. Die "Fertig heißt"-Liste der Übersicht muss je Check einen Beleg außerhalb der eigenen Meinung haben. Das Frontmatter jeder Taskdatei braucht modell und modell_begruendung; Abschrift-Tasks mit Anführungszeichen, Gedanken- und Streckenstrichen oder Auslassungspunkten im Code tragen modell sonnet und effort low statt haiku.`,
|
||||
}
|
||||
|
||||
const BEFUNDE = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
befunde: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
schweregrad: { type: 'string', enum: ['blockierend', 'wichtig', 'klein'] },
|
||||
datei: { type: 'string' },
|
||||
zeile: { type: 'integer' },
|
||||
beschreibung: { type: 'string' },
|
||||
reproduktion: { type: 'string' },
|
||||
},
|
||||
required: ['schweregrad', 'datei', 'zeile', 'beschreibung', 'reproduktion'],
|
||||
},
|
||||
},
|
||||
selbst_ausgefuehrt: { type: 'array', items: { type: 'string' } },
|
||||
nicht_pruefbar: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
required: ['befunde', 'selbst_ausgefuehrt', 'nicht_pruefbar'],
|
||||
}
|
||||
|
||||
const LAUF = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
laeufe: { type: 'array', items: { type: 'object', properties: { befehl: { type: 'string' }, exit: { type: 'integer' }, ausgabe: { type: 'string' } }, required: ['befehl', 'exit', 'ausgabe'] } },
|
||||
},
|
||||
required: ['laeufe'],
|
||||
}
|
||||
|
||||
const URTEIL = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
urteil: { type: 'string', enum: ['bestaetigt', 'widerlegt', 'offen'] },
|
||||
schweregrad: { type: 'string', enum: ['blockierend', 'wichtig', 'klein'], description: 'dein eigenes Urteil über die Schwere' },
|
||||
bedingung: { type: 'string', description: 'unter welcher Bedingung der Befund gilt; leer, wenn immer' },
|
||||
begruendung: { type: 'string' },
|
||||
beleg: { type: 'string', description: 'eigener Befehl mit Ausgabe oder wörtliches Zitat mit Fundstelle' },
|
||||
},
|
||||
required: ['urteil', 'schweregrad', 'bedingung', 'begruendung', 'beleg'],
|
||||
}
|
||||
|
||||
function pruefeArgs(a) {
|
||||
if (!a || typeof a !== 'object') throw new Error('plankritik: args fehlen')
|
||||
const fehlt = ['projekt', 'planordner'].filter(k => !a[k])
|
||||
if (fehlt.length) throw new Error('plankritik: args fehlen: ' + fehlt.join(', '))
|
||||
if (a.linsen !== undefined && !Array.isArray(a.linsen)) throw new Error('plankritik: linsen muss eine Liste sein, bekannt: ' + Object.keys(LINSEN).join(', '))
|
||||
if (a.materialisieren && a.materialisieren.tasks !== undefined && !Array.isArray(a.materialisieren.tasks)) throw new Error('plankritik: materialisieren.tasks muss eine Liste von Taskdateien sein')
|
||||
const unbekannt = (a.linsen || []).filter(l => !LINSEN[l])
|
||||
if (unbekannt.length) throw new Error('plankritik: unbekannte Linse: ' + unbekannt.join(', ') + '; bekannt: ' + Object.keys(LINSEN).join(', '))
|
||||
if (a.linsen && new Set(a.linsen).size !== a.linsen.length) throw new Error('plankritik: eine Linse steht doppelt in linsen: ' + a.linsen.join(', '))
|
||||
if (a.effort !== undefined && !EFFORTS.includes(a.effort)) throw new Error('plankritik: effort "' + a.effort + '" ungültig')
|
||||
if (a.verifizieren_ab !== undefined && !['wichtig', 'blockierend'].includes(a.verifizieren_ab)) throw new Error('plankritik: verifizieren_ab muss wichtig oder blockierend sein')
|
||||
if (a.plaetze !== undefined && !(Number.isInteger(a.plaetze) && a.plaetze >= 1 && a.plaetze <= LINSEN_HOECHSTENS)) throw new Error('plankritik: plaetze muss eine ganze Zahl von 1 bis ' + LINSEN_HOECHSTENS + ' sein')
|
||||
if (a.materialisieren && !(Array.isArray(a.materialisieren.ohne) && a.materialisieren.ohne.length > 0 && a.materialisieren.ohne.every(x => typeof x === 'string' && x))) throw new Error('plankritik: materialisieren.ohne muss eine nicht leere Liste sein, Ausschlüsse als Pfade vom Projektwurzelordner, mindestens .git (falls vorhanden) und Geheimnisdateien')
|
||||
if (a.mutanten !== undefined && !['voll', 'muster', 'aus'].includes(a.mutanten)) throw new Error('plankritik: mutanten muss voll, muster oder aus sein')
|
||||
}
|
||||
|
||||
// Höchstens n Aufgaben gleichzeitig, ohne Leerlauf zwischen Paaren.
|
||||
async function hoechstens(n, items, fn) {
|
||||
const ergebnisse = new Array(items.length).fill(null)
|
||||
let naechster = 0
|
||||
async function arbeiter() {
|
||||
while (naechster < items.length) {
|
||||
const i = naechster++
|
||||
try { ergebnisse[i] = await fn(items[i], i) } catch (e) { log(`Aufgabe ${i + 1} abgebrochen: ${e && e.message}`) }
|
||||
}
|
||||
}
|
||||
await Promise.all(Array.from({ length: Math.min(n, items.length) }, () => arbeiter()))
|
||||
return ergebnisse
|
||||
}
|
||||
|
||||
// Eine Hilfsfunktion ablesen() steht auch in planung.js (gleiche Form) sowie in pruefung.js und
|
||||
// welle-mit-pruefung.js (dort nur messend, ohne Rückgabe). Die Regel (Fenster, Stufen, Rückfall)
|
||||
// steht nur in verbrauch.js; hier steht nur der Rückfall für den Fall, dass verbrauch.js selbst
|
||||
// nicht läuft, etwa weil dieser Workflow als Kind läuft.
|
||||
const ablesungen = []
|
||||
async function ablesen(anlass, wunsch) {
|
||||
let r = null
|
||||
try {
|
||||
r = await workflow({ scriptPath: VERBRAUCH }, { anlass: 'plankritik:' + anlass, wunsch })
|
||||
} catch (e) {
|
||||
r = { status: 'nicht_gelesen', grund: 'verbrauch.js nicht ausführbar: ' + (e && e.message) }
|
||||
}
|
||||
if (!r || typeof r.plaetze !== 'number') {
|
||||
r = { status: 'nicht_gelesen', quelle: 'keine', fuenf_stunden: null, woche: null, plaetze: Math.min(wunsch, OHNE_ABLESUNG), gebremst: false, grund: (r && r.grund) || 'verbrauch.js ohne verwertbares Ergebnis' }
|
||||
}
|
||||
ablesungen.push({ anlass, ...r })
|
||||
log(`Verbrauch ${anlass}: ${r.status}, ${r.plaetze} Plätze${r.gebremst ? ', gebremst' : ''} (${r.grund})`)
|
||||
return r
|
||||
}
|
||||
|
||||
function werkzeugAgent(label, phase, befehl, zweck, zusatz) {
|
||||
return agent(`Du führst einen Werkzeugbefehl aus und gibst seine Ausgabe roh zurück. Zweck: ${zweck}
|
||||
|
||||
Führe genau diesen Befehl einmal im Bash-Werkzeug (Git-Bash) aus, unverändert:
|
||||
${befehl}
|
||||
${zusatz ? '\n' + zusatz + '\n' : ''}
|
||||
Ändere keine Datei außer denen, die der Befehl selbst anlegt. Deute nichts und fasse nichts zusammen. Gib Befehl, Exit-Code und die vollständige Ausgabe zurück; bei mehr als 200 Zeilen die ersten 60 und die letzten 120, mit einer Zeile "[... N Zeilen gekürzt ...]" dazwischen.`,
|
||||
{ label, phase, schema: LAUF, model: 'sonnet', effort: 'low' })
|
||||
}
|
||||
|
||||
function testort(ctx) {
|
||||
return ctx.kopie
|
||||
? `Tests fährst du nur in der Kopie ${ctx.kopie}, nie im echten Projektbaum: Dort kann Betrieb laufen, und andere Prüfer arbeiten gleichzeitig. Mutanten legst du nur in einer eigenen Kopie dieser Kopie an, in einem neuen Ordner unter dem System-Temp, nie in ${ctx.kopie} selbst.`
|
||||
: 'Tests fährst du in dieser Plankritik nicht, auch nicht im echten Projektbaum: Dort kann Betrieb laufen, und andere Prüfer arbeiten gleichzeitig.'
|
||||
}
|
||||
|
||||
function auftrag(a, linse, ctx) {
|
||||
return `Lies den Plan im Ordner ${a.planordner} für das Projekt ${a.projekt}, bevor ein Executer startet. Du änderst keine Datei im Projekt und keine im Plan. Eine Umsetzung gibt es noch nicht, also auch keinen Executer-Bericht, dessen Testlauf du wiederholen könntest. ${testort(ctx)}
|
||||
|
||||
${LINSEN[linse](a, ctx)}
|
||||
|
||||
Melde jeden Befund mit Schweregrad, auch unsichere; ein zweiter Prüfer versucht danach jeden wichtigen Befund zu widerlegen. Nenn je Befund Datei, Zeile und wie er sich reproduzieren lässt.
|
||||
${a.zusatz ? a.zusatz + '\n' : ''}Übertrage deinen Schlussbericht in die Felder des StructuredOutput-Werkzeugs.`
|
||||
}
|
||||
|
||||
function pfadnorm(p) { return String(p).replace(/\\/g, '/').replace(/\/+/g, '/').toLowerCase() }
|
||||
function dateiname(p) { return pfadnorm(p).split('/').pop() }
|
||||
|
||||
// Doppelte Befunde verschiedener Linsen zusammenfassen: gleicher Pfad und gleiche Zeile.
|
||||
// Zeile 0 oder kleiner heißt "ganze Datei" und wird nie zusammengefasst, weil darunter
|
||||
// verschiedene Befunde derselben Datei liegen.
|
||||
function zusammenfassen(befunde) {
|
||||
const gruppen = new Map()
|
||||
befunde.forEach((b, i) => {
|
||||
const k = b.zeile > 0 ? pfadnorm(b.datei) + ':' + b.zeile : 'einzeln:' + i
|
||||
if (!gruppen.has(k)) gruppen.set(k, { ...b, linsen: [b.linse], quellen: [b] })
|
||||
else {
|
||||
const g = gruppen.get(k)
|
||||
g.quellen.push(b)
|
||||
if (!g.linsen.includes(b.linse)) g.linsen.push(b.linse)
|
||||
if (RANG[b.schweregrad] < RANG[g.schweregrad]) g.schweregrad = b.schweregrad
|
||||
}
|
||||
})
|
||||
return [...gruppen.values()]
|
||||
}
|
||||
|
||||
function widerlegungsauftrag(a, g, ctx) {
|
||||
const texte = g.quellen.map((b, i) => `Fassung ${i + 1} (Linse ${b.linse}, ${b.schweregrad}, ${b.datei}:${b.zeile}): ${b.beschreibung}\nReproduktion laut Linse: ${b.reproduktion}`).join('\n\n')
|
||||
return `Eine Plankritik hat zum Plan im Ordner ${a.planordner} (Projekt ${a.projekt}) diesen Befund gemeldet, Fundstelle ${g.datei}:${g.zeile}:
|
||||
|
||||
<befund>
|
||||
${texte}
|
||||
</befund>
|
||||
|
||||
Versuche ihn zu widerlegen. Lies die Stelle selbst und reproduziere den Befund mit einem eigenen Befehl. Du änderst keine Datei im Projekt und keine im Plan. ${testort(ctx)}
|
||||
- "bestaetigt" nur mit eigener Reproduktion oder einem wörtlichen Beleg aus der Quelle.
|
||||
- "widerlegt" nur mit einem Gegenbeleg: ein Befehl mit Ausgabe oder ein Zitat, das die Aussage falsch macht.
|
||||
- Sonst "offen".
|
||||
Gilt der Befund nur unter einer Bedingung, nenn sie. Gib deine eigene Einschätzung der Schwere an.
|
||||
Übertrage dein Urteil in die Felder des StructuredOutput-Werkzeugs.`
|
||||
}
|
||||
|
||||
pruefeArgs(args)
|
||||
const W = args.werkzeuge || 'D:/Claude/.vault-tools/plan-kopie'
|
||||
const PY = 'PYTHONIOENCODING=utf-8 python -X utf8'
|
||||
const ctx = { kopie: args.kopie || null, werkzeuge: null, temp: null, mutantenLauf: null, mutantenStatus: null }
|
||||
const MUTANTEN_MODUS = args.mutanten || 'voll'
|
||||
const vorbereitung = []
|
||||
|
||||
// Eine Kopie gilt nur als verwendbar, wenn der Lauf sauber endet, den Pfad bis zum
|
||||
// Zeilenende nennt (Pfade mit Leerzeichen) und das Planwerkzeug mindestens einen Block
|
||||
// angewendet hat. Sonst rechnet Linse 1 am Text, und das steht im Log.
|
||||
function kopieAusLauf(r, wozu) {
|
||||
const lauf = r && r.laeufe && r.laeufe[0]
|
||||
if (!lauf) { log(`${wozu}: Hilfsagent ohne Ergebnis, keine Kopie`); return null }
|
||||
const m = /^KOPIE=(.+?)\s*$/m.exec(lauf.ausgabe)
|
||||
const angewendet = /'(neu|ersetzt|angehaengt)': [1-9]/.test(lauf.ausgabe)
|
||||
if (lauf.exit !== 0 || !m) { log(`${wozu}: Kopie nicht verwendbar (Exit ${lauf.exit}${m ? '' : ', kein KOPIE-Pfad'})`); return null }
|
||||
ctx.temp = m[1].replace(/\/mat$/, '')
|
||||
if (!angewendet) { log(`${wozu}: das Planwerkzeug hat keinen Block angewendet; die Kopie ist der unveränderte Stand und wird nicht verwendet`); return null }
|
||||
return m[1]
|
||||
}
|
||||
|
||||
function tempText() {
|
||||
return ctx.temp ? `${ctx.temp} bleibt liegen (Projektkopie ohne die Ausschlüsse); löschen, wenn nicht mehr gebraucht` : null
|
||||
}
|
||||
|
||||
phase('Vorbereitung')
|
||||
if (ctx.kopie && args.materialisieren) log('kopie ist gesetzt, materialisieren wird übergangen')
|
||||
if (!ctx.kopie && args.materialisieren) {
|
||||
const liste = (args.materialisieren.tasks || []).map(q).join(' ')
|
||||
const auswahl = liste
|
||||
? `TASKS=(${liste})`
|
||||
: `shopt -s nullglob; pushd ${q(args.planordner)} >/dev/null && TASKS=(task-*.md) && popd >/dev/null; [ \${#TASKS[@]} -gt 0 ] || { echo "KEINE_TASKS: im Planordner liegt keine task-*.md; materialisieren.tasks angeben"; exit 3; }`
|
||||
const befehl = `${auswahl}; T=$(mktemp -d) && echo "KOPIE=$(cygpath -m "$T/mat")" && ${PY} ${q(W + '/kopie_bauen.py')} ${q(args.projekt)} "$T/mat" --ohne ${args.materialisieren.ohne.map(q).join(' ')} && ${PY} ${q(W + '/plan_materialisiere.py')} ${q(args.planordner)} "$T/mat" "\${TASKS[@]}"`
|
||||
const r = await werkzeugAgent('werkzeug:kopie', 'Vorbereitung', befehl, 'Kopie des Projekts bauen und den Plan darauf anwenden')
|
||||
ctx.kopie = kopieAusLauf(r, 'Plankritik')
|
||||
vorbereitung.push(r)
|
||||
}
|
||||
{
|
||||
const befehl = `${PY} ${q(W + '/plan_zeichen.py')} ${q(args.planordner)}; echo "EXIT plan_zeichen: $?"; ls ${q(args.planordner)}/task-*.md >/dev/null 2>&1 && { ${PY} ${q(W + '/plan_bloecke.py')} vor ${q(args.planordner)}/task-*.md --projekt ${q(args.projekt)}; echo "EXIT plan_bloecke: $?"; } || echo "plan_bloecke: keine task-*.md im Planordner, nicht gefahren"`
|
||||
const r = await werkzeugAgent('werkzeug:planwerkzeuge', 'Vorbereitung', befehl, 'Codeblöcke auf unsichtbare Zeichen und Aus-Blöcke gegen den Baum prüfen')
|
||||
// Nur wenn beide Werkzeuge mit 0 endeten, gilt die Ausgabe als gelaufen (2026-09-24: ein Protokollsatz statt
|
||||
// Pfad in args.werkzeuge endete mit Errno 2 und ging trotzdem als Rohausgabe an die Linse). Der Exit des
|
||||
// ganzen Befehls hilft dabei nicht, er ist der des letzten echo; gelesen werden die EXIT-Zeilen.
|
||||
const aus = r && r.laeufe && r.laeufe[0] ? String(r.laeufe[0].ausgabe || '') : ''
|
||||
const gelaufen = /EXIT plan_zeichen: 0(?!\d)/.test(aus) && (/EXIT plan_bloecke: 0(?!\d)/.test(aus) || /plan_bloecke: keine task-/.test(aus))
|
||||
if (gelaufen) ctx.werkzeuge = aus
|
||||
else log('Planwerkzeuge: Hilfsagent ohne Ergebnis; Linse bezeichner fährt sie selbst')
|
||||
vorbereitung.push(r)
|
||||
}
|
||||
// Mutantenlauf (seit 2026-09-27). mutanten_lauf.py ändert Dateien in der Wurzel, die es bekommt,
|
||||
// und setzt sie nach jedem Mutanten zurück. Die Linsen fahren gleichzeitig Tests in ctx.kopie;
|
||||
// deshalb arbeitet mutanten_lauf_kopie.sh auf einer Kopie der Kopie unter /tmp in WSL. Dieser
|
||||
// await steht in der Vorbereitung, also vor phase('Plankritik'): Der Mutantenlauf ist abgewartet,
|
||||
// bevor die Linsen starten, und die Kopie der Kopie schützt dabei ctx.kopie selbst - die Linsen
|
||||
// arbeiten gleichzeitig darin, mutanten_lauf.py aber nur in seiner eigenen Kopie unter /tmp.
|
||||
// MSYS_NO_PATHCONV=1 ist Pflicht: Ohne sie übersetzt die Git-Bash /mnt/d/... in einen
|
||||
// Windows-Pfad (Erinnerung git-bash-uebersetzt-pfade-fuer-wsl-exe). cygpath -l ist Pflicht, weil
|
||||
// die Kopie oft unter dem Kurznamen PASCAL~1 liegt. cygpath -m statt -w, weil wsl -d Debian -- ...
|
||||
// die Befehlszeile an die Linux-Shell weiterreicht: Die Backslash-Begründung dafür
|
||||
// ("D:\Claude\foo" kam als "D:Claudefoo" an, gemessen 2026-09-27) konnte der Prüfer am
|
||||
// 2026-09-28 in dieser Aufrufform nicht nachstellen; -m bleibt trotzdem, weil es nicht schadet,
|
||||
// und wslpath -u im Skript wandelt den Pfad ohnehin um. Das Bash-Werkzeug des Hilfsagenten endet
|
||||
// nach höchstens zehn Minuten; das Skript begrenzt den Lauf deshalb selbst auf 540 s und meldet
|
||||
// eine überschrittene Grenze mit ZEITGRENZE.
|
||||
// mutanten_status (N8, seit 2026-09-28): aus, ohne_kopie, keine_toml, ohne_ergebnis, sonst exit_<n>.
|
||||
if (MUTANTEN_MODUS === 'aus') ctx.mutantenStatus = 'aus'
|
||||
else if (!ctx.kopie) ctx.mutantenStatus = 'ohne_kopie'
|
||||
if (ctx.kopie && MUTANTEN_MODUS !== 'aus') {
|
||||
const befehl = `MSYS_NO_PATHCONV=1 wsl -d Debian -- bash /mnt/d/Claude/.vault-tools/plan-kopie/mutanten_lauf_kopie.sh "$(cygpath -m -l ${q(ctx.kopie)})" "$(cygpath -m -l ${q(args.planordner)})" ${MUTANTEN_MODUS === 'muster' ? 'muster' : 'voll'}`
|
||||
const r = await werkzeugAgent('werkzeug:mutanten', 'Vorbereitung', befehl, 'Mutanten aus mutanten.toml auf einer Kopie der Kopie fahren', 'Setze im Bash-Werkzeug den Parameter timeout auf 600000; der Befehl darf bis zu zehn Minuten laufen.')
|
||||
const aus = r && r.laeufe && r.laeufe[0] ? String(r.laeufe[0].ausgabe || '') : ''
|
||||
const exitMatch = /EXIT mutanten_lauf: (\d+)/.exec(aus)
|
||||
if (/KEINE_MUTANTEN_TOML/.test(aus)) { ctx.mutantenLauf = { keineToml: true, rohausgabe: aus }; ctx.mutantenStatus = 'keine_toml' }
|
||||
else if (exitMatch) { ctx.mutantenLauf = { keineToml: false, rohausgabe: aus }; ctx.mutantenStatus = 'exit_' + exitMatch[1] }
|
||||
else { log('Mutantenlauf: Hilfsagent ohne verwertbares Ergebnis (kein KEINE_MUTANTEN_TOML, kein EXIT mutanten_lauf); Linsen laufen ohne Mutantenlauf weiter'); ctx.mutantenStatus = 'ohne_ergebnis' }
|
||||
vorbereitung.push(r)
|
||||
}
|
||||
|
||||
phase('Plankritik')
|
||||
const gewaehlt = args.linsen && args.linsen.length ? args.linsen : Object.keys(LINSEN)
|
||||
const vorLinsen = await ablesen('vor Linsen', Math.min(args.plaetze || gewaehlt.length, gewaehlt.length, LINSEN_HOECHSTENS))
|
||||
if (vorLinsen.gebremst) {
|
||||
log('Plankritik gebremst vor den Linsen: ' + vorLinsen.grund)
|
||||
return {
|
||||
status: 'gebremst',
|
||||
gebremst_vor: 'Linsen',
|
||||
zaehlung: { roh: 0, zusammengefasst: 0, bestaetigt: 0, offen: 0, widerlegt: 0, nicht_geprueft: 0 },
|
||||
kopie: ctx.kopie,
|
||||
temp_ordner: tempText(),
|
||||
mutanten_lauf: ctx.mutantenLauf ? ctx.mutantenLauf.rohausgabe : null,
|
||||
mutanten_status: ctx.mutantenStatus,
|
||||
mutanten_modus: MUTANTEN_MODUS,
|
||||
befunde: [],
|
||||
selbst_ausgefuehrt: [],
|
||||
nicht_pruefbar: [],
|
||||
vorbereitung,
|
||||
ohne_ergebnis: gewaehlt,
|
||||
verbrauch: ablesungen,
|
||||
plaetze: { linsen: 0, widerlegung: 0 },
|
||||
naechster_schritt: 'Nach der Zurücksetzung des Fensters neu starten, nicht mit resumeFromRunId fortsetzen: Die Ablesung käme sonst aus dem Zwischenspeicher.',
|
||||
}
|
||||
}
|
||||
const kritiken = await hoechstens(vorLinsen.plaetze, gewaehlt, linse => {
|
||||
const opts = { label: 'plankritik:' + linse, phase: 'Plankritik', schema: BEFUNDE, agentType: 'pruefer', model: 'opus' }
|
||||
if (args.effort) opts.effort = args.effort
|
||||
return agent(auftrag(args, linse, ctx), opts).then(r => ({ linse, ergebnis: r }))
|
||||
})
|
||||
const ergebnisse = kritiken.filter(k => k && k.ergebnis)
|
||||
const ohneErgebnis = gewaehlt.filter(l => !ergebnisse.some(e => e.linse === l))
|
||||
if (ohneErgebnis.length) log('Linsen ohne Ergebnis: ' + ohneErgebnis.join(', '))
|
||||
|
||||
const roh = ergebnisse.flatMap(e => e.ergebnis.befunde.map(b => ({ linse: e.linse, ...b })))
|
||||
const gruppen = zusammenfassen(roh)
|
||||
const ab = RANG[args.verifizieren_ab || 'wichtig']
|
||||
const zuPruefen = args.verifizieren === false ? [] : gruppen.filter(g => RANG[g.schweregrad] <= ab)
|
||||
log(`${roh.length} Befunde, zusammengefasst ${gruppen.length}; ${zuPruefen.length} gehen in die Widerlegung, ${gruppen.length - zuPruefen.length} nicht (${args.verifizieren === false ? 'abgeschaltet' : 'unter der Schwelle ' + (args.verifizieren_ab || 'wichtig')})`)
|
||||
|
||||
phase('Widerlegung')
|
||||
let gebremstVor = null
|
||||
let plaetzeWiderlegung = 0
|
||||
if (zuPruefen.length) {
|
||||
const vorWiderlegung = await ablesen('vor Widerlegung', Math.min(WIDERLEGUNG_HOECHSTENS, zuPruefen.length))
|
||||
if (vorWiderlegung.gebremst) {
|
||||
gebremstVor = 'Widerlegung'
|
||||
log('Widerlegung gebremst, die Befunde bleiben ungeprüft: ' + vorWiderlegung.grund)
|
||||
} else plaetzeWiderlegung = vorWiderlegung.plaetze
|
||||
}
|
||||
if (!gebremstVor) {
|
||||
const urteile = await hoechstens(plaetzeWiderlegung, zuPruefen, (g, i) => {
|
||||
const opts = { label: `widerlegung:${i + 1}:${dateiname(g.datei)}:${g.zeile}`, phase: 'Widerlegung', schema: URTEIL, agentType: 'pruefer', model: 'opus' }
|
||||
if (args.effort) opts.effort = args.effort
|
||||
return agent(widerlegungsauftrag(args, g, ctx), opts)
|
||||
})
|
||||
zuPruefen.forEach((g, i) => {
|
||||
if (!urteile[i]) log(`Widerlegung ${i + 1} (${g.datei}:${g.zeile}) ohne Ergebnis; Befund bleibt offen`)
|
||||
g.widerlegung = urteile[i] || { urteil: 'offen', schweregrad: g.schweregrad, bedingung: '', begruendung: 'Widerlegungsagent ohne Ergebnis', beleg: '' }
|
||||
})
|
||||
}
|
||||
await ablesen('Ende', 0)
|
||||
|
||||
const URTEILRANG = { bestaetigt: 0, offen: 1, undefined: 2, widerlegt: 3 }
|
||||
const befunde = gruppen
|
||||
.map(g => ({ urteil: g.widerlegung ? g.widerlegung.urteil : gebremstVor && zuPruefen.includes(g) ? 'nicht geprüft (gebremst)' : 'nicht geprüft', ...g }))
|
||||
.sort((x, y) => (URTEILRANG[x.widerlegung && x.widerlegung.urteil] - URTEILRANG[y.widerlegung && y.widerlegung.urteil]) || (RANG[x.schweregrad] - RANG[y.schweregrad]))
|
||||
const zaehlung = { roh: roh.length, zusammengefasst: gruppen.length, bestaetigt: 0, offen: 0, widerlegt: 0, nicht_geprueft: gruppen.filter(g => !g.widerlegung).length }
|
||||
befunde.forEach(b => { if (b.widerlegung) zaehlung[b.widerlegung.urteil]++ })
|
||||
|
||||
return {
|
||||
status: gebremstVor ? 'gebremst' : 'fertig',
|
||||
gebremst_vor: gebremstVor,
|
||||
zaehlung,
|
||||
kopie: ctx.kopie,
|
||||
temp_ordner: tempText(),
|
||||
mutanten_lauf: ctx.mutantenLauf ? ctx.mutantenLauf.rohausgabe : null,
|
||||
mutanten_status: ctx.mutantenStatus,
|
||||
mutanten_modus: MUTANTEN_MODUS,
|
||||
befunde,
|
||||
selbst_ausgefuehrt: ergebnisse.map(e => ({ linse: e.linse, befehle: e.ergebnis.selbst_ausgefuehrt })),
|
||||
nicht_pruefbar: ergebnisse.flatMap(e => e.ergebnis.nicht_pruefbar.map(x => e.linse + ': ' + x)),
|
||||
vorbereitung,
|
||||
ohne_ergebnis: ohneErgebnis,
|
||||
verbrauch: ablesungen,
|
||||
plaetze: { linsen: vorLinsen.plaetze, widerlegung: plaetzeWiderlegung },
|
||||
naechster_schritt: gebremstVor ? 'Die Widerlegung nach der Zurücksetzung des Fensters nachholen: plankritik neu starten, nicht mit resumeFromRunId fortsetzen.' : 'Bestätigte und offene Befunde am Plan korrigieren, danach die Kopie neu bauen.',
|
||||
}
|
||||
Reference in new issue
Block a user