from __future__ import annotations import re from typing import Any from memrelay.errors import AppError class FakeBasicMemory: def __init__(self) -> None: self.notes: dict[str, dict[str, Any]] = {} self.write_calls = 0 self.fail_semantic = False async def health(self) -> dict[str, Any]: return {"status": "ok", "project_count": 1, "url": "memory://fake"} async def write_note( self, *, title: str, content: str, directory: str, tags: list[str], note_type: str, metadata: dict[str, Any], overwrite: bool, ) -> dict[str, Any]: slug = re.sub(r"[^a-z0-9]+", "-", title.casefold()).strip("-") or "memory" permalink = f"{directory}/{slug}" if permalink in self.notes and not overwrite: raise AppError("BASIC_MEMORY_CONFLICT", "note exists", 409) self.write_calls += 1 self.notes[permalink] = { "title": title, "content": content, "tags": tags, "note_type": note_type, "metadata": metadata, "permalink": permalink, } return { "title": title, "permalink": permalink, "action": "updated" if overwrite else "created", } async def read_note(self, identifier: str) -> dict[str, Any]: if identifier not in self.notes: raise AppError("MEMORY_NOT_FOUND", "not found", 404) return self.notes[identifier] async def search_notes( self, query: str, search_type: str = "hybrid", page_size: int = 20 ) -> dict[str, Any]: if self.fail_semantic and search_type in {"hybrid", "vector"}: raise AppError("BASIC_MEMORY_UNAVAILABLE", "semantic unavailable", 503) words = query.casefold().split() results = [] for note in self.notes.values(): haystack = f"{note['title']} {note['content']}".casefold() if any(word in haystack for word in words): results.append( {"title": note["title"], "permalink": note["permalink"], "score": 1.0} ) return {"results": results[:page_size]} async def move_note(self, identifier: str, destination_path: str) -> dict[str, Any]: note = self.notes.pop(identifier) permalink = destination_path.removesuffix(".md") note["permalink"] = permalink self.notes[permalink] = note return {"permalink": permalink} async def delete_note(self, identifier: str) -> None: self.notes.pop(identifier, None)