fix: multi-graph bugs — embed path, git sync routing, dynamic project detection

- 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 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-02-28 22:23:01 -06:00
parent 5312402a7b
commit adc9a64c8d
3 changed files with 24 additions and 11 deletions

View File

@ -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]] = {}

View File

@ -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

View File

@ -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"})