146 lines
4.5 KiB
Python
146 lines
4.5 KiB
Python
|
|
"""Briefing helpers for specialist handover – DB-backed.
|
|||
|
|
|
|||
|
|
Vorher: persistierte als .md-Datei in <project_root>/.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)
|