from __future__ import annotations from pathlib import Path import re DIARY_START_MARKER = "" DIARY_END_MARKER = "" BACKFILL_ENTRY_MARKER = "oclaw:dreaming:backfill-entry" def _resolve_dreams_path(workspace_dir: str) -> Path: base = Path(workspace_dir) upper = base / "DREAMS.md" lower = base / "dreams.md" if upper.exists(): return upper if lower.exists(): return lower return upper def _read_text(path: Path) -> str: try: return path.read_text(encoding="utf-8") except FileNotFoundError: return "" def _split_diary_blocks(text: str) -> list[str]: return [b.strip() for b in text.split("\n---\n") if b.strip()] def _ensure_diary_section(existing: str) -> str: if DIARY_START_MARKER in existing and DIARY_END_MARKER in existing: return existing section = f"# Dream Diary\n\n{DIARY_START_MARKER}\n{DIARY_END_MARKER}\n" return section if not existing.strip() else f"{section}\n{existing}" def _replace_diary_content(existing: str, diary_content: str) -> str: ensured = _ensure_diary_section(existing) start_idx = ensured.find(DIARY_START_MARKER) end_idx = ensured.find(DIARY_END_MARKER) if start_idx < 0 or end_idx < 0 or end_idx < start_idx: return ensured before = ensured[: start_idx + len(DIARY_START_MARKER)] after = ensured[end_idx:] middle = f"\n{diary_content.strip()}\n" if diary_content.strip() else "\n" return before + middle + after def _join_diary_blocks(blocks: list[str]) -> str: if not blocks: return "" return "\n".join([f"---\n\n{b.strip()}\n" for b in blocks]).strip() + "\n" def write_backfill_diary_entries(*, workspace_dir: str, entries: list[dict], timezone: str | None = None) -> dict: _ = timezone dreams_path = _resolve_dreams_path(workspace_dir) existing = _read_text(dreams_path) ensured = _ensure_diary_section(existing) start_idx = ensured.find(DIARY_START_MARKER) end_idx = ensured.find(DIARY_END_MARKER) inner = ensured[start_idx + len(DIARY_START_MARKER) : end_idx] if start_idx >= 0 and end_idx > start_idx else "" kept = [b for b in _split_diary_blocks(inner) if BACKFILL_ENTRY_MARKER not in b] replaced = len(_split_diary_blocks(inner)) - len(kept) for entry in entries: iso_day = str(entry.get("isoDay") or "").strip() body_lines = entry.get("bodyLines") or [] source_path = str(entry.get("sourcePath") or "").strip() marker = f"" body = "\n".join(str(x).rstrip() for x in body_lines).strip() block = f"*{iso_day or 'unknown-day'}*\n\n{marker}\n\n{body}".strip() kept.append(block) updated = _replace_diary_content(ensured, _join_diary_blocks(kept)) dreams_path.parent.mkdir(parents=True, exist_ok=True) dreams_path.write_text(updated if updated.endswith("\n") else updated + "\n", encoding="utf-8") return {"dreamsPath": str(dreams_path), "written": len(entries), "replaced": replaced} def remove_backfill_diary_entries(*, workspace_dir: str) -> dict: dreams_path = _resolve_dreams_path(workspace_dir) existing = _read_text(dreams_path) ensured = _ensure_diary_section(existing) start_idx = ensured.find(DIARY_START_MARKER) end_idx = ensured.find(DIARY_END_MARKER) inner = ensured[start_idx + len(DIARY_START_MARKER) : end_idx] if start_idx >= 0 and end_idx > start_idx else "" blocks = _split_diary_blocks(inner) kept = [b for b in blocks if BACKFILL_ENTRY_MARKER not in b] removed = len(blocks) - len(kept) if removed > 0: updated = _replace_diary_content(ensured, _join_diary_blocks(kept)) dreams_path.parent.mkdir(parents=True, exist_ok=True) dreams_path.write_text(updated if updated.endswith("\n") else updated + "\n", encoding="utf-8") return {"dreamsPath": str(dreams_path), "removed": removed} def dedupe_dream_diary_entries(*, workspace_dir: str) -> dict: dreams_path = _resolve_dreams_path(workspace_dir) existing = _read_text(dreams_path) ensured = _ensure_diary_section(existing) start_idx = ensured.find(DIARY_START_MARKER) end_idx = ensured.find(DIARY_END_MARKER) inner = ensured[start_idx + len(DIARY_START_MARKER) : end_idx] if start_idx >= 0 and end_idx > start_idx else "" blocks = _split_diary_blocks(inner) seen: set[str] = set() kept: list[str] = [] for b in blocks: key = "\n".join(line.strip() for line in b.splitlines() if line.strip() and not line.strip().startswith("