from __future__ import annotations import hashlib import re from pathlib import Path class MemoryStore: def __init__(self, cwd: Path) -> None: self.cwd = cwd.resolve() self.root = Path.home() / ".config" / "cc-slim" / "memory" / self._sanitize_cwd(self.cwd) self.root.mkdir(parents=True, exist_ok=True) self.ensure_structure() def ensure_structure(self) -> None: path = self.path() if not path.exists(): path.write_text(self._default_template(), encoding="utf-8") return existing = path.read_text(encoding="utf-8").strip() if self._is_structured(existing): return migrated = self._default_template(existing) path.write_text(migrated, encoding="utf-8") def append(self, text: str) -> str: content = self.read().rstrip() entry = self._format_scratch_entry(text) updated = f"{content}\n\n{entry}\n" self.path().write_text(updated, encoding="utf-8") return "Scratch Notes" def read(self) -> str: self.ensure_structure() return self.path().read_text(encoding="utf-8").strip() def path(self) -> Path: return self.root / "MEMORY.md" def _default_template(self, scratch_notes: str = "") -> str: scratch = self._format_scratch_entry(scratch_notes) if scratch_notes.strip() else "" template = ( "# Memory\n\n" "## User Memory\n\n" "## Project Memory\n\n" "## Constraints\n\n" "## Consolidated Facts\n\n" "## Scratch Notes\n" ) return f"{template}\n{scratch}\n" if scratch else f"{template}\n" def _format_scratch_entry(self, text: str) -> str: lines = [line.rstrip() for line in text.strip().splitlines() if line.strip()] if not lines: return "" if len(lines) == 1: return f"- {lines[0]}" tail = "\n".join(f" {line}" for line in lines[1:]) return f"- {lines[0]}\n{tail}" def _is_structured(self, text: str) -> bool: required_sections = [ "# Memory", "## User Memory", "## Project Memory", "## Constraints", "## Consolidated Facts", "## Scratch Notes", ] return all(section in text for section in required_sections) def _sanitize_cwd(self, cwd: Path) -> str: text = re.sub(r"[^A-Za-z0-9._-]+", "_", str(cwd)) digest = hashlib.sha1(str(cwd).encode("utf-8")).hexdigest()[:8] return f"{text[:48]}-{digest}"