From adc9a64c8d0459f6ca71850bfed599eeb61e72ea Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Sat, 28 Feb 2026 22:23:01 -0600 Subject: [PATCH] =?UTF-8?q?fix:=20multi-graph=20bugs=20=E2=80=94=20embed?= =?UTF-8?q?=20path,=20git=20sync=20routing,=20dynamic=20project=20detectio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - embeddings.py: embed() early-exit returned hardcoded EMBEDDINGS_PATH constant instead of self.memory_dir path, giving wrong path for non-default graphs - mcp_server.py: _trigger_git_sync() now passes the active graph's memory_dir via COGNITIVE_MEMORY_DIR env var so non-default graphs get synced correctly - analysis.py: replaced hardcoded project name list with auto-detection (tags in 5+ memories with 4+ co-occurring tags) so reflection works for any graph without manual maintenance Co-Authored-By: Claude Opus 4.6 --- analysis.py | 20 ++++++++++++++------ embeddings.py | 3 +-- mcp_server.py | 12 +++++++++--- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/analysis.py b/analysis.py index 3bf1c4e..0d0534a 100644 --- a/analysis.py +++ b/analysis.py @@ -737,13 +737,21 @@ class AnalysisMixin: lines.append("Tags that span multiple projects:") lines.append("") + # Auto-detect project tags: tags with high fanout (appear in 5+ memories + # and co-occur with 4+ distinct other tags) are likely project identifiers + tag_counts: Dict[str, int] = {} + tag_cooccur: Dict[str, set] = {} + for entry in entries.values(): + mem_tags = [t.lower().strip() for t in entry.get("tags", [])] + for t in mem_tags: + tag_counts[t] = tag_counts.get(t, 0) + 1 + if t not in tag_cooccur: + tag_cooccur[t] = set() + tag_cooccur[t].update(tt for tt in mem_tags if tt != t) known_projects = [ - "major-domo", - "paper-dynasty", - "homelab", - "vagabond-rpg", - "foundryvtt", - "strat-gameplay-webapp", + t + for t, count in tag_counts.items() + if count >= 5 and len(tag_cooccur.get(t, set())) >= 4 ] # Build tag -> {project -> count} mapping tag_project_map: Dict[str, Dict[str, int]] = {} diff --git a/embeddings.py b/embeddings.py index 460c4d3..b32a441 100644 --- a/embeddings.py +++ b/embeddings.py @@ -12,7 +12,6 @@ from typing import Any, Dict, List, Optional, Tuple from common import ( EMBEDDING_MODEL, - EMBEDDINGS_PATH, OPENAI_MODEL_DEFAULT, _cosine_similarity, _load_memory_config, @@ -83,7 +82,7 @@ class EmbeddingsMixin: "embedded": 0, "provider": "none", "model": "", - "path": str(EMBEDDINGS_PATH), + "path": str(self.memory_dir / "_embeddings.json"), } # Check for provider change diff --git a/mcp_server.py b/mcp_server.py index e7ad8c7..31e918e 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -52,15 +52,21 @@ def get_client(graph: Optional[str] = None) -> CognitiveMemoryClient: return _clients[key] -def _trigger_git_sync(): +def _trigger_git_sync(memory_dir: Optional[Path] = None): """Fire-and-forget git sync after a write operation.""" if SYNC_SCRIPT.exists(): try: + env = None + if memory_dir is not None: + import os + + env = {**os.environ, "COGNITIVE_MEMORY_DIR": str(memory_dir)} subprocess.Popen( [str(SYNC_SCRIPT)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True, + env=env, ) except Exception: pass # Non-critical — daily timer is the fallback @@ -680,7 +686,7 @@ def handle_tool_call(tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any auto_edges, auto_edge_error = _auto_create_edges( client, memory_id, title, mem_type, tags ) - _trigger_git_sync() + _trigger_git_sync(client.memory_dir) result = { "success": True, "memory_id": memory_id, @@ -723,7 +729,7 @@ def handle_tool_call(tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any strength=arguments.get("strength", 0.8), ) if edge_id: - _trigger_git_sync() + _trigger_git_sync(client.memory_dir) return ok({"success": True, "edge_id": edge_id}) return ok({"success": False, "message": "Relationship already exists"})