"""Briefing helpers for specialist handover – DB-backed. Vorher: persistierte als .md-Datei in /.a0proj/handover/. Jetzt: append-only in der Tabelle `orch_briefings` (patterns.db). Kompatible Rückgabe-Schemata, aber statt "path" jetzt "briefing_id". """ from __future__ import annotations import os from typing import Any, Dict, List, Optional from usr.plugins.a0_software_orchestrator.helpers.db_state_store import ( get_project_id, append_briefing, get_briefing, list_briefings, ) from usr.plugins.a0_software_orchestrator.helpers.handover_protocol import is_known_specialist def ensure_handover_dir(project_root: str = "", project_name: str = "") -> Dict[str, Any]: """No-op in DB-mode. Wird für Rückwärtskompatibilität gehalten und gibt statt eines Pfads einfach einen Marker-Dict zurück. """ return {"kind": "db", "project_name": project_name or None} def write_initial_briefing( *, project_name: str = "", project_root: str = "", specialist: str, topic: str, summary: str, decisions: List[str], artifacts: List[str], recent_turns: List[str], return_condition: str, handover_reason: str, ) -> Dict[str, Any]: """Hängt eine neue Briefing-Section an `orch_briefings` an. Returns dict mit: - briefing_id (DB-PK) - project_id - project_name - specialist - topic - created_at - newly_created (immer True – es gibt keine "vorhanden" Files mehr) - existed_before (deprecated, immer False für neue Einträge) - size_bytes (Größe des section_md-Texts in Bytes) """ if not is_known_specialist(specialist): raise ValueError(f"Unknown specialist profile: {specialist!r}") # Projekt-Name ableiten: project_name ist Pflicht (basename-Fallback # entfernt – würde ungültige/Phantom-Namen erzeugen). if not project_name or not project_name.strip(): raise ValueError( "project_name is required (basename-fallback removed; " "pass explicit project_name or registered name)" ) name = project_name.strip() pid = get_project_id(name) if pid is None: raise LookupError(f"project '{name}' is not registered") # Section-Markdown aufbauen (aus handover_protocol) from .handover_protocol import render_briefing_section section_md = render_briefing_section( specialist=specialist, topic=topic, summary=summary, decisions=decisions, artifacts=artifacts, recent_turns=recent_turns, return_condition=return_condition, handover_reason=handover_reason, ) briefing_id = append_briefing( project_id=pid, specialist=specialist, topic=topic, section_md=section_md, summary=summary, decisions=decisions, artifacts=artifacts, recent_turns=recent_turns, return_condition=return_condition, handover_reason=handover_reason, ) return { "briefing_id": briefing_id, "project_id": pid, "project_name": name, "specialist": specialist, "topic": topic, "newly_created": True, "existed_before": False, "size_bytes": len(section_md.encode("utf-8")), } def read_briefing( briefing_id: int, max_bytes: int = 32_000, ) -> str: """Liest den Markdown-Text eines Briefings aus der DB. Returns "" wenn nicht gefunden. Truncated mit Marker wenn zu lang. """ full = get_briefing(int(briefing_id)) if not full: return "" content = full.get("section_md") or "" if len(content.encode("utf-8")) <= max_bytes: return content truncated = content.encode("utf-8")[:max_bytes].decode("utf-8", errors="ignore") return truncated + "\n\n_…[truncated, use a smaller max_bytes or split topic]…_\n" def list_briefings_for( project_name: str = "", project_root: str = "", specialist: Optional[str] = None, limit: int = 50, ) -> List[Dict[str, Any]]: """Listet alle Briefings (latest first) eines Projekts. Komfort-Wrapper um db_state_store.list_briefings mit project_name/project_root-Auflösung. """ # Bug-Fix: basename-Fallback entfernt – würde ungültige/Phantom-Namen # erzeugen. project_name ist Pflicht; ohne Name → leere Liste. if not project_name or not project_name.strip(): return [] name = project_name.strip() pid = get_project_id(name) if pid is None: return [] return list_briefings(pid, specialist=specialist, limit=limit)