Fix: ruff format with CI-pinned version (0.4.x)

This commit is contained in:
eblander
2026-04-13 18:29:48 -04:00
parent 1e86892e62
commit 8dc5970ca9
6 changed files with 53 additions and 175 deletions
+3 -10
View File
@@ -35,13 +35,8 @@ def _fix_blob_seq_ids(palace_path: str):
continue continue
if not rows: if not rows:
continue continue
updates = [ updates = [(int.from_bytes(blob, byteorder="big"), rowid) for rowid, blob in rows]
(int.from_bytes(blob, byteorder="big"), rowid) conn.executemany(f"UPDATE {table} SET seq_id = ? WHERE rowid = ?", updates)
for rowid, blob in rows
]
conn.executemany(
f"UPDATE {table} SET seq_id = ? WHERE rowid = ?", updates
)
logger.info("Fixed %d BLOB seq_ids in %s", len(updates), table) logger.info("Fixed %d BLOB seq_ids in %s", len(updates), table)
conn.commit() conn.commit()
except Exception: except Exception:
@@ -76,9 +71,7 @@ class ChromaCollection(BaseCollection):
class ChromaBackend: class ChromaBackend:
"""Factory for MemPalace's default ChromaDB backend.""" """Factory for MemPalace's default ChromaDB backend."""
def get_collection( def get_collection(self, palace_path: str, collection_name: str, create: bool = False):
self, palace_path: str, collection_name: str, create: bool = False
):
if not create and not os.path.isdir(palace_path): if not create and not os.path.isdir(palace_path):
raise FileNotFoundError(palace_path) raise FileNotFoundError(palace_path)
+18 -68
View File
@@ -48,11 +48,7 @@ def cmd_init(args):
if files: if files:
print(f" Reading {len(files)} files...") print(f" Reading {len(files)} files...")
detected = detect_entities(files) detected = detect_entities(files)
total = ( total = len(detected["people"]) + len(detected["projects"]) + len(detected["uncertain"])
len(detected["people"])
+ len(detected["projects"])
+ len(detected["uncertain"])
)
if total > 0: if total > 0:
confirmed = confirm_entities(detected, yes=getattr(args, "yes", False)) confirmed = confirm_entities(detected, yes=getattr(args, "yes", False))
# Save confirmed entities to <project>/entities.json for the miner # Save confirmed entities to <project>/entities.json for the miner
@@ -70,11 +66,7 @@ def cmd_init(args):
def cmd_mine(args): def cmd_mine(args):
palace_path = ( palace_path = os.path.expanduser(args.palace) if args.palace else MempalaceConfig().palace_path
os.path.expanduser(args.palace)
if args.palace
else MempalaceConfig().palace_path
)
include_ignored = [] include_ignored = []
for raw in args.include_ignored or []: for raw in args.include_ignored or []:
include_ignored.extend(part.strip() for part in raw.split(",") if part.strip()) include_ignored.extend(part.strip() for part in raw.split(",") if part.strip())
@@ -109,11 +101,7 @@ def cmd_mine(args):
def cmd_search(args): def cmd_search(args):
from .searcher import search, SearchError from .searcher import search, SearchError
palace_path = ( palace_path = os.path.expanduser(args.palace) if args.palace else MempalaceConfig().palace_path
os.path.expanduser(args.palace)
if args.palace
else MempalaceConfig().palace_path
)
try: try:
search( search(
query=args.query, query=args.query,
@@ -130,11 +118,7 @@ def cmd_wakeup(args):
"""Show L0 (identity) + L1 (essential story) — the wake-up context.""" """Show L0 (identity) + L1 (essential story) — the wake-up context."""
from .layers import MemoryStack from .layers import MemoryStack
palace_path = ( palace_path = os.path.expanduser(args.palace) if args.palace else MempalaceConfig().palace_path
os.path.expanduser(args.palace)
if args.palace
else MempalaceConfig().palace_path
)
stack = MemoryStack(palace_path=palace_path) stack = MemoryStack(palace_path=palace_path)
text = stack.wake_up(wing=args.wing) text = stack.wake_up(wing=args.wing)
@@ -171,11 +155,7 @@ def cmd_migrate(args):
"""Migrate palace from a different ChromaDB version.""" """Migrate palace from a different ChromaDB version."""
from .migrate import migrate from .migrate import migrate
palace_path = ( palace_path = os.path.expanduser(args.palace) if args.palace else MempalaceConfig().palace_path
os.path.expanduser(args.palace)
if args.palace
else MempalaceConfig().palace_path
)
migrate( migrate(
palace_path=palace_path, palace_path=palace_path,
dry_run=args.dry_run, dry_run=args.dry_run,
@@ -186,11 +166,7 @@ def cmd_migrate(args):
def cmd_status(args): def cmd_status(args):
from .miner import status from .miner import status
palace_path = ( palace_path = os.path.expanduser(args.palace) if args.palace else MempalaceConfig().palace_path
os.path.expanduser(args.palace)
if args.palace
else MempalaceConfig().palace_path
)
status(palace_path=palace_path) status(palace_path=palace_path)
@@ -201,9 +177,7 @@ def cmd_repair(args):
from .migrate import confirm_destructive_action, contains_palace_database from .migrate import confirm_destructive_action, contains_palace_database
palace_path = os.path.abspath( palace_path = os.path.abspath(
os.path.expanduser(args.palace) os.path.expanduser(args.palace) if args.palace else MempalaceConfig().palace_path
if args.palace
else MempalaceConfig().palace_path
) )
db_path = os.path.join(palace_path, "chroma.sqlite3") db_path = os.path.join(palace_path, "chroma.sqlite3")
@@ -247,9 +221,7 @@ def cmd_repair(args):
all_metas = [] all_metas = []
offset = 0 offset = 0
while offset < total: while offset < total:
batch = col.get( batch = col.get(limit=batch_size, offset=offset, include=["documents", "metadatas"])
limit=batch_size, offset=offset, include=["documents", "metadatas"]
)
all_ids.extend(batch["ids"]) all_ids.extend(batch["ids"])
all_docs.extend(batch["documents"]) all_docs.extend(batch["documents"])
all_metas.extend(batch["metadatas"]) all_metas.extend(batch["metadatas"])
@@ -272,9 +244,7 @@ def cmd_repair(args):
print(" Rebuilding collection...") print(" Rebuilding collection...")
client.delete_collection("mempalace_drawers") client.delete_collection("mempalace_drawers")
new_col = client.create_collection( new_col = client.create_collection("mempalace_drawers", metadata={"hnsw:space": "cosine"})
"mempalace_drawers", metadata={"hnsw:space": "cosine"}
)
filed = 0 filed = 0
for i in range(0, len(all_ids), batch_size): for i in range(0, len(all_ids), batch_size):
@@ -321,9 +291,7 @@ def cmd_mcp(args):
if not args.palace: if not args.palace:
print("\nOptional custom palace:") print("\nOptional custom palace:")
print( print(f" claude mcp add mempalace -- {base_server_cmd} --palace /path/to/palace")
f" claude mcp add mempalace -- {base_server_cmd} --palace /path/to/palace"
)
print(f" {base_server_cmd} --palace /path/to/palace") print(f" {base_server_cmd} --palace /path/to/palace")
@@ -332,11 +300,7 @@ def cmd_compress(args):
import chromadb import chromadb
from .dialect import Dialect from .dialect import Dialect
palace_path = ( palace_path = os.path.expanduser(args.palace) if args.palace else MempalaceConfig().palace_path
os.path.expanduser(args.palace)
if args.palace
else MempalaceConfig().palace_path
)
# Load dialect (with optional entity config) # Load dialect (with optional entity config)
config_path = args.config config_path = args.config
@@ -491,9 +455,7 @@ def main():
default="projects", default="projects",
help="Ingest mode: 'projects' for code/docs (default), 'convos' for chat exports", help="Ingest mode: 'projects' for code/docs (default), 'convos' for chat exports",
) )
p_mine.add_argument( p_mine.add_argument("--wing", default=None, help="Wing name (default: directory name)")
"--wing", default=None, help="Wing name (default: directory name)"
)
p_mine.add_argument( p_mine.add_argument(
"--no-gitignore", "--no-gitignore",
action="store_true", action="store_true",
@@ -510,9 +472,7 @@ def main():
default="mempalace", default="mempalace",
help="Your name — recorded on every drawer (default: mempalace)", help="Your name — recorded on every drawer (default: mempalace)",
) )
p_mine.add_argument( p_mine.add_argument("--limit", type=int, default=0, help="Max files to process (0 = all)")
"--limit", type=int, default=0, help="Max files to process (0 = all)"
)
p_mine.add_argument( p_mine.add_argument(
"--dry-run", action="store_true", help="Show what would be filed without filing" "--dry-run", action="store_true", help="Show what would be filed without filing"
) )
@@ -534,9 +494,7 @@ def main():
p_compress = sub.add_parser( p_compress = sub.add_parser(
"compress", help="Compress drawers using AAAK Dialect (~30x reduction)" "compress", help="Compress drawers using AAAK Dialect (~30x reduction)"
) )
p_compress.add_argument( p_compress.add_argument("--wing", default=None, help="Wing to compress (default: all wings)")
"--wing", default=None, help="Wing to compress (default: all wings)"
)
p_compress.add_argument( p_compress.add_argument(
"--dry-run", action="store_true", help="Preview compression without storing" "--dry-run", action="store_true", help="Preview compression without storing"
) )
@@ -545,12 +503,8 @@ def main():
) )
# wake-up # wake-up
p_wakeup = sub.add_parser( p_wakeup = sub.add_parser("wake-up", help="Show L0 + L1 wake-up context (~600-900 tokens)")
"wake-up", help="Show L0 + L1 wake-up context (~600-900 tokens)" p_wakeup.add_argument("--wing", default=None, help="Wake-up for a specific project/wing")
)
p_wakeup.add_argument(
"--wing", default=None, help="Wake-up for a specific project/wing"
)
# split # split
p_split = sub.add_parser( p_split = sub.add_parser(
@@ -602,17 +556,13 @@ def main():
) )
instructions_sub = p_instructions.add_subparsers(dest="instructions_name") instructions_sub = p_instructions.add_subparsers(dest="instructions_name")
for instr_name in ["init", "search", "mine", "help", "status"]: for instr_name in ["init", "search", "mine", "help", "status"]:
instructions_sub.add_parser( instructions_sub.add_parser(instr_name, help=f"Output {instr_name} instructions")
instr_name, help=f"Output {instr_name} instructions"
)
# repair # repair
sub.add_parser( sub.add_parser(
"repair", "repair",
help="Rebuild palace vector index from stored data (fixes segfaults after corruption)", help="Rebuild palace vector index from stored data (fixes segfaults after corruption)",
).add_argument( ).add_argument("--yes", action="store_true", help="Skip confirmation for destructive changes")
"--yes", action="store_true", help="Skip confirmation for destructive changes"
)
# mcp # mcp
sub.add_parser( sub.add_parser(
+2 -6
View File
@@ -97,9 +97,7 @@ def detect_chromadb_version(db_path: str) -> str:
# 0.6.x has embeddings_queue but no schema_str # 0.6.x has embeddings_queue but no schema_str
tables = [ tables = [
r[0] r[0]
for r in conn.execute( for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
"SELECT name FROM sqlite_master WHERE type='table'"
).fetchall()
] ]
if "embeddings_queue" in tables: if "embeddings_queue" in tables:
return "0.6.x" return "0.6.x"
@@ -211,9 +209,7 @@ def migrate(palace_path: str, dry_run: bool = False, confirm: bool = False):
temp_palace = tempfile.mkdtemp(prefix="mempalace_migrate_") temp_palace = tempfile.mkdtemp(prefix="mempalace_migrate_")
print(f" Creating fresh palace in {temp_palace}...") print(f" Creating fresh palace in {temp_palace}...")
client = chromadb.PersistentClient(path=temp_palace) client = chromadb.PersistentClient(path=temp_palace)
col = client.get_or_create_collection( col = client.get_or_create_collection("mempalace_drawers", metadata={"hnsw:space": "cosine"})
"mempalace_drawers", metadata={"hnsw:space": "cosine"}
)
# Re-import in batches # Re-import in batches
batch_size = 500 batch_size = 500
+2 -6
View File
@@ -101,9 +101,7 @@ def config(tmp_dir, palace_path):
def collection(palace_path): def collection(palace_path):
"""A ChromaDB collection pre-seeded in the temp palace.""" """A ChromaDB collection pre-seeded in the temp palace."""
client = chromadb.PersistentClient(path=palace_path) client = chromadb.PersistentClient(path=palace_path)
col = client.get_or_create_collection( col = client.get_or_create_collection("mempalace_drawers", metadata={"hnsw:space": "cosine"})
"mempalace_drawers", metadata={"hnsw:space": "cosine"}
)
yield col yield col
client.delete_collection("mempalace_drawers") client.delete_collection("mempalace_drawers")
del client del client
@@ -187,9 +185,7 @@ def seeded_kg(kg):
kg.add_triple("Alice", "parent_of", "Max", valid_from="2015-04-01") kg.add_triple("Alice", "parent_of", "Max", valid_from="2015-04-01")
kg.add_triple("Max", "does", "swimming", valid_from="2025-01-01") kg.add_triple("Max", "does", "swimming", valid_from="2025-01-01")
kg.add_triple("Max", "does", "chess", valid_from="2024-06-01") kg.add_triple("Max", "does", "chess", valid_from="2024-06-01")
kg.add_triple( kg.add_triple("Alice", "works_at", "Acme Corp", valid_from="2020-01-01", valid_to="2024-12-31")
"Alice", "works_at", "Acme Corp", valid_from="2020-01-01", valid_to="2024-12-31"
)
kg.add_triple("Alice", "works_at", "NewCo", valid_from="2025-01-01") kg.add_triple("Alice", "works_at", "NewCo", valid_from="2025-01-01")
return kg return kg
+23 -70
View File
@@ -32,9 +32,7 @@ def _get_collection(palace_path, create=False):
if create: if create:
return ( return (
client, client,
client.get_or_create_collection( client.get_or_create_collection("mempalace_drawers", metadata={"hnsw:space": "cosine"}),
"mempalace_drawers", metadata={"hnsw:space": "cosine"}
),
) )
return client, client.get_collection("mempalace_drawers") return client, client.get_collection("mempalace_drawers")
@@ -97,9 +95,7 @@ class TestHandleRequest:
def test_notifications_initialized_returns_none(self): def test_notifications_initialized_returns_none(self):
from mempalace.mcp_server import handle_request from mempalace.mcp_server import handle_request
resp = handle_request( resp = handle_request({"method": "notifications/initialized", "id": None, "params": {}})
{"method": "notifications/initialized", "id": None, "params": {}}
)
assert resp is None assert resp is None
def test_ping_returns_empty_result(self): def test_ping_returns_empty_result(self):
@@ -120,9 +116,7 @@ class TestHandleRequest:
assert "mempalace_add_drawer" in names assert "mempalace_add_drawer" in names
assert "mempalace_kg_add" in names assert "mempalace_kg_add" in names
def test_null_arguments_does_not_hang( def test_null_arguments_does_not_hang(self, monkeypatch, config, palace_path, seeded_kg):
self, monkeypatch, config, palace_path, seeded_kg
):
"""Sending arguments: null should return a result, not hang (#394).""" """Sending arguments: null should return a result, not hang (#394)."""
_patch_mcp_server(monkeypatch, config, seeded_kg) _patch_mcp_server(monkeypatch, config, seeded_kg)
from mempalace.mcp_server import handle_request from mempalace.mcp_server import handle_request
@@ -227,9 +221,7 @@ class TestReadTools:
assert result["total_drawers"] == 0 assert result["total_drawers"] == 0
assert result["wings"] == {} assert result["wings"] == {}
def test_status_with_data( def test_status_with_data(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_status from mempalace.mcp_server import tool_status
@@ -246,9 +238,7 @@ class TestReadTools:
assert result["wings"]["project"] == 3 assert result["wings"]["project"] == 3
assert result["wings"]["notes"] == 1 assert result["wings"]["notes"] == 1
def test_list_rooms_all( def test_list_rooms_all(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_list_rooms from mempalace.mcp_server import tool_list_rooms
@@ -257,9 +247,7 @@ class TestReadTools:
assert "frontend" in result["rooms"] assert "frontend" in result["rooms"]
assert "planning" in result["rooms"] assert "planning" in result["rooms"]
def test_list_rooms_filtered( def test_list_rooms_filtered(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_list_rooms from mempalace.mcp_server import tool_list_rooms
@@ -267,9 +255,7 @@ class TestReadTools:
assert "backend" in result["rooms"] assert "backend" in result["rooms"]
assert "planning" not in result["rooms"] assert "planning" not in result["rooms"]
def test_get_taxonomy( def test_get_taxonomy(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_get_taxonomy from mempalace.mcp_server import tool_get_taxonomy
@@ -290,9 +276,7 @@ class TestReadTools:
class TestSearchTool: class TestSearchTool:
def test_search_basic( def test_search_basic(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_search from mempalace.mcp_server import tool_search
@@ -303,18 +287,14 @@ class TestSearchTool:
top = result["results"][0] top = result["results"][0]
assert "JWT" in top["text"] or "authentication" in top["text"].lower() assert "JWT" in top["text"] or "authentication" in top["text"].lower()
def test_search_with_wing_filter( def test_search_with_wing_filter(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_search from mempalace.mcp_server import tool_search
result = tool_search(query="planning", wing="notes") result = tool_search(query="planning", wing="notes")
assert all(r["wing"] == "notes" for r in result["results"]) assert all(r["wing"] == "notes" for r in result["results"])
def test_search_with_room_filter( def test_search_with_room_filter(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_search from mempalace.mcp_server import tool_search
@@ -333,9 +313,7 @@ class TestSearchTool:
assert "results" in result assert "results" in result
# Old name takes precedence when both provided # Old name takes precedence when both provided
result_strict = tool_search( result_strict = tool_search(query="JWT", max_distance=999.0, min_similarity=0.01)
query="JWT", max_distance=999.0, min_similarity=0.01
)
result_loose = tool_search(query="JWT", max_distance=0.01, min_similarity=999.0) result_loose = tool_search(query="JWT", max_distance=0.01, min_similarity=999.0)
assert len(result_strict["results"]) <= len(result_loose["results"]) assert len(result_strict["results"]) <= len(result_loose["results"])
@@ -427,9 +405,7 @@ class TestWriteTools:
assert result2["success"] is True assert result2["success"] is True
assert result2["reason"] == "already_exists" assert result2["reason"] == "already_exists"
def test_add_drawer_shared_header_no_collision( def test_add_drawer_shared_header_no_collision(self, monkeypatch, config, palace_path, kg):
self, monkeypatch, config, palace_path, kg
):
"""Documents sharing a >100-char header must get distinct IDs (full-content hash).""" """Documents sharing a >100-char header must get distinct IDs (full-content hash)."""
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
_client, _col = _get_collection(palace_path, create=True) _client, _col = _get_collection(palace_path, create=True)
@@ -441,10 +417,7 @@ class TestWriteTools:
header header
+ "Decision: Use PostgreSQL for primary storage. Rationale: ACID compliance required." + "Decision: Use PostgreSQL for primary storage. Rationale: ACID compliance required."
) )
doc2 = ( doc2 = header + "Decision: Use Redis for session caching. Rationale: sub-ms latency needed."
header
+ "Decision: Use Redis for session caching. Rationale: sub-ms latency needed."
)
result1 = tool_add_drawer(wing="work", room="decisions", content=doc1) result1 = tool_add_drawer(wing="work", room="decisions", content=doc1)
result2 = tool_add_drawer(wing="work", room="decisions", content=doc2) result2 = tool_add_drawer(wing="work", room="decisions", content=doc2)
@@ -455,9 +428,7 @@ class TestWriteTools:
result1["drawer_id"] != result2["drawer_id"] result1["drawer_id"] != result2["drawer_id"]
), "Documents with shared header but different content must have distinct drawer IDs" ), "Documents with shared header but different content must have distinct drawer IDs"
def test_delete_drawer( def test_delete_drawer(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_delete_drawer from mempalace.mcp_server import tool_delete_drawer
@@ -465,18 +436,14 @@ class TestWriteTools:
assert result["success"] is True assert result["success"] is True
assert seeded_collection.count() == 3 assert seeded_collection.count() == 3
def test_delete_drawer_not_found( def test_delete_drawer_not_found(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_delete_drawer from mempalace.mcp_server import tool_delete_drawer
result = tool_delete_drawer("nonexistent_drawer") result = tool_delete_drawer("nonexistent_drawer")
assert result["success"] is False assert result["success"] is False
def test_check_duplicate( def test_check_duplicate(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_check_duplicate from mempalace.mcp_server import tool_check_duplicate
@@ -505,18 +472,14 @@ class TestWriteTools:
assert result["room"] == "backend" assert result["room"] == "backend"
assert "JWT tokens" in result["content"] assert "JWT tokens" in result["content"]
def test_get_drawer_not_found( def test_get_drawer_not_found(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_get_drawer from mempalace.mcp_server import tool_get_drawer
result = tool_get_drawer("nonexistent_drawer") result = tool_get_drawer("nonexistent_drawer")
assert "error" in result assert "error" in result
def test_list_drawers( def test_list_drawers(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_list_drawers from mempalace.mcp_server import tool_list_drawers
@@ -544,9 +507,7 @@ class TestWriteTools:
assert result["count"] == 2 assert result["count"] == 2
assert all(d["room"] == "backend" for d in result["drawers"]) assert all(d["room"] == "backend" for d in result["drawers"])
def test_list_drawers_pagination( def test_list_drawers_pagination(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_list_drawers from mempalace.mcp_server import tool_list_drawers
@@ -564,9 +525,7 @@ class TestWriteTools:
result = tool_list_drawers(offset=-5) result = tool_list_drawers(offset=-5)
assert result["offset"] == 0 assert result["offset"] == 0
def test_update_drawer_content( def test_update_drawer_content(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_update_drawer, tool_get_drawer from mempalace.mcp_server import tool_update_drawer, tool_get_drawer
@@ -584,25 +543,19 @@ class TestWriteTools:
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_update_drawer from mempalace.mcp_server import tool_update_drawer
result = tool_update_drawer( result = tool_update_drawer("drawer_proj_backend_aaa", wing="new_wing", room="new_room")
"drawer_proj_backend_aaa", wing="new_wing", room="new_room"
)
assert result["success"] is True assert result["success"] is True
assert result["wing"] == "new_wing" assert result["wing"] == "new_wing"
assert result["room"] == "new_room" assert result["room"] == "new_room"
def test_update_drawer_not_found( def test_update_drawer_not_found(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_update_drawer from mempalace.mcp_server import tool_update_drawer
result = tool_update_drawer("nonexistent_drawer", content="hello") result = tool_update_drawer("nonexistent_drawer", content="hello")
assert result["success"] is False assert result["success"] is False
def test_update_drawer_noop( def test_update_drawer_noop(self, monkeypatch, config, palace_path, seeded_collection, kg):
self, monkeypatch, config, palace_path, seeded_collection, kg
):
_patch_mcp_server(monkeypatch, config, kg) _patch_mcp_server(monkeypatch, config, kg)
from mempalace.mcp_server import tool_update_drawer from mempalace.mcp_server import tool_update_drawer
+5 -15
View File
@@ -60,9 +60,7 @@ def test_scan_project_respects_gitignore():
write_file(project_root / ".gitignore", "ignored.py\ngenerated/\n") write_file(project_root / ".gitignore", "ignored.py\ngenerated/\n")
write_file(project_root / "src" / "app.py", "print('hello')\n" * 20) write_file(project_root / "src" / "app.py", "print('hello')\n" * 20)
write_file(project_root / "ignored.py", "print('ignore me')\n" * 20) write_file(project_root / "ignored.py", "print('ignore me')\n" * 20)
write_file( write_file(project_root / "generated" / "artifact.py", "print('artifact')\n" * 20)
project_root / "generated" / "artifact.py", "print('artifact')\n" * 20
)
assert scanned_files(project_root) == ["src/app.py"] assert scanned_files(project_root) == ["src/app.py"]
finally: finally:
@@ -77,9 +75,7 @@ def test_scan_project_respects_nested_gitignore():
write_file(project_root / ".gitignore", "*.log\n") write_file(project_root / ".gitignore", "*.log\n")
write_file(project_root / "subrepo" / ".gitignore", "tasks/\n") write_file(project_root / "subrepo" / ".gitignore", "tasks/\n")
write_file(project_root / "subrepo" / "src" / "main.py", "print('main')\n" * 20) write_file(project_root / "subrepo" / "src" / "main.py", "print('main')\n" * 20)
write_file( write_file(project_root / "subrepo" / "tasks" / "task.py", "print('task')\n" * 20)
project_root / "subrepo" / "tasks" / "task.py", "print('task')\n" * 20
)
write_file(project_root / "subrepo" / "debug.log", "debug\n" * 20) write_file(project_root / "subrepo" / "debug.log", "debug\n" * 20)
assert scanned_files(project_root) == ["subrepo/src/main.py"] assert scanned_files(project_root) == ["subrepo/src/main.py"]
@@ -138,9 +134,7 @@ def test_scan_project_can_disable_gitignore():
write_file(project_root / ".gitignore", "data/\n") write_file(project_root / ".gitignore", "data/\n")
write_file(project_root / "data" / "stuff.csv", "a,b,c\n" * 20) write_file(project_root / "data" / "stuff.csv", "a,b,c\n" * 20)
assert scanned_files(project_root, respect_gitignore=False) == [ assert scanned_files(project_root, respect_gitignore=False) == ["data/stuff.csv"]
"data/stuff.csv"
]
finally: finally:
shutil.rmtree(tmpdir) shutil.rmtree(tmpdir)
@@ -153,9 +147,7 @@ def test_scan_project_can_include_ignored_directory():
write_file(project_root / ".gitignore", "docs/\n") write_file(project_root / ".gitignore", "docs/\n")
write_file(project_root / "docs" / "guide.md", "# Guide\n" * 20) write_file(project_root / "docs" / "guide.md", "# Guide\n" * 20)
assert scanned_files(project_root, include_ignored=["docs"]) == [ assert scanned_files(project_root, include_ignored=["docs"]) == ["docs/guide.md"]
"docs/guide.md"
]
finally: finally:
shutil.rmtree(tmpdir) shutil.rmtree(tmpdir)
@@ -280,9 +272,7 @@ def test_mine_dry_run_with_tiny_file_no_crash():
project_root = Path(tmpdir).resolve() project_root = Path(tmpdir).resolve()
# One normal file and one that falls below MIN_CHUNK_SIZE # One normal file and one that falls below MIN_CHUNK_SIZE
write_file( write_file(project_root / "good.py", "def main():\n print('hello world')\n" * 20)
project_root / "good.py", "def main():\n print('hello world')\n" * 20
)
write_file(project_root / "tiny.txt", "x") write_file(project_root / "tiny.txt", "x")
with open(project_root / "mempalace.yaml", "w") as f: with open(project_root / "mempalace.yaml", "w") as f: