1717
1818import hashlib
1919import json
20+ import logging
2021import os
2122import threading
2223import time
2728from .palace import get_collection as _get_palace_collection
2829from .palace import mine_lock
2930
31+ logger = logging .getLogger ("mempalace_graph" )
32+
33+
34+ def _normalize_wing (wing : str | None ) -> str | None :
35+ """Normalize a wing name for consistent lookup.
36+
37+ ``init`` stores wing names with hyphens and spaces replaced by underscores
38+ (e.g. ``mempalace_public``). Callers that pass the raw directory name
39+ (``mempalace-public``) would silently miss. This helper aligns the lookup
40+ key with the stored metadata.
41+ """
42+ if wing is None :
43+ return None
44+ return wing .lower ().replace (" " , "_" ).replace ("-" , "_" )
45+
3046# Module-level graph cache with TTL and write-invalidation.
3147# Warm cache serves build_graph() in O(1); invalidate_graph_cache() clears on writes.
3248_graph_cache_lock = threading .Lock ()
@@ -215,15 +231,18 @@ def find_tunnels(wing_a: str = None, wing_b: str = None, col=None, config=None):
215231 """
216232 nodes , edges = build_graph (col , config )
217233
234+ norm_a = _normalize_wing (wing_a )
235+ norm_b = _normalize_wing (wing_b )
236+
218237 tunnels = []
219238 for room , data in nodes .items ():
220239 wings = data ["wings" ]
221240 if len (wings ) < 2 :
222241 continue
223242
224- if wing_a and wing_a not in wings :
243+ if norm_a and norm_a not in wings :
225244 continue
226- if wing_b and wing_b not in wings :
245+ if norm_b and norm_b not in wings :
227246 continue
228247
229248 tunnels .append (
@@ -236,6 +255,15 @@ def find_tunnels(wing_a: str = None, wing_b: str = None, col=None, config=None):
236255 }
237256 )
238257
258+ if not tunnels and (wing_a or wing_b ):
259+ logger .warning (
260+ "No tunnels found for wing filter(s): wing_a=%r (normalized=%r), wing_b=%r (normalized=%r)" ,
261+ wing_a ,
262+ norm_a ,
263+ wing_b ,
264+ norm_b ,
265+ )
266+
239267 tunnels .sort (key = lambda x : - x ["count" ])
240268 return tunnels [:50 ]
241269
@@ -394,6 +422,9 @@ def create_tunnel(
394422 target_wing = _require_name (target_wing , "target_wing" )
395423 target_room = _require_name (target_room , "target_room" )
396424
425+ source_wing = _normalize_wing (source_wing )
426+ target_wing = _normalize_wing (target_wing )
427+
397428 tunnel_id = _canonical_tunnel_id (source_wing , source_room , target_wing , target_room )
398429
399430 tunnel = {
@@ -433,9 +464,13 @@ def list_tunnels(wing: str = None):
433464 Returns tunnels where ``wing`` appears as either source or target
434465 (tunnels are symmetric, so either endpoint is a valid filter match).
435466 """
467+ norm_wing = _normalize_wing (wing )
436468 tunnels = _load_tunnels ()
437- if wing :
438- tunnels = [t for t in tunnels if t ["source" ]["wing" ] == wing or t ["target" ]["wing" ] == wing ]
469+ if norm_wing :
470+ tunnels = [
471+ t for t in tunnels
472+ if t ["source" ]["wing" ] == norm_wing or t ["target" ]["wing" ] == norm_wing
473+ ]
439474 return tunnels
440475
441476
@@ -454,14 +489,15 @@ def follow_tunnels(wing: str, room: str, col=None, config=None):
454489 Given a location (wing/room), finds all tunnels leading from or to it,
455490 and optionally fetches the connected drawer content.
456491 """
492+ norm_wing = _normalize_wing (wing ) or wing
457493 tunnels = _load_tunnels ()
458494 connections = []
459495
460496 for t in tunnels :
461497 src = t ["source" ]
462498 tgt = t ["target" ]
463499
464- if src ["wing" ] == wing and src ["room" ] == room :
500+ if src ["wing" ] == norm_wing and src ["room" ] == room :
465501 connections .append (
466502 {
467503 "direction" : "outgoing" ,
@@ -472,7 +508,7 @@ def follow_tunnels(wing: str, room: str, col=None, config=None):
472508 "tunnel_id" : t ["id" ],
473509 }
474510 )
475- elif tgt ["wing" ] == wing and tgt ["room" ] == room :
511+ elif tgt ["wing" ] == norm_wing and tgt ["room" ] == room :
476512 connections .append (
477513 {
478514 "direction" : "incoming" ,
@@ -484,6 +520,9 @@ def follow_tunnels(wing: str, room: str, col=None, config=None):
484520 }
485521 )
486522
523+ if not connections :
524+ logger .warning ("No explicit tunnels found for %s/%s" , wing , room )
525+
487526 # If we have a collection, fetch drawer content for connected items
488527 if col and connections :
489528 drawer_ids = [c ["drawer_id" ] for c in connections if c .get ("drawer_id" )]
0 commit comments