superlocalmemory 2.8.2 → 2.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ATTRIBUTION.md +1 -1
- package/CHANGELOG.md +17 -0
- package/README.md +7 -5
- package/api_server.py +5 -0
- package/bin/slm +35 -0
- package/bin/slm.bat +3 -3
- package/docs/SECURITY-QUICK-REFERENCE.md +214 -0
- package/install.ps1 +11 -11
- package/mcp_server.py +78 -10
- package/package.json +2 -2
- package/requirements-core.txt +16 -18
- package/requirements-learning.txt +8 -8
- package/requirements.txt +9 -7
- package/scripts/prepack.js +33 -0
- package/scripts/verify-v27.ps1 +301 -0
- package/src/agent_registry.py +32 -28
- package/src/auto_backup.py +12 -6
- package/src/cache_manager.py +2 -2
- package/src/compression/__init__.py +25 -0
- package/src/compression/cli.py +150 -0
- package/src/compression/cold_storage.py +217 -0
- package/src/compression/config.py +72 -0
- package/src/compression/orchestrator.py +133 -0
- package/src/compression/tier2_compressor.py +228 -0
- package/src/compression/tier3_compressor.py +153 -0
- package/src/compression/tier_classifier.py +148 -0
- package/src/db_connection_manager.py +5 -5
- package/src/event_bus.py +24 -22
- package/src/hnsw_index.py +3 -3
- package/src/learning/__init__.py +5 -4
- package/src/learning/adaptive_ranker.py +14 -265
- package/src/learning/bootstrap/__init__.py +69 -0
- package/src/learning/bootstrap/constants.py +93 -0
- package/src/learning/bootstrap/db_queries.py +316 -0
- package/src/learning/bootstrap/sampling.py +82 -0
- package/src/learning/bootstrap/text_utils.py +71 -0
- package/src/learning/cross_project_aggregator.py +58 -57
- package/src/learning/db/__init__.py +40 -0
- package/src/learning/db/constants.py +44 -0
- package/src/learning/db/schema.py +279 -0
- package/src/learning/learning_db.py +15 -234
- package/src/learning/ranking/__init__.py +33 -0
- package/src/learning/ranking/constants.py +84 -0
- package/src/learning/ranking/helpers.py +278 -0
- package/src/learning/source_quality_scorer.py +66 -65
- package/src/learning/synthetic_bootstrap.py +28 -310
- package/src/memory/__init__.py +36 -0
- package/src/memory/cli.py +205 -0
- package/src/memory/constants.py +39 -0
- package/src/memory/helpers.py +28 -0
- package/src/memory/schema.py +166 -0
- package/src/memory-profiles.py +94 -86
- package/src/memory-reset.py +187 -185
- package/src/memory_compression.py +2 -2
- package/src/memory_store_v2.py +44 -354
- package/src/migrate_v1_to_v2.py +11 -10
- package/src/patterns/analyzers.py +104 -100
- package/src/patterns/learner.py +17 -13
- package/src/patterns/scoring.py +25 -21
- package/src/patterns/store.py +40 -38
- package/src/patterns/terminology.py +53 -51
- package/src/provenance_tracker.py +2 -2
- package/src/qualixar_attribution.py +1 -1
- package/src/search/engine.py +16 -14
- package/src/search/index_loader.py +13 -11
- package/src/setup_validator.py +160 -158
- package/src/subscription_manager.py +20 -18
- package/src/tree/builder.py +66 -64
- package/src/tree/nodes.py +103 -97
- package/src/tree/queries.py +142 -137
- package/src/tree/schema.py +46 -42
- package/src/webhook_dispatcher.py +3 -3
- package/ui_server.py +7 -4
package/src/memory_store_v2.py
CHANGED
|
@@ -59,9 +59,19 @@ except ImportError:
|
|
|
59
59
|
import logging
|
|
60
60
|
logger = logging.getLogger(__name__)
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
# Import constants and utilities from memory package
|
|
63
|
+
from memory.constants import (
|
|
64
|
+
MEMORY_DIR, DB_PATH, VECTORS_PATH,
|
|
65
|
+
MAX_CONTENT_SIZE, MAX_SUMMARY_SIZE, MAX_TAG_LENGTH, MAX_TAGS,
|
|
66
|
+
CREATOR_METADATA
|
|
67
|
+
)
|
|
68
|
+
from memory.schema import (
|
|
69
|
+
V2_COLUMNS, V28_MIGRATIONS, V2_INDEXES,
|
|
70
|
+
get_memories_table_sql, get_sessions_table_sql, get_fts_table_sql,
|
|
71
|
+
get_fts_trigger_insert_sql, get_fts_trigger_delete_sql, get_fts_trigger_update_sql,
|
|
72
|
+
get_creator_metadata_table_sql
|
|
73
|
+
)
|
|
74
|
+
from memory.helpers import format_content
|
|
65
75
|
|
|
66
76
|
|
|
67
77
|
class MemoryStoreV2:
|
|
@@ -247,62 +257,11 @@ class MemoryStoreV2:
|
|
|
247
257
|
existing_columns = {row[1] for row in cursor.fetchall()}
|
|
248
258
|
|
|
249
259
|
# Main memories table (V1 compatible + V2 extensions)
|
|
250
|
-
cursor.execute(
|
|
251
|
-
CREATE TABLE IF NOT EXISTS memories (
|
|
252
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
253
|
-
content TEXT NOT NULL,
|
|
254
|
-
summary TEXT,
|
|
255
|
-
|
|
256
|
-
-- Organization
|
|
257
|
-
project_path TEXT,
|
|
258
|
-
project_name TEXT,
|
|
259
|
-
tags TEXT,
|
|
260
|
-
category TEXT,
|
|
261
|
-
|
|
262
|
-
-- Hierarchy (Layer 2 link)
|
|
263
|
-
parent_id INTEGER,
|
|
264
|
-
tree_path TEXT,
|
|
265
|
-
depth INTEGER DEFAULT 0,
|
|
266
|
-
|
|
267
|
-
-- Metadata
|
|
268
|
-
memory_type TEXT DEFAULT 'session',
|
|
269
|
-
importance INTEGER DEFAULT 5,
|
|
270
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
271
|
-
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
272
|
-
last_accessed TIMESTAMP,
|
|
273
|
-
access_count INTEGER DEFAULT 0,
|
|
274
|
-
|
|
275
|
-
-- Deduplication
|
|
276
|
-
content_hash TEXT UNIQUE,
|
|
277
|
-
|
|
278
|
-
-- Graph (Layer 3 link)
|
|
279
|
-
cluster_id INTEGER,
|
|
280
|
-
|
|
281
|
-
FOREIGN KEY (parent_id) REFERENCES memories(id) ON DELETE CASCADE
|
|
282
|
-
)
|
|
283
|
-
''')
|
|
260
|
+
cursor.execute(get_memories_table_sql())
|
|
284
261
|
|
|
285
262
|
# Add missing V2 columns to existing table (migration support)
|
|
286
263
|
# This handles upgrades from very old databases that might be missing columns
|
|
287
|
-
|
|
288
|
-
'summary': 'TEXT',
|
|
289
|
-
'project_path': 'TEXT',
|
|
290
|
-
'project_name': 'TEXT',
|
|
291
|
-
'category': 'TEXT',
|
|
292
|
-
'parent_id': 'INTEGER',
|
|
293
|
-
'tree_path': 'TEXT',
|
|
294
|
-
'depth': 'INTEGER DEFAULT 0',
|
|
295
|
-
'memory_type': 'TEXT DEFAULT "session"',
|
|
296
|
-
'importance': 'INTEGER DEFAULT 5',
|
|
297
|
-
'updated_at': 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
|
|
298
|
-
'last_accessed': 'TIMESTAMP',
|
|
299
|
-
'access_count': 'INTEGER DEFAULT 0',
|
|
300
|
-
'content_hash': 'TEXT',
|
|
301
|
-
'cluster_id': 'INTEGER',
|
|
302
|
-
'profile': 'TEXT DEFAULT "default"'
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
for col_name, col_type in v2_columns.items():
|
|
264
|
+
for col_name, col_type in V2_COLUMNS.items():
|
|
306
265
|
if col_name not in existing_columns:
|
|
307
266
|
try:
|
|
308
267
|
cursor.execute(f'ALTER TABLE memories ADD COLUMN {col_name} {col_type}')
|
|
@@ -311,73 +270,25 @@ class MemoryStoreV2:
|
|
|
311
270
|
pass
|
|
312
271
|
|
|
313
272
|
# v2.8.0 schema migration — lifecycle + access control columns
|
|
314
|
-
|
|
315
|
-
("lifecycle_state", "TEXT DEFAULT 'active'"),
|
|
316
|
-
("lifecycle_updated_at", "TIMESTAMP"),
|
|
317
|
-
("lifecycle_history", "TEXT DEFAULT '[]'"),
|
|
318
|
-
("access_level", "TEXT DEFAULT 'public'"),
|
|
319
|
-
]
|
|
320
|
-
for col_name, col_type in _v28_migrations:
|
|
273
|
+
for col_name, col_type in V28_MIGRATIONS:
|
|
321
274
|
try:
|
|
322
275
|
cursor.execute(f"ALTER TABLE memories ADD COLUMN {col_name} {col_type}")
|
|
323
276
|
except sqlite3.OperationalError:
|
|
324
277
|
pass # Column already exists
|
|
325
278
|
|
|
326
279
|
# Sessions table (V1 compatible)
|
|
327
|
-
cursor.execute(
|
|
328
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
329
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
330
|
-
session_id TEXT UNIQUE,
|
|
331
|
-
project_path TEXT,
|
|
332
|
-
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
333
|
-
ended_at TIMESTAMP,
|
|
334
|
-
summary TEXT
|
|
335
|
-
)
|
|
336
|
-
''')
|
|
280
|
+
cursor.execute(get_sessions_table_sql())
|
|
337
281
|
|
|
338
282
|
# Full-text search index (V1 compatible)
|
|
339
|
-
cursor.execute(
|
|
340
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts
|
|
341
|
-
USING fts5(content, summary, tags, content='memories', content_rowid='id')
|
|
342
|
-
''')
|
|
283
|
+
cursor.execute(get_fts_table_sql())
|
|
343
284
|
|
|
344
285
|
# FTS Triggers (V1 compatible)
|
|
345
|
-
cursor.execute(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
VALUES (new.id, new.content, new.summary, new.tags);
|
|
349
|
-
END
|
|
350
|
-
''')
|
|
351
|
-
|
|
352
|
-
cursor.execute('''
|
|
353
|
-
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
354
|
-
INSERT INTO memories_fts(memories_fts, rowid, content, summary, tags)
|
|
355
|
-
VALUES('delete', old.id, old.content, old.summary, old.tags);
|
|
356
|
-
END
|
|
357
|
-
''')
|
|
358
|
-
|
|
359
|
-
cursor.execute('''
|
|
360
|
-
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
361
|
-
INSERT INTO memories_fts(memories_fts, rowid, content, summary, tags)
|
|
362
|
-
VALUES('delete', old.id, old.content, old.summary, old.tags);
|
|
363
|
-
INSERT INTO memories_fts(rowid, content, summary, tags)
|
|
364
|
-
VALUES (new.id, new.content, new.summary, new.tags);
|
|
365
|
-
END
|
|
366
|
-
''')
|
|
286
|
+
cursor.execute(get_fts_trigger_insert_sql())
|
|
287
|
+
cursor.execute(get_fts_trigger_delete_sql())
|
|
288
|
+
cursor.execute(get_fts_trigger_update_sql())
|
|
367
289
|
|
|
368
290
|
# Create indexes for V2 fields (safe for old databases without V2 columns)
|
|
369
|
-
|
|
370
|
-
('idx_project', 'project_path'),
|
|
371
|
-
('idx_tags', 'tags'),
|
|
372
|
-
('idx_category', 'category'),
|
|
373
|
-
('idx_tree_path', 'tree_path'),
|
|
374
|
-
('idx_cluster', 'cluster_id'),
|
|
375
|
-
('idx_last_accessed', 'last_accessed'),
|
|
376
|
-
('idx_parent_id', 'parent_id'),
|
|
377
|
-
('idx_profile', 'profile')
|
|
378
|
-
]
|
|
379
|
-
|
|
380
|
-
for idx_name, col_name in v2_indexes:
|
|
291
|
+
for idx_name, col_name in V2_INDEXES:
|
|
381
292
|
try:
|
|
382
293
|
cursor.execute(f'CREATE INDEX IF NOT EXISTS {idx_name} ON memories({col_name})')
|
|
383
294
|
except sqlite3.OperationalError:
|
|
@@ -394,31 +305,10 @@ class MemoryStoreV2:
|
|
|
394
305
|
|
|
395
306
|
# Creator Attribution Metadata Table (REQUIRED by MIT License)
|
|
396
307
|
# This table embeds creator information directly in the database
|
|
397
|
-
cursor.execute(
|
|
398
|
-
CREATE TABLE IF NOT EXISTS creator_metadata (
|
|
399
|
-
key TEXT PRIMARY KEY,
|
|
400
|
-
value TEXT NOT NULL,
|
|
401
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
402
|
-
)
|
|
403
|
-
''')
|
|
308
|
+
cursor.execute(get_creator_metadata_table_sql())
|
|
404
309
|
|
|
405
310
|
# Insert creator attribution (embedded in database body)
|
|
406
|
-
|
|
407
|
-
'creator_name': 'Varun Pratap Bhardwaj',
|
|
408
|
-
'creator_role': 'Solution Architect & Original Creator',
|
|
409
|
-
'creator_github': 'varun369',
|
|
410
|
-
'project_name': 'SuperLocalMemory V2',
|
|
411
|
-
'project_url': 'https://github.com/varun369/SuperLocalMemoryV2',
|
|
412
|
-
'license': 'MIT',
|
|
413
|
-
'attribution_required': 'yes',
|
|
414
|
-
'version': '2.5.0',
|
|
415
|
-
'architecture_date': '2026-01-15',
|
|
416
|
-
'release_date': '2026-02-07',
|
|
417
|
-
'signature': 'VBPB-SLM-V2-2026-ARCHITECT',
|
|
418
|
-
'verification_hash': 'sha256:c9f3d1a8b5e2f4c6d8a9b3e7f1c4d6a8b9c3e7f2d5a8c1b4e6f9d2a7c5b8e1'
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
for key, value in creator_data.items():
|
|
311
|
+
for key, value in CREATOR_METADATA.items():
|
|
422
312
|
cursor.execute('''
|
|
423
313
|
INSERT OR IGNORE INTO creator_metadata (key, value)
|
|
424
314
|
VALUES (?, ?)
|
|
@@ -432,12 +322,6 @@ class MemoryStoreV2:
|
|
|
432
322
|
"""Generate hash for deduplication."""
|
|
433
323
|
return hashlib.sha256(content.encode()).hexdigest()[:32]
|
|
434
324
|
|
|
435
|
-
# SECURITY: Input validation limits
|
|
436
|
-
MAX_CONTENT_SIZE = 1_000_000 # 1MB max content
|
|
437
|
-
MAX_SUMMARY_SIZE = 10_000 # 10KB max summary
|
|
438
|
-
MAX_TAG_LENGTH = 50 # 50 chars per tag
|
|
439
|
-
MAX_TAGS = 20 # 20 tags max
|
|
440
|
-
|
|
441
325
|
def add_memory(
|
|
442
326
|
self,
|
|
443
327
|
content: str,
|
|
@@ -478,18 +362,18 @@ class MemoryStoreV2:
|
|
|
478
362
|
if not content:
|
|
479
363
|
raise ValueError("Content cannot be empty")
|
|
480
364
|
|
|
481
|
-
if len(content) >
|
|
482
|
-
raise ValueError(f"Content exceeds maximum size of {
|
|
365
|
+
if len(content) > MAX_CONTENT_SIZE:
|
|
366
|
+
raise ValueError(f"Content exceeds maximum size of {MAX_CONTENT_SIZE} bytes")
|
|
483
367
|
|
|
484
|
-
if summary and len(summary) >
|
|
485
|
-
raise ValueError(f"Summary exceeds maximum size of {
|
|
368
|
+
if summary and len(summary) > MAX_SUMMARY_SIZE:
|
|
369
|
+
raise ValueError(f"Summary exceeds maximum size of {MAX_SUMMARY_SIZE} bytes")
|
|
486
370
|
|
|
487
371
|
if tags:
|
|
488
|
-
if len(tags) >
|
|
489
|
-
raise ValueError(f"Too many tags (max {
|
|
372
|
+
if len(tags) > MAX_TAGS:
|
|
373
|
+
raise ValueError(f"Too many tags (max {MAX_TAGS})")
|
|
490
374
|
for tag in tags:
|
|
491
|
-
if len(tag) >
|
|
492
|
-
raise ValueError(f"Tag '{tag[:20]}...' exceeds max length of {
|
|
375
|
+
if len(tag) > MAX_TAG_LENGTH:
|
|
376
|
+
raise ValueError(f"Tag '{tag[:20]}...' exceeds max length of {MAX_TAG_LENGTH}")
|
|
493
377
|
|
|
494
378
|
if importance < 1 or importance > 10:
|
|
495
379
|
importance = max(1, min(10, importance)) # Clamp to valid range
|
|
@@ -1221,7 +1105,8 @@ class MemoryStoreV2:
|
|
|
1221
1105
|
|
|
1222
1106
|
# Qualixar platform provenance (non-breaking additions)
|
|
1223
1107
|
attribution['platform'] = 'Qualixar'
|
|
1224
|
-
attribution['
|
|
1108
|
+
attribution['website'] = 'https://superlocalmemory.com'
|
|
1109
|
+
attribution['author_website'] = 'https://varunpratap.com'
|
|
1225
1110
|
|
|
1226
1111
|
return attribution
|
|
1227
1112
|
|
|
@@ -1252,214 +1137,19 @@ class MemoryStoreV2:
|
|
|
1252
1137
|
output.append(entry)
|
|
1253
1138
|
char_count += len(entry)
|
|
1254
1139
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1140
|
+
text = ''.join(output)
|
|
1257
1141
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1142
|
+
# Layer 3: Steganographic watermark on text exports
|
|
1143
|
+
try:
|
|
1144
|
+
from qualixar_watermark import encode_watermark
|
|
1145
|
+
text = encode_watermark(text, "slm")
|
|
1146
|
+
except ImportError:
|
|
1147
|
+
pass
|
|
1261
1148
|
|
|
1262
|
-
|
|
1263
|
-
content: Content to format
|
|
1264
|
-
full: If True, always show full content
|
|
1265
|
-
threshold: Max length before truncation (default 5000)
|
|
1266
|
-
preview_len: Preview length when truncating (default 2000)
|
|
1267
|
-
|
|
1268
|
-
Returns:
|
|
1269
|
-
Formatted content string
|
|
1270
|
-
"""
|
|
1271
|
-
if full or len(content) < threshold:
|
|
1272
|
-
return content
|
|
1273
|
-
else:
|
|
1274
|
-
return f"{content[:preview_len]}..."
|
|
1149
|
+
return text
|
|
1275
1150
|
|
|
1276
1151
|
|
|
1277
1152
|
# CLI interface (V1 compatible + V2 extensions)
|
|
1278
1153
|
if __name__ == "__main__":
|
|
1279
|
-
import
|
|
1280
|
-
|
|
1281
|
-
store = MemoryStoreV2()
|
|
1282
|
-
|
|
1283
|
-
if len(sys.argv) < 2:
|
|
1284
|
-
print("MemoryStore V2 CLI")
|
|
1285
|
-
print("\nV1 Compatible Commands:")
|
|
1286
|
-
print(" python memory_store_v2.py add <content> [--project <path>] [--tags tag1,tag2]")
|
|
1287
|
-
print(" python memory_store_v2.py search <query> [--full]")
|
|
1288
|
-
print(" python memory_store_v2.py list [limit] [--full]")
|
|
1289
|
-
print(" python memory_store_v2.py get <id>")
|
|
1290
|
-
print(" python memory_store_v2.py recent [limit] [--full]")
|
|
1291
|
-
print(" python memory_store_v2.py stats")
|
|
1292
|
-
print(" python memory_store_v2.py context <query>")
|
|
1293
|
-
print(" python memory_store_v2.py delete <id>")
|
|
1294
|
-
print("\nV2 Extensions:")
|
|
1295
|
-
print(" python memory_store_v2.py tree [parent_id]")
|
|
1296
|
-
print(" python memory_store_v2.py cluster <cluster_id> [--full]")
|
|
1297
|
-
print("\nOptions:")
|
|
1298
|
-
print(" --full Show complete content (default: smart truncation at 5000 chars)")
|
|
1299
|
-
sys.exit(0)
|
|
1300
|
-
|
|
1301
|
-
command = sys.argv[1]
|
|
1302
|
-
|
|
1303
|
-
if command == "tree":
|
|
1304
|
-
parent_id = int(sys.argv[2]) if len(sys.argv) > 2 else None
|
|
1305
|
-
results = store.get_tree(parent_id)
|
|
1306
|
-
|
|
1307
|
-
if not results:
|
|
1308
|
-
print("No memories in tree.")
|
|
1309
|
-
else:
|
|
1310
|
-
for r in results:
|
|
1311
|
-
indent = " " * r['depth']
|
|
1312
|
-
print(f"{indent}[{r['id']}] {r['content'][:50]}...")
|
|
1313
|
-
if r.get('category'):
|
|
1314
|
-
print(f"{indent} Category: {r['category']}")
|
|
1315
|
-
|
|
1316
|
-
elif command == "cluster" and len(sys.argv) >= 3:
|
|
1317
|
-
cluster_id = int(sys.argv[2])
|
|
1318
|
-
show_full = '--full' in sys.argv
|
|
1319
|
-
results = store.get_by_cluster(cluster_id)
|
|
1320
|
-
|
|
1321
|
-
if not results:
|
|
1322
|
-
print(f"No memories in cluster {cluster_id}.")
|
|
1323
|
-
else:
|
|
1324
|
-
print(f"Cluster {cluster_id} - {len(results)} memories:")
|
|
1325
|
-
for r in results:
|
|
1326
|
-
print(f"\n[{r['id']}] Importance: {r['importance']}")
|
|
1327
|
-
print(f" {format_content(r['content'], full=show_full)}")
|
|
1328
|
-
|
|
1329
|
-
elif command == "stats":
|
|
1330
|
-
stats = store.get_stats()
|
|
1331
|
-
print(json.dumps(stats, indent=2))
|
|
1332
|
-
|
|
1333
|
-
elif command == "add":
|
|
1334
|
-
# Parse content and options
|
|
1335
|
-
if len(sys.argv) < 3:
|
|
1336
|
-
print("Error: Content required")
|
|
1337
|
-
print("Usage: python memory_store_v2.py add <content> [--project <path>] [--tags tag1,tag2]")
|
|
1338
|
-
sys.exit(1)
|
|
1339
|
-
|
|
1340
|
-
content = sys.argv[2]
|
|
1341
|
-
project_path = None
|
|
1342
|
-
tags = []
|
|
1343
|
-
|
|
1344
|
-
i = 3
|
|
1345
|
-
while i < len(sys.argv):
|
|
1346
|
-
if sys.argv[i] == '--project' and i + 1 < len(sys.argv):
|
|
1347
|
-
project_path = sys.argv[i + 1]
|
|
1348
|
-
i += 2
|
|
1349
|
-
elif sys.argv[i] == '--tags' and i + 1 < len(sys.argv):
|
|
1350
|
-
tags = [t.strip() for t in sys.argv[i + 1].split(',')]
|
|
1351
|
-
i += 2
|
|
1352
|
-
else:
|
|
1353
|
-
i += 1
|
|
1354
|
-
|
|
1355
|
-
mem_id = store.add_memory(content, project_path=project_path, tags=tags)
|
|
1356
|
-
print(f"Memory added with ID: {mem_id}")
|
|
1357
|
-
|
|
1358
|
-
elif command == "search":
|
|
1359
|
-
if len(sys.argv) < 3:
|
|
1360
|
-
print("Error: Search query required")
|
|
1361
|
-
print("Usage: python memory_store_v2.py search <query> [--full]")
|
|
1362
|
-
sys.exit(1)
|
|
1363
|
-
|
|
1364
|
-
query = sys.argv[2]
|
|
1365
|
-
show_full = '--full' in sys.argv
|
|
1366
|
-
results = store.search(query, limit=5)
|
|
1367
|
-
|
|
1368
|
-
if not results:
|
|
1369
|
-
print("No results found.")
|
|
1370
|
-
else:
|
|
1371
|
-
for r in results:
|
|
1372
|
-
print(f"\n[{r['id']}] Score: {r['score']:.2f}")
|
|
1373
|
-
if r.get('project_name'):
|
|
1374
|
-
print(f"Project: {r['project_name']}")
|
|
1375
|
-
if r.get('tags'):
|
|
1376
|
-
print(f"Tags: {', '.join(r['tags'])}")
|
|
1377
|
-
print(f"Content: {format_content(r['content'], full=show_full)}")
|
|
1378
|
-
print(f"Created: {r['created_at']}")
|
|
1379
|
-
|
|
1380
|
-
elif command == "recent":
|
|
1381
|
-
show_full = '--full' in sys.argv
|
|
1382
|
-
# Parse limit (skip --full flag)
|
|
1383
|
-
limit = 10
|
|
1384
|
-
for i, arg in enumerate(sys.argv[2:], start=2):
|
|
1385
|
-
if arg != '--full' and arg.isdigit():
|
|
1386
|
-
limit = int(arg)
|
|
1387
|
-
break
|
|
1388
|
-
|
|
1389
|
-
results = store.get_recent(limit)
|
|
1390
|
-
|
|
1391
|
-
if not results:
|
|
1392
|
-
print("No memories found.")
|
|
1393
|
-
else:
|
|
1394
|
-
for r in results:
|
|
1395
|
-
print(f"\n[{r['id']}] {r['created_at']}")
|
|
1396
|
-
if r.get('project_name'):
|
|
1397
|
-
print(f"Project: {r['project_name']}")
|
|
1398
|
-
if r.get('tags'):
|
|
1399
|
-
print(f"Tags: {', '.join(r['tags'])}")
|
|
1400
|
-
print(f"Content: {format_content(r['content'], full=show_full)}")
|
|
1401
|
-
|
|
1402
|
-
elif command == "list":
|
|
1403
|
-
show_full = '--full' in sys.argv
|
|
1404
|
-
# Parse limit (skip --full flag)
|
|
1405
|
-
limit = 10
|
|
1406
|
-
for i, arg in enumerate(sys.argv[2:], start=2):
|
|
1407
|
-
if arg != '--full' and arg.isdigit():
|
|
1408
|
-
limit = int(arg)
|
|
1409
|
-
break
|
|
1410
|
-
|
|
1411
|
-
results = store.get_recent(limit)
|
|
1412
|
-
|
|
1413
|
-
if not results:
|
|
1414
|
-
print("No memories found.")
|
|
1415
|
-
else:
|
|
1416
|
-
for r in results:
|
|
1417
|
-
print(f"[{r['id']}] {format_content(r['content'], full=show_full)}")
|
|
1418
|
-
|
|
1419
|
-
elif command == "get":
|
|
1420
|
-
if len(sys.argv) < 3:
|
|
1421
|
-
print("Error: Memory ID required")
|
|
1422
|
-
print("Usage: python memory_store_v2.py get <id>")
|
|
1423
|
-
sys.exit(1)
|
|
1424
|
-
|
|
1425
|
-
mem_id = int(sys.argv[2])
|
|
1426
|
-
memory = store.get_by_id(mem_id)
|
|
1427
|
-
|
|
1428
|
-
if not memory:
|
|
1429
|
-
print(f"Memory {mem_id} not found.")
|
|
1430
|
-
else:
|
|
1431
|
-
print(f"\nID: {memory['id']}")
|
|
1432
|
-
print(f"Content: {memory['content']}")
|
|
1433
|
-
if memory.get('summary'):
|
|
1434
|
-
print(f"Summary: {memory['summary']}")
|
|
1435
|
-
if memory.get('project_name'):
|
|
1436
|
-
print(f"Project: {memory['project_name']}")
|
|
1437
|
-
if memory.get('tags'):
|
|
1438
|
-
print(f"Tags: {', '.join(memory['tags'])}")
|
|
1439
|
-
print(f"Created: {memory['created_at']}")
|
|
1440
|
-
print(f"Importance: {memory['importance']}")
|
|
1441
|
-
print(f"Access Count: {memory['access_count']}")
|
|
1442
|
-
|
|
1443
|
-
elif command == "context":
|
|
1444
|
-
if len(sys.argv) < 3:
|
|
1445
|
-
print("Error: Query required")
|
|
1446
|
-
print("Usage: python memory_store_v2.py context <query>")
|
|
1447
|
-
sys.exit(1)
|
|
1448
|
-
|
|
1449
|
-
query = sys.argv[2]
|
|
1450
|
-
context = store.export_for_context(query)
|
|
1451
|
-
print(context)
|
|
1452
|
-
|
|
1453
|
-
elif command == "delete":
|
|
1454
|
-
if len(sys.argv) < 3:
|
|
1455
|
-
print("Error: Memory ID required")
|
|
1456
|
-
print("Usage: python memory_store_v2.py delete <id>")
|
|
1457
|
-
sys.exit(1)
|
|
1458
|
-
|
|
1459
|
-
mem_id = int(sys.argv[2])
|
|
1460
|
-
store.delete_memory(mem_id)
|
|
1461
|
-
print(f"Memory {mem_id} deleted.")
|
|
1462
|
-
|
|
1463
|
-
else:
|
|
1464
|
-
print(f"Unknown command: {command}")
|
|
1465
|
-
print("Run without arguments to see available commands.")
|
|
1154
|
+
from memory.cli import run_cli
|
|
1155
|
+
run_cli()
|
package/src/migrate_v1_to_v2.py
CHANGED
|
@@ -25,12 +25,13 @@ from pathlib import Path
|
|
|
25
25
|
from datetime import datetime
|
|
26
26
|
import sys
|
|
27
27
|
import traceback
|
|
28
|
+
from typing import Tuple, Dict, Any
|
|
28
29
|
|
|
29
30
|
DB_PATH = Path.home() / '.claude-memory' / 'memory.db'
|
|
30
31
|
BACKUP_PATH = Path.home() / '.claude-memory' / 'backups' / f'pre-v2-{datetime.now().strftime("%Y%m%d-%H%M%S")}.db'
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
def create_backup():
|
|
34
|
+
def create_backup() -> None:
|
|
34
35
|
"""Create a backup of the database before migration."""
|
|
35
36
|
print("=" * 60)
|
|
36
37
|
print("CREATING BACKUP")
|
|
@@ -49,7 +50,7 @@ def create_backup():
|
|
|
49
50
|
print()
|
|
50
51
|
|
|
51
52
|
|
|
52
|
-
def check_schema_version(conn):
|
|
53
|
+
def check_schema_version(conn: sqlite3.Connection) -> bool:
|
|
53
54
|
"""Check if migration has already been completed."""
|
|
54
55
|
cursor = conn.cursor()
|
|
55
56
|
|
|
@@ -67,7 +68,7 @@ def check_schema_version(conn):
|
|
|
67
68
|
return False
|
|
68
69
|
|
|
69
70
|
|
|
70
|
-
def add_new_columns(conn):
|
|
71
|
+
def add_new_columns(conn: sqlite3.Connection) -> None:
|
|
71
72
|
"""Add new columns to the memories table."""
|
|
72
73
|
print("=" * 60)
|
|
73
74
|
print("ADDING NEW COLUMNS TO MEMORIES TABLE")
|
|
@@ -99,7 +100,7 @@ def add_new_columns(conn):
|
|
|
99
100
|
print()
|
|
100
101
|
|
|
101
102
|
|
|
102
|
-
def create_new_tables(conn):
|
|
103
|
+
def create_new_tables(conn: sqlite3.Connection) -> None:
|
|
103
104
|
"""Create all new tables for V2 architecture."""
|
|
104
105
|
print("=" * 60)
|
|
105
106
|
print("CREATING NEW TABLES")
|
|
@@ -223,7 +224,7 @@ def create_new_tables(conn):
|
|
|
223
224
|
print()
|
|
224
225
|
|
|
225
226
|
|
|
226
|
-
def create_indexes(conn):
|
|
227
|
+
def create_indexes(conn: sqlite3.Connection) -> None:
|
|
227
228
|
"""Create all indexes for performance optimization."""
|
|
228
229
|
print("=" * 60)
|
|
229
230
|
print("CREATING INDEXES")
|
|
@@ -259,7 +260,7 @@ def create_indexes(conn):
|
|
|
259
260
|
print()
|
|
260
261
|
|
|
261
262
|
|
|
262
|
-
def migrate_to_tree_structure(conn):
|
|
263
|
+
def migrate_to_tree_structure(conn: sqlite3.Connection) -> None:
|
|
263
264
|
"""Migrate existing memories to tree structure."""
|
|
264
265
|
print("=" * 60)
|
|
265
266
|
print("MIGRATING MEMORIES TO TREE STRUCTURE")
|
|
@@ -384,7 +385,7 @@ def migrate_to_tree_structure(conn):
|
|
|
384
385
|
print()
|
|
385
386
|
|
|
386
387
|
|
|
387
|
-
def update_metadata(conn):
|
|
388
|
+
def update_metadata(conn: sqlite3.Connection) -> None:
|
|
388
389
|
"""Update system metadata with migration info."""
|
|
389
390
|
print("=" * 60)
|
|
390
391
|
print("UPDATING SYSTEM METADATA")
|
|
@@ -417,7 +418,7 @@ def update_metadata(conn):
|
|
|
417
418
|
print()
|
|
418
419
|
|
|
419
420
|
|
|
420
|
-
def verify_migration(conn):
|
|
421
|
+
def verify_migration(conn: sqlite3.Connection) -> Tuple[bool, Dict[str, Any]]:
|
|
421
422
|
"""Verify that migration completed successfully."""
|
|
422
423
|
print("=" * 60)
|
|
423
424
|
print("VERIFYING MIGRATION")
|
|
@@ -477,7 +478,7 @@ def verify_migration(conn):
|
|
|
477
478
|
return True
|
|
478
479
|
|
|
479
480
|
|
|
480
|
-
def print_summary(conn):
|
|
481
|
+
def print_summary(conn: sqlite3.Connection) -> None:
|
|
481
482
|
"""Print migration summary statistics."""
|
|
482
483
|
print("=" * 60)
|
|
483
484
|
print("MIGRATION SUMMARY")
|
|
@@ -519,7 +520,7 @@ def print_summary(conn):
|
|
|
519
520
|
print()
|
|
520
521
|
|
|
521
522
|
|
|
522
|
-
def migrate():
|
|
523
|
+
def migrate() -> None:
|
|
523
524
|
"""Main migration function."""
|
|
524
525
|
print()
|
|
525
526
|
print("╔" + "═" * 58 + "╗")
|