This commit is contained in:
team3
2026-07-08 21:14:33 +02:00
parent 9a6ab0937b
commit f9d77a113b
30 changed files with 1064 additions and 654 deletions

View File

@@ -103,12 +103,6 @@ async def board_env(testdb, tmp_path, monkeypatch):
("_artefakte_block", fake_artefakte), ("_outline_block", fake_outline)]:
monkeypatch.setattr(ba, name, fn)
class _EmbOff: # Cross-Block-Barrier reicht ohne Modell alle Karten durch
@staticmethod
def available():
return False
monkeypatch.setattr(ba, "embedding", _EmbOff)
work = tmp_path / "arbeit"
work.mkdir()
files = {"arbeit": work, "final": tmp_path / "blocks.md",
@@ -973,8 +967,8 @@ async def test_qa_gate_auto_stillstand_pauses(board_env, monkeypatch):
async def bad_qa(topic, llm=False):
return {"note": 5.0, "topic": TOPIC, "quoten": {}, "fremd": [], "artefakte": {}}
async def stuck_repair(topic, ebene=None):
return {"note": 5.0} # bewegt nichts
async def stuck_repair(topic, ebene=None, luecken_urteil=True):
return {"note": 5.0, "aktionen": 0} # bewegt nichts
monkeypatch.setattr(qa_mod, "qa_report", bad_qa)
monkeypatch.setattr(qa_mod, "_write_report", lambda r: None)
@@ -988,6 +982,45 @@ async def test_qa_gate_auto_stillstand_pauses(board_env, monkeypatch):
assert await db.kanban_count(TOPIC, "done_artefact", board="artefacts") == 0
async def test_qa_gate_luecken_research_runden(board_env, monkeypatch):
"""Offene Lücken im Auto-Loop: erst LUECKEN_RESEARCH_MAX Recherche-Runden
(Lücken-Urteil ausgesetzt), dann das Urteil — erst danach Stillstand/Pause."""
import qa as qa_mod
import repair as repair_mod
db, ctx, files = board_env
gap_report = {"note": 8.0, "topic": TOPIC, "quoten": {}, "fremd": [], "artefakte": {},
"luecken": [{"datei": "f.txt", "abschnitt": 1, "vorschau": "x"}],
"konzept_luecken": []}
async def bad_qa(topic, llm=False):
return dict(gap_report)
research_calls = []
async def fake_producer(ctx2, flow, rep):
research_calls.append(1)
return 1 # Recherche hat Karten erzeugt → Runde gilt als bewegt
urteile = []
async def stuck_repair(topic, ebene=None, luecken_urteil=True):
urteile.append(luecken_urteil)
return {"note": 8.0, "aktionen": 0}
monkeypatch.setattr(qa_mod, "qa_report", bad_qa)
monkeypatch.setattr(qa_mod, "latest_report", lambda t, guide=False: dict(gap_report))
monkeypatch.setattr(qa_mod, "_write_report", lambda r: None)
monkeypatch.setattr(bi, "_luecken_producer", fake_producer)
monkeypatch.setattr(repair_mod, "repair_befunde", stuck_repair)
monkeypatch.setattr(bi, "_QA_GATE_POLL", 0.05)
await _seed(db)
ok = await _run_flow(ctx, files)
assert ok
assert len(research_calls) == 2 # LUECKEN_RESEARCH_MAX
assert urteile[:2] == [False, False] and urteile[2] is True
assert await db.kanban_count(TOPIC, "done_artefact", board="artefacts") == 0 # pausiert
async def test_qa_gate_auto_reaches_100(board_env, monkeypatch):
"""Auto an, Repair hebt die Note auf 100 % → Gate öffnet, Board 2 läuft durch."""
import qa as qa_mod
@@ -997,8 +1030,8 @@ async def test_qa_gate_auto_reaches_100(board_env, monkeypatch):
async def bad_qa(topic, llm=False):
return {"note": 8.0, "topic": TOPIC, "quoten": {}, "fremd": [], "artefakte": {}}
async def good_repair(topic, ebene=None):
return {"note": 10.0}
async def good_repair(topic, ebene=None, luecken_urteil=True):
return {"note": 10.0, "aktionen": 1}
monkeypatch.setattr(qa_mod, "qa_report", bad_qa)
monkeypatch.setattr(qa_mod, "_write_report", lambda r: None)
@@ -1019,7 +1052,7 @@ async def test_qa_gate_auto_off_pauses(board_env, monkeypatch):
async def good_qa(topic, llm=False):
return {"note": 10.0, "topic": TOPIC, "quoten": {}, "fremd": [], "artefakte": {}}
async def no_repair(topic, ebene=None):
async def no_repair(topic, ebene=None, luecken_urteil=True):
raise AssertionError("Auto aus darf nicht reparieren")
monkeypatch.setattr(qa_mod, "qa_report", good_qa)
@@ -1094,6 +1127,11 @@ def test_qa_view_pausiert_logic(tmp_path, monkeypatch):
counts = {"inventory": {"done_block": 3}, "artefacts": {"generate": 4}}
v = bi._qa_view(TOPIC, counts, None)
assert v["pausiert"] is True and v["note"] == 5.0 and v["befunde"] == ["X", "Y"]
# 9.6 lag über QA_GATE_NOTE, pausiert den Auto-Loop aber trotzdem (Gate öffnet nur bei VOLL)
(tmp_path / TOPIC / "r1.json").write_text(_json.dumps(
{"note": 9.6, "quoten": {}, "fremd": [], "unecht": []}), encoding="utf-8")
qa_mod._latest_cache.clear()
assert bi._qa_view(TOPIC, counts, None)["pausiert"] is True
from types import SimpleNamespace
laufend = SimpleNamespace(state={})
assert bi._qa_view(TOPIC, counts, laufend)["pausiert"] is False # Flow läuft noch
@@ -1515,3 +1553,27 @@ async def test_sanierung_anker_und_beschreibung_ok_kein_judge(testdb, tmp_path,
ctx = GenContext(topic=TOPIC, provider="claude", is_cancelled=lambda: False)
await bi._name_one(ctx, _mk_flow(tmp_path), {"card_id": "c4", "payload": payload})
assert (await db.kanban_get_card(TOPIC, B, "c4"))["stage"] == "naming_check"
def test_themen_zielband_skaliert_mit_atomzahl():
"""√n-Band: kleine Themen deutlich unter 15, große über dem alten 18er-Cluster-Cap."""
lo30, hi30 = bi._themen_zielband(30)
assert lo30 >= 2 and hi30 < 15
lo285, hi285 = bi._themen_zielband(285)
assert hi285 > 18
lo4, hi4 = bi._themen_zielband(4)
assert lo4 >= 2 and hi4 > lo4
# Monotonie: mehr Items → nie kleineres Band
prev = (0, 0)
for n in (4, 30, 100, 285, 1000):
band = bi._themen_zielband(n)
assert band >= prev
prev = band
def test_gruppierung_prompt_rendert_zielband(tmp_path):
"""Template↔Call-Site-Vertrag: alle Platzhalter versorgt, Band-String im Prompt."""
from pipeline import _prompt
p = _prompt("Blocks-Gruppierung", topic="t", candidates="x", list="1. a",
out_path=tmp_path / "g.json", theme_lo=4, theme_hi=8, n_items=9)
assert "~48 themes" in p and "(9 items)" in p