update
This commit is contained in:
52
README.md
52
README.md
@@ -77,40 +77,42 @@ einziger Vorschlag für das Feld — einzelne Zeilen daraus ergäben Dubletten o
|
||||
Jede Anfrage bekommt nur ihren Ausschnitt der Karte und nur den Katalogteil, der für sie
|
||||
gilt. Die Herkunft eines Problems stammt aus dem Code, nicht aus der Modellantwort.
|
||||
|
||||
## Mit dem Code abgleichen
|
||||
## Abgleich mit dem Code
|
||||
|
||||
Das ⌗-Icon prüft die andere Frage: bilden alle Karten zusammen ab, was das Programm
|
||||
kann? Gesucht werden Lücken — Fähigkeiten ohne Funktion auf einer Karte.
|
||||
Zwei weitere Prüfungen, in beide Richtungen. Beide arbeiten mit derselben
|
||||
Beweislast, nur andersherum: ein Vorhandensein lässt sich zitieren, ein
|
||||
Nichtvorhandensein nicht.
|
||||
|
||||
```
|
||||
1 Inventar quellcode.py Bedienstellen aus dem Code, deterministisch
|
||||
2 Abdecken 6 Stimmen deckt eine bestehende Funktion die Stelle ab?
|
||||
3 Ergänzen Code 5 von 6 sagen nein -> Vorschlag mit Fundort
|
||||
↧ Features im Code steht hinter jeder Funktion auch Code?
|
||||
↥ Code in Features hat jede Bedienstelle auch eine Funktion?
|
||||
```
|
||||
|
||||
**Inventar.** `interaktionen()` liest die Stellen aus, an denen ein Mensch etwas
|
||||
auslöst: Ereignisse im Markup und Einträge der Kontextmenüs. Zusammengefasst nach
|
||||
dem aufgerufenen Namen — `starte('ziel')` steht zwölfmal da und ist eine Fähigkeit.
|
||||
Ein Modell hatte die Liste anfangs frei aufgezählt: jede Runde eine andere.
|
||||
**↧ Features im Code.** Je Funktion sechs unabhängige Versuche, die Codestelle zu
|
||||
finden. Jeder Fund wird gegen die Datei geschlagen. Gemeldet wird erst, wenn keine
|
||||
einzige Stimme etwas findet — auch ein misslungenes Zitat gilt als Widerspruch, denn
|
||||
ein verschriebener Backtick darf keine Löschempfehlung auslösen. Unter vier
|
||||
brauchbaren Stimmen wird gar nicht erst angeklagt.
|
||||
|
||||
Nicht im Inventar: Backend-Routen (die Verdrahtung hinter den Knöpfen), Zuweisungen,
|
||||
Speichern beim Verlassen, Abbrechen mit Escape.
|
||||
**↥ Code in Features.** Die Liste der Bedienstellen kommt aus dem Code, nicht aus
|
||||
dem Modell: `quellcode.interaktionen()` liest Ereignisse im Markup, Kontextmenü-
|
||||
Einträge und Fenster-Lauscher. Zusammengefasst nach dem aufgerufenen Namen —
|
||||
`starte('ziel')` steht zwölfmal da und ist eine Fähigkeit. Je Stelle eine Ja/Nein-
|
||||
Frage mit Code-Umfeld, Funktionsrumpf und dem vollen Text aller Karten; gemeldet ab
|
||||
fünf von sechs Nein-Stimmen.
|
||||
|
||||
**Abdecken.** Je Stelle eine Ja/Nein-Frage, mit dem Code-Umfeld, dem Rumpf der
|
||||
Funktion dahinter und dem vollen Text aller Karten. Nur Titel reichten nicht — der
|
||||
Richter musste raten und urteilte jede Runde anders.
|
||||
Nicht im Inventar: Backend-Routen (jede hängt an einem Knopf, der schon dasteht),
|
||||
Zuweisungen, Speichern beim Verlassen, Abbrechen mit Escape.
|
||||
|
||||
**Schwelle.** Fünf von sechs Stimmen. Einstimmigkeit war zu scharf: dieselbe Frage
|
||||
kippt auch bei Temperatur 0 gelegentlich, und ein Ausreißer löschte klare Funde.
|
||||
Eindeutige Stellen antworten 6:0 oder 0:6, Grenzfälle etwa 3:3.
|
||||
**Warum diese Schwellen.** Einstimmigkeit war für ↥ zu scharf: dieselbe Frage kippt
|
||||
auch bei Temperatur 0 gelegentlich, und ein Ausreißer löschte klare Funde.
|
||||
Eindeutige Stellen antworten 6:0 oder 0:6, Grenzfälle etwa 3:3. Für ↧ gilt das
|
||||
Gegenteil — dort ist die Anklage destruktiv, also blockiert schon eine
|
||||
widersprechende Stimme.
|
||||
|
||||
Jeder Vorschlag nennt seinen Fundort als `datei:zeile`. Die Gegenrichtung — „diese
|
||||
Funktion hat keinen Code, weg damit" — ist bewusst nicht eingebaut: ein
|
||||
Nichtvorhandensein lässt sich nicht belegen, und die Vorschläge zielten wiederholt
|
||||
auf gültige Inhalte. Karten dürfen Dinge festhalten, die kein Knopf sind.
|
||||
|
||||
Gemessen an diesem Projekt: auf den unsynchronisierten Karten findet der Abgleich
|
||||
die offenen Stellen, nach dem Übernehmen dreimal hintereinander nichts mehr.
|
||||
Nachgemessen an diesem Projekt: auf gepflegten Karten melden beide nichts. Eine
|
||||
untergeschobene Funktion ohne Code fällt ↧ in jedem Lauf auf. Fehlende Funktionen
|
||||
findet ↥, nach dem Übernehmen ist es still.
|
||||
|
||||
Der Katalog liegt in `backend/stil/` — eine Datei je Feld, alphabetisch zusammengesetzt
|
||||
(`allgemein` landet dadurch vorn). Das Präfix entscheidet, wer sie zu sehen bekommt:
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
"""Inhaltsprüfung: gleicht die Karten mit dem Quelltext ab.
|
||||
"""Zwei Abgleiche zwischen Karten und Quelltext, in beide Richtungen.
|
||||
|
||||
Die Stilprüfung fragt „ist das gut geschrieben", diese hier fragt „stimmt das
|
||||
überhaupt". Ziel ist, dass alle Karten zusammen alles abbilden, was das Programm
|
||||
kann — und nichts, was es nicht kann.
|
||||
**Feature im Code** — steht hinter jeder Funktion einer Karte auch Code?
|
||||
**Code im Feature** — hat jede Stelle im Code auch eine Funktion auf einer Karte?
|
||||
|
||||
In drei Schritten, weil ein einzelner nicht funktioniert hat:
|
||||
Beide arbeiten mit derselben Beweislast, nur andersherum.
|
||||
|
||||
1. **Inventar** — das Modell liest nur den Quelltext und listet die Fähigkeiten.
|
||||
Ohne die Karten daneben, sonst bestätigt es bloß, was schon dasteht. Jede
|
||||
Fähigkeit braucht eine wörtlich zitierte Codezeile; ohne Beleg fällt sie weg.
|
||||
Mehrere Durchgänge werden vereinigt: hier zählt Vollständigkeit.
|
||||
2. **Abdeckung** — je Fähigkeit eine kurze Frage: deckt eine bestehende Funktion
|
||||
das ab? Eine Ja/Nein-Frage ist weit stabiler als offenes Suchen. Nur wenn alle
|
||||
Stimmen „nein" sagen, entsteht ein Vorschlag.
|
||||
Geprüft wird nur eine Richtung: was fehlt. Die Gegenrichtung — „diese Funktion hat
|
||||
keinen Code, weg damit" — ist bewusst nicht eingebaut. Sie ließ sich nicht belegen
|
||||
(ein Nichtvorhandensein lässt sich nicht zitieren), urteilte von Runde zu Runde
|
||||
anders und schlug dabei vor, gültige Inhalte zu löschen. Karten dürfen außerdem
|
||||
Dinge festhalten, die kein Knopf sind — Schreibregeln etwa.
|
||||
Ein Vorhandensein lässt sich zitieren, ein Nichtvorhandensein nicht. Darum fragt
|
||||
„Feature im Code" sechsmal unabhängig nach dem Beleg und meldet erst, wenn keine
|
||||
einzige Stimme eine Codezeile findet, die auch wirklich dasteht. Ein geprüfter Fund
|
||||
genügt zum Freispruch — sechs Fehlversuche sind die Anklage. Ein früherer Versuch
|
||||
ließ das Modell direkt urteilen und schlug dabei vor, gültige Inhalte zu löschen.
|
||||
|
||||
„Code im Feature" fragt umgekehrt je Stelle, ob eine bestehende Funktion sie
|
||||
abdeckt, und meldet ab fünf von sechs Nein-Stimmen. Einstimmigkeit war zu scharf:
|
||||
dieselbe Frage kippt auch bei Temperatur 0 gelegentlich.
|
||||
|
||||
Was der Code selbst weiß, entscheidet der Code: die Liste der Bedienstellen kommt
|
||||
aus `quellcode.interaktionen()`, jeder Beleg wird gegen die Datei geschlagen.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -28,8 +27,8 @@ import agents
|
||||
import config
|
||||
import quellcode
|
||||
|
||||
# Ohne Drossel liefen alle Punkte gleichzeitig los und der Provider antwortete
|
||||
# mit 429. Die ausgefallenen Stimmen sahen dann aus wie „alles abgedeckt".
|
||||
# Ohne Drossel liefen alle Anfragen gleichzeitig los und der Provider antwortete
|
||||
# mit 429. Die ausgefallenen Stimmen sahen dann aus wie „nichts gefunden".
|
||||
_schranke = None
|
||||
|
||||
|
||||
@@ -47,14 +46,43 @@ async def _frage(system: str, nachricht: str, temperatur: float) -> str:
|
||||
|
||||
async def _urteil(system: str, nachricht: str, temperatur: float):
|
||||
"""Eine Stimme als JSON. Unlesbares wird einmal neu gefragt, dann aufgegeben —
|
||||
bei 240 Anfragen je Lauf ist ein Ausrutscher normal, ein Abbruch wäre es nicht."""
|
||||
for versuch in range(2):
|
||||
bei hunderten Anfragen je Lauf ist ein Ausrutscher normal, ein Abbruch nicht."""
|
||||
for versuch in range(3):
|
||||
try:
|
||||
return _json(await _frage(system, nachricht, temperatur + 0.1 * versuch))
|
||||
except ValueError:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
# Achtung: diese Texte gehen unformatiert als System-Prompt raus. Doppelte Klammern
|
||||
# wären hier keine Escapes, sondern landeten wörtlich in der Antwort.
|
||||
SUCHE_SYSTEM = """Du suchst im Quelltext den Code zu einer beschriebenen Funktion.
|
||||
|
||||
Antworte ausschließlich mit JSON:
|
||||
{"gefunden": true, "datei": "...", "beleg": "..."}
|
||||
oder
|
||||
{"gefunden": false, "grund": "..."}
|
||||
|
||||
- datei: der Pfad genau so, wie er über der Datei steht
|
||||
- beleg: eine Zeile aus dieser Datei, Zeichen für Zeichen abgeschrieben
|
||||
- grund: höchstens 8 Wörter, wenn es den Code nicht gibt
|
||||
|
||||
Suche gründlich und im ganzen Quelltext. Es zählt nicht nur, was einen Knopf hat:
|
||||
Hintergrundlogik, Parser, Dateiverwaltung, Prüfregeln und Anzeigeverhalten sind
|
||||
genauso Code. Ein erfundener Beleg ist wertlos — er wird nachgeschlagen."""
|
||||
|
||||
SUCHE_NACHRICHT = """{quelle}
|
||||
|
||||
Ende des Quelltextes.
|
||||
|
||||
Eine Karte beschreibt diese Funktion:
|
||||
|
||||
{funktion}
|
||||
|
||||
Finde die Stelle im Quelltext, die das umsetzt. Antworte ausschließlich mit dem
|
||||
JSON-Objekt."""
|
||||
|
||||
ABDECKUNG_SYSTEM = """Du entscheidest, ob eine Stelle im Programm schon von einer
|
||||
beschriebenen Funktion abgedeckt ist. Antworte ausschließlich mit JSON."""
|
||||
|
||||
@@ -66,7 +94,6 @@ ABDECKUNG = """An dieser Stelle kann eine Person etwas auslösen:
|
||||
{umfeld}
|
||||
```
|
||||
{rumpf}
|
||||
|
||||
Die Karten des Projekts beschreiben bisher das hier:
|
||||
|
||||
{funktionen}
|
||||
@@ -89,14 +116,14 @@ Rein technische Aufhänger gelten als abgedeckt: Speichern beim Verlassen eines
|
||||
Feldes, Abbrechen mit Escape, Gesundheitsprüfungen, interne Weiterleitung.
|
||||
Im Zweifel abgedeckt. Nur was einer Person wirklich fehlt, soll dazukommen."""
|
||||
|
||||
# Achtung: dieser Text geht unformatiert als System-Prompt raus. Doppelte Klammern
|
||||
# wären hier keine Escapes, sondern landeten wörtlich in der Antwort.
|
||||
# Sechs Stimmen, fünf müssen „fehlt" sagen.
|
||||
#
|
||||
# Einstimmigkeit war zu scharf: gemessen kippt dieselbe Frage auch bei Temperatur 0
|
||||
# gelegentlich, und eine einzelne Ausreißerstimme löschte damit auch klare Funde.
|
||||
# Eindeutige Stellen antworten 6:0 oder 0:6, Grenzfälle etwa 3:3 — die Schwelle bei
|
||||
# fünf trennt beides und übersteht einen Ausrutscher.
|
||||
# Sechs unabhängige Versuche, den Beleg zu finden. Eine einzige Stimme, die etwas
|
||||
# findet, genügt zum Freispruch — die Anklage lautet auf Löschen, da wiegt ein
|
||||
# Fehlurteil schwerer als ein übersehener Rest.
|
||||
SUCHE_TEMPERATUREN = (0.0, 0.2, 0.4, 0.6, 0.8, 1.0)
|
||||
# So viele Stimmen müssen überhaupt geantwortet haben, bevor angeklagt wird
|
||||
ANKLAGE_AB = 4
|
||||
# Sechs Stimmen, fünf müssen „fehlt" sagen. Eindeutige Stellen antworten 6:0 oder
|
||||
# 0:6, Grenzfälle etwa 3:3 — die Schwelle bei fünf trennt beides.
|
||||
URTEIL_TEMPERATUREN = (0.0, 0.2, 0.4, 0.6, 0.8, 1.0)
|
||||
FEHLT_AB = 5
|
||||
# Ab dieser Ähnlichkeit gelten zwei Titel als dieselbe Sache
|
||||
@@ -117,12 +144,99 @@ def _json(antwort: str) -> dict:
|
||||
return pruefung._json_lesen(antwort)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Feature im Code
|
||||
|
||||
def _funktionstext(karte: str, f: dict) -> str:
|
||||
"""Alles, was die Funktion behauptet — danach wird im Code gesucht."""
|
||||
zeilen = [f"Karte: {karte}", f'Funktion: {f["text"]}']
|
||||
if f["ziel"]:
|
||||
zeilen.append(f'Ziel: {f["ziel"]}')
|
||||
for feld in ("regeln", "testen"):
|
||||
if f[feld]:
|
||||
eintraege = [z.strip("- ").strip() for z in f[feld].splitlines() if z.strip()]
|
||||
zeilen.append(f'{feld.capitalize()}: {"; ".join(eintraege)}')
|
||||
return "\n".join(zeilen)
|
||||
|
||||
|
||||
async def feature_im_code(features: list, quelle: dict, quelltext: str,
|
||||
zaehl=None) -> list:
|
||||
"""Funktionen, für die keine Stimme einen nachprüfbaren Beleg findet."""
|
||||
auftraege = [(f, x) for f in features for x in f["sektionen"]["funktionen"]]
|
||||
|
||||
async def eine(paar):
|
||||
feature, x = paar
|
||||
nachricht = SUCHE_NACHRICHT.format(
|
||||
quelle=quelltext,
|
||||
funktion=_funktionstext(feature["sektionen"]["titel"], x))
|
||||
proben = await asyncio.gather(*(
|
||||
_urteil(SUCHE_SYSTEM, nachricht, t) for t in SUCHE_TEMPERATUREN
|
||||
), return_exceptions=True)
|
||||
|
||||
if zaehl:
|
||||
zaehl()
|
||||
gruende = []
|
||||
brauchbar = belegt = behauptet = 0
|
||||
for u in proben:
|
||||
if not isinstance(u, dict):
|
||||
continue
|
||||
brauchbar += 1
|
||||
if u.get("gefunden"):
|
||||
# Auch ein misslungenes Zitat ist ein Widerspruch. Ein Modell
|
||||
# verschreibt sich beim Abschreiben von Backticks — daraus eine
|
||||
# Löschempfehlung zu machen, wäre grob falsch.
|
||||
behauptet += 1
|
||||
if quellcode.belegt(quelle, str(u.get("datei") or "").strip(),
|
||||
str(u.get("beleg") or "").strip()):
|
||||
belegt += 1
|
||||
elif u.get("grund"):
|
||||
gruende.append(str(u["grund"]).strip())
|
||||
|
||||
if belegt:
|
||||
return None # geprüfter Beleg: die Funktion steht im Code
|
||||
if behauptet:
|
||||
return None # widersprüchliche Stimmen: nichts behaupten
|
||||
if brauchbar < ANKLAGE_AB:
|
||||
# Zu wenige Stimmen für eine Löschempfehlung. Lieber laut scheitern
|
||||
# als eine gültige Funktion wegen eines Ausfalls anzuklagen.
|
||||
raise RuntimeError(
|
||||
f'{x["text"]}: nur {brauchbar} von '
|
||||
f"{len(SUCHE_TEMPERATUREN)} Stimmen brauchbar")
|
||||
return {"feature": feature["id"], "titel": x["text"],
|
||||
"grund": gruende[0] if gruende else "Kein Beleg im Code gefunden"}
|
||||
|
||||
return [x for x in await asyncio.gather(*(eine(a) for a in auftraege)) if x]
|
||||
|
||||
|
||||
def probleme_feature(features: list, ohne_code: list) -> list:
|
||||
"""Ein Vorschlag je Karte: die betroffenen Funktionen aus der Liste nehmen."""
|
||||
import pruefung
|
||||
|
||||
raus = []
|
||||
for f in features:
|
||||
ist = [x["text"] for x in f["sektionen"]["funktionen"]]
|
||||
betroffen = [x for x in ohne_code if x["feature"] == f["id"]]
|
||||
if not betroffen:
|
||||
continue
|
||||
weg = [x["titel"] for x in betroffen]
|
||||
neu = [t for t in ist if t not in weg]
|
||||
grund = (f"{len(weg)} Funktion ohne Code" if len(weg) == 1
|
||||
else f"{len(weg)} Funktionen ohne Code")
|
||||
beleg = " · ".join(f'{x["titel"]}: {x["grund"]}' for x in betroffen)
|
||||
for p in pruefung._stellen("funktionen", "\n".join(ist), "\n".join(neu),
|
||||
grund, beleg):
|
||||
p["feature"] = f["id"]
|
||||
p["funktion"] = ""
|
||||
raus.append(p)
|
||||
return raus
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Code im Feature
|
||||
|
||||
def _funktionsliste(features: list) -> str:
|
||||
"""Die Karten mit allem, was sie sagen.
|
||||
|
||||
Nur die Titel reichten nicht: der Richter musste raten, ob eine Funktion eine
|
||||
Stelle abdeckt, und urteilte von Runde zu Runde anders. Ziel, Regeln und
|
||||
Testschritte sagen es dagegen ausdrücklich.
|
||||
Stelle abdeckt, und urteilte von Runde zu Runde anders.
|
||||
"""
|
||||
teile = []
|
||||
for f in features:
|
||||
@@ -154,40 +268,44 @@ async def _abdeckung(punkt: dict, liste: str) -> list:
|
||||
return [p for p in proben if isinstance(p, dict)]
|
||||
|
||||
|
||||
async def luecken(punkte: list, features: list) -> list:
|
||||
"""Interaktionspunkte, die einstimmig von keiner Funktion abgedeckt sind."""
|
||||
def _naechste_karte(name: str, karten: dict):
|
||||
if not name:
|
||||
return None
|
||||
treffer = max(karten, key=lambda k: _aehnlich(name, k))
|
||||
return karten[treffer] if _aehnlich(name, treffer) > 0.6 else None
|
||||
|
||||
|
||||
async def code_im_feature(features: list, punkte: list, zaehl=None) -> list:
|
||||
"""Bedienstellen, die von keiner Funktion abgedeckt sind."""
|
||||
liste = _funktionsliste(features)
|
||||
karten = {f["sektionen"]["titel"]: f for f in features}
|
||||
getroffen = set() # Funktionen, die eine Stimme als Treffer genannt hat
|
||||
|
||||
async def eine(punkt):
|
||||
urteile = await _abdeckung(punkt, liste)
|
||||
for u in urteile:
|
||||
if u.get("abgedeckt") and u.get("funktion"):
|
||||
getroffen.add(str(u["funktion"]).strip())
|
||||
if len(urteile) < len(URTEIL_TEMPERATUREN):
|
||||
# Nicht verschweigen: eine ausgefallene Stimme sähe sonst aus wie
|
||||
# „ist abgedeckt" und der Lauf meldete stillschweigend zu wenig.
|
||||
if zaehl:
|
||||
zaehl()
|
||||
if len(urteile) < FEHLT_AB:
|
||||
# Nicht verschweigen: ausgefallene Stimmen sähen sonst aus wie „ist
|
||||
# abgedeckt" und der Lauf meldete stillschweigend zu wenig. Ein
|
||||
# einzelner Ausfall darf den Lauf aber nicht abreißen — es zählt, ob
|
||||
# noch genug Stimmen übrig sind, um die Schwelle zu erreichen.
|
||||
raise RuntimeError(
|
||||
f'{punkt["schluessel"]}: nur {len(urteile)} von '
|
||||
f"{len(URTEIL_TEMPERATUREN)} Stimmen brauchbar")
|
||||
if sum(u.get("abgedeckt") is False for u in urteile) < FEHLT_AB:
|
||||
return None
|
||||
ziele = [str(u.get("karte") or "").strip() for u in urteile]
|
||||
karte = max(set(ziele), key=ziele.count)
|
||||
treffer = karten.get(karte) or _naechste_karte(karte, karten)
|
||||
if not treffer:
|
||||
return None
|
||||
name = max(set(ziele), key=ziele.count)
|
||||
treffer = karten.get(name) or _naechste_karte(name, karten)
|
||||
titel = [str(u.get("titel") or "").strip() for u in urteile if u.get("titel")]
|
||||
if not titel:
|
||||
if not treffer or not titel:
|
||||
return None
|
||||
return {"feature": treffer["id"],
|
||||
"titel": max(titel, key=lambda t: sum(_aehnlich(t, x) for x in titel)),
|
||||
"beleg": f'{punkt["datei"]}:{punkt["zeile"]}'}
|
||||
|
||||
gefunden = [x for x in await asyncio.gather(*(eine(p) for p in punkte)) if x]
|
||||
luecken.getroffen = getroffen
|
||||
# Zwei Punkte können auf dieselbe fehlende Funktion zeigen
|
||||
# Zwei Stellen können auf dieselbe fehlende Funktion zeigen
|
||||
raus = []
|
||||
for x in gefunden:
|
||||
gleich = next((y for y in raus if y["feature"] == x["feature"]
|
||||
@@ -199,15 +317,8 @@ async def luecken(punkte: list, features: list) -> list:
|
||||
return raus
|
||||
|
||||
|
||||
def _naechste_karte(name: str, karten: dict):
|
||||
if not name:
|
||||
return None
|
||||
treffer = max(karten, key=lambda k: _aehnlich(name, k))
|
||||
return karten[treffer] if _aehnlich(name, treffer) > 0.6 else None
|
||||
|
||||
|
||||
def probleme(features: list, fehlend: list) -> list:
|
||||
"""Aus den Lücken die Ergänzungen an den Funktionslisten ableiten."""
|
||||
def probleme_code(features: list, fehlend: list) -> list:
|
||||
"""Ein Vorschlag je Karte: die fehlenden Funktionen anhängen."""
|
||||
import pruefung
|
||||
|
||||
raus = []
|
||||
@@ -216,28 +327,30 @@ def probleme(features: list, fehlend: list) -> list:
|
||||
dazu = [x for x in fehlend if x["feature"] == f["id"]]
|
||||
if not dazu:
|
||||
continue
|
||||
# Bestehende Reihenfolge behalten, Neues hinten anhängen: sortieren würde
|
||||
# die halbe Liste verschieben und aus einer Ergänzung einen Umbau machen.
|
||||
# Angezeigt wird ohnehin alphabetisch.
|
||||
# Bestehende Reihenfolge behalten, Neues hinten anhängen — sortieren würde
|
||||
# aus einer Ergänzung einen Umbau machen. Angezeigt wird ohnehin alphabetisch.
|
||||
neu = list(dict.fromkeys(ist + [x["titel"] for x in dazu]))
|
||||
belege = " · ".join(x["beleg"] for x in dazu)
|
||||
grund = (f"{len(dazu)} Stelle ohne Funktion" if len(dazu) == 1
|
||||
else f"{len(dazu)} Stellen ohne Funktion")
|
||||
beleg = " · ".join(x["beleg"] for x in dazu)
|
||||
for p in pruefung._stellen("funktionen", "\n".join(ist), "\n".join(neu),
|
||||
_grund(dazu), belege):
|
||||
grund, beleg):
|
||||
p["feature"] = f["id"]
|
||||
p["funktion"] = ""
|
||||
raus.append(p)
|
||||
return raus
|
||||
|
||||
|
||||
def _grund(dazu: list) -> str:
|
||||
return (f"{len(dazu)} Fähigkeit ohne Funktion" if len(dazu) == 1
|
||||
else f"{len(dazu)} Fähigkeiten ohne Funktion")
|
||||
# ---------------------------------------------------------------- Einstiege
|
||||
|
||||
async def pruefe_feature_im_code(features: list, quelle: dict, quelltext: str,
|
||||
zaehl=None) -> list:
|
||||
ohne = await feature_im_code(features, quelle, quelltext, zaehl)
|
||||
return probleme_feature(features, ohne)
|
||||
|
||||
|
||||
async def pruefe(features: list, quelle: dict, quelltext: str):
|
||||
"""Der ganze Abgleich: Interaktionspunkte, Lücken, Überhang."""
|
||||
async def pruefe_code_im_feature(features: list, quelle: dict, zaehl=None) -> list:
|
||||
punkte = quellcode.interaktionen(quelle)
|
||||
if not punkte:
|
||||
raise RuntimeError("Keine Interaktionspunkte im Quelltext gefunden")
|
||||
fehlend = await luecken(punkte, features)
|
||||
return probleme(features, fehlend), punkte
|
||||
raise RuntimeError("Keine Bedienstellen im Quelltext gefunden")
|
||||
return probleme_code(features, await code_im_feature(features, punkte, zaehl))
|
||||
|
||||
@@ -403,16 +403,21 @@ def _konsens(fassungen: list[dict]) -> dict:
|
||||
return max(fassungen, key=naehe)
|
||||
|
||||
|
||||
def _mehrheit(proben: list[dict]) -> dict:
|
||||
"""Nur was mehrere Stimmen unabhängig bemängeln, wird ein Vorschlag.
|
||||
def _mehrheit(proben: list[dict], gemessen: tuple = ()) -> dict:
|
||||
"""Nur was alle Stimmen unabhängig bemängeln, wird ein Vorschlag.
|
||||
|
||||
Bei Grenzfällen urteilt ein Modell mal so, mal so — einzelne Stimmen führten
|
||||
dazu, dass jede Runde andere Felder aufschlugen und der Lauf nie zur Ruhe kam.
|
||||
|
||||
Gemessene Verstöße sind davon ausgenommen. Sie stehen fest, bevor gefragt wird;
|
||||
zur Abstimmung steht nur noch der Wortlaut. Vorher verschwand ein gezählter
|
||||
Verstoß, sobald eine einzige Stimme das Feld nicht anfasste.
|
||||
"""
|
||||
zaehler = Counter(feld for p in proben for feld in p)
|
||||
return {
|
||||
feld: _konsens([p[feld] for p in proben if feld in p])
|
||||
for feld, treffer in zaehler.items() if treffer >= MEHRHEIT
|
||||
for feld, treffer in zaehler.items()
|
||||
if treffer >= (1 if feld in gemessen else MEHRHEIT)
|
||||
}
|
||||
|
||||
|
||||
@@ -426,7 +431,18 @@ async def _neue_felder(system: str, aufgabe: dict, offen: list, erlaubt: tuple,
|
||||
gute = [p for p in proben if isinstance(p, dict)]
|
||||
if not gute:
|
||||
raise next(p for p in proben if isinstance(p, BaseException))
|
||||
return _mehrheit(gute)
|
||||
gemessen = tuple({b["feld"] for b in offen})
|
||||
vorschlaege = _mehrheit(gute, gemessen)
|
||||
|
||||
# Bleibt ein gezählter Verstoß ohne Vorschlag, greift der eigene Ersatz des
|
||||
# Messers. Sonst bliebe ein Fehler stehen, den der Code sicher kennt.
|
||||
for b in offen:
|
||||
if b["feld"] not in vorschlaege and b["vorschlag"]:
|
||||
alt = struktur._zeilen(b["feld"], aufgabe["felder"].get(b["feld"], ""))
|
||||
neu = [b["vorschlag"] if z == b["original"] else z for z in alt]
|
||||
vorschlaege[b["feld"]] = {"neu": "\n".join(neu), "grund": b["problem"],
|
||||
"regel": "Vom System gemessen"}
|
||||
return vorschlaege
|
||||
|
||||
|
||||
async def _pruefe(stil: dict, aufgabe: dict) -> list[dict]:
|
||||
@@ -457,38 +473,73 @@ async def _stil_aufgaben(projekt: str):
|
||||
"""Prüfstellen der Stilprüfung samt der Kataloge, die sie brauchen."""
|
||||
stil = {b: _stil_lesen(b) for b in BEREICHE}
|
||||
liste = [a for f in features.liste(projekt) for a in aufgaben(f)]
|
||||
return liste, lambda a: _pruefe(stil, a)
|
||||
return liste, lambda a, _zaehl: _pruefe(stil, a)
|
||||
|
||||
|
||||
async def _inhalt_aufgaben(projekt: str):
|
||||
"""Ein Abgleich über alle Karten — Vollständigkeit ist eine Eigenschaft der
|
||||
Menge, nicht der einzelnen Karte."""
|
||||
def _quelle(projekt: str):
|
||||
alle = features.liste(projekt)
|
||||
quelle = quellcode.dateien()
|
||||
if not quelle:
|
||||
raise ValueError("Kein Quelltext gefunden")
|
||||
return alle, quelle
|
||||
|
||||
|
||||
async def _feature_aufgaben(projekt: str):
|
||||
"""Ein Lauf über alle Funktionen: steht hinter jeder auch Code?
|
||||
|
||||
Ein Aufruf statt vieler, weil das Ergebnis je Karte gebündelt wird — zwei
|
||||
Vorschläge für dieselbe Funktionsliste wären nicht unabhängig anwendbar.
|
||||
Der Zähler läuft trotzdem je Funktion mit.
|
||||
"""
|
||||
alle, quelle = _quelle(projekt)
|
||||
text = quellcode.als_text(quelle)
|
||||
anzahl = sum(len(f["sektionen"]["funktionen"]) for f in alle)
|
||||
|
||||
async def abgleich(_):
|
||||
probleme, _inv = await inhaltspruefung.pruefe(alle, quelle, text)
|
||||
return probleme
|
||||
async def abgleich(_, zaehl):
|
||||
return await inhaltspruefung.pruefe_feature_im_code(alle, quelle, text, zaehl)
|
||||
|
||||
return [{"feature": "", "funktion": ""}], abgleich
|
||||
return _platzhalter(anzahl), abgleich
|
||||
|
||||
|
||||
ARTEN = {"stil": _stil_aufgaben, "inhalt": _inhalt_aufgaben}
|
||||
async def _code_aufgaben(projekt: str):
|
||||
"""Ein Lauf über alle Bedienstellen — Vollständigkeit ist eine Eigenschaft der
|
||||
Menge, nicht der einzelnen Karte."""
|
||||
alle, quelle = _quelle(projekt)
|
||||
punkte = quellcode.interaktionen(quelle)
|
||||
|
||||
async def abgleich(_, zaehl):
|
||||
return await inhaltspruefung.pruefe_code_im_feature(alle, quelle, zaehl)
|
||||
|
||||
return _platzhalter(len(punkte)), abgleich
|
||||
|
||||
|
||||
def _platzhalter(anzahl: int) -> list:
|
||||
"""Eine echte Aufgabe, der Rest nur für den Fortschrittsbalken."""
|
||||
return [True] + [None] * max(0, anzahl - 1)
|
||||
|
||||
|
||||
ARTEN = {
|
||||
"stil": _stil_aufgaben,
|
||||
"feature": _feature_aufgaben, # Feature im Code: was steht ohne Code da?
|
||||
"code": _code_aufgaben, # Code im Feature: was fehlt auf den Karten?
|
||||
}
|
||||
|
||||
|
||||
async def _lauf(projekt: str, liste: list[dict], arbeite):
|
||||
lauf = _laeufe[projekt]
|
||||
schranke = asyncio.Semaphore(config.PRUEFUNG_PARALLEL)
|
||||
|
||||
def zaehl():
|
||||
lauf["fertig"] += 1
|
||||
|
||||
async def eine(aufgabe):
|
||||
if aufgabe is None:
|
||||
return # Platzhalter: zählt der Sammellauf selbst hoch
|
||||
async with schranke:
|
||||
if _laeufe.get(projekt) is not lauf:
|
||||
return # verworfen (Panel geschlossen) — keine weitere Anfrage
|
||||
try:
|
||||
lauf["probleme"].extend(await arbeite(aufgabe))
|
||||
lauf["probleme"].extend(await arbeite(aufgabe, zaehl))
|
||||
except Exception as e:
|
||||
# Zählt trotzdem als erledigt, sonst hängt der Fortschritt.
|
||||
# Die Meldung nennt die Zahl, damit „100 %" nicht vollständig vortäuscht.
|
||||
@@ -496,6 +547,7 @@ async def _lauf(projekt: str, liste: list[dict], arbeite):
|
||||
lauf["meldung"] = (
|
||||
f"{lauf['fehler']} von {lauf['gesamt']} Prüfungen fehlgeschlagen: {e}"
|
||||
)
|
||||
if aufgabe is not True:
|
||||
lauf["fertig"] += 1
|
||||
|
||||
try:
|
||||
|
||||
@@ -71,6 +71,8 @@ def belegt(quelle: dict, pfad: str, schnipsel: str) -> bool:
|
||||
_EREIGNIS = re.compile(r'@(click|auxclick|contextmenu|keyup|keydown|blur|submit|change)'
|
||||
r'[\w.]*\s*=\s*"([^"]*)"')
|
||||
_MENUEPUNKT = re.compile(r"""\{\s*text:\s*['"](.+?)['"]\s*,\s*aktion:""")
|
||||
_ROUTE = re.compile(r'@router\.(get|post|put|delete)\("([^"]+)"\)')
|
||||
_LAUSCHER = re.compile(r'(window|document)\.addEventListener\(\s*[\'"](\w+)[\'"]')
|
||||
_NAME = re.compile(r"[A-Za-zÄÖÜäöü_$][\w$]*")
|
||||
|
||||
# Rein technische Aufhänger: Speichern beim Verlassen, Abbrechen mit Escape.
|
||||
@@ -116,9 +118,11 @@ def interaktionen(quelle: dict = None) -> list[dict]:
|
||||
Zusammengefasst nach dem aufgerufenen Namen: `@click="starte('ziel')"` steht
|
||||
zwölfmal im Markup, ist aber eine Fähigkeit.
|
||||
|
||||
Backend-Routen stehen bewusst nicht drin. Sie sind die Verdrahtung hinter den
|
||||
Knöpfen, nicht die Bedienung — als eigene Einträge erzeugten sie nur Meldungen
|
||||
wie „Prüfungsstatus abrufen", die niemand auf einer Karte sehen will.
|
||||
Dazu Fenster-Lauscher: ein Tastaturgriff ist eine Fähigkeit, auch ohne Knopf.
|
||||
|
||||
Backend-Routen stehen bewusst nicht drin. Jede nutzbare Route hängt an einem
|
||||
Knopf, der schon in der Liste steht — als eigene Einträge wurden sie doppelt
|
||||
gemeldet und dabei der falschen Karte zugeordnet.
|
||||
"""
|
||||
quelle = dateien() if quelle is None else quelle
|
||||
raus, gesehen = [], set()
|
||||
@@ -137,6 +141,9 @@ def interaktionen(quelle: dict = None) -> list[dict]:
|
||||
for nr, zeile in enumerate(zeilen, 1):
|
||||
for m in _MENUEPUNKT.finditer(zeile):
|
||||
merke(f"menue:{m.group(1)}", "Menüpunkt", pfad, nr, zeilen, zeile)
|
||||
for m in _LAUSCHER.finditer(zeile):
|
||||
merke(f"lauscher:{m.group(1)}.{m.group(2)}", "Tastatur oder Maus",
|
||||
pfad, nr, zeilen, zeile)
|
||||
if not pfad.endswith(".vue"):
|
||||
continue
|
||||
for m in _EREIGNIS.finditer(zeile):
|
||||
|
||||
@@ -44,7 +44,7 @@ def test_interaktionen_sind_deterministisch():
|
||||
# Zwölf Aufrufe von starte() sind eine Fähigkeit, nicht zwölf
|
||||
assert schluessel.count("bedienen:starte") == 1
|
||||
assert "bedienen:menue" in schluessel
|
||||
assert not any(s.startswith("route:") for s in schluessel) # Verdrahtung, keine Bedienung
|
||||
assert not any(s.startswith("schnittstelle:") for s in schluessel) # Verdrahtung
|
||||
assert "menue:+ Projekt erstellen" in schluessel
|
||||
# Technische Aufhänger und Zuweisungen sind keine Fähigkeiten
|
||||
assert not any(s.startswith("bedienen:fertig") for s in schluessel)
|
||||
|
||||
@@ -430,3 +430,12 @@ def test_konsens_nimmt_die_mittlere_fassung():
|
||||
proben = _proben(f("Anmeldung starten"), f("Anmeldung startet"),
|
||||
f("Anmeldung beginnen"), f("Völlig anderer Text"))
|
||||
assert pruefung._mehrheit(proben)["titel"]["neu"].startswith("Anmeldung")
|
||||
|
||||
|
||||
def test_gemessenes_feld_braucht_keine_einstimmigkeit():
|
||||
"""Ein gezählter Verstoß steht fest, bevor gefragt wird. Vorher verschwand er,
|
||||
sobald eine einzige Stimme das Feld nicht anfasste."""
|
||||
eins = {"titel": {"neu": "Kurz", "grund": "x"}}
|
||||
proben = [eins, {}, {}, {}]
|
||||
assert pruefung._mehrheit(proben) == {}
|
||||
assert pruefung._mehrheit(proben, ("titel",)) == eins
|
||||
|
||||
@@ -130,7 +130,9 @@ async def test_messung_ist_auftrag_und_tuersteher(datenordner, monkeypatch):
|
||||
assert probleme[0]["funktion"] == "Anmelden"
|
||||
|
||||
|
||||
async def test_unverbesserlicher_vorschlag_faellt_weg(datenordner, monkeypatch):
|
||||
async def test_gemessener_verstoss_wird_notfalls_selbst_behoben(datenordner, monkeypatch):
|
||||
"""Ein gezählter Verstoß steht fest. Liefert keine Stimme einen brauchbaren
|
||||
Ersatz, greift der Ersatz des Messers — sonst bliebe der Fehler stehen."""
|
||||
import agents
|
||||
import features
|
||||
import projekte
|
||||
@@ -147,7 +149,8 @@ async def test_unverbesserlicher_vorschlag_faellt_weg(datenordner, monkeypatch):
|
||||
|
||||
monkeypatch.setattr(agents, "frage", sturr)
|
||||
probleme = await pruefung._pruefe({"funktion": "<KATALOG>"}, pruefung.aufgaben(f)[1])
|
||||
assert probleme == [] # lieber nichts als ein Vorschlag, der wieder auffällt
|
||||
assert [p["vorschlag"] for p in probleme] == ["Passwort prüfen"]
|
||||
assert probleme[0]["regel"] == "Vom System gemessen"
|
||||
|
||||
|
||||
def test_gemessene_befunde_haengen_am_ausschnitt(datenordner):
|
||||
|
||||
@@ -5,10 +5,12 @@ Darkmode
|
||||
optional
|
||||
----------------------------------------
|
||||
<!-- ziel -->
|
||||
Zwischen Tag- und Nachtmodus wechseln
|
||||
Passt die Anwendung ans Umgebungslicht an
|
||||
----------------------------------------
|
||||
<!-- regeln -->
|
||||
- Standard sind die Systemeinstellungen
|
||||
- Auto folgt der Systemeinstellung
|
||||
- Zustand überlebt das Neuladen
|
||||
- Kein Flackern beim Laden
|
||||
----------------------------------------
|
||||
<!-- funktion -->
|
||||
Darkmode umschalten
|
||||
@@ -16,11 +18,6 @@ Darkmode umschalten
|
||||
- Nacht-Icon: ☾
|
||||
- Tag-Icon: ☀
|
||||
- Auto-Icon: A
|
||||
- Zustand bleibt gespeichert
|
||||
<!-- testen -->
|
||||
- LMB auf Sidebar-Icon
|
||||
- Erster Modus ist Auto
|
||||
- LMB auf Icon
|
||||
- Zweiter Modus ist Tag
|
||||
- LMB auf Icon
|
||||
- Dritter Modus ist Nacht
|
||||
- Modus gewechselt
|
||||
|
||||
31
daten/Planer/design.md
Normal file
31
daten/Planer/design.md
Normal file
@@ -0,0 +1,31 @@
|
||||
<!-- titel -->
|
||||
Design
|
||||
----------------------------------------
|
||||
<!-- art -->
|
||||
optional
|
||||
----------------------------------------
|
||||
<!-- ziel -->
|
||||
Hält die Oberfläche lesbar und ruhig
|
||||
----------------------------------------
|
||||
<!-- regeln -->
|
||||
- Karten stapeln sich lückenlos in Spalten
|
||||
- Lesefeld ist in der Breite gedeckelt
|
||||
- Leisten scrollen nicht mit
|
||||
- Schmale Fenster stapeln die Bereiche
|
||||
- Klickflächen sind mindestens 28 Pixel
|
||||
- Textkontrast erfüllt die Lesbarkeitsschwelle
|
||||
- Kontextmenü bleibt im Fenster
|
||||
- Modal setzt den Fokus ins Eingabefeld
|
||||
- Escape schließt das Modal
|
||||
----------------------------------------
|
||||
<!-- funktion -->
|
||||
Kontextmenü schließen
|
||||
<!-- ziel -->
|
||||
Räumt ein offenes Menü aus dem Weg
|
||||
<!-- regeln -->
|
||||
- Klick daneben schließt es
|
||||
- Ein neues Menü verdrängt das alte
|
||||
<!-- testen -->
|
||||
- RMB auf eine Karte
|
||||
- LMB daneben
|
||||
- Menü geschlossen
|
||||
@@ -5,40 +5,53 @@ Features
|
||||
kern
|
||||
----------------------------------------
|
||||
<!-- ziel -->
|
||||
Gruppiert Informationen zu ähnlichen Funktionen
|
||||
Hält fest, was ein Projekt können soll
|
||||
----------------------------------------
|
||||
<!-- regeln -->
|
||||
- Eine Markdown-Datei je Feature
|
||||
- Die Datei ist die Wahrheit
|
||||
- Titel bestimmt den Dateinamen
|
||||
- Namensgleichheit bekommt eine Nummer
|
||||
- Fremde Marker bleiben erhalten
|
||||
- Alphabetisch sortiert
|
||||
- Unter daten als md gespeichert
|
||||
- Anpassen nur im Vollmodus
|
||||
- Verlassen speichert
|
||||
- Escape verwirft
|
||||
----------------------------------------
|
||||
<!-- funktion -->
|
||||
Anzeigemodus umschalten
|
||||
<!-- ziel -->
|
||||
Erlaubt schnelles Lesen oder detaillierte Bearbeitung
|
||||
Kompaktmodus zum Lesen, Vollmodus zum Anpassen
|
||||
<!-- regeln -->
|
||||
- Standard ist Kompaktmodus
|
||||
- Vollmodus geht über responsive Spalten
|
||||
- Vollmodus zeigt alle Felder
|
||||
- Kompaktmodus zeigt Ziel und Funktionstitel als Liste
|
||||
- Augen-Icon rechts oben
|
||||
<!-- testen -->
|
||||
- MMB auf Karte
|
||||
- Modus der Karte wechselt
|
||||
- Alternativ Klick auf Augen-Icon
|
||||
- MMB auf Featurecard
|
||||
- Modus umgeschaltet
|
||||
<!-- funktion -->
|
||||
Badge umschalten
|
||||
<!-- ziel -->
|
||||
Unterscheidet die Kern- und optionalen Features
|
||||
Feature anlegen
|
||||
<!-- regeln -->
|
||||
- Kernfarbe blau
|
||||
- Optionalfarbe grün
|
||||
- Verändert die Randfarbe vom Feature
|
||||
- Prüfung meldet falsche Einstufung
|
||||
- +-Icon
|
||||
- Feature öffnet im Vollmodus
|
||||
<!-- testen -->
|
||||
- RMB auf Hauptbereich
|
||||
- Klick auf "Feature erstellen"
|
||||
- Namen im Modal eingeben
|
||||
- Bestätigen mit LMB
|
||||
- Feature angelegt
|
||||
<!-- funktion -->
|
||||
Feature einstufen
|
||||
<!-- ziel -->
|
||||
Trennt tragende von verzichtbaren Features
|
||||
<!-- regeln -->
|
||||
- Kern blau umrandet
|
||||
- Optional grün umrandet
|
||||
- Badge zeigt die Einstufung
|
||||
<!-- testen -->
|
||||
- Vollmodus öffnen
|
||||
- Klick auf Badge mit LMB
|
||||
- LMB auf Badge klicken
|
||||
- Einstufung gewechselt
|
||||
<!-- funktion -->
|
||||
Feature entfernen
|
||||
<!-- regeln -->
|
||||
@@ -46,41 +59,28 @@ Feature entfernen
|
||||
<!-- testen -->
|
||||
- RMB auf Featurecard
|
||||
- Klick auf "Feature entfernen"
|
||||
- Bestätigen mit LMB im Modal
|
||||
- Bestätigen im Modal
|
||||
- Feature entfernt
|
||||
<!-- funktion -->
|
||||
Feature erstellen
|
||||
<!-- regeln -->
|
||||
- +-Icon
|
||||
- Feature in Vollmodus geöffnet
|
||||
<!-- testen -->
|
||||
- RMB auf Hauptbereich
|
||||
- Klick auf "Feature erstellen"
|
||||
- Im Modal Namen eingeben
|
||||
- Bestätigen mit LMB
|
||||
<!-- funktion -->
|
||||
Regeln anpassen
|
||||
<!-- ziel -->
|
||||
Spezifische Hinweise für Features
|
||||
<!-- testen -->
|
||||
- Vollmodus öffnen
|
||||
- Klick auf Regeln
|
||||
- Inhalt angepasst
|
||||
- LMB auf Regeln
|
||||
- Text ändern
|
||||
- Regeln angepasst
|
||||
<!-- funktion -->
|
||||
Titel anpassen
|
||||
<!-- regeln -->
|
||||
- Aktualisiert den Dateinamen
|
||||
<!-- testen -->
|
||||
- Vollmodus öffnen
|
||||
- Klick auf Titel
|
||||
- Inhalt ändern
|
||||
- Titel aktualisiert
|
||||
- LMB auf Titel
|
||||
- Text ändern
|
||||
- Titel angepasst
|
||||
<!-- funktion -->
|
||||
Ziel anpassen
|
||||
<!-- ziel -->
|
||||
Beschreibt kurz das Kernziel vom Feature
|
||||
<!-- testen -->
|
||||
- Vollmodus öffnen
|
||||
- Klick auf Ziel
|
||||
- Inhalt anpassen
|
||||
- LMB auf Ziel
|
||||
- Text ändern
|
||||
- Ziel angepasst
|
||||
|
||||
@@ -5,15 +5,30 @@ Funktionen
|
||||
kern
|
||||
----------------------------------------
|
||||
<!-- ziel -->
|
||||
Konkrete Funktionen zum Feature
|
||||
Zerlegt ein Feature in prüfbare Stücke
|
||||
----------------------------------------
|
||||
<!-- regeln -->
|
||||
- Gehört zu genau einem Feature
|
||||
- Alphabetisch sortiert
|
||||
- Ziel bleibt freiwillig
|
||||
- Regeln bleiben freiwillig
|
||||
- Testen bleibt freiwillig
|
||||
- Anpassen nur im Vollmodus
|
||||
- Verlassen speichert
|
||||
- Escape verwirft
|
||||
----------------------------------------
|
||||
<!-- funktion -->
|
||||
Funktion anlegen
|
||||
<!-- regeln -->
|
||||
- +-Icon
|
||||
<!-- testen -->
|
||||
- Vollmodus öffnen
|
||||
- RMB auf Featurecard
|
||||
- Klick auf "Funktion erstellen"
|
||||
- Namen im Modal eingeben
|
||||
- Bestätigen mit LMB
|
||||
- Funktion angelegt
|
||||
<!-- funktion -->
|
||||
Funktion entfernen
|
||||
<!-- regeln -->
|
||||
- x-Icon
|
||||
@@ -24,48 +39,34 @@ Funktion entfernen
|
||||
- Bestätigen im Modal
|
||||
- Funktion entfernt
|
||||
<!-- funktion -->
|
||||
Funktion erstellen
|
||||
<!-- regeln -->
|
||||
- +-Icon
|
||||
Titel anpassen
|
||||
<!-- testen -->
|
||||
- Vollmodus öffnen
|
||||
- RMB auf Featurecard
|
||||
- Klick auf "Funktion erstellen"
|
||||
- Namen im Modal eingeben
|
||||
- Funktion erstellt
|
||||
- LMB auf Funktionstitel
|
||||
- Text ändern
|
||||
- Titel angepasst
|
||||
<!-- funktion -->
|
||||
Ziel anpassen
|
||||
<!-- testen -->
|
||||
- Vollmodus öffnen
|
||||
- LMB auf Ziel der Funktion
|
||||
- Text ändern
|
||||
- Ziel angepasst
|
||||
<!-- funktion -->
|
||||
Regeln anpassen
|
||||
<!-- ziel -->
|
||||
Spezifische Hinweise für die Funktion
|
||||
<!-- regeln -->
|
||||
- Eine Regel je Zeile
|
||||
<!-- testen -->
|
||||
- Vollmodus öffnen
|
||||
- Klick auf Regeln der Funktion
|
||||
- LMB auf Regeln der Funktion
|
||||
- Text ändern
|
||||
- Regeln angepasst
|
||||
<!-- funktion -->
|
||||
Testen anpassen
|
||||
<!-- ziel -->
|
||||
Beschreibt die Schritte zum Prüfen der Funktion
|
||||
Testschritte anpassen
|
||||
<!-- regeln -->
|
||||
- Ein Schritt je Zeile
|
||||
<!-- testen -->
|
||||
- Vollmodus öffnen
|
||||
- Klick auf Testen der Funktion
|
||||
- Tests angepasst
|
||||
<!-- funktion -->
|
||||
Titel anpassen
|
||||
<!-- ziel -->
|
||||
Beschreibt die Funktion so kompakt wie möglich
|
||||
<!-- testen -->
|
||||
- Vollmodus öffnen
|
||||
- Klick auf Titel der Funktion
|
||||
- Titel angepasst
|
||||
<!-- funktion -->
|
||||
Ziel anpassen
|
||||
<!-- ziel -->
|
||||
Beschreibt kurz das Kernziel der Funktion
|
||||
<!-- testen -->
|
||||
- Vollmodus öffnen
|
||||
- Klick auf Ziel der Funktion
|
||||
- Ziel angepasst
|
||||
- LMB auf Testen der Funktion
|
||||
- Text ändern
|
||||
- Testschritte angepasst
|
||||
|
||||
@@ -5,82 +5,98 @@ Karten prüfen
|
||||
optional
|
||||
----------------------------------------
|
||||
<!-- ziel -->
|
||||
Prüft alle Karten auf Stilvorgaben und schlägt Verbesserungen vor
|
||||
Meldet, wo eine Karte vom Soll abweicht
|
||||
----------------------------------------
|
||||
<!-- regeln -->
|
||||
- Verwendet Minimax M2.7-HS per API
|
||||
- Alle Inhalte der Features haben globale Layouts und Stile
|
||||
- API Zugänge stehen in .env
|
||||
- Ein Lauf je Projekt
|
||||
- Ergebnis überlebt keinen Neustart
|
||||
- Zugang steht in .env
|
||||
- Fehlender Zugang bricht sichtbar ab
|
||||
- Jeder Vorschlag ist für sich anwendbar
|
||||
- Jeder Vorschlag nennt seinen Beleg
|
||||
- Eine Katalogdatei je Feld im Ordner stil
|
||||
- Neue Katalogdatei wirkt ohne Codeänderung
|
||||
----------------------------------------
|
||||
<!-- funktion -->
|
||||
Änderungsvorschläge anzeigen
|
||||
Alle Vorschläge annehmen
|
||||
<!-- regeln -->
|
||||
- Annehmen als Haken-Icon: ✓
|
||||
- Ablehnen als x-Icon: ✕
|
||||
- Icons stehen oben rechts
|
||||
- Angenommener Vorschlag landet in der Datei
|
||||
- Haken-Icon oben in der Karte
|
||||
- Zahl nennt die Menge
|
||||
<!-- testen -->
|
||||
- Prüfung durchlaufen lassen
|
||||
- LMB auf Haken-Icon der Karte
|
||||
- Alle Vorschläge übernommen
|
||||
<!-- funktion -->
|
||||
Alle Vorschläge verwerfen
|
||||
<!-- regeln -->
|
||||
- x-Icon oben in der Karte
|
||||
<!-- testen -->
|
||||
- Prüfung durchlaufen lassen
|
||||
- LMB auf x-Icon der Karte
|
||||
- Alle Vorschläge verworfen
|
||||
<!-- funktion -->
|
||||
Code in Features prüfen
|
||||
<!-- ziel -->
|
||||
Findet Bedienstellen ohne Funktion auf einer Karte
|
||||
<!-- regeln -->
|
||||
- Bedienstellen kommen aus dem Code
|
||||
- Fünf von sechs Stimmen entscheiden
|
||||
<!-- testen -->
|
||||
- LMB auf Aufwärts-Icon
|
||||
- Fehlende Funktionen gelistet
|
||||
<!-- funktion -->
|
||||
Features im Code prüfen
|
||||
<!-- ziel -->
|
||||
Findet Funktionen ohne Entsprechung im Code
|
||||
<!-- regeln -->
|
||||
- Sechs Stimmen suchen den Beleg
|
||||
- Ein Fund genügt zum Freispruch
|
||||
<!-- testen -->
|
||||
- LMB auf Abwärts-Icon
|
||||
- Funktionen ohne Code gelistet
|
||||
<!-- funktion -->
|
||||
Problemstelle anspringen
|
||||
<!-- ziel -->
|
||||
Führt vom Panel-Eintrag zur betroffenen Karte
|
||||
<!-- regeln -->
|
||||
- Karte blitzt kurz auf
|
||||
<!-- testen -->
|
||||
- Prüfung durchlaufen lassen
|
||||
- LMB auf Eintrag im Panel
|
||||
- Karte im Editor sichtbar
|
||||
<!-- funktion -->
|
||||
Prüfung beenden
|
||||
<!-- regeln -->
|
||||
- x-Icon oben im Panel
|
||||
<!-- testen -->
|
||||
- Prüfung durchlaufen lassen
|
||||
- LMB auf x-Icon im Panel
|
||||
- Panel geschlossen
|
||||
<!-- funktion -->
|
||||
Stil prüfen
|
||||
<!-- ziel -->
|
||||
Prüft die Form gegen den Katalog
|
||||
<!-- regeln -->
|
||||
- Prüf-Icon in der Sidebar
|
||||
- Wortzahl und Satzzeichen misst der Code
|
||||
<!-- testen -->
|
||||
- LMB auf Prüf-Icon
|
||||
- Fortschritt unter dem Icon
|
||||
- Fehlerhafte Karten rot markiert
|
||||
<!-- funktion -->
|
||||
Vorschlag annehmen
|
||||
<!-- regeln -->
|
||||
- Haken-Icon: ✓
|
||||
- Übrige Vorschläge bleiben stehen
|
||||
<!-- testen -->
|
||||
- Prüfung durchlaufen lassen
|
||||
- Unter den fehlerhaften Problemen werden Verbesserungsvorschläge angezeigt
|
||||
- Beide Icons angezeigt
|
||||
- LMB auf Haken-Icon
|
||||
- Vorschlag in der Datei
|
||||
<!-- funktion -->
|
||||
Badge prüfen
|
||||
<!-- ziel -->
|
||||
Prüft die Einstufung als Kern oder Optional
|
||||
Vorschlag verwerfen
|
||||
<!-- regeln -->
|
||||
- Nur kern und optional erlaubt
|
||||
- Einstufung muss zum Ziel passen
|
||||
- Falscher Badge wird rot markiert
|
||||
- x-Icon: ✕
|
||||
<!-- testen -->
|
||||
- Prüfung durchlaufen lassen
|
||||
- Badge der Karte rot markiert
|
||||
- Vorschlag unter dem Titel angezeigt
|
||||
<!-- funktion -->
|
||||
Fehlerhafte Cards markieren
|
||||
<!-- regeln -->
|
||||
- Fehlerhafte Cards werden in Vollmodus angezeigt
|
||||
<!-- testen -->
|
||||
- Prüfung durchlaufen
|
||||
- Fehlerhafte Cards werden rot markiert
|
||||
- In den Cards werden die Probleme rot markiert
|
||||
<!-- funktion -->
|
||||
Prüfung ausführen
|
||||
<!-- regeln -->
|
||||
- Prüf-Icon in Sidebar Navigation
|
||||
<!-- testen -->
|
||||
- LMB auf Icon
|
||||
- Prüfung gestartet
|
||||
- Rechte Sidebar geöffnet
|
||||
- Fortschritt unter Icon angezeigt
|
||||
- Probleme live gelistet
|
||||
<!-- funktion -->
|
||||
Stellen einzeln prüfen
|
||||
<!-- ziel -->
|
||||
Jede Stelle bekommt eigene Vorgaben und einen eigenen Ausschnitt
|
||||
<!-- regeln -->
|
||||
- Eine Anfrage je Kartenebene
|
||||
- Eine Anfrage je Funktion
|
||||
- Kartenebene prüft Titel, Badge, Ziel, Regeln
|
||||
- Funktion prüft Titel, Ziel, Regeln, Testen
|
||||
- Prüfer sieht nur seinen Ausschnitt
|
||||
- Herkunft eines Problems stammt aus dem Code
|
||||
<!-- testen -->
|
||||
- Prüfung starten
|
||||
- Fortschritt beobachten
|
||||
- Probleme mit Herkunft gelistet
|
||||
<!-- funktion -->
|
||||
Stil-Katalog ablegen
|
||||
<!-- ziel -->
|
||||
Trennt die Vorgaben je Feld voneinander
|
||||
<!-- regeln -->
|
||||
- Eine Datei je Feld im Ordner stil
|
||||
- allgemein gilt für beide Prüfer
|
||||
- feature-Präfix gilt für die Kartenebene
|
||||
- funktion-Präfix gilt für die Funktionen
|
||||
- Neue Datei wirkt ohne Codeänderung
|
||||
- Leerer Ordner bricht die Prüfung ab
|
||||
<!-- testen -->
|
||||
- Datei im Ordner stil ergänzen
|
||||
- Prüfung starten
|
||||
- Neue Vorgabe wirkt
|
||||
- LMB auf x-Icon
|
||||
- Vorschlag verschwunden
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
<!-- titel -->
|
||||
Karten Schreibstil
|
||||
----------------------------------------
|
||||
<!-- art -->
|
||||
kern
|
||||
----------------------------------------
|
||||
<!-- ziel -->
|
||||
|
||||
----------------------------------------
|
||||
<!-- regeln -->
|
||||
- Gilt für alle Karten
|
||||
- Feld leer lassen statt füllen
|
||||
----------------------------------------
|
||||
<!-- funktion -->
|
||||
Badge einstufen
|
||||
<!-- ziel -->
|
||||
Stuft die Karte als Kern oder Optional ein
|
||||
<!-- regeln -->
|
||||
- Nur kern oder optional
|
||||
- kern wenn das Projekt es braucht
|
||||
- Im Zweifel kern
|
||||
<!-- funktion -->
|
||||
Regel schreiben
|
||||
<!-- ziel -->
|
||||
Spezifische Hinweise zum Titel
|
||||
<!-- regeln -->
|
||||
- Ein kurzer Satz je Regel
|
||||
- Eine Aussage je Zeile
|
||||
<!-- funktion -->
|
||||
Testschritt schreiben
|
||||
<!-- ziel -->
|
||||
Beschreibt das Prüfen als Ablauf
|
||||
<!-- regeln -->
|
||||
- Ein Schritt je Zeile
|
||||
- Letzte Zeile beschreibt Endzustand
|
||||
<!-- funktion -->
|
||||
Titel schreiben
|
||||
<!-- ziel -->
|
||||
Benennt die Sache so kompakt wie möglich
|
||||
<!-- regeln -->
|
||||
- Nur wenige Wörter
|
||||
- Kurzsatz bei Funktionen
|
||||
<!-- funktion -->
|
||||
Ziel schreiben
|
||||
<!-- ziel -->
|
||||
Beschreibt kurz den Kern
|
||||
<!-- regeln -->
|
||||
- 1-2 simple Sätze
|
||||
- Nur wenn der Titel nicht selbsterklärend ist
|
||||
@@ -8,44 +8,46 @@ kern
|
||||
Gruppiert Features für ein Projekt
|
||||
----------------------------------------
|
||||
<!-- regeln -->
|
||||
- Ordner unter daten ist das Projekt
|
||||
- Alphabetisch sortiert
|
||||
- Aktives Projekt ist hervorgehoben
|
||||
- Auswahl überlebt das Neuladen
|
||||
- Entfernen nimmt die Features mit
|
||||
- Verlassen speichert
|
||||
- Escape bricht ab
|
||||
----------------------------------------
|
||||
<!-- funktion -->
|
||||
Projekt anpassen
|
||||
Projekt anlegen
|
||||
<!-- regeln -->
|
||||
- +-Icon
|
||||
- Projekt wird direkt aufgerufen
|
||||
<!-- testen -->
|
||||
- RMB auf Sidebar
|
||||
- Klick auf "Projekt erstellen"
|
||||
- Namen im Modal eingeben
|
||||
- Bestätigen mit LMB
|
||||
- Projekt angelegt
|
||||
<!-- funktion -->
|
||||
Projekt aufrufen
|
||||
<!-- testen -->
|
||||
- LMB auf Projektname
|
||||
- Features angezeigt
|
||||
<!-- funktion -->
|
||||
Projekt umbenennen
|
||||
<!-- regeln -->
|
||||
- Stift-Icon
|
||||
<!-- testen -->
|
||||
- RMB auf Projektname
|
||||
- Klick auf "Projekt anpassen"
|
||||
- Inhalt anpassen
|
||||
- Namen ändern
|
||||
- Außerhalb klicken
|
||||
- Name ist angepasst
|
||||
<!-- funktion -->
|
||||
Projekt aufrufen
|
||||
<!-- testen -->
|
||||
- LMB auf Projektname
|
||||
- Features werden angezeigt
|
||||
- Name angepasst
|
||||
<!-- funktion -->
|
||||
Projekt entfernen
|
||||
<!-- regeln -->
|
||||
- x-Icon
|
||||
- Features mit entfernen
|
||||
<!-- testen -->
|
||||
- RMB auf Projektname
|
||||
- Klick auf "Projekt entfernen"
|
||||
- Bestätigen im Modal
|
||||
- Projekt entfernt
|
||||
<!-- funktion -->
|
||||
Projekt erstellen
|
||||
<!-- regeln -->
|
||||
- Projekt wird direkt aufgerufen
|
||||
- +-Icon
|
||||
<!-- testen -->
|
||||
- RMB auf Sidebar
|
||||
- Klick auf "Projekt erstellen"
|
||||
- Im Modal Namen eingeben
|
||||
- Bestätigen
|
||||
- Projekt erstellt
|
||||
|
||||
@@ -89,7 +89,8 @@ onMounted(async () => {
|
||||
<span v-if="aktivesProjekt" class="pruefen">
|
||||
<span class="knoepfe">
|
||||
<button class="icon-knopf" title="Stil prüfen" @click="startePruefung(aktivesProjekt, 'stil')">⌾</button>
|
||||
<button class="icon-knopf" title="Mit Code abgleichen" @click="startePruefung(aktivesProjekt, 'inhalt')">⌗</button>
|
||||
<button class="icon-knopf" title="Features im Code: was steht ohne Code da?" @click="startePruefung(aktivesProjekt, 'feature')">↧</button>
|
||||
<button class="icon-knopf" title="Code in Features: was fehlt auf den Karten?" @click="startePruefung(aktivesProjekt, 'code')">↥</button>
|
||||
</span>
|
||||
<span v-if="pruefungLaeuft" class="fortschritt">{{ pruefungProzent }} %</span>
|
||||
</span>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script setup>
|
||||
import { art, laeuft, prozent, fertig, gesamt, meldung, probleme, schliesse } from '../composables/pruefung.js'
|
||||
|
||||
const UEBERSCHRIFT = { stil: 'Stilprüfung', inhalt: 'Abgleich mit Code' }
|
||||
const UEBERSCHRIFT = {
|
||||
stil: 'Stilprüfung',
|
||||
feature: 'Features im Code',
|
||||
code: 'Code in Features',
|
||||
}
|
||||
|
||||
// Nur die erste Zeile — mehrzeilige Stellen liefen hier sonst zu einem Klumpen zusammen
|
||||
function erste(text) {
|
||||
|
||||
Reference in New Issue
Block a user