| import json |
| import os |
| import hashlib |
| from datetime import datetime |
| from typing import List, Dict, Any, Optional |
| from pathlib import Path |
|
|
| class JSONLibrary: |
| def __init__(self, base_path: str = "./data/json_library/"): |
| self.base_path = Path(base_path) |
| self.base_path.mkdir(parents=True, exist_ok=True) |
| |
| self.categories = [ |
| "user_preferences", |
| "project_context", |
| "conversation_history", |
| "important_facts", |
| "code_patterns", |
| "learned_skills" |
| ] |
| |
| self._init_categories() |
| |
| def _init_categories(self): |
| for category in self.categories: |
| category_path = self.base_path / f"{category}.json" |
| if not category_path.exists(): |
| with open(category_path, "w") as f: |
| json.dump([], f) |
| |
| def _get_category_path(self, category: str) -> Path: |
| if category not in self.categories: |
| category = "important_facts" |
| return self.base_path / f"{category}.json" |
| |
| MAX_ENTRIES_PER_CATEGORY = 1000 |
|
|
| def store(self, content: Dict[str, Any], category: str = "important_facts") -> str: |
| path = self._get_category_path(category) |
|
|
| with open(path, "r") as f: |
| data = json.load(f) |
|
|
| |
| if len(data) >= self.MAX_ENTRIES_PER_CATEGORY: |
| data = data[-(self.MAX_ENTRIES_PER_CATEGORY - 1):] |
|
|
| memory_id = hashlib.md5(f"{datetime.now().isoformat()}{content}".encode()).hexdigest()[:8] |
|
|
| entry = { |
| "id": memory_id, |
| "timestamp": datetime.now().isoformat(), |
| "content": content, |
| "category": category, |
| "access_count": 0, |
| "last_accessed": None |
| } |
|
|
| data.append(entry) |
|
|
| with open(path, "w") as f: |
| json.dump(data, f, indent=2) |
|
|
| return memory_id |
| |
| def recall(self, query: Optional[str] = None, category: Optional[str] = None, limit: int = 10) -> List[Dict]: |
| cats = [category] if (category and category in self.categories) else self.categories |
|
|
| |
| file_data: Dict[str, list] = {} |
| all_memories = [] |
| for cat in cats: |
| path = self._get_category_path(cat) |
| if path.exists(): |
| with open(path, "r") as f: |
| data = json.load(f) |
| file_data[cat] = data |
| all_memories.extend(data) |
|
|
| all_memories.sort(key=lambda x: x["timestamp"], reverse=True) |
|
|
| if query: |
| query_lower = query.lower() |
| all_memories = [m for m in all_memories if query_lower in json.dumps(m["content"]).lower()] |
|
|
| result = all_memories[:limit] |
|
|
| |
| accessed_ids = {m["id"] for m in result} |
| now = datetime.now().isoformat() |
| for cat, data in file_data.items(): |
| modified = False |
| for entry in data: |
| if entry["id"] in accessed_ids: |
| entry["access_count"] += 1 |
| entry["last_accessed"] = now |
| modified = True |
| if modified: |
| with open(self._get_category_path(cat), "w") as f: |
| json.dump(data, f, indent=2) |
|
|
| return result |
| |
| def get_context_string(self, limit: int = 5) -> str: |
| recent = self.recall(limit=limit) |
| if not recent: |
| return "" |
| |
| context = "\n## SHOREKEEPER Memory\n" |
| context += "The following information should be remembered:\n\n" |
| |
| for mem in recent: |
| content = mem["content"] |
| if isinstance(content, dict): |
| for key, value in content.items(): |
| context += f"- {key}: {value}\n" |
| else: |
| context += f"- {content}\n" |
| |
| return context |
|
|