refactor: fix ruff bugbear and silent-except findings

- B904: chain OSError/collection errors with "raise ... from e" in
  normalize.py and searcher.py so the original traceback is preserved.
- B007: rename unused loop variables to _name in dedup, dialect, layers,
  and room_detector_local.
- S110/S112: replace bare "try/except/pass" and "try/except/continue"
  with logger.debug(..., exc_info=True) in mcp_server, searcher,
  palace, palace_graph, miner, convo_miner, and fact_checker so
  background failures are observable without changing behaviour.

A module-level logger ("mempalace_mcp", matching mcp_server/searcher)
is added to the five files that didn't already have one. Configured
ruff checks (E/F/W/C901) and ruff --select B, S110, S112 all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Anthony Clendenen
2026-04-23 13:33:38 -07:00
committed by Igor Lins e Silva
parent b68485dfd4
commit ca5899e361
12 changed files with 32 additions and 17 deletions
+4 -1
View File
@@ -11,6 +11,7 @@ Same palace as project mining. Different ingest strategy.
import os import os
import sys import sys
import hashlib import hashlib
import logging
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from collections import defaultdict from collections import defaultdict
@@ -24,6 +25,8 @@ from .palace import (
mine_lock, mine_lock,
) )
logger = logging.getLogger("mempalace_mcp")
# Cached hall keywords — avoids re-reading config per drawer # Cached hall keywords — avoids re-reading config per drawer
_HALL_KEYWORDS_CACHE = None _HALL_KEYWORDS_CACHE = None
@@ -331,7 +334,7 @@ def _file_chunks_locked(collection, source_file, chunks, wing, room, agent, extr
try: try:
collection.delete(where={"source_file": source_file}) collection.delete(where={"source_file": source_file})
except Exception: except Exception:
pass logger.debug("Stale-drawer purge failed for %s", source_file, exc_info=True)
# Batch chunks into bounded upserts so large transcripts keep most of # Batch chunks into bounded upserts so large transcripts keep most of
# the embedding speedup without one huge Chroma/SQLite request. Keep # the embedding speedup without one huge Chroma/SQLite request. Keep
+1 -1
View File
@@ -89,7 +89,7 @@ def dedup_source_group(col, drawer_ids, threshold=DEFAULT_THRESHOLD, dry_run=Tru
kept = [] kept = []
to_delete = [] to_delete = []
for did, doc, meta in items: for did, doc, _meta in items:
if not doc or len(doc) < 20: if not doc or len(doc) < 20:
to_delete.append(did) to_delete.append(did)
continue continue
+1 -1
View File
@@ -873,7 +873,7 @@ class Dialect:
for date_key in sorted(by_date.keys()): for date_key in sorted(by_date.keys()):
lines.append(f"=MOMENTS[{date_key}]=") lines.append(f"=MOMENTS[{date_key}]=")
for z, fnum in by_date[date_key]: for z, _fnum in by_date[date_key]:
entities = [] entities = []
for p in z.get("people", []): for p in z.get("people", []):
code = self.encode_entity(p) code = self.encode_entity(p)
+4
View File
@@ -27,6 +27,7 @@ Usage:
from __future__ import annotations from __future__ import annotations
import logging
import os import os
import re import re
from datetime import datetime, timezone from datetime import datetime, timezone
@@ -35,6 +36,8 @@ from datetime import datetime, timezone
# ~/.mempalace/known_entities.json on every check_text call. # ~/.mempalace/known_entities.json on every check_text call.
from .miner import _load_known_entities_raw from .miner import _load_known_entities_raw
logger = logging.getLogger("mempalace_mcp")
# Narrow detection patterns — parse "X is Y's Z" and "X's Z is Y". # Narrow detection patterns — parse "X is Y's Z" and "X's Z is Y".
# Names are captured greedily as word sequences (letters + optional # Names are captured greedily as word sequences (letters + optional
@@ -214,6 +217,7 @@ def _check_kg_contradictions(text: str, palace_path: str) -> list:
try: try:
facts = kg.query_entity(subject, direction="outgoing") facts = kg.query_entity(subject, direction="outgoing")
except Exception: except Exception:
logger.debug("KG lookup failed for subject %r", subject, exc_info=True)
continue continue
if not facts: if not facts:
continue continue
+1 -1
View File
@@ -157,7 +157,7 @@ class Layer1:
lines.append(room_line) lines.append(room_line)
total_len += len(room_line) total_len += len(room_line)
for imp, meta, doc in entries: for _imp, meta, doc in entries:
source = Path(meta.get("source_file", "")).name if meta.get("source_file") else "" source = Path(meta.get("source_file", "")).name if meta.get("source_file") else ""
# Truncate doc to keep L1 compact # Truncate doc to keep L1 compact
+2 -2
View File
@@ -900,7 +900,7 @@ def tool_add_drawer(
if existing and existing["ids"]: if existing and existing["ids"]:
return {"success": True, "reason": "already_exists", "drawer_id": drawer_id} return {"success": True, "reason": "already_exists", "drawer_id": drawer_id}
except Exception: except Exception:
pass logger.debug("Idempotency pre-check failed for %s", drawer_id, exc_info=True)
try: try:
col.upsert( col.upsert(
@@ -1418,7 +1418,7 @@ def tool_hook_settings(silent_save: bool = None, desktop_toast: bool = None):
try: try:
config = MempalaceConfig() config = MempalaceConfig()
except Exception: except Exception:
pass logger.debug("Could not re-read config after update", exc_info=True)
result = { result = {
"success": True, "success": True,
+4 -1
View File
@@ -12,6 +12,7 @@ import sys
import shlex import shlex
import hashlib import hashlib
import fnmatch import fnmatch
import logging
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from collections import defaultdict from collections import defaultdict
@@ -31,6 +32,8 @@ from .palace import (
upsert_closet_lines, upsert_closet_lines,
) )
logger = logging.getLogger("mempalace_mcp")
READABLE_EXTENSIONS = { READABLE_EXTENSIONS = {
".txt", ".txt",
".md", ".md",
@@ -842,7 +845,7 @@ def process_file(
try: try:
collection.delete(where={"source_file": source_file}) collection.delete(where={"source_file": source_file})
except Exception: except Exception:
pass logger.debug("Stale-drawer purge failed for %s", source_file, exc_info=True)
# Batch chunks into bounded upserts so the embedding model sees many # Batch chunks into bounded upserts so the embedding model sees many
# chunks per forward pass without building one huge Chroma/SQLite # chunks per forward pass without building one huge Chroma/SQLite
+2 -2
View File
@@ -118,14 +118,14 @@ def normalize(filepath: str) -> str:
try: try:
file_size = os.path.getsize(filepath) file_size = os.path.getsize(filepath)
except OSError as e: except OSError as e:
raise IOError(f"Could not read {filepath}: {e}") raise IOError(f"Could not read {filepath}: {e}") from e
if file_size > 500 * 1024 * 1024: # 500 MB safety limit if file_size > 500 * 1024 * 1024: # 500 MB safety limit
raise IOError(f"File too large ({file_size // (1024 * 1024)} MB): {filepath}") raise IOError(f"File too large ({file_size // (1024 * 1024)} MB): {filepath}")
try: try:
with open(filepath, "r", encoding="utf-8", errors="replace") as f: with open(filepath, "r", encoding="utf-8", errors="replace") as f:
content = f.read() content = f.read()
except OSError as e: except OSError as e:
raise IOError(f"Could not read {filepath}: {e}") raise IOError(f"Could not read {filepath}: {e}") from e
if not content.strip(): if not content.strip():
return content return content
+5 -2
View File
@@ -6,12 +6,15 @@ Consolidates collection access patterns used by both miners and the MCP server.
import contextlib import contextlib
import hashlib import hashlib
import logging
import os import os
import re import re
import threading import threading
from .backends.chroma import ChromaBackend from .backends.chroma import ChromaBackend
logger = logging.getLogger("mempalace_mcp")
SKIP_DIRS = { SKIP_DIRS = {
".git", ".git",
"node_modules", "node_modules",
@@ -229,7 +232,7 @@ def purge_file_closets(closets_col, source_file: str) -> None:
try: try:
closets_col.delete(where={"source_file": source_file}) closets_col.delete(where={"source_file": source_file})
except Exception: except Exception:
pass logger.debug("Closet purge failed for %s", source_file, exc_info=True)
def upsert_closet_lines(closets_col, closet_id_base, lines, metadata): def upsert_closet_lines(closets_col, closet_id_base, lines, metadata):
@@ -307,7 +310,7 @@ def mine_lock(source_file: str):
fcntl.flock(lf, fcntl.LOCK_UN) fcntl.flock(lf, fcntl.LOCK_UN)
except Exception: except Exception:
pass logger.debug("Mine-lock release failed", exc_info=True)
lf.close() lf.close()
+1 -1
View File
@@ -575,7 +575,7 @@ def follow_tunnels(wing: str, room: str, col=None, config=None):
if did and did in drawer_map: if did and did in drawer_map:
c["drawer_preview"] = drawer_map[did][:300] c["drawer_preview"] = drawer_map[did][:300]
except Exception: except Exception:
pass logger.debug("Drawer preview hydration failed", exc_info=True)
return connections return connections
+1 -1
View File
@@ -202,7 +202,7 @@ def detect_rooms_from_files(project_dir: str) -> list:
SKIP_DIRS = {".git", "node_modules", "__pycache__", ".venv", "venv", "dist", "build"} SKIP_DIRS = {".git", "node_modules", "__pycache__", ".venv", "venv", "dist", "build"}
for root, dirs, filenames in os.walk(project_path): for _root, dirs, filenames in os.walk(project_path):
dirs[:] = [d for d in dirs if d not in SKIP_DIRS] dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
for filename in filenames: for filename in filenames:
name_lower = filename.lower().replace("-", "_").replace(" ", "_") name_lower = filename.lower().replace("-", "_").replace(" ", "_")
+6 -4
View File
@@ -245,7 +245,7 @@ def _expand_with_neighbors(drawers_col, matched_doc: str, matched_meta: dict, ra
all_meta = drawers_col.get(where={"source_file": src}, include=["metadatas"]) all_meta = drawers_col.get(where={"source_file": src}, include=["metadatas"])
total_drawers = len(all_meta.ids) if all_meta.ids else None total_drawers = len(all_meta.ids) if all_meta.ids else None
except Exception: except Exception:
pass logger.debug("total_drawers lookup failed for %s", src, exc_info=True)
return { return {
"text": combined_text, "text": combined_text,
@@ -297,10 +297,10 @@ def search(query: str, palace_path: str, wing: str = None, room: str = None, n_r
""" """
try: try:
col = get_collection(palace_path, create=False) col = get_collection(palace_path, create=False)
except Exception: except Exception as e:
print(f"\n No palace found at {palace_path}") print(f"\n No palace found at {palace_path}")
print(" Run: mempalace init <dir> then mempalace mine <dir>") print(" Run: mempalace init <dir> then mempalace mine <dir>")
raise SearchError(f"No palace found at {palace_path}") raise SearchError(f"No palace found at {palace_path}") from e
# Alert the user if this palace predates hnsw:space=cosine being set on # Alert the user if this palace predates hnsw:space=cosine being set on
# creation — their similarity scores will be junk until they run repair. # creation — their similarity scores will be junk until they run repair.
@@ -795,7 +795,8 @@ def search_memories(
if source and source not in closet_boost_by_source: if source and source not in closet_boost_by_source:
closet_boost_by_source[source] = (rank, cdist, cdoc[:200]) closet_boost_by_source[source] = (rank, cdist, cdoc[:200])
except Exception: except Exception:
pass # no closets yet — hybrid degrades to pure drawer search # No closets yet — hybrid degrades to pure drawer search.
logger.debug("Closet collection unavailable; using drawer-only search", exc_info=True)
# Rank-based boost. The ordinal signal ("which closet matched best") is # Rank-based boost. The ordinal signal ("which closet matched best") is
# more reliable than absolute distance on narrative content, where # more reliable than absolute distance on narrative content, where
@@ -877,6 +878,7 @@ def search_memories(
include=["documents", "metadatas"], include=["documents", "metadatas"],
) )
except Exception: except Exception:
logger.debug("Neighbor fetch failed for %s", full_source, exc_info=True)
continue continue
docs = source_drawers.documents docs = source_drawers.documents
metas_ = source_drawers.metadatas metas_ = source_drawers.metadatas