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,117 @@
|
||||
export const meta = {
|
||||
name: 'executer-welle',
|
||||
description: 'Führt die Taskdateien einer Welle mit dem executer aus, Modell und Effort je Task aus dem Task-Frontmatter; gibt die Berichte für den Byte-Vergleich zurück',
|
||||
whenToUse: 'Schritt 3 der Standardschleife (AI/Regelwerk/codeworkflow.md), sobald eine Welle mit disjunkten Zieldateien steht. Args: {projekt, planordner, tasks: [{datei, modell, effort?}], rot_nachweis_erbracht?, zusatz?}. modell und effort liest der Koordinator vorher aus dem Frontmatter der Taskdateien (grep "^modell:\\|^effort:"). Ein Opus-Task ohne effort läuft auf high.',
|
||||
phases: [{ title: 'Welle', detail: 'ein executer je Taskdatei, Opus höchstens zwei gleichzeitig' }],
|
||||
}
|
||||
|
||||
// Rollenparameter: model wird immer gesetzt (Stamm, "Agenten & Modelle"). Der Effort kommt
|
||||
// aus dem Frontmatter von .claude/agents/executer.md (medium); ein effort im Task überschreibt
|
||||
// ihn, etwa low für Abschrift mit typografischen Zeichen. Ein Opus-Task ist die Hochstufung
|
||||
// nach zwei Fehlschlägen und läuft ohne Angabe auf high statt auf dem medium des Executers.
|
||||
// Haiku kennt keinen Effort.
|
||||
const MODELLE = ['haiku', 'sonnet', 'opus']
|
||||
const EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max']
|
||||
const OPUS_PARALLEL = 2
|
||||
|
||||
function pruefeArgs(a) {
|
||||
if (!a || typeof a !== 'object') throw new Error('executer-welle: args fehlen')
|
||||
const fehlt = ['projekt', 'planordner', 'tasks'].filter(k => !a[k])
|
||||
if (fehlt.length) throw new Error('executer-welle: args fehlen: ' + fehlt.join(', '))
|
||||
if (!Array.isArray(a.tasks) || a.tasks.length === 0) throw new Error('executer-welle: tasks muss eine nicht leere Liste von {datei, modell, effort?} sein')
|
||||
a.tasks.forEach((t, i) => {
|
||||
if (!t || typeof t !== 'object') throw new Error(`executer-welle: tasks[${i}] ist kein Objekt`)
|
||||
if (!t.datei || typeof t.datei !== 'string') throw new Error(`executer-welle: tasks[${i}] ohne datei`)
|
||||
if (!MODELLE.includes(t.modell)) throw new Error(`executer-welle: ${t.datei}: modell "${t.modell}" nicht in ${MODELLE.join('/')}`)
|
||||
if (t.effort !== undefined && !EFFORTS.includes(t.effort)) throw new Error(`executer-welle: ${t.datei}: effort "${t.effort}" nicht in ${EFFORTS.join('/')}`)
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
const BERICHT = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
erledigt: { type: 'array', items: { type: 'string' } },
|
||||
geaenderte_dateien: { type: 'array', items: { type: 'string' } },
|
||||
verifikation: { type: 'array', items: { type: 'object', properties: { befehl: { type: 'string' }, ausgabe: { type: 'string' } }, required: ['befehl', 'ausgabe'] } },
|
||||
abweichungen: { type: 'array', items: { type: 'string' } },
|
||||
annahmen: { type: 'array', items: { type: 'string' } },
|
||||
beobachtet: { type: 'array', items: { type: 'string' } },
|
||||
},
|
||||
required: ['erledigt', 'geaenderte_dateien', 'verifikation', 'abweichungen', 'annahmen', 'beobachtet'],
|
||||
}
|
||||
|
||||
function auftrag(a, t) {
|
||||
const rot = a.rot_nachweis_erbracht
|
||||
? 'Der Rot-Nachweis dieses Tasks ist mit rot_nachweis.py schon erbracht. Du musst keine Produktivdatei beiseitelegen.'
|
||||
: 'Verlangt der Task erst den roten Test und dann die Umsetzung: Leg die fertige Produktivdatei kurz beiseite (den alten Stand etwa aus git show HEAD:<pfad>), beobachte und zitiere den roten Lauf und leg danach die Fassung aus dem Plan byte-gleich zurück. Schreib sie nie neu.'
|
||||
return `Setze die Taskdatei ${a.planordner}/${t.datei} um. Arbeitsverzeichnis ist das Projekt ${a.projekt}.
|
||||
|
||||
Verweist die Taskdatei auf globale Vorgaben, stehen sie in ${a.planordner}/00-uebersicht.md.
|
||||
Erlaubt sind genau die Dateien unter "## Zieldateien" der Taskdatei. Jede andere Datei ist verboten, ebenso git commit und git push.
|
||||
Code, der im Plan wörtlich steht, übernimmst du byte-genau, auch Kommentare, Docstrings und Sonderzeichen.
|
||||
Ein Widerspruch im Plan wird im Bericht unter abweichungen gemeldet und nicht still aufgelöst. Das gilt auch dann, wenn ein Prüfbefehl nur grün würde, weil du einen Kommentar umformulierst. Gibt es keine Abweichung, steht dort genau ein Eintrag "keine".
|
||||
${rot}
|
||||
Endet eine FAILED-Zeile von pytest auf "...", ist sie gekürzt: pytest kürzt ohne Terminal auf 80 Zeichen. Wiederhole den Befehl dann mit COLUMNS=300 davor, bevor du eine Fehlerart mit dem Plan vergleichst.
|
||||
Ist das Projekt ein Git-Repo, zeig am Ende die Ausgabe von git status --porcelain -- <deine Zieldateien>.
|
||||
${a.zusatz ? a.zusatz + '\n' : ''}Übertrage deinen Schlussbericht in die Felder des StructuredOutput-Werkzeugs.`
|
||||
}
|
||||
|
||||
function effortFuer(t) {
|
||||
if (t.modell === 'haiku') {
|
||||
if (t.effort) log(`${t.datei}: effort ${t.effort} bei haiku wirkungslos, nicht gesetzt`)
|
||||
return undefined
|
||||
}
|
||||
if (t.effort) return t.effort
|
||||
return t.modell === 'opus' ? 'high' : undefined
|
||||
}
|
||||
|
||||
function starte(a, t) {
|
||||
const opts = { label: 'executer:' + t.datei.replace(/\.md$/, ''), phase: 'Welle', schema: BERICHT, agentType: 'executer', model: t.modell }
|
||||
const effort = effortFuer(t)
|
||||
if (effort) opts.effort = effort
|
||||
return agent(auftrag(a, t), opts).then(r => {
|
||||
if (!r) log(`${t.datei}: kein Bericht (Agent abgebrochen oder übersprungen)`)
|
||||
return { datei: t.datei, modell: t.modell, effort: effort || '(Frontmatter)', bericht: r }
|
||||
})
|
||||
}
|
||||
|
||||
// "keine", "Keine.", "keine — alles wie beauftragt" zählen als keine Abweichung;
|
||||
// "Keine Abweichung im Code, aber ..." und alles andere zählt als Abweichung.
|
||||
function istKeine(x) {
|
||||
return /^\s*keine(\s+abweichung(en)?)?\s*($|[.:;!—–-])/i.test(x) && !/\baber\b/i.test(x)
|
||||
}
|
||||
|
||||
pruefeArgs(args)
|
||||
phase('Welle')
|
||||
const opus = args.tasks.filter(t => t.modell === 'opus')
|
||||
const rest = args.tasks.filter(t => t.modell !== 'opus')
|
||||
const [restErg, opusErg] = await Promise.all([
|
||||
parallel(rest.map(t => () => starte(args, t))),
|
||||
hoechstens(OPUS_PARALLEL, opus, t => starte(args, t)),
|
||||
])
|
||||
|
||||
const berichte = [...restErg, ...opusErg].filter(Boolean)
|
||||
const ohneBericht = args.tasks.map(t => t.datei).filter(d => !berichte.some(b => b.datei === d && b.bericht))
|
||||
const mitAbweichung = berichte.filter(b => b.bericht && b.bericht.abweichungen.some(x => !istKeine(x))).map(b => b.datei)
|
||||
if (ohneBericht.length) log('Ohne Bericht: ' + ohneBericht.join(', '))
|
||||
|
||||
return {
|
||||
berichte,
|
||||
ohne_bericht: ohneBericht,
|
||||
mit_abweichung: mitAbweichung,
|
||||
naechster_schritt: 'Ein Bericht ist eine Behauptung. Jetzt Byte-Vergleich gegen die Kopie mit angewendetem Plan (.vault-tools/plan-kopie/welle_vergleich.py) und Gegenprüfung nach Schritt 4.',
|
||||
}
|
||||
Reference in new issue
Block a user