This commit is contained in:
team3
2026-07-08 23:46:35 +02:00
parent f9d77a113b
commit 2178c6faf4
15 changed files with 557 additions and 379 deletions

View File

@@ -1272,6 +1272,71 @@ async def _proc_dedup(ctx: GenContext, flow: Flow, cards):
flow.wake.set()
async def _attach_nachzuegler(ctx: GenContext, flow: Flow, rows: list[dict]) -> set[str]:
"""Nachzügler-Atome (Supplement-/Lücken-Welle) zuerst an BESTEHENDE fertige Blöcke
anschließen: das Atom wird Sub des Blocks (Bottom-up-Persistenz) statt Zwergblock
mit einem Sub; die Board-2-Karte des Blocks geht zurück auf generate (Re-Anreicherung,
Resume-Hash ändert sich mit der Sub-Menge). Erstwelle hat keine done_block-Blöcke →
no-op. Ohne Zuordnung bleibt das Atom standalone (echtes neues Konzept).
→ card_ids der angeschlossenen rows."""
topic = flow.topic
done = [c for c in await db.kanban_cards(topic, board=BOARD, kind="block", stage="done_block")
if c["payload"].get("mirrored_norm")]
if not done or not rows:
return set()
work_dir = flow.work_dir
h = _h(*[r["card_id"] for r in rows], "attach")
path = work_dir / f"gruppierung-attach-{h}.json"
ids = set(range(1, len(rows) + 1))
add = _completion_schema(_json_file(path), len(done), ids)
if add is None:
anchors = "\n".join(
f"UMBRELLA {k}: {c['payload'].get('title', '')}{(c['payload'].get('description') or '')[:160]}"
for k, c in enumerate(done))
rest = "\n".join(f"{i}. {_t_text(r)}" for i, r in enumerate(rows, 1))
status, add = await run_single_slot(
ctx, "Gruppierung Anschluss", key=f"blocks-{topic}-gruppierung-attach-{h}",
prompt=_prompt("Blocks-Gruppierung-Completion", topic=topic, umbrellas=anchors,
rest=rest, out_path=path),
role="judge", capabilities="files",
payload=lambda result, p=path: _completion_schema(_json_file(p), len(done), ids),
timeout=_timeout("research_mapping", len(rows)))
if status != OK or add is None:
return set()
attached: set[str] = set()
moves = []
for k, members in (add or []):
c = done[k]
bnorm = c["payload"]["mirrored_norm"]
btitle = c["payload"].get("title", "")
traf = False
for m in members:
if not (1 <= m <= len(rows)) or rows[m - 1]["card_id"] in attached:
continue
r = rows[m - 1]
sn = _norm_title(r["title"])
if not sn:
continue
await db.put_subblock(topic, bnorm, sn, btitle, r["title"], status="consensus")
r["payload"].update(reason="attach", merged_into=btitle)
await db.kanban_set_payload(topic, BOARD, r["card_id"], r["payload"])
moves.append((r["card_id"], "grouped"))
attached.add(r["card_id"])
ch = c["payload"].setdefault("children", [])
if r["title"] not in ch:
ch.append(r["title"])
traf = True
if traf:
await db.kanban_set_payload(topic, BOARD, c["card_id"], c["payload"])
b2 = await db.kanban_get_card(topic, "artefacts", bnorm)
if b2 and b2["stage"] != "generate": # neuen Sub anreichern lassen
await db.kanban_advance(topic, "artefacts", bnorm, "generate")
if moves:
await db.kanban_advance_many(topic, BOARD, moves)
_log(topic, f"Gruppierung: {len(moves)} Nachzügler an bestehende Blöcke angeschlossen")
return attached
def _themen_zielband(n: int) -> tuple[int, int]:
"""Weiches Prompt-Band der Themenzahl: k = GROUP_THEMES_PER_SQRT·√n, ±30 %, min 2.
n ist die Item-Zahl der GROUPING-WELLE — die Supplement-Welle bekommt bewusst ein
@@ -1291,6 +1356,13 @@ async def _proc_grouping(ctx: GenContext, flow: Flow, cards):
"title": c["payload"].get("title", ""),
"description": c["payload"].get("description") or "",
"title_norm": _norm_title(c["payload"].get("title", ""))} for c in cards]
# Nachzügler-Wellen: erst an bestehende fertige Blöcke anschließen (Sub statt Zwergblock)
attached = await _attach_nachzuegler(ctx, flow, rows)
if attached:
rows = [r for r in rows if r["card_id"] not in attached]
if not rows:
flow.wake.set()
return
n = len(rows)
if not (BLOCKS_GRUPPIERUNG_AKTIV and await _emb_ok(flow)) or n < 3:
await db.kanban_advance_many(topic, BOARD, [(r["card_id"], "gap_check") for r in rows])