This commit is contained in:
team3
2026-09-07 07:44:00 +02:00
parent 5d42b4cfc5
commit c6bbed4049
250 changed files with 13372 additions and 6203 deletions

View File

@@ -11,6 +11,15 @@ funktioniert, Annehmen schreibt die Datei.
Das Modell schreibt nie selbst in eine Datei. Es beschreibt den Sollzustand einer
Karte; was sich dadurch ändert, rechnet der Code aus. Das ist dieselbe Beweislast wie
bei der Prüfung und der Grund, warum ein Missverständnis hier nichts kaputt macht.
Gefragt wird über die Claude-CLI, nicht über MiniMax: das Gespräch soll planen helfen.
Ein Modell, das nur Kataloge abarbeitet, gibt hier die Frage zurück statt Vorschläge.
Zum Planen gehört der Quelltext, nicht das Web. Wer eine Karte entwirft, muss wissen,
was heute schon dasteht — das steht im Arbeitsverzeichnis und nirgends sonst. Fremdes
Wissen kommt aus der Recherche und ist ein eigener Raum (`recherche.py`): was dort
gefunden wird, bringt die Person selbst mit, statt dass es unbemerkt in einen Entwurf
sickert.
"""
import json
@@ -20,20 +29,28 @@ import features
import parser
import projekte
import pruefung
import verzeichnis
DATEI = ".chat.json"
# So viele Wortmeldungen bleiben stehen. Mehr wäre teuer je Anfrage und hilft wenig:
# was vor zwanzig Schritten besprochen wurde, steht längst auf den Karten.
TIEFE = 30
# Antworten in einem Rutsch. Ein Gespräch verträgt keine sechs Stimmen — es soll
# schnell sein, und was falsch ist, sieht man am Vorschlag.
TEMPERATUR = 0.3
# schnell sein, und was falsch ist, sieht man am Vorschlag. Dafür das starke Modell:
# hier wird geplant, nicht ein Katalog abgearbeitet.
SYSTEM = """Du entwickelst mit einer Person zusammen die Feature-Karten eines Projekts.
Eine Karte ist ein Auftrag: die Person will ein Problem gelöst haben. Die Lösung
besteht aus Regeln und Funktionen. Eine Funktion ist etwas, das man für sich testen
kann. Was nur die Umsetzung betrifft, gehört auf keine Karte.
kann. Wie eine Sache gebaut ist, gehört auf keine Karte — wie man sie bedient, immer.
Bedienung ist nicht nur Maus und Menü. Einrichten, Installieren und Starten sind
Funktionen wie jede andere; ihr Auslöser ist ein Kommando. Setze es wörtlich in
Anführungszeichen, damit die Person es abtippen kann: `Befehl "make install"
ausführen`. Was das Kommando innen tut, bleibt draußen.
{code}
{stil}
@@ -53,16 +70,55 @@ Antworte ausschließlich mit JSON:
VOLLSTÄNDIG, so wie sie danach aussehen sollen. Der Stand oben hat dieselbe Form:
kopiere die Karte von dort und ändere nur, was sich ändern soll. Jede Zeile, die
du nicht wörtlich mit abschreibst, gilt als gestrichen.
- art: „kern", wenn die Anwendung ohne die Karte keinen Sinn hätte, sonst „optional"
- ziel, regeln und testen dürfen leer sein oder fehlen
- art: „kern", wenn die Anwendung ohne die Karte nicht laufen kann, sonst „optional"
- ziel und regeln dürfen leer sein oder fehlen
- testen ist Pflicht bei jeder Funktion: der Ablauf, an dem man sie prüft. Weißt du
ihn nicht, gehört die Funktion nicht auf die Karte — dann frag danach
Ist nichts zu ändern, ist karten leer. Ändere nur, wonach gefragt wurde; Karten,
die niemand erwähnt hat, lässt du weg.
Frag nach, was du nicht weißt — aber leg trotzdem einen Entwurf daneben. Eine Frage
ohne Vorschlag hilft niemandem: an einem Entwurf sieht die Person sofort, was du
falsch verstanden hast. Erfinde dabei nichts, was nicht gesagt wurde; lass eine
Funktion lieber knapp als ausgedacht."""
Zwei Arten von Wortmeldung, und sie werden verschieden behandelt.
Eine Frage will eine Antwort, keinen Entwurf. „Wie ist das heute?", „Muss ich noch
etwas tun?", „Wozu dient das?" — antworte in Prosa und lass karten leer. Ungefragt
eine Karte anzufassen ist ein Fehler, auch wenn dir dabei etwas auffällt. Sag es in
einem Satz und frag, ob du es aufnehmen sollst.
Ein Wunsch will einen Entwurf. Sobald etwas anders werden soll oder dazukommen soll,
leg die Karten daneben, wie sie danach aussehen. Fehlt dir dafür eine Angabe, frag
danach — aber leg trotzdem einen Entwurf daneben. An einem Entwurf sieht die Person
sofort, was du falsch verstanden hast, und er gilt erst, wenn sie ihn annimmt.
Bei einem Wunsch gibst du die Frage nie unbeantwortet zurück. Wer nach Namen fragt
— für Kommandos, Funktionen oder Karten — bekommt konkrete Namen, keine Gegenfrage.
Nenne das Übliche für diese Art von Anwendung, sag in einem Satz, worauf du dich
stützt, und frag danach, was daran nicht passt. „Das kann ich nicht vorschlagen" ist
keine zulässige Antwort.
Gehört etwas nicht auf eine Karte, sag in einem Satz, wohin es stattdessen gehört.
Beim Bestand bleibst du bei der Wahrheit: über das, was das Projekt heute kann,
erfindest du nichts. Ein Entwurf für etwas Neues kommt von dir."""
# Zwei Fassungen desselben Absatzes. Ohne eingestelltes Arbeitsverzeichnis gibt es
# nichts zu lesen, und ein Angebot, das ins Leere greift, wäre schlimmer als keines:
# das Modell behauptete sonst, nachgesehen zu haben.
CODE_MIT = """Der Quelltext des Projekts liegt in deinem Arbeitsverzeichnis. Du darfst
darin lesen und suchen, und du sollst es tun, sobald dein Entwurf davon abhängt, wie
etwas heute gebaut ist: ob es eine Funktion schon gibt, wie eine Bedienstelle heißt,
was ein Ablauf wirklich auslöst. Rate nicht, sieh nach.
Was du dort findest, ist ein Beleg, kein Kartentext. Wie eine Sache gebaut ist, gehört
auf keine Karte — dorthin kommt nur, was die Person davon merkt. Stützt du dich auf
eine Stelle, nenne im Antwortsatz die Datei.
Widerspricht der Code den Karten, schreib die Karte nicht still um. Sag in einem Satz,
was du gefunden hast, und frag, welche Seite gilt."""
CODE_OHNE = """Für dieses Projekt ist kein Arbeitsverzeichnis eingestellt. Du kannst
den Quelltext also nicht ansehen. Was das Projekt heute kann, weißt du nur aus den
Karten unten — behaupte nichts darüber hinaus."""
# Die Formatregel steht auch hier, nicht nur im System-Prompt. Bei langen Antworten
# vergaß das Modell sie sonst und schrieb reinen Fließtext — die ganze Wortmeldung
@@ -80,6 +136,14 @@ Schreibe sie noch einmal, ausschließlich als das JSON-Objekt aus der Vorgabe
keine Einleitung, kein Nachsatz, keine Code-Zäune."""
def _arbeitsordner(projekt: str) -> str | None:
"""Wo der Quelltext liegt — oder nichts, wenn dort nichts zu holen ist."""
try:
return str(verzeichnis.pruefe(verzeichnis.lies(projekt)))
except (ValueError, FileNotFoundError, OSError):
return None
def _pfad(projekt: str):
return projekte.wurzel(projekt) / DATEI
@@ -239,9 +303,11 @@ def vorschlaege(projekt: str, karten: list) -> list:
if bestand is None:
# Neue Karte: es gibt keine Stelle, an der man ansetzen könnte —
# deshalb ein eigener Vorschlag, der die ganze Datei mitbringt.
# `sektionen` liegt daneben: nur am Titel lässt sich nicht entscheiden,
# ob die Karte taugt. Zerlegt wird hier, damit es keinen zweiten Parser gibt.
raus.append({"feld": "neue_karte", "original": "", "vorschlag": titel,
"roh": text, "ganzes_feld": True, "feature": "",
"funktion": "", "regel": "Aus dem Gespräch",
"roh": text, "sektionen": neu, "ganzes_feld": True,
"feature": "", "funktion": "", "regel": "Aus dem Gespräch",
"problem": "Neue Karte"})
continue
grund = f"Aus dem Gespräch: {titel}"
@@ -256,24 +322,29 @@ async def frage(projekt: str, text: str) -> dict:
text = (text or "").strip()
if not text:
raise ValueError("Nachricht ist leer")
agents.pruefe_konfiguration()
bisher = verlauf(projekt)
gespraech = "\n\n".join(f'{"Person" if e["rolle"] == "person" else "Du"}: {e["text"]}'
for e in bisher) or "Noch nichts besprochen."
# Der Ordner entscheidet über beides zugleich: was im Prompt angeboten wird und
# was die CLI überhaupt zulässt. Ein toter Pfad zählt wie keiner — der Prozess
# ließe sich darin nicht einmal starten.
ordner = _arbeitsordner(projekt)
# Beide Kataloge: der Chat schreibt Karten UND Funktionen. Vorher kannte er nur
# die Kartenregeln und erfand Funktionstitel wie „Galerie im Modal" — ein
# Bereich statt einer Handlung, und die Prüfung merkte es erst hinterher.
system = SYSTEM.format(
code=CODE_MIT if ordner else CODE_OHNE,
stil="\n\n".join(pruefung._stil_lesen(b) for b in pruefung.BEREICHE),
karten=_kartenstand(projekt))
nachricht = NACHRICHT.format(verlauf=gespraech, text=text)
kommando = agents.PLANUNG_KOMMANDO if ordner else agents.PLANUNG_OHNE_CODE
# Ein Ausrutscher beim JSON kostet sonst die ganze Wortmeldung. Zweiter Versuch
# mit etwas mehr Streuung; danach fliegt der Fehler nach oben und ist sichtbar.
# Ein Ausrutscher beim JSON kostet sonst die ganze Wortmeldung. Danach fliegt der
# Fehler nach oben und ist sichtbar.
daten = None
for versuch in range(3):
antwort = await agents.frage(system, nachricht, TEMPERATUR + 0.1 * versuch)
antwort = await agents.frage_lokal(system, nachricht, kommando, ordner)
try:
daten = pruefung._json_lesen(antwort)
break