update
This commit is contained in:
@@ -42,11 +42,20 @@ def _safe(norm: str) -> str:
|
||||
|
||||
def _card_set_p(flow: Flow, norm: str):
|
||||
"""Per-card progress: the inner step messages land in-memory on the flow —
|
||||
board_snapshot shows them as the card's info line while it is active."""
|
||||
board_snapshot shows them as the card's info line + phase stepper while active.
|
||||
The step INDEX is resolved to its NAME at write time (indices shift with the
|
||||
source type, names are stable)."""
|
||||
info = flow.state.setdefault("card_info", {})
|
||||
|
||||
def set_p(msg: str, step: int | None = None) -> None:
|
||||
info[f"{BOARD}:{norm}"] = msg
|
||||
name = ""
|
||||
if step is not None:
|
||||
steps = flow.state.get("blocks_steps")
|
||||
if steps is None:
|
||||
steps = flow.state["blocks_steps"] = blocks._blocks_steps(flow.topic)
|
||||
if 0 <= step < len(steps):
|
||||
name = steps[step]
|
||||
info[f"{BOARD}:{norm}"] = {"msg": msg, "step": name}
|
||||
return set_p
|
||||
|
||||
|
||||
@@ -94,28 +103,73 @@ def _fail_or_cancel(ctx: GenContext, what: str):
|
||||
|
||||
|
||||
# ── Stage processors (one call per card, all parallel) ─────────────────────────────
|
||||
async def _seed_map(topic: str) -> dict[str, list[str]]:
|
||||
"""Demoted fragments become seed candidates of their SURVIVING parent block.
|
||||
parent_norm may point at a block that itself got grouped/merged/renamed — follow the
|
||||
redirect chain (grouped → merged_into, rejected → parent_norm, done → mirrored_norm)
|
||||
to the living board-2 card id (= mirrored_norm). A dead end drops the seed (as before)."""
|
||||
alive: set[str] = set()
|
||||
redirect: dict[str, str] = {}
|
||||
rejected: list[dict] = []
|
||||
for r in await db.kanban_cards(topic, board="inventory", kind="block"):
|
||||
p = r["payload"]
|
||||
tn = _norm_title(p.get("title", ""))
|
||||
if not tn:
|
||||
continue
|
||||
if r["stage"] in ("done", "done_block"):
|
||||
mn = p.get("mirrored_norm") or tn
|
||||
alive.add(mn)
|
||||
if tn != mn:
|
||||
redirect.setdefault(tn, mn)
|
||||
elif r["stage"] == "grouped" and p.get("merged_into"):
|
||||
redirect.setdefault(tn, _norm_title(p["merged_into"]))
|
||||
# umbrella members are absorbed WHOLE topics ("Aufgabenlisten" → "Listen") —
|
||||
# without a seed the umbrella's finders may simply miss them (measured).
|
||||
rejected.append({"title": p.get("title", ""), "parent_norm": _norm_title(p["merged_into"])})
|
||||
elif r["stage"] == "rejected":
|
||||
if p.get("parent_norm"):
|
||||
redirect.setdefault(tn, p["parent_norm"])
|
||||
rejected.append(p)
|
||||
|
||||
def _resolve(norm: str) -> str | None:
|
||||
seen: set[str] = set()
|
||||
cur = norm
|
||||
while cur and cur not in seen:
|
||||
if cur in alive: # alive check BEFORE following (self-edges like „Listen"→„Listen")
|
||||
return cur
|
||||
seen.add(cur)
|
||||
cur = redirect.get(cur, "")
|
||||
return None
|
||||
|
||||
seeds: dict[str, list[str]] = {}
|
||||
for p in rejected:
|
||||
pn = p.get("parent_norm")
|
||||
if pn and (target := _resolve(pn)):
|
||||
seeds.setdefault(target, []).append(p.get("title", ""))
|
||||
return seeds
|
||||
|
||||
|
||||
async def _proc_subblocks(ctx: GenContext, flow: Flow, files: dict, instructions: str, cards):
|
||||
topic = flow.topic
|
||||
# Fix 4: fragments demoted to a parent become seed candidates of the parent's subblocks.
|
||||
seeds: dict[str, list[str]] = {}
|
||||
for r in await db.kanban_cards(topic, board="inventory", stage="rejected"):
|
||||
pn = r["payload"].get("parent_norm")
|
||||
if pn:
|
||||
seeds.setdefault(pn, []).append(r["payload"].get("title", ""))
|
||||
seeds = await _seed_map(topic)
|
||||
|
||||
async def one(c):
|
||||
p = c["payload"]
|
||||
norm = c["card_id"]
|
||||
instr = instructions
|
||||
if (sd := [s for s in seeds.get(norm, []) if s]):
|
||||
sd = [s for s in seeds.get(norm, []) if s]
|
||||
if sd:
|
||||
instr = (instructions + "\n\nBereits identifizierte Unterpunkt-Kandidaten dieses "
|
||||
"Blocks (unbedingt prüfen und, wenn belegt, aufnehmen):\n"
|
||||
+ "\n".join(f"- {s}" for s in sd))
|
||||
raw = await _subblocks_block(ctx, _card_set_p(flow, norm), _pfiles(files, norm),
|
||||
{1: _entry_line(p)}, instr, wipe=False, ns=f"{_safe(norm)}-")
|
||||
{1: _entry_line(p)}, instr, wipe=False, ns=f"{_safe(norm)}-",
|
||||
seeds=sd or None, lbl=f"{p.get('title', norm)} · ")
|
||||
if raw is None:
|
||||
return _fail_or_cancel(ctx, f"Subblocks {p.get('title', norm)}")
|
||||
p["raw"] = raw
|
||||
p["subs_n"] = sum(len(v) for v in raw.values()) # LPT: bigger blocks pull first
|
||||
await db.kanban_set_payload(topic, BOARD, norm, p)
|
||||
await db.kanban_advance(topic, BOARD, norm, "facts")
|
||||
|
||||
@@ -131,7 +185,8 @@ async def _proc_facts(ctx: GenContext, flow: Flow, files: dict, q: dict, folder,
|
||||
norm = c["card_id"]
|
||||
raw = p.get("raw") or {}
|
||||
res = await _facts_block(ctx, _card_set_p(flow, norm), _pfiles(files, norm), raw, q,
|
||||
folder, instructions, ns=f"{_safe(norm)}-")
|
||||
folder, instructions, ns=f"{_safe(norm)}-",
|
||||
lbl=f"{p.get('title', norm)} · ")
|
||||
if res is None:
|
||||
return _fail_or_cancel(ctx, f"Facts {p.get('title', norm)}")
|
||||
facts_map, discarded = res
|
||||
@@ -154,7 +209,8 @@ async def _proc_levels(ctx: GenContext, flow: Flow, files: dict, instructions: s
|
||||
p = c["payload"]
|
||||
norm = c["card_id"]
|
||||
sidecar = await _levels_block(ctx, _card_set_p(flow, norm), _pfiles(files, norm),
|
||||
p.get("raw") or {}, instructions, ns=f"{_safe(norm)}-")
|
||||
p.get("raw") or {}, instructions, ns=f"{_safe(norm)}-",
|
||||
lbl=f"{p.get('title', norm)} · ")
|
||||
if sidecar is None:
|
||||
return _fail_or_cancel(ctx, f"Levels {p.get('title', norm)}")
|
||||
facts_map = p.get("facts") or {}
|
||||
@@ -178,7 +234,8 @@ async def _proc_relevance(ctx: GenContext, flow: Flow, files: dict, instructions
|
||||
norm = c["card_id"]
|
||||
sidecar = p.get("sidecar") or {}
|
||||
rel = await _relevance_block(ctx, _card_set_p(flow, norm), _pfiles(files, norm),
|
||||
sidecar, instructions, ns=f"{_safe(norm)}-")
|
||||
sidecar, instructions, ns=f"{_safe(norm)}-",
|
||||
lbl=f"{p.get('title', norm)} · ")
|
||||
if rel is None:
|
||||
return _fail_or_cancel(ctx, f"Relevance {p.get('title', norm)}")
|
||||
gid = 0
|
||||
@@ -201,7 +258,7 @@ async def _proc_question_pattern(ctx: GenContext, flow: Flow, files: dict, instr
|
||||
norm = c["card_id"]
|
||||
pattern = await _question_pattern_block(ctx, _card_set_p(flow, norm), _pfiles(files, norm),
|
||||
p.get("sidecar") or {}, instructions,
|
||||
ns=f"{_safe(norm)}-")
|
||||
ns=f"{_safe(norm)}-", lbl=f"{p.get('title', norm)} · ")
|
||||
if pattern is None:
|
||||
return _fail_or_cancel(ctx, f"Fragen {p.get('title', norm)}")
|
||||
p["pattern"] = pattern
|
||||
@@ -219,7 +276,7 @@ async def _proc_artefacts(ctx: GenContext, flow: Flow, files: dict, instructions
|
||||
norm = c["card_id"]
|
||||
artefacts = await _artefacts_block(ctx, _card_set_p(flow, norm), _pfiles(files, norm),
|
||||
p.get("sidecar") or {}, instructions,
|
||||
ns=f"{_safe(norm)}-")
|
||||
ns=f"{_safe(norm)}-", lbl=f"{p.get('title', norm)} · ")
|
||||
if artefacts is None and ctx.is_cancelled():
|
||||
return None
|
||||
p["artefacts"] = artefacts or {} # artefacts are optional — never fatal
|
||||
@@ -301,7 +358,15 @@ async def _proc_outline(ctx: GenContext, flow: Flow, files: dict, instructions:
|
||||
entries = {i: _entry_line(c["payload"]) for i, c in enumerate(done, 1)
|
||||
if c["payload"].get("title")}
|
||||
if entries:
|
||||
plan = await _outline_block(ctx, _nset, files, entries, instructions)
|
||||
# The outline may run BEFORE finalize has merged the global facts.json — feed the
|
||||
# prereq hints of _learning_order from the card payloads instead (complete as soon
|
||||
# as every block passed the facts stage, which the trimmed barrier guarantees).
|
||||
facts_map: dict = {}
|
||||
for bc in await db.kanban_cards(topic, board=BOARD, kind="ablock"):
|
||||
facts_map.update(bc["payload"].get("facts") or {})
|
||||
fp = flow.work_dir / "outline-facts.json"
|
||||
atomic_write_json(fp, facts_map, indent=1)
|
||||
plan = await _outline_block(ctx, _nset, {**files, "facts": fp}, entries, instructions)
|
||||
if ctx.is_cancelled():
|
||||
return
|
||||
if isinstance(plan, dict) and plan.get("chapters"):
|
||||
|
||||
Reference in New Issue
Block a user