fix: support partial UUID prefix matching in memory/edge lookups

Closes cal/claude-memory#3. All ID-based lookups now resolve partial
UUID prefixes (git-style) instead of requiring exact full UUIDs.
Affects _resolve_memory_path, _resolve_edge_path, edge_search, and
related(). Ambiguous prefixes raise ValueError with candidates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-02-27 23:57:08 -06:00
parent 5cfb91d65f
commit 471d8709f6

View File

@ -662,11 +662,32 @@ class CognitiveMemoryClient:
content = f"{fm_str}\n\n{body.strip()}\n" if body.strip() else f"{fm_str}\n"
path.write_text(content, encoding="utf-8")
@staticmethod
def _resolve_prefix(partial_id: str, keys) -> Optional[str]:
"""Resolve a partial UUID prefix to a full ID (git-style).
Returns the full key if exactly one match is found, None if zero
matches, or raises ValueError on ambiguous (multiple) matches.
"""
if partial_id in keys:
return partial_id
matches = [k for k in keys if k.startswith(partial_id)]
if len(matches) == 1:
return matches[0]
if len(matches) > 1:
raise ValueError(
f"Ambiguous ID prefix '{partial_id}' matches {len(matches)} "
f"entries: {', '.join(sorted(matches)[:5])}"
)
return None
def _resolve_memory_path(self, memory_id: str) -> Optional[Path]:
"""Find the file path for a memory by ID using the index."""
index = self._load_index()
entry = index.get("entries", {}).get(memory_id)
if entry:
entries = index.get("entries", {})
full_id = self._resolve_prefix(memory_id, entries)
if full_id:
entry = entries[full_id]
path = self.memory_dir / entry["path"]
if path.exists():
return path
@ -834,8 +855,10 @@ class CognitiveMemoryClient:
def _resolve_edge_path(self, edge_id: str) -> Optional[Path]:
"""Find the file path for an edge by ID using the index."""
index = self._load_index()
entry = index.get("edges", {}).get(edge_id)
if entry:
edges = index.get("edges", {})
full_id = self._resolve_prefix(edge_id, edges)
if full_id:
entry = edges[full_id]
path = self.memory_dir / entry["path"]
if path.exists():
return path
@ -1243,6 +1266,16 @@ class CognitiveMemoryClient:
results = []
query_lower = query.lower().strip() if query else ""
# Resolve partial IDs to full UUIDs via prefix match
if from_id:
entries = index.get("entries", {})
resolved = self._resolve_prefix(from_id, entries)
from_id = resolved or from_id
if to_id:
entries = index.get("entries", {})
resolved = self._resolve_prefix(to_id, entries)
to_id = resolved or to_id
for eid, entry in index.get("edges", {}).items():
if types and entry.get("type") not in types:
continue
@ -1532,12 +1565,18 @@ class CognitiveMemoryClient:
visited = set()
results = []
# Resolve partial ID prefix to full UUID
entries = index.get("entries", {})
resolved = self._resolve_prefix(memory_id, entries)
if resolved:
memory_id = resolved
def traverse(mid: str, depth: int):
if depth > max_depth or mid in visited:
return
visited.add(mid)
entry = index.get("entries", {}).get(mid)
entry = entries.get(mid)
if not entry:
return