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
|
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.
|
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
|
Zwei weitere Prüfungen, in beide Richtungen. Beide arbeiten mit derselben
|
||||||
kann? Gesucht werden Lücken — Fähigkeiten ohne Funktion auf einer Karte.
|
Beweislast, nur andersherum: ein Vorhandensein lässt sich zitieren, ein
|
||||||
|
Nichtvorhandensein nicht.
|
||||||
|
|
||||||
```
|
```
|
||||||
1 Inventar quellcode.py Bedienstellen aus dem Code, deterministisch
|
↧ Features im Code steht hinter jeder Funktion auch Code?
|
||||||
2 Abdecken 6 Stimmen deckt eine bestehende Funktion die Stelle ab?
|
↥ Code in Features hat jede Bedienstelle auch eine Funktion?
|
||||||
3 Ergänzen Code 5 von 6 sagen nein -> Vorschlag mit Fundort
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Inventar.** `interaktionen()` liest die Stellen aus, an denen ein Mensch etwas
|
**↧ Features im Code.** Je Funktion sechs unabhängige Versuche, die Codestelle zu
|
||||||
auslöst: Ereignisse im Markup und Einträge der Kontextmenüs. Zusammengefasst nach
|
finden. Jeder Fund wird gegen die Datei geschlagen. Gemeldet wird erst, wenn keine
|
||||||
dem aufgerufenen Namen — `starte('ziel')` steht zwölfmal da und ist eine Fähigkeit.
|
einzige Stimme etwas findet — auch ein misslungenes Zitat gilt als Widerspruch, denn
|
||||||
Ein Modell hatte die Liste anfangs frei aufgezählt: jede Runde eine andere.
|
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,
|
**↥ Code in Features.** Die Liste der Bedienstellen kommt aus dem Code, nicht aus
|
||||||
Speichern beim Verlassen, Abbrechen mit Escape.
|
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
|
Nicht im Inventar: Backend-Routen (jede hängt an einem Knopf, der schon dasteht),
|
||||||
Funktion dahinter und dem vollen Text aller Karten. Nur Titel reichten nicht — der
|
Zuweisungen, Speichern beim Verlassen, Abbrechen mit Escape.
|
||||||
Richter musste raten und urteilte jede Runde anders.
|
|
||||||
|
|
||||||
**Schwelle.** Fünf von sechs Stimmen. Einstimmigkeit war zu scharf: dieselbe Frage
|
**Warum diese Schwellen.** Einstimmigkeit war für ↥ zu scharf: dieselbe Frage kippt
|
||||||
kippt auch bei Temperatur 0 gelegentlich, und ein Ausreißer löschte klare Funde.
|
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.
|
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
|
Nachgemessen an diesem Projekt: auf gepflegten Karten melden beide nichts. Eine
|
||||||
Funktion hat keinen Code, weg damit" — ist bewusst nicht eingebaut: ein
|
untergeschobene Funktion ohne Code fällt ↧ in jedem Lauf auf. Fehlende Funktionen
|
||||||
Nichtvorhandensein lässt sich nicht belegen, und die Vorschläge zielten wiederholt
|
findet ↥, nach dem Übernehmen ist es still.
|
||||||
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.
|
|
||||||
|
|
||||||
Der Katalog liegt in `backend/stil/` — eine Datei je Feld, alphabetisch zusammengesetzt
|
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:
|
(`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
|
**Feature im Code** — steht hinter jeder Funktion einer Karte auch Code?
|
||||||
überhaupt". Ziel ist, dass alle Karten zusammen alles abbilden, was das Programm
|
**Code im Feature** — hat jede Stelle im Code auch eine Funktion auf einer Karte?
|
||||||
kann — und nichts, was es nicht kann.
|
|
||||||
|
|
||||||
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.
|
Ein Vorhandensein lässt sich zitieren, ein Nichtvorhandensein nicht. Darum fragt
|
||||||
Ohne die Karten daneben, sonst bestätigt es bloß, was schon dasteht. Jede
|
„Feature im Code" sechsmal unabhängig nach dem Beleg und meldet erst, wenn keine
|
||||||
Fähigkeit braucht eine wörtlich zitierte Codezeile; ohne Beleg fällt sie weg.
|
einzige Stimme eine Codezeile findet, die auch wirklich dasteht. Ein geprüfter Fund
|
||||||
Mehrere Durchgänge werden vereinigt: hier zählt Vollständigkeit.
|
genügt zum Freispruch — sechs Fehlversuche sind die Anklage. Ein früherer Versuch
|
||||||
2. **Abdeckung** — je Fähigkeit eine kurze Frage: deckt eine bestehende Funktion
|
ließ das Modell direkt urteilen und schlug dabei vor, gültige Inhalte zu löschen.
|
||||||
das ab? Eine Ja/Nein-Frage ist weit stabiler als offenes Suchen. Nur wenn alle
|
|
||||||
Stimmen „nein" sagen, entsteht ein Vorschlag.
|
„Code im Feature" fragt umgekehrt je Stelle, ob eine bestehende Funktion sie
|
||||||
Geprüft wird nur eine Richtung: was fehlt. Die Gegenrichtung — „diese Funktion hat
|
abdeckt, und meldet ab fünf von sechs Nein-Stimmen. Einstimmigkeit war zu scharf:
|
||||||
keinen Code, weg damit" — ist bewusst nicht eingebaut. Sie ließ sich nicht belegen
|
dieselbe Frage kippt auch bei Temperatur 0 gelegentlich.
|
||||||
(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
|
Was der Code selbst weiß, entscheidet der Code: die Liste der Bedienstellen kommt
|
||||||
Dinge festhalten, die kein Knopf sind — Schreibregeln etwa.
|
aus `quellcode.interaktionen()`, jeder Beleg wird gegen die Datei geschlagen.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -28,8 +27,8 @@ import agents
|
|||||||
import config
|
import config
|
||||||
import quellcode
|
import quellcode
|
||||||
|
|
||||||
# Ohne Drossel liefen alle Punkte gleichzeitig los und der Provider antwortete
|
# Ohne Drossel liefen alle Anfragen gleichzeitig los und der Provider antwortete
|
||||||
# mit 429. Die ausgefallenen Stimmen sahen dann aus wie „alles abgedeckt".
|
# mit 429. Die ausgefallenen Stimmen sahen dann aus wie „nichts gefunden".
|
||||||
_schranke = None
|
_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):
|
async def _urteil(system: str, nachricht: str, temperatur: float):
|
||||||
"""Eine Stimme als JSON. Unlesbares wird einmal neu gefragt, dann aufgegeben —
|
"""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."""
|
bei hunderten Anfragen je Lauf ist ein Ausrutscher normal, ein Abbruch nicht."""
|
||||||
for versuch in range(2):
|
for versuch in range(3):
|
||||||
try:
|
try:
|
||||||
return _json(await _frage(system, nachricht, temperatur + 0.1 * versuch))
|
return _json(await _frage(system, nachricht, temperatur + 0.1 * versuch))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
return None
|
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
|
ABDECKUNG_SYSTEM = """Du entscheidest, ob eine Stelle im Programm schon von einer
|
||||||
beschriebenen Funktion abgedeckt ist. Antworte ausschließlich mit JSON."""
|
beschriebenen Funktion abgedeckt ist. Antworte ausschließlich mit JSON."""
|
||||||
|
|
||||||
@@ -66,7 +94,6 @@ ABDECKUNG = """An dieser Stelle kann eine Person etwas auslösen:
|
|||||||
{umfeld}
|
{umfeld}
|
||||||
```
|
```
|
||||||
{rumpf}
|
{rumpf}
|
||||||
|
|
||||||
Die Karten des Projekts beschreiben bisher das hier:
|
Die Karten des Projekts beschreiben bisher das hier:
|
||||||
|
|
||||||
{funktionen}
|
{funktionen}
|
||||||
@@ -89,14 +116,14 @@ Rein technische Aufhänger gelten als abgedeckt: Speichern beim Verlassen eines
|
|||||||
Feldes, Abbrechen mit Escape, Gesundheitsprüfungen, interne Weiterleitung.
|
Feldes, Abbrechen mit Escape, Gesundheitsprüfungen, interne Weiterleitung.
|
||||||
Im Zweifel abgedeckt. Nur was einer Person wirklich fehlt, soll dazukommen."""
|
Im Zweifel abgedeckt. Nur was einer Person wirklich fehlt, soll dazukommen."""
|
||||||
|
|
||||||
# Achtung: dieser Text geht unformatiert als System-Prompt raus. Doppelte Klammern
|
# Sechs unabhängige Versuche, den Beleg zu finden. Eine einzige Stimme, die etwas
|
||||||
# wären hier keine Escapes, sondern landeten wörtlich in der Antwort.
|
# findet, genügt zum Freispruch — die Anklage lautet auf Löschen, da wiegt ein
|
||||||
# Sechs Stimmen, fünf müssen „fehlt" sagen.
|
# Fehlurteil schwerer als ein übersehener Rest.
|
||||||
#
|
SUCHE_TEMPERATUREN = (0.0, 0.2, 0.4, 0.6, 0.8, 1.0)
|
||||||
# Einstimmigkeit war zu scharf: gemessen kippt dieselbe Frage auch bei Temperatur 0
|
# So viele Stimmen müssen überhaupt geantwortet haben, bevor angeklagt wird
|
||||||
# gelegentlich, und eine einzelne Ausreißerstimme löschte damit auch klare Funde.
|
ANKLAGE_AB = 4
|
||||||
# Eindeutige Stellen antworten 6:0 oder 0:6, Grenzfälle etwa 3:3 — die Schwelle bei
|
# Sechs Stimmen, fünf müssen „fehlt" sagen. Eindeutige Stellen antworten 6:0 oder
|
||||||
# fünf trennt beides und übersteht einen Ausrutscher.
|
# 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)
|
URTEIL_TEMPERATUREN = (0.0, 0.2, 0.4, 0.6, 0.8, 1.0)
|
||||||
FEHLT_AB = 5
|
FEHLT_AB = 5
|
||||||
# Ab dieser Ähnlichkeit gelten zwei Titel als dieselbe Sache
|
# Ab dieser Ähnlichkeit gelten zwei Titel als dieselbe Sache
|
||||||
@@ -117,12 +144,99 @@ def _json(antwort: str) -> dict:
|
|||||||
return pruefung._json_lesen(antwort)
|
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:
|
def _funktionsliste(features: list) -> str:
|
||||||
"""Die Karten mit allem, was sie sagen.
|
"""Die Karten mit allem, was sie sagen.
|
||||||
|
|
||||||
Nur die Titel reichten nicht: der Richter musste raten, ob eine Funktion eine
|
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
|
Stelle abdeckt, und urteilte von Runde zu Runde anders.
|
||||||
Testschritte sagen es dagegen ausdrücklich.
|
|
||||||
"""
|
"""
|
||||||
teile = []
|
teile = []
|
||||||
for f in features:
|
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)]
|
return [p for p in proben if isinstance(p, dict)]
|
||||||
|
|
||||||
|
|
||||||
async def luecken(punkte: list, features: list) -> list:
|
def _naechste_karte(name: str, karten: dict):
|
||||||
"""Interaktionspunkte, die einstimmig von keiner Funktion abgedeckt sind."""
|
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)
|
liste = _funktionsliste(features)
|
||||||
karten = {f["sektionen"]["titel"]: f for f in features}
|
karten = {f["sektionen"]["titel"]: f for f in features}
|
||||||
getroffen = set() # Funktionen, die eine Stimme als Treffer genannt hat
|
|
||||||
|
|
||||||
async def eine(punkt):
|
async def eine(punkt):
|
||||||
urteile = await _abdeckung(punkt, liste)
|
urteile = await _abdeckung(punkt, liste)
|
||||||
for u in urteile:
|
if zaehl:
|
||||||
if u.get("abgedeckt") and u.get("funktion"):
|
zaehl()
|
||||||
getroffen.add(str(u["funktion"]).strip())
|
if len(urteile) < FEHLT_AB:
|
||||||
if len(urteile) < len(URTEIL_TEMPERATUREN):
|
# Nicht verschweigen: ausgefallene Stimmen sähen sonst aus wie „ist
|
||||||
# Nicht verschweigen: eine ausgefallene Stimme sähe sonst aus wie
|
# abgedeckt" und der Lauf meldete stillschweigend zu wenig. Ein
|
||||||
# „ist abgedeckt" und der Lauf meldete stillschweigend zu wenig.
|
# 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(
|
raise RuntimeError(
|
||||||
f'{punkt["schluessel"]}: nur {len(urteile)} von '
|
f'{punkt["schluessel"]}: nur {len(urteile)} von '
|
||||||
f"{len(URTEIL_TEMPERATUREN)} Stimmen brauchbar")
|
f"{len(URTEIL_TEMPERATUREN)} Stimmen brauchbar")
|
||||||
if sum(u.get("abgedeckt") is False for u in urteile) < FEHLT_AB:
|
if sum(u.get("abgedeckt") is False for u in urteile) < FEHLT_AB:
|
||||||
return None
|
return None
|
||||||
ziele = [str(u.get("karte") or "").strip() for u in urteile]
|
ziele = [str(u.get("karte") or "").strip() for u in urteile]
|
||||||
karte = max(set(ziele), key=ziele.count)
|
name = max(set(ziele), key=ziele.count)
|
||||||
treffer = karten.get(karte) or _naechste_karte(karte, karten)
|
treffer = karten.get(name) or _naechste_karte(name, karten)
|
||||||
if not treffer:
|
|
||||||
return None
|
|
||||||
titel = [str(u.get("titel") or "").strip() for u in urteile if u.get("titel")]
|
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 None
|
||||||
return {"feature": treffer["id"],
|
return {"feature": treffer["id"],
|
||||||
"titel": max(titel, key=lambda t: sum(_aehnlich(t, x) for x in titel)),
|
"titel": max(titel, key=lambda t: sum(_aehnlich(t, x) for x in titel)),
|
||||||
"beleg": f'{punkt["datei"]}:{punkt["zeile"]}'}
|
"beleg": f'{punkt["datei"]}:{punkt["zeile"]}'}
|
||||||
|
|
||||||
gefunden = [x for x in await asyncio.gather(*(eine(p) for p in punkte)) if x]
|
gefunden = [x for x in await asyncio.gather(*(eine(p) for p in punkte)) if x]
|
||||||
luecken.getroffen = getroffen
|
# Zwei Stellen können auf dieselbe fehlende Funktion zeigen
|
||||||
# Zwei Punkte können auf dieselbe fehlende Funktion zeigen
|
|
||||||
raus = []
|
raus = []
|
||||||
for x in gefunden:
|
for x in gefunden:
|
||||||
gleich = next((y for y in raus if y["feature"] == x["feature"]
|
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
|
return raus
|
||||||
|
|
||||||
|
|
||||||
def _naechste_karte(name: str, karten: dict):
|
def probleme_code(features: list, fehlend: list) -> list:
|
||||||
if not name:
|
"""Ein Vorschlag je Karte: die fehlenden Funktionen anhängen."""
|
||||||
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."""
|
|
||||||
import pruefung
|
import pruefung
|
||||||
|
|
||||||
raus = []
|
raus = []
|
||||||
@@ -216,28 +327,30 @@ def probleme(features: list, fehlend: list) -> list:
|
|||||||
dazu = [x for x in fehlend if x["feature"] == f["id"]]
|
dazu = [x for x in fehlend if x["feature"] == f["id"]]
|
||||||
if not dazu:
|
if not dazu:
|
||||||
continue
|
continue
|
||||||
# Bestehende Reihenfolge behalten, Neues hinten anhängen: sortieren würde
|
# Bestehende Reihenfolge behalten, Neues hinten anhängen — sortieren würde
|
||||||
# die halbe Liste verschieben und aus einer Ergänzung einen Umbau machen.
|
# aus einer Ergänzung einen Umbau machen. Angezeigt wird ohnehin alphabetisch.
|
||||||
# Angezeigt wird ohnehin alphabetisch.
|
|
||||||
neu = list(dict.fromkeys(ist + [x["titel"] for x in dazu]))
|
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),
|
for p in pruefung._stellen("funktionen", "\n".join(ist), "\n".join(neu),
|
||||||
_grund(dazu), belege):
|
grund, beleg):
|
||||||
p["feature"] = f["id"]
|
p["feature"] = f["id"]
|
||||||
p["funktion"] = ""
|
p["funktion"] = ""
|
||||||
raus.append(p)
|
raus.append(p)
|
||||||
return raus
|
return raus
|
||||||
|
|
||||||
|
|
||||||
def _grund(dazu: list) -> str:
|
# ---------------------------------------------------------------- Einstiege
|
||||||
return (f"{len(dazu)} Fähigkeit ohne Funktion" if len(dazu) == 1
|
|
||||||
else f"{len(dazu)} Fähigkeiten ohne Funktion")
|
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):
|
async def pruefe_code_im_feature(features: list, quelle: dict, zaehl=None) -> list:
|
||||||
"""Der ganze Abgleich: Interaktionspunkte, Lücken, Überhang."""
|
|
||||||
punkte = quellcode.interaktionen(quelle)
|
punkte = quellcode.interaktionen(quelle)
|
||||||
if not punkte:
|
if not punkte:
|
||||||
raise RuntimeError("Keine Interaktionspunkte im Quelltext gefunden")
|
raise RuntimeError("Keine Bedienstellen im Quelltext gefunden")
|
||||||
fehlend = await luecken(punkte, features)
|
return probleme_code(features, await code_im_feature(features, punkte, zaehl))
|
||||||
return probleme(features, fehlend), punkte
|
|
||||||
|
|||||||
@@ -403,16 +403,21 @@ def _konsens(fassungen: list[dict]) -> dict:
|
|||||||
return max(fassungen, key=naehe)
|
return max(fassungen, key=naehe)
|
||||||
|
|
||||||
|
|
||||||
def _mehrheit(proben: list[dict]) -> dict:
|
def _mehrheit(proben: list[dict], gemessen: tuple = ()) -> dict:
|
||||||
"""Nur was mehrere Stimmen unabhängig bemängeln, wird ein Vorschlag.
|
"""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
|
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.
|
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)
|
zaehler = Counter(feld for p in proben for feld in p)
|
||||||
return {
|
return {
|
||||||
feld: _konsens([p[feld] for p in proben if feld in p])
|
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)]
|
gute = [p for p in proben if isinstance(p, dict)]
|
||||||
if not gute:
|
if not gute:
|
||||||
raise next(p for p in proben if isinstance(p, BaseException))
|
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]:
|
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."""
|
"""Prüfstellen der Stilprüfung samt der Kataloge, die sie brauchen."""
|
||||||
stil = {b: _stil_lesen(b) for b in BEREICHE}
|
stil = {b: _stil_lesen(b) for b in BEREICHE}
|
||||||
liste = [a for f in features.liste(projekt) for a in aufgaben(f)]
|
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):
|
def _quelle(projekt: str):
|
||||||
"""Ein Abgleich über alle Karten — Vollständigkeit ist eine Eigenschaft der
|
|
||||||
Menge, nicht der einzelnen Karte."""
|
|
||||||
alle = features.liste(projekt)
|
alle = features.liste(projekt)
|
||||||
quelle = quellcode.dateien()
|
quelle = quellcode.dateien()
|
||||||
if not quelle:
|
if not quelle:
|
||||||
raise ValueError("Kein Quelltext gefunden")
|
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)
|
text = quellcode.als_text(quelle)
|
||||||
|
anzahl = sum(len(f["sektionen"]["funktionen"]) for f in alle)
|
||||||
|
|
||||||
async def abgleich(_):
|
async def abgleich(_, zaehl):
|
||||||
probleme, _inv = await inhaltspruefung.pruefe(alle, quelle, text)
|
return await inhaltspruefung.pruefe_feature_im_code(alle, quelle, text, zaehl)
|
||||||
return probleme
|
|
||||||
|
|
||||||
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):
|
async def _lauf(projekt: str, liste: list[dict], arbeite):
|
||||||
lauf = _laeufe[projekt]
|
lauf = _laeufe[projekt]
|
||||||
schranke = asyncio.Semaphore(config.PRUEFUNG_PARALLEL)
|
schranke = asyncio.Semaphore(config.PRUEFUNG_PARALLEL)
|
||||||
|
|
||||||
|
def zaehl():
|
||||||
|
lauf["fertig"] += 1
|
||||||
|
|
||||||
async def eine(aufgabe):
|
async def eine(aufgabe):
|
||||||
|
if aufgabe is None:
|
||||||
|
return # Platzhalter: zählt der Sammellauf selbst hoch
|
||||||
async with schranke:
|
async with schranke:
|
||||||
if _laeufe.get(projekt) is not lauf:
|
if _laeufe.get(projekt) is not lauf:
|
||||||
return # verworfen (Panel geschlossen) — keine weitere Anfrage
|
return # verworfen (Panel geschlossen) — keine weitere Anfrage
|
||||||
try:
|
try:
|
||||||
lauf["probleme"].extend(await arbeite(aufgabe))
|
lauf["probleme"].extend(await arbeite(aufgabe, zaehl))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Zählt trotzdem als erledigt, sonst hängt der Fortschritt.
|
# Zählt trotzdem als erledigt, sonst hängt der Fortschritt.
|
||||||
# Die Meldung nennt die Zahl, damit „100 %" nicht vollständig vortäuscht.
|
# Die Meldung nennt die Zahl, damit „100 %" nicht vollständig vortäuscht.
|
||||||
@@ -496,7 +547,8 @@ async def _lauf(projekt: str, liste: list[dict], arbeite):
|
|||||||
lauf["meldung"] = (
|
lauf["meldung"] = (
|
||||||
f"{lauf['fehler']} von {lauf['gesamt']} Prüfungen fehlgeschlagen: {e}"
|
f"{lauf['fehler']} von {lauf['gesamt']} Prüfungen fehlgeschlagen: {e}"
|
||||||
)
|
)
|
||||||
lauf["fertig"] += 1
|
if aufgabe is not True:
|
||||||
|
lauf["fertig"] += 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await asyncio.gather(*(eine(a) for a in liste))
|
await asyncio.gather(*(eine(a) for a in liste))
|
||||||
|
|||||||
@@ -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)'
|
_EREIGNIS = re.compile(r'@(click|auxclick|contextmenu|keyup|keydown|blur|submit|change)'
|
||||||
r'[\w.]*\s*=\s*"([^"]*)"')
|
r'[\w.]*\s*=\s*"([^"]*)"')
|
||||||
_MENUEPUNKT = re.compile(r"""\{\s*text:\s*['"](.+?)['"]\s*,\s*aktion:""")
|
_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$]*")
|
_NAME = re.compile(r"[A-Za-zÄÖÜäöü_$][\w$]*")
|
||||||
|
|
||||||
# Rein technische Aufhänger: Speichern beim Verlassen, Abbrechen mit Escape.
|
# 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
|
Zusammengefasst nach dem aufgerufenen Namen: `@click="starte('ziel')"` steht
|
||||||
zwölfmal im Markup, ist aber eine Fähigkeit.
|
zwölfmal im Markup, ist aber eine Fähigkeit.
|
||||||
|
|
||||||
Backend-Routen stehen bewusst nicht drin. Sie sind die Verdrahtung hinter den
|
Dazu Fenster-Lauscher: ein Tastaturgriff ist eine Fähigkeit, auch ohne Knopf.
|
||||||
Knöpfen, nicht die Bedienung — als eigene Einträge erzeugten sie nur Meldungen
|
|
||||||
wie „Prüfungsstatus abrufen", die niemand auf einer Karte sehen will.
|
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
|
quelle = dateien() if quelle is None else quelle
|
||||||
raus, gesehen = [], set()
|
raus, gesehen = [], set()
|
||||||
@@ -137,6 +141,9 @@ def interaktionen(quelle: dict = None) -> list[dict]:
|
|||||||
for nr, zeile in enumerate(zeilen, 1):
|
for nr, zeile in enumerate(zeilen, 1):
|
||||||
for m in _MENUEPUNKT.finditer(zeile):
|
for m in _MENUEPUNKT.finditer(zeile):
|
||||||
merke(f"menue:{m.group(1)}", "Menüpunkt", pfad, nr, zeilen, 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"):
|
if not pfad.endswith(".vue"):
|
||||||
continue
|
continue
|
||||||
for m in _EREIGNIS.finditer(zeile):
|
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
|
# Zwölf Aufrufe von starte() sind eine Fähigkeit, nicht zwölf
|
||||||
assert schluessel.count("bedienen:starte") == 1
|
assert schluessel.count("bedienen:starte") == 1
|
||||||
assert "bedienen:menue" in schluessel
|
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
|
assert "menue:+ Projekt erstellen" in schluessel
|
||||||
# Technische Aufhänger und Zuweisungen sind keine Fähigkeiten
|
# Technische Aufhänger und Zuweisungen sind keine Fähigkeiten
|
||||||
assert not any(s.startswith("bedienen:fertig") for s in schluessel)
|
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"),
|
proben = _proben(f("Anmeldung starten"), f("Anmeldung startet"),
|
||||||
f("Anmeldung beginnen"), f("Völlig anderer Text"))
|
f("Anmeldung beginnen"), f("Völlig anderer Text"))
|
||||||
assert pruefung._mehrheit(proben)["titel"]["neu"].startswith("Anmeldung")
|
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"
|
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 agents
|
||||||
import features
|
import features
|
||||||
import projekte
|
import projekte
|
||||||
@@ -147,7 +149,8 @@ async def test_unverbesserlicher_vorschlag_faellt_weg(datenordner, monkeypatch):
|
|||||||
|
|
||||||
monkeypatch.setattr(agents, "frage", sturr)
|
monkeypatch.setattr(agents, "frage", sturr)
|
||||||
probleme = await pruefung._pruefe({"funktion": "<KATALOG>"}, pruefung.aufgaben(f)[1])
|
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):
|
def test_gemessene_befunde_haengen_am_ausschnitt(datenordner):
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ Darkmode
|
|||||||
optional
|
optional
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- ziel -->
|
<!-- ziel -->
|
||||||
Zwischen Tag- und Nachtmodus wechseln
|
Passt die Anwendung ans Umgebungslicht an
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- Standard sind die Systemeinstellungen
|
- Auto folgt der Systemeinstellung
|
||||||
|
- Zustand überlebt das Neuladen
|
||||||
|
- Kein Flackern beim Laden
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Darkmode umschalten
|
Darkmode umschalten
|
||||||
@@ -16,11 +18,6 @@ Darkmode umschalten
|
|||||||
- Nacht-Icon: ☾
|
- Nacht-Icon: ☾
|
||||||
- Tag-Icon: ☀
|
- Tag-Icon: ☀
|
||||||
- Auto-Icon: A
|
- Auto-Icon: A
|
||||||
- Zustand bleibt gespeichert
|
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- LMB auf Sidebar-Icon
|
- LMB auf Sidebar-Icon
|
||||||
- Erster Modus ist Auto
|
- Modus gewechselt
|
||||||
- LMB auf Icon
|
|
||||||
- Zweiter Modus ist Tag
|
|
||||||
- LMB auf Icon
|
|
||||||
- Dritter Modus ist Nacht
|
|
||||||
|
|||||||
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
|
kern
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- ziel -->
|
<!-- ziel -->
|
||||||
Gruppiert Informationen zu ähnlichen Funktionen
|
Hält fest, was ein Projekt können soll
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- regeln -->
|
<!-- 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
|
- Alphabetisch sortiert
|
||||||
- Unter daten als md gespeichert
|
- Anpassen nur im Vollmodus
|
||||||
- Verlassen speichert
|
- Verlassen speichert
|
||||||
- Escape verwirft
|
- Escape verwirft
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Anzeigemodus umschalten
|
Anzeigemodus umschalten
|
||||||
<!-- ziel -->
|
<!-- ziel -->
|
||||||
Erlaubt schnelles Lesen oder detaillierte Bearbeitung
|
Kompaktmodus zum Lesen, Vollmodus zum Anpassen
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- Standard ist Kompaktmodus
|
- Standard ist Kompaktmodus
|
||||||
- Vollmodus geht über responsive Spalten
|
|
||||||
- Vollmodus zeigt alle Felder
|
- Vollmodus zeigt alle Felder
|
||||||
- Kompaktmodus zeigt Ziel und Funktionstitel als Liste
|
|
||||||
- Augen-Icon rechts oben
|
- Augen-Icon rechts oben
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- MMB auf Karte
|
- MMB auf Featurecard
|
||||||
- Modus der Karte wechselt
|
- Modus umgeschaltet
|
||||||
- Alternativ Klick auf Augen-Icon
|
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Badge umschalten
|
Feature anlegen
|
||||||
<!-- ziel -->
|
|
||||||
Unterscheidet die Kern- und optionalen Features
|
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- Kernfarbe blau
|
- +-Icon
|
||||||
- Optionalfarbe grün
|
- Feature öffnet im Vollmodus
|
||||||
- Verändert die Randfarbe vom Feature
|
<!-- testen -->
|
||||||
- Prüfung meldet falsche Einstufung
|
- 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 -->
|
<!-- testen -->
|
||||||
- Vollmodus öffnen
|
- Vollmodus öffnen
|
||||||
- Klick auf Badge mit LMB
|
- LMB auf Badge klicken
|
||||||
|
- Einstufung gewechselt
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Feature entfernen
|
Feature entfernen
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
@@ -46,41 +59,28 @@ Feature entfernen
|
|||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- RMB auf Featurecard
|
- RMB auf Featurecard
|
||||||
- Klick auf "Feature entfernen"
|
- Klick auf "Feature entfernen"
|
||||||
- Bestätigen mit LMB im Modal
|
- Bestätigen im Modal
|
||||||
- Feature entfernt
|
- Feature entfernt
|
||||||
<!-- funktion -->
|
<!-- 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
|
Regeln anpassen
|
||||||
<!-- ziel -->
|
|
||||||
Spezifische Hinweise für Features
|
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- Vollmodus öffnen
|
- Vollmodus öffnen
|
||||||
- Klick auf Regeln
|
- LMB auf Regeln
|
||||||
- Inhalt angepasst
|
- Text ändern
|
||||||
|
- Regeln angepasst
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Titel anpassen
|
Titel anpassen
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- Aktualisiert den Dateinamen
|
- Aktualisiert den Dateinamen
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- Vollmodus öffnen
|
- Vollmodus öffnen
|
||||||
- Klick auf Titel
|
- LMB auf Titel
|
||||||
- Inhalt ändern
|
- Text ändern
|
||||||
- Titel aktualisiert
|
- Titel angepasst
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Ziel anpassen
|
Ziel anpassen
|
||||||
<!-- ziel -->
|
|
||||||
Beschreibt kurz das Kernziel vom Feature
|
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- Vollmodus öffnen
|
- Vollmodus öffnen
|
||||||
- Klick auf Ziel
|
- LMB auf Ziel
|
||||||
- Inhalt anpassen
|
- Text ändern
|
||||||
- Ziel angepasst
|
- Ziel angepasst
|
||||||
|
|||||||
@@ -5,15 +5,30 @@ Funktionen
|
|||||||
kern
|
kern
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- ziel -->
|
<!-- ziel -->
|
||||||
Konkrete Funktionen zum Feature
|
Zerlegt ein Feature in prüfbare Stücke
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
|
- Gehört zu genau einem Feature
|
||||||
- Alphabetisch sortiert
|
- Alphabetisch sortiert
|
||||||
|
- Ziel bleibt freiwillig
|
||||||
|
- Regeln bleiben freiwillig
|
||||||
|
- Testen bleibt freiwillig
|
||||||
- Anpassen nur im Vollmodus
|
- Anpassen nur im Vollmodus
|
||||||
- Verlassen speichert
|
- Verlassen speichert
|
||||||
- Escape verwirft
|
- Escape verwirft
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- funktion -->
|
<!-- 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
|
Funktion entfernen
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- x-Icon
|
- x-Icon
|
||||||
@@ -24,48 +39,34 @@ Funktion entfernen
|
|||||||
- Bestätigen im Modal
|
- Bestätigen im Modal
|
||||||
- Funktion entfernt
|
- Funktion entfernt
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Funktion erstellen
|
Titel anpassen
|
||||||
<!-- regeln -->
|
|
||||||
- +-Icon
|
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- Vollmodus öffnen
|
- Vollmodus öffnen
|
||||||
- RMB auf Featurecard
|
- LMB auf Funktionstitel
|
||||||
- Klick auf "Funktion erstellen"
|
- Text ändern
|
||||||
- Namen im Modal eingeben
|
- Titel angepasst
|
||||||
- Funktion erstellt
|
<!-- funktion -->
|
||||||
|
Ziel anpassen
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf Ziel der Funktion
|
||||||
|
- Text ändern
|
||||||
|
- Ziel angepasst
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Regeln anpassen
|
Regeln anpassen
|
||||||
<!-- ziel -->
|
|
||||||
Spezifische Hinweise für die Funktion
|
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- Eine Regel je Zeile
|
- Eine Regel je Zeile
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- Vollmodus öffnen
|
- Vollmodus öffnen
|
||||||
- Klick auf Regeln der Funktion
|
- LMB auf Regeln der Funktion
|
||||||
|
- Text ändern
|
||||||
- Regeln angepasst
|
- Regeln angepasst
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Testen anpassen
|
Testschritte anpassen
|
||||||
<!-- ziel -->
|
|
||||||
Beschreibt die Schritte zum Prüfen der Funktion
|
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- Ein Schritt je Zeile
|
- Ein Schritt je Zeile
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- Vollmodus öffnen
|
- Vollmodus öffnen
|
||||||
- Klick auf Testen der Funktion
|
- LMB auf Testen der Funktion
|
||||||
- Tests angepasst
|
- Text ändern
|
||||||
<!-- funktion -->
|
- Testschritte angepasst
|
||||||
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
|
|
||||||
|
|||||||
@@ -5,82 +5,98 @@ Karten prüfen
|
|||||||
optional
|
optional
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- ziel -->
|
<!-- ziel -->
|
||||||
Prüft alle Karten auf Stilvorgaben und schlägt Verbesserungen vor
|
Meldet, wo eine Karte vom Soll abweicht
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- Verwendet Minimax M2.7-HS per API
|
- Ein Lauf je Projekt
|
||||||
- Alle Inhalte der Features haben globale Layouts und Stile
|
- Ergebnis überlebt keinen Neustart
|
||||||
- API Zugänge stehen in .env
|
- 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 -->
|
<!-- funktion -->
|
||||||
Änderungsvorschläge anzeigen
|
Alle Vorschläge annehmen
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- Annehmen als Haken-Icon: ✓
|
- Haken-Icon oben in der Karte
|
||||||
- Ablehnen als x-Icon: ✕
|
- Zahl nennt die Menge
|
||||||
- Icons stehen oben rechts
|
<!-- testen -->
|
||||||
- Angenommener Vorschlag landet in der Datei
|
- 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
|
- Übrige Vorschläge bleiben stehen
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- Prüfung durchlaufen lassen
|
- Prüfung durchlaufen lassen
|
||||||
- Unter den fehlerhaften Problemen werden Verbesserungsvorschläge angezeigt
|
- LMB auf Haken-Icon
|
||||||
- Beide Icons angezeigt
|
- Vorschlag in der Datei
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Badge prüfen
|
Vorschlag verwerfen
|
||||||
<!-- ziel -->
|
|
||||||
Prüft die Einstufung als Kern oder Optional
|
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- Nur kern und optional erlaubt
|
- x-Icon: ✕
|
||||||
- Einstufung muss zum Ziel passen
|
|
||||||
- Falscher Badge wird rot markiert
|
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- Prüfung durchlaufen lassen
|
- Prüfung durchlaufen lassen
|
||||||
- Badge der Karte rot markiert
|
- LMB auf x-Icon
|
||||||
- Vorschlag unter dem Titel angezeigt
|
- Vorschlag verschwunden
|
||||||
<!-- 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
|
|
||||||
|
|||||||
@@ -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
|
Gruppiert Features für ein Projekt
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
|
- Ordner unter daten ist das Projekt
|
||||||
- Alphabetisch sortiert
|
- Alphabetisch sortiert
|
||||||
- Aktives Projekt ist hervorgehoben
|
- Aktives Projekt ist hervorgehoben
|
||||||
|
- Auswahl überlebt das Neuladen
|
||||||
|
- Entfernen nimmt die Features mit
|
||||||
- Verlassen speichert
|
- Verlassen speichert
|
||||||
- Escape bricht ab
|
- Escape bricht ab
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- funktion -->
|
<!-- 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 -->
|
<!-- regeln -->
|
||||||
- Stift-Icon
|
- Stift-Icon
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- RMB auf Projektname
|
- RMB auf Projektname
|
||||||
- Klick auf "Projekt anpassen"
|
- Klick auf "Projekt anpassen"
|
||||||
- Inhalt anpassen
|
- Namen ändern
|
||||||
- Außerhalb klicken
|
- Außerhalb klicken
|
||||||
- Name ist angepasst
|
- Name angepasst
|
||||||
<!-- funktion -->
|
|
||||||
Projekt aufrufen
|
|
||||||
<!-- testen -->
|
|
||||||
- LMB auf Projektname
|
|
||||||
- Features werden angezeigt
|
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Projekt entfernen
|
Projekt entfernen
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- x-Icon
|
- x-Icon
|
||||||
- Features mit entfernen
|
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- RMB auf Projektname
|
- RMB auf Projektname
|
||||||
- Klick auf "Projekt entfernen"
|
- Klick auf "Projekt entfernen"
|
||||||
- Bestätigen im Modal
|
- Bestätigen im Modal
|
||||||
- Projekt entfernt
|
- 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 v-if="aktivesProjekt" class="pruefen">
|
||||||
<span class="knoepfe">
|
<span class="knoepfe">
|
||||||
<button class="icon-knopf" title="Stil prüfen" @click="startePruefung(aktivesProjekt, 'stil')">⌾</button>
|
<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>
|
||||||
<span v-if="pruefungLaeuft" class="fortschritt">{{ pruefungProzent }} %</span>
|
<span v-if="pruefungLaeuft" class="fortschritt">{{ pruefungProzent }} %</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { art, laeuft, prozent, fertig, gesamt, meldung, probleme, schliesse } from '../composables/pruefung.js'
|
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
|
// Nur die erste Zeile — mehrzeilige Stellen liefen hier sonst zu einem Klumpen zusammen
|
||||||
function erste(text) {
|
function erste(text) {
|
||||||
|
|||||||
Reference in New Issue
Block a user