superlocalmemory 2.8.2 → 2.8.3
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/README.md +7 -5
- package/api_server.py +5 -0
- package/bin/slm.bat +3 -3
- package/docs/SECURITY-QUICK-REFERENCE.md +214 -0
- package/install.ps1 +11 -11
- package/mcp_server.py +3 -3
- 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 +34 -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/setup_validator.py
CHANGED
|
@@ -144,7 +144,7 @@ def check_database() -> Tuple[bool, str, List[str]]:
|
|
|
144
144
|
try:
|
|
145
145
|
cursor.execute("SELECT COUNT(*) FROM memories")
|
|
146
146
|
memory_count = cursor.fetchone()[0]
|
|
147
|
-
except:
|
|
147
|
+
except Exception:
|
|
148
148
|
memory_count = 0
|
|
149
149
|
|
|
150
150
|
conn.close()
|
|
@@ -192,163 +192,165 @@ def initialize_database() -> Tuple[bool, str]:
|
|
|
192
192
|
MEMORY_DIR.mkdir(parents=True, exist_ok=True)
|
|
193
193
|
|
|
194
194
|
conn = sqlite3.connect(DB_PATH)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
195
|
+
try:
|
|
196
|
+
cursor = conn.cursor()
|
|
197
|
+
|
|
198
|
+
# Create memories table (core)
|
|
199
|
+
cursor.execute('''
|
|
200
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
201
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
202
|
+
content TEXT NOT NULL,
|
|
203
|
+
summary TEXT,
|
|
204
|
+
project_path TEXT,
|
|
205
|
+
project_name TEXT,
|
|
206
|
+
tags TEXT DEFAULT '[]',
|
|
207
|
+
category TEXT,
|
|
208
|
+
parent_id INTEGER,
|
|
209
|
+
tree_path TEXT DEFAULT '/',
|
|
210
|
+
depth INTEGER DEFAULT 0,
|
|
211
|
+
memory_type TEXT DEFAULT 'session',
|
|
212
|
+
importance INTEGER DEFAULT 5,
|
|
213
|
+
content_hash TEXT,
|
|
214
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
215
|
+
last_accessed TIMESTAMP,
|
|
216
|
+
access_count INTEGER DEFAULT 0,
|
|
217
|
+
compressed_at TIMESTAMP,
|
|
218
|
+
tier INTEGER DEFAULT 1,
|
|
219
|
+
cluster_id INTEGER,
|
|
220
|
+
FOREIGN KEY (parent_id) REFERENCES memories(id)
|
|
221
|
+
)
|
|
222
|
+
''')
|
|
223
|
+
|
|
224
|
+
# Create graph tables
|
|
225
|
+
cursor.execute('''
|
|
226
|
+
CREATE TABLE IF NOT EXISTS graph_nodes (
|
|
227
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
228
|
+
memory_id INTEGER UNIQUE NOT NULL,
|
|
229
|
+
entities TEXT DEFAULT '[]',
|
|
230
|
+
embedding_vector BLOB,
|
|
231
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
232
|
+
FOREIGN KEY (memory_id) REFERENCES memories(id)
|
|
233
|
+
)
|
|
234
|
+
''')
|
|
235
|
+
|
|
236
|
+
cursor.execute('''
|
|
237
|
+
CREATE TABLE IF NOT EXISTS graph_edges (
|
|
238
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
239
|
+
source_memory_id INTEGER NOT NULL,
|
|
240
|
+
target_memory_id INTEGER NOT NULL,
|
|
241
|
+
similarity REAL NOT NULL,
|
|
242
|
+
relationship_type TEXT,
|
|
243
|
+
shared_entities TEXT DEFAULT '[]',
|
|
244
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
245
|
+
FOREIGN KEY (source_memory_id) REFERENCES memories(id),
|
|
246
|
+
FOREIGN KEY (target_memory_id) REFERENCES memories(id),
|
|
247
|
+
UNIQUE(source_memory_id, target_memory_id)
|
|
248
|
+
)
|
|
249
|
+
''')
|
|
250
|
+
|
|
251
|
+
cursor.execute('''
|
|
252
|
+
CREATE TABLE IF NOT EXISTS graph_clusters (
|
|
253
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
254
|
+
cluster_name TEXT,
|
|
255
|
+
name TEXT,
|
|
256
|
+
description TEXT,
|
|
257
|
+
summary TEXT,
|
|
258
|
+
memory_count INTEGER DEFAULT 0,
|
|
259
|
+
member_count INTEGER DEFAULT 0,
|
|
260
|
+
avg_importance REAL DEFAULT 5.0,
|
|
261
|
+
top_entities TEXT DEFAULT '[]',
|
|
262
|
+
parent_cluster_id INTEGER,
|
|
263
|
+
depth INTEGER DEFAULT 0,
|
|
264
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
265
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
266
|
+
FOREIGN KEY (parent_cluster_id) REFERENCES graph_clusters(id) ON DELETE SET NULL
|
|
267
|
+
)
|
|
268
|
+
''')
|
|
269
|
+
|
|
270
|
+
# Create pattern learning tables
|
|
271
|
+
cursor.execute('''
|
|
272
|
+
CREATE TABLE IF NOT EXISTS identity_patterns (
|
|
273
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
274
|
+
pattern_type TEXT NOT NULL,
|
|
275
|
+
pattern_key TEXT NOT NULL,
|
|
276
|
+
pattern_value TEXT,
|
|
277
|
+
confidence REAL DEFAULT 0.0,
|
|
278
|
+
frequency INTEGER DEFAULT 1,
|
|
279
|
+
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
280
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
281
|
+
UNIQUE(pattern_type, pattern_key)
|
|
282
|
+
)
|
|
283
|
+
''')
|
|
284
|
+
|
|
285
|
+
cursor.execute('''
|
|
286
|
+
CREATE TABLE IF NOT EXISTS pattern_examples (
|
|
287
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
288
|
+
pattern_id INTEGER NOT NULL,
|
|
289
|
+
memory_id INTEGER NOT NULL,
|
|
290
|
+
context TEXT,
|
|
291
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
292
|
+
FOREIGN KEY (pattern_id) REFERENCES identity_patterns(id),
|
|
293
|
+
FOREIGN KEY (memory_id) REFERENCES memories(id)
|
|
294
|
+
)
|
|
295
|
+
''')
|
|
296
|
+
|
|
297
|
+
# Create tree table
|
|
298
|
+
cursor.execute('''
|
|
299
|
+
CREATE TABLE IF NOT EXISTS memory_tree (
|
|
300
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
301
|
+
node_type TEXT NOT NULL,
|
|
302
|
+
name TEXT NOT NULL,
|
|
303
|
+
parent_id INTEGER,
|
|
304
|
+
tree_path TEXT DEFAULT '/',
|
|
305
|
+
depth INTEGER DEFAULT 0,
|
|
306
|
+
memory_count INTEGER DEFAULT 0,
|
|
307
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
308
|
+
FOREIGN KEY (parent_id) REFERENCES memory_tree(id)
|
|
309
|
+
)
|
|
310
|
+
''')
|
|
311
|
+
|
|
312
|
+
# Create archive table
|
|
313
|
+
cursor.execute('''
|
|
314
|
+
CREATE TABLE IF NOT EXISTS memory_archive (
|
|
315
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
316
|
+
original_memory_id INTEGER,
|
|
317
|
+
compressed_content TEXT NOT NULL,
|
|
318
|
+
compression_type TEXT DEFAULT 'tier2',
|
|
319
|
+
original_size INTEGER,
|
|
320
|
+
compressed_size INTEGER,
|
|
321
|
+
archived_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
322
|
+
)
|
|
323
|
+
''')
|
|
324
|
+
|
|
325
|
+
# Create system metadata table for watermarking
|
|
326
|
+
cursor.execute('''
|
|
327
|
+
CREATE TABLE IF NOT EXISTS system_metadata (
|
|
328
|
+
key TEXT PRIMARY KEY,
|
|
329
|
+
value TEXT NOT NULL
|
|
330
|
+
)
|
|
331
|
+
''')
|
|
332
|
+
|
|
333
|
+
# Add system watermark
|
|
334
|
+
cursor.execute('''
|
|
335
|
+
INSERT OR REPLACE INTO system_metadata (key, value) VALUES
|
|
336
|
+
('product', 'SuperLocalMemory'),
|
|
337
|
+
('website', 'https://superlocalmemory.com'),
|
|
338
|
+
('repository', 'https://github.com/varun369/SuperLocalMemoryV2'),
|
|
339
|
+
('license', 'MIT'),
|
|
340
|
+
('schema_version', '2.0.0')
|
|
341
|
+
''')
|
|
342
|
+
|
|
343
|
+
# Create indexes for performance
|
|
344
|
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_name)')
|
|
345
|
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_memories_category ON memories(category)')
|
|
346
|
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_memories_cluster ON memories(cluster_id)')
|
|
347
|
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_memories_hash ON memories(content_hash)')
|
|
348
|
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges(source_memory_id)')
|
|
349
|
+
cursor.execute('CREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges(target_memory_id)')
|
|
350
|
+
|
|
351
|
+
conn.commit()
|
|
352
|
+
finally:
|
|
353
|
+
conn.close()
|
|
352
354
|
|
|
353
355
|
return True, "Database initialized successfully"
|
|
354
356
|
|
|
@@ -53,7 +53,7 @@ class SubscriptionManager:
|
|
|
53
53
|
return cls._instances[key]
|
|
54
54
|
|
|
55
55
|
@classmethod
|
|
56
|
-
def reset_instance(cls, db_path: Optional[Path] = None):
|
|
56
|
+
def reset_instance(cls, db_path: Optional[Path] = None) -> None:
|
|
57
57
|
"""Remove singleton. Used for testing."""
|
|
58
58
|
with cls._instances_lock:
|
|
59
59
|
if db_path is None:
|
|
@@ -103,22 +103,24 @@ class SubscriptionManager:
|
|
|
103
103
|
except ImportError:
|
|
104
104
|
import sqlite3
|
|
105
105
|
conn = sqlite3.connect(str(self.db_path))
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
106
|
+
try:
|
|
107
|
+
conn.execute('''
|
|
108
|
+
CREATE TABLE IF NOT EXISTS subscriptions (
|
|
109
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
110
|
+
subscriber_id TEXT NOT NULL UNIQUE,
|
|
111
|
+
channel TEXT NOT NULL,
|
|
112
|
+
filter TEXT NOT NULL DEFAULT '{}',
|
|
113
|
+
webhook_url TEXT,
|
|
114
|
+
durable INTEGER DEFAULT 1,
|
|
115
|
+
last_event_id INTEGER DEFAULT 0,
|
|
116
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
117
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
118
|
+
)
|
|
119
|
+
''')
|
|
120
|
+
conn.execute('CREATE INDEX IF NOT EXISTS idx_subs_channel ON subscriptions(channel)')
|
|
121
|
+
conn.commit()
|
|
122
|
+
finally:
|
|
123
|
+
conn.close()
|
|
122
124
|
|
|
123
125
|
# =========================================================================
|
|
124
126
|
# Subscribe / Unsubscribe
|
|
@@ -244,7 +246,7 @@ class SubscriptionManager:
|
|
|
244
246
|
|
|
245
247
|
return removed
|
|
246
248
|
|
|
247
|
-
def update_last_event_id(self, subscriber_id: str, event_id: int):
|
|
249
|
+
def update_last_event_id(self, subscriber_id: str, event_id: int) -> None:
|
|
248
250
|
"""Update the last event ID received by a durable subscriber (for replay)."""
|
|
249
251
|
try:
|
|
250
252
|
from db_connection_manager import DbConnectionManager
|
package/src/tree/builder.py
CHANGED
|
@@ -22,70 +22,72 @@ class TreeBuilderMixin:
|
|
|
22
22
|
5. Update aggregated counts
|
|
23
23
|
"""
|
|
24
24
|
conn = sqlite3.connect(self.db_path)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
25
|
+
try:
|
|
26
|
+
cursor = conn.cursor()
|
|
27
|
+
|
|
28
|
+
# Clear existing tree (keep root)
|
|
29
|
+
cursor.execute('DELETE FROM memory_tree WHERE node_type != ?', ('root',))
|
|
30
|
+
|
|
31
|
+
# Step 1: Create project nodes
|
|
32
|
+
cursor.execute('''
|
|
33
|
+
SELECT DISTINCT project_path, project_name
|
|
34
|
+
FROM memories
|
|
35
|
+
WHERE project_path IS NOT NULL
|
|
36
|
+
ORDER BY project_path
|
|
37
|
+
''')
|
|
38
|
+
projects = cursor.fetchall()
|
|
39
|
+
|
|
40
|
+
project_map = {} # project_path -> node_id
|
|
41
|
+
|
|
42
|
+
for project_path, project_name in projects:
|
|
43
|
+
name = project_name or project_path.split('/')[-1]
|
|
44
|
+
node_id = self.add_node('project', name, self.root_id, description=project_path)
|
|
45
|
+
project_map[project_path] = node_id
|
|
46
|
+
|
|
47
|
+
# Step 2: Create category nodes within projects
|
|
48
|
+
cursor.execute('''
|
|
49
|
+
SELECT DISTINCT project_path, category
|
|
50
|
+
FROM memories
|
|
51
|
+
WHERE project_path IS NOT NULL AND category IS NOT NULL
|
|
52
|
+
ORDER BY project_path, category
|
|
53
|
+
''')
|
|
54
|
+
categories = cursor.fetchall()
|
|
55
|
+
|
|
56
|
+
category_map = {} # (project_path, category) -> node_id
|
|
57
|
+
|
|
58
|
+
for project_path, category in categories:
|
|
59
|
+
parent_id = project_map.get(project_path)
|
|
60
|
+
if parent_id:
|
|
61
|
+
node_id = self.add_node('category', category, parent_id)
|
|
62
|
+
category_map[(project_path, category)] = node_id
|
|
63
|
+
|
|
64
|
+
# Step 3: Link memories as leaf nodes
|
|
65
|
+
cursor.execute('''
|
|
66
|
+
SELECT id, content, summary, project_path, category, importance, created_at
|
|
67
|
+
FROM memories
|
|
68
|
+
ORDER BY created_at DESC
|
|
69
|
+
''')
|
|
70
|
+
memories = cursor.fetchall()
|
|
71
|
+
|
|
72
|
+
for mem_id, content, summary, project_path, category, importance, created_at in memories:
|
|
73
|
+
# Determine parent node
|
|
74
|
+
if project_path and category and (project_path, category) in category_map:
|
|
75
|
+
parent_id = category_map[(project_path, category)]
|
|
76
|
+
elif project_path and project_path in project_map:
|
|
77
|
+
parent_id = project_map[project_path]
|
|
78
|
+
else:
|
|
79
|
+
parent_id = self.root_id
|
|
80
|
+
|
|
81
|
+
# Create memory node
|
|
82
|
+
name = summary or content[:60].replace('\n', ' ')
|
|
83
|
+
self.add_node('memory', name, parent_id, memory_id=mem_id, description=content[:200])
|
|
84
|
+
|
|
85
|
+
# Step 4: Update aggregated counts
|
|
86
|
+
self._update_all_counts()
|
|
87
|
+
|
|
88
|
+
conn.commit()
|
|
89
|
+
finally:
|
|
90
|
+
conn.close()
|
|
89
91
|
|
|
90
92
|
|
|
91
93
|
def run_cli():
|