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.
Files changed (73) hide show
  1. package/ATTRIBUTION.md +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +7 -5
  4. package/api_server.py +5 -0
  5. package/bin/slm +35 -0
  6. package/bin/slm.bat +3 -3
  7. package/docs/SECURITY-QUICK-REFERENCE.md +214 -0
  8. package/install.ps1 +11 -11
  9. package/mcp_server.py +78 -10
  10. package/package.json +2 -2
  11. package/requirements-core.txt +16 -18
  12. package/requirements-learning.txt +8 -8
  13. package/requirements.txt +9 -7
  14. package/scripts/prepack.js +33 -0
  15. package/scripts/verify-v27.ps1 +301 -0
  16. package/src/agent_registry.py +32 -28
  17. package/src/auto_backup.py +12 -6
  18. package/src/cache_manager.py +2 -2
  19. package/src/compression/__init__.py +25 -0
  20. package/src/compression/cli.py +150 -0
  21. package/src/compression/cold_storage.py +217 -0
  22. package/src/compression/config.py +72 -0
  23. package/src/compression/orchestrator.py +133 -0
  24. package/src/compression/tier2_compressor.py +228 -0
  25. package/src/compression/tier3_compressor.py +153 -0
  26. package/src/compression/tier_classifier.py +148 -0
  27. package/src/db_connection_manager.py +5 -5
  28. package/src/event_bus.py +24 -22
  29. package/src/hnsw_index.py +3 -3
  30. package/src/learning/__init__.py +5 -4
  31. package/src/learning/adaptive_ranker.py +14 -265
  32. package/src/learning/bootstrap/__init__.py +69 -0
  33. package/src/learning/bootstrap/constants.py +93 -0
  34. package/src/learning/bootstrap/db_queries.py +316 -0
  35. package/src/learning/bootstrap/sampling.py +82 -0
  36. package/src/learning/bootstrap/text_utils.py +71 -0
  37. package/src/learning/cross_project_aggregator.py +58 -57
  38. package/src/learning/db/__init__.py +40 -0
  39. package/src/learning/db/constants.py +44 -0
  40. package/src/learning/db/schema.py +279 -0
  41. package/src/learning/learning_db.py +15 -234
  42. package/src/learning/ranking/__init__.py +33 -0
  43. package/src/learning/ranking/constants.py +84 -0
  44. package/src/learning/ranking/helpers.py +278 -0
  45. package/src/learning/source_quality_scorer.py +66 -65
  46. package/src/learning/synthetic_bootstrap.py +28 -310
  47. package/src/memory/__init__.py +36 -0
  48. package/src/memory/cli.py +205 -0
  49. package/src/memory/constants.py +39 -0
  50. package/src/memory/helpers.py +28 -0
  51. package/src/memory/schema.py +166 -0
  52. package/src/memory-profiles.py +94 -86
  53. package/src/memory-reset.py +187 -185
  54. package/src/memory_compression.py +2 -2
  55. package/src/memory_store_v2.py +44 -354
  56. package/src/migrate_v1_to_v2.py +11 -10
  57. package/src/patterns/analyzers.py +104 -100
  58. package/src/patterns/learner.py +17 -13
  59. package/src/patterns/scoring.py +25 -21
  60. package/src/patterns/store.py +40 -38
  61. package/src/patterns/terminology.py +53 -51
  62. package/src/provenance_tracker.py +2 -2
  63. package/src/qualixar_attribution.py +1 -1
  64. package/src/search/engine.py +16 -14
  65. package/src/search/index_loader.py +13 -11
  66. package/src/setup_validator.py +160 -158
  67. package/src/subscription_manager.py +20 -18
  68. package/src/tree/builder.py +66 -64
  69. package/src/tree/nodes.py +103 -97
  70. package/src/tree/queries.py +142 -137
  71. package/src/tree/schema.py +46 -42
  72. package/src/webhook_dispatcher.py +3 -3
  73. package/ui_server.py +7 -4
package/src/tree/nodes.py CHANGED
@@ -35,46 +35,48 @@ class TreeNodesMixin:
35
35
  New node ID
36
36
  """
37
37
  conn = sqlite3.connect(self.db_path)
38
- cursor = conn.cursor()
39
-
40
- # Get parent path and depth
41
- cursor.execute('SELECT tree_path, depth FROM memory_tree WHERE id = ?', (parent_id,))
42
- result = cursor.fetchone()
43
-
44
- if not result:
45
- raise ValueError(f"Parent node {parent_id} not found")
46
-
47
- parent_path, parent_depth = result
48
-
49
- # Calculate new node position
50
- depth = parent_depth + 1
51
-
52
- cursor.execute('''
53
- INSERT INTO memory_tree (
54
- node_type, name, description,
55
- parent_id, tree_path, depth,
56
- memory_id, last_updated
57
- )
58
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
59
- ''', (
60
- node_type,
61
- name,
62
- description,
63
- parent_id,
64
- '', # Placeholder, updated below
65
- depth,
66
- memory_id,
67
- datetime.now().isoformat()
68
- ))
69
-
70
- node_id = cursor.lastrowid
71
-
72
- # Update tree_path with actual node_id
73
- tree_path = f"{parent_path}.{node_id}"
74
- cursor.execute('UPDATE memory_tree SET tree_path = ? WHERE id = ?', (tree_path, node_id))
75
-
76
- conn.commit()
77
- conn.close()
38
+ try:
39
+ cursor = conn.cursor()
40
+
41
+ # Get parent path and depth
42
+ cursor.execute('SELECT tree_path, depth FROM memory_tree WHERE id = ?', (parent_id,))
43
+ result = cursor.fetchone()
44
+
45
+ if not result:
46
+ raise ValueError(f"Parent node {parent_id} not found")
47
+
48
+ parent_path, parent_depth = result
49
+
50
+ # Calculate new node position
51
+ depth = parent_depth + 1
52
+
53
+ cursor.execute('''
54
+ INSERT INTO memory_tree (
55
+ node_type, name, description,
56
+ parent_id, tree_path, depth,
57
+ memory_id, last_updated
58
+ )
59
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
60
+ ''', (
61
+ node_type,
62
+ name,
63
+ description,
64
+ parent_id,
65
+ '', # Placeholder, updated below
66
+ depth,
67
+ memory_id,
68
+ datetime.now().isoformat()
69
+ ))
70
+
71
+ node_id = cursor.lastrowid
72
+
73
+ # Update tree_path with actual node_id
74
+ tree_path = f"{parent_path}.{node_id}"
75
+ cursor.execute('UPDATE memory_tree SET tree_path = ? WHERE id = ?', (tree_path, node_id))
76
+
77
+ conn.commit()
78
+ finally:
79
+ conn.close()
78
80
 
79
81
  return node_id
80
82
 
@@ -92,25 +94,26 @@ class TreeNodesMixin:
92
94
  raise ValueError("Cannot delete root node")
93
95
 
94
96
  conn = sqlite3.connect(self.db_path)
95
- cursor = conn.cursor()
97
+ try:
98
+ cursor = conn.cursor()
96
99
 
97
- # Get tree_path
98
- cursor.execute('SELECT tree_path, parent_id FROM memory_tree WHERE id = ?', (node_id,))
99
- result = cursor.fetchone()
100
+ # Get tree_path
101
+ cursor.execute('SELECT tree_path, parent_id FROM memory_tree WHERE id = ?', (node_id,))
102
+ result = cursor.fetchone()
100
103
 
101
- if not result:
102
- conn.close()
103
- return False
104
+ if not result:
105
+ return False
104
106
 
105
- tree_path, parent_id = result
107
+ tree_path, parent_id = result
106
108
 
107
- # Delete node and all descendants (CASCADE handles children)
108
- cursor.execute('DELETE FROM memory_tree WHERE id = ? OR tree_path LIKE ?',
109
- (node_id, f"{tree_path}.%"))
109
+ # Delete node and all descendants (CASCADE handles children)
110
+ cursor.execute('DELETE FROM memory_tree WHERE id = ? OR tree_path LIKE ?',
111
+ (node_id, f"{tree_path}.%"))
110
112
 
111
- deleted = cursor.rowcount > 0
112
- conn.commit()
113
- conn.close()
113
+ deleted = cursor.rowcount > 0
114
+ conn.commit()
115
+ finally:
116
+ conn.close()
114
117
 
115
118
  # Update parent counts
116
119
  if deleted and parent_id:
@@ -127,56 +130,59 @@ class TreeNodesMixin:
127
130
  node_id: Node ID to update
128
131
  """
129
132
  conn = sqlite3.connect(self.db_path)
130
- cursor = conn.cursor()
131
-
132
- # Get all descendant memory nodes
133
- cursor.execute('SELECT tree_path FROM memory_tree WHERE id = ?', (node_id,))
134
- result = cursor.fetchone()
135
-
136
- if not result:
133
+ try:
134
+ cursor = conn.cursor()
135
+
136
+ # Get all descendant memory nodes
137
+ cursor.execute('SELECT tree_path FROM memory_tree WHERE id = ?', (node_id,))
138
+ result = cursor.fetchone()
139
+
140
+ if not result:
141
+ return
142
+
143
+ tree_path = result[0]
144
+
145
+ # Count memories in subtree
146
+ cursor.execute('''
147
+ SELECT COUNT(*), COALESCE(SUM(LENGTH(m.content)), 0)
148
+ FROM memory_tree t
149
+ LEFT JOIN memories m ON t.memory_id = m.id
150
+ WHERE t.tree_path LIKE ? AND t.memory_id IS NOT NULL
151
+ ''', (f"{tree_path}%",))
152
+
153
+ memory_count, total_size = cursor.fetchone()
154
+
155
+ # Update node
156
+ cursor.execute('''
157
+ UPDATE memory_tree
158
+ SET memory_count = ?, total_size = ?, last_updated = ?
159
+ WHERE id = ?
160
+ ''', (memory_count, total_size, datetime.now().isoformat(), node_id))
161
+
162
+ # Update all ancestors
163
+ path_ids = [int(x) for x in tree_path.split('.')]
164
+ for ancestor_id in path_ids[:-1]: # Exclude current node
165
+ self.update_counts(ancestor_id)
166
+
167
+ conn.commit()
168
+ finally:
137
169
  conn.close()
138
- return
139
-
140
- tree_path = result[0]
141
-
142
- # Count memories in subtree
143
- cursor.execute('''
144
- SELECT COUNT(*), COALESCE(SUM(LENGTH(m.content)), 0)
145
- FROM memory_tree t
146
- LEFT JOIN memories m ON t.memory_id = m.id
147
- WHERE t.tree_path LIKE ? AND t.memory_id IS NOT NULL
148
- ''', (f"{tree_path}%",))
149
-
150
- memory_count, total_size = cursor.fetchone()
151
-
152
- # Update node
153
- cursor.execute('''
154
- UPDATE memory_tree
155
- SET memory_count = ?, total_size = ?, last_updated = ?
156
- WHERE id = ?
157
- ''', (memory_count, total_size, datetime.now().isoformat(), node_id))
158
-
159
- # Update all ancestors
160
- path_ids = [int(x) for x in tree_path.split('.')]
161
- for ancestor_id in path_ids[:-1]: # Exclude current node
162
- self.update_counts(ancestor_id)
163
-
164
- conn.commit()
165
- conn.close()
166
170
 
167
171
  def _update_all_counts(self):
168
172
  """Update counts for all nodes (used after build_tree)."""
169
173
  conn = sqlite3.connect(self.db_path)
170
- cursor = conn.cursor()
174
+ try:
175
+ cursor = conn.cursor()
171
176
 
172
- # Get all nodes in reverse depth order (leaves first)
173
- cursor.execute('''
174
- SELECT id FROM memory_tree
175
- ORDER BY depth DESC
176
- ''')
177
+ # Get all nodes in reverse depth order (leaves first)
178
+ cursor.execute('''
179
+ SELECT id FROM memory_tree
180
+ ORDER BY depth DESC
181
+ ''')
177
182
 
178
- node_ids = [row[0] for row in cursor.fetchall()]
179
- conn.close()
183
+ node_ids = [row[0] for row in cursor.fetchall()]
184
+ finally:
185
+ conn.close()
180
186
 
181
187
  # Update each node (will cascade to parents)
182
188
  processed = set()
@@ -28,60 +28,61 @@ class TreeQueriesMixin:
28
28
  Nested dictionary representing tree structure
29
29
  """
30
30
  conn = sqlite3.connect(self.db_path)
31
- cursor = conn.cursor()
31
+ try:
32
+ cursor = conn.cursor()
32
33
 
33
- # Build query
34
- if project_name:
35
- # Find project node
36
- cursor.execute('''
37
- SELECT id, tree_path FROM memory_tree
38
- WHERE node_type = 'project' AND name = ?
39
- ''', (project_name,))
40
- result = cursor.fetchone()
41
-
42
- if not result:
43
- conn.close()
44
- return {'error': f"Project '{project_name}' not found"}
45
-
46
- project_id, project_path = result
47
-
48
- # Get subtree
49
- if max_depth is not None:
34
+ # Build query
35
+ if project_name:
36
+ # Find project node
50
37
  cursor.execute('''
51
- SELECT id, node_type, name, description, parent_id, tree_path,
52
- depth, memory_count, total_size, last_updated, memory_id
53
- FROM memory_tree
54
- WHERE (id = ? OR tree_path LIKE ?) AND depth <= ?
55
- ORDER BY tree_path
56
- ''', (project_id, f"{project_path}.%", max_depth))
38
+ SELECT id, tree_path FROM memory_tree
39
+ WHERE node_type = 'project' AND name = ?
40
+ ''', (project_name,))
41
+ result = cursor.fetchone()
42
+
43
+ if not result:
44
+ return {'error': f"Project '{project_name}' not found"}
45
+
46
+ project_id, project_path = result
47
+
48
+ # Get subtree
49
+ if max_depth is not None:
50
+ cursor.execute('''
51
+ SELECT id, node_type, name, description, parent_id, tree_path,
52
+ depth, memory_count, total_size, last_updated, memory_id
53
+ FROM memory_tree
54
+ WHERE (id = ? OR tree_path LIKE ?) AND depth <= ?
55
+ ORDER BY tree_path
56
+ ''', (project_id, f"{project_path}.%", max_depth))
57
+ else:
58
+ cursor.execute('''
59
+ SELECT id, node_type, name, description, parent_id, tree_path,
60
+ depth, memory_count, total_size, last_updated, memory_id
61
+ FROM memory_tree
62
+ WHERE id = ? OR tree_path LIKE ?
63
+ ORDER BY tree_path
64
+ ''', (project_id, f"{project_path}.%"))
57
65
  else:
58
- cursor.execute('''
59
- SELECT id, node_type, name, description, parent_id, tree_path,
60
- depth, memory_count, total_size, last_updated, memory_id
61
- FROM memory_tree
62
- WHERE id = ? OR tree_path LIKE ?
63
- ORDER BY tree_path
64
- ''', (project_id, f"{project_path}.%"))
65
- else:
66
- # Get entire tree
67
- if max_depth is not None:
68
- cursor.execute('''
69
- SELECT id, node_type, name, description, parent_id, tree_path,
70
- depth, memory_count, total_size, last_updated, memory_id
71
- FROM memory_tree
72
- WHERE depth <= ?
73
- ORDER BY tree_path
74
- ''', (max_depth,))
75
- else:
76
- cursor.execute('''
77
- SELECT id, node_type, name, description, parent_id, tree_path,
78
- depth, memory_count, total_size, last_updated, memory_id
79
- FROM memory_tree
80
- ORDER BY tree_path
81
- ''')
82
-
83
- rows = cursor.fetchall()
84
- conn.close()
66
+ # Get entire tree
67
+ if max_depth is not None:
68
+ cursor.execute('''
69
+ SELECT id, node_type, name, description, parent_id, tree_path,
70
+ depth, memory_count, total_size, last_updated, memory_id
71
+ FROM memory_tree
72
+ WHERE depth <= ?
73
+ ORDER BY tree_path
74
+ ''', (max_depth,))
75
+ else:
76
+ cursor.execute('''
77
+ SELECT id, node_type, name, description, parent_id, tree_path,
78
+ depth, memory_count, total_size, last_updated, memory_id
79
+ FROM memory_tree
80
+ ORDER BY tree_path
81
+ ''')
82
+
83
+ rows = cursor.fetchall()
84
+ finally:
85
+ conn.close()
85
86
 
86
87
  if not rows:
87
88
  return {'error': 'No tree nodes found'}
@@ -125,44 +126,45 @@ class TreeQueriesMixin:
125
126
  List of descendant nodes
126
127
  """
127
128
  conn = sqlite3.connect(self.db_path)
128
- cursor = conn.cursor()
129
+ try:
130
+ cursor = conn.cursor()
131
+
132
+ # Get node's tree_path
133
+ cursor.execute('SELECT tree_path FROM memory_tree WHERE id = ?', (node_id,))
134
+ result = cursor.fetchone()
135
+
136
+ if not result:
137
+ return []
129
138
 
130
- # Get node's tree_path
131
- cursor.execute('SELECT tree_path FROM memory_tree WHERE id = ?', (node_id,))
132
- result = cursor.fetchone()
139
+ tree_path = result[0]
133
140
 
134
- if not result:
141
+ # Get all descendants
142
+ cursor.execute('''
143
+ SELECT id, node_type, name, description, parent_id, tree_path,
144
+ depth, memory_count, total_size, last_updated, memory_id
145
+ FROM memory_tree
146
+ WHERE tree_path LIKE ?
147
+ ORDER BY tree_path
148
+ ''', (f"{tree_path}.%",))
149
+
150
+ results = []
151
+ for row in cursor.fetchall():
152
+ results.append({
153
+ 'id': row[0],
154
+ 'type': row[1],
155
+ 'name': row[2],
156
+ 'description': row[3],
157
+ 'parent_id': row[4],
158
+ 'tree_path': row[5],
159
+ 'depth': row[6],
160
+ 'memory_count': row[7],
161
+ 'total_size': row[8],
162
+ 'last_updated': row[9],
163
+ 'memory_id': row[10]
164
+ })
165
+ finally:
135
166
  conn.close()
136
- return []
137
-
138
- tree_path = result[0]
139
-
140
- # Get all descendants
141
- cursor.execute('''
142
- SELECT id, node_type, name, description, parent_id, tree_path,
143
- depth, memory_count, total_size, last_updated, memory_id
144
- FROM memory_tree
145
- WHERE tree_path LIKE ?
146
- ORDER BY tree_path
147
- ''', (f"{tree_path}.%",))
148
-
149
- results = []
150
- for row in cursor.fetchall():
151
- results.append({
152
- 'id': row[0],
153
- 'type': row[1],
154
- 'name': row[2],
155
- 'description': row[3],
156
- 'parent_id': row[4],
157
- 'tree_path': row[5],
158
- 'depth': row[6],
159
- 'memory_count': row[7],
160
- 'total_size': row[8],
161
- 'last_updated': row[9],
162
- 'memory_id': row[10]
163
- })
164
167
 
165
- conn.close()
166
168
  return results
167
169
 
168
170
  def get_path_to_root(self, node_id: int) -> List[Dict[str, Any]]:
@@ -176,72 +178,75 @@ class TreeQueriesMixin:
176
178
  List of nodes from root to target node
177
179
  """
178
180
  conn = sqlite3.connect(self.db_path)
179
- cursor = conn.cursor()
181
+ try:
182
+ cursor = conn.cursor()
180
183
 
181
- # Get node's tree_path
182
- cursor.execute('SELECT tree_path FROM memory_tree WHERE id = ?', (node_id,))
183
- result = cursor.fetchone()
184
+ # Get node's tree_path
185
+ cursor.execute('SELECT tree_path FROM memory_tree WHERE id = ?', (node_id,))
186
+ result = cursor.fetchone()
184
187
 
185
- if not result:
188
+ if not result:
189
+ return []
190
+
191
+ tree_path = result[0]
192
+
193
+ # Parse path to get all ancestor IDs
194
+ path_ids = [int(x) for x in tree_path.split('.')]
195
+
196
+ # Get all ancestor nodes
197
+ placeholders = ','.join('?' * len(path_ids))
198
+ cursor.execute(f'''
199
+ SELECT id, node_type, name, description, parent_id, tree_path,
200
+ depth, memory_count, total_size, last_updated, memory_id
201
+ FROM memory_tree
202
+ WHERE id IN ({placeholders})
203
+ ORDER BY depth
204
+ ''', path_ids)
205
+
206
+ results = []
207
+ for row in cursor.fetchall():
208
+ results.append({
209
+ 'id': row[0],
210
+ 'type': row[1],
211
+ 'name': row[2],
212
+ 'description': row[3],
213
+ 'parent_id': row[4],
214
+ 'tree_path': row[5],
215
+ 'depth': row[6],
216
+ 'memory_count': row[7],
217
+ 'total_size': row[8],
218
+ 'last_updated': row[9],
219
+ 'memory_id': row[10]
220
+ })
221
+ finally:
186
222
  conn.close()
187
- return []
188
-
189
- tree_path = result[0]
190
-
191
- # Parse path to get all ancestor IDs
192
- path_ids = [int(x) for x in tree_path.split('.')]
193
-
194
- # Get all ancestor nodes
195
- placeholders = ','.join('?' * len(path_ids))
196
- cursor.execute(f'''
197
- SELECT id, node_type, name, description, parent_id, tree_path,
198
- depth, memory_count, total_size, last_updated, memory_id
199
- FROM memory_tree
200
- WHERE id IN ({placeholders})
201
- ORDER BY depth
202
- ''', path_ids)
203
-
204
- results = []
205
- for row in cursor.fetchall():
206
- results.append({
207
- 'id': row[0],
208
- 'type': row[1],
209
- 'name': row[2],
210
- 'description': row[3],
211
- 'parent_id': row[4],
212
- 'tree_path': row[5],
213
- 'depth': row[6],
214
- 'memory_count': row[7],
215
- 'total_size': row[8],
216
- 'last_updated': row[9],
217
- 'memory_id': row[10]
218
- })
219
223
 
220
- conn.close()
221
224
  return results
222
225
 
223
226
  def get_stats(self) -> Dict[str, Any]:
224
227
  """Get tree statistics."""
225
228
  conn = sqlite3.connect(self.db_path)
226
- cursor = conn.cursor()
229
+ try:
230
+ cursor = conn.cursor()
227
231
 
228
- cursor.execute('SELECT COUNT(*) FROM memory_tree')
229
- total_nodes = cursor.fetchone()[0]
232
+ cursor.execute('SELECT COUNT(*) FROM memory_tree')
233
+ total_nodes = cursor.fetchone()[0]
230
234
 
231
- cursor.execute('SELECT node_type, COUNT(*) FROM memory_tree GROUP BY node_type')
232
- by_type = dict(cursor.fetchall())
235
+ cursor.execute('SELECT node_type, COUNT(*) FROM memory_tree GROUP BY node_type')
236
+ by_type = dict(cursor.fetchall())
233
237
 
234
- cursor.execute('SELECT MAX(depth) FROM memory_tree')
235
- max_depth = cursor.fetchone()[0] or 0
238
+ cursor.execute('SELECT MAX(depth) FROM memory_tree')
239
+ max_depth = cursor.fetchone()[0] or 0
236
240
 
237
- cursor.execute('''
238
- SELECT SUM(memory_count), SUM(total_size)
239
- FROM memory_tree
240
- WHERE node_type = 'root'
241
- ''')
242
- total_memories, total_size = cursor.fetchone()
241
+ cursor.execute('''
242
+ SELECT SUM(memory_count), SUM(total_size)
243
+ FROM memory_tree
244
+ WHERE node_type = 'root'
245
+ ''')
246
+ total_memories, total_size = cursor.fetchone()
243
247
 
244
- conn.close()
248
+ finally:
249
+ conn.close()
245
250
 
246
251
  return {
247
252
  'total_nodes': total_nodes,
@@ -20,57 +20,61 @@ class TreeSchemaMixin:
20
20
  def _init_db(self):
21
21
  """Initialize memory_tree table."""
22
22
  conn = sqlite3.connect(self.db_path)
23
- cursor = conn.cursor()
23
+ try:
24
+ cursor = conn.cursor()
24
25
 
25
- cursor.execute('''
26
- CREATE TABLE IF NOT EXISTS memory_tree (
27
- id INTEGER PRIMARY KEY AUTOINCREMENT,
28
- node_type TEXT NOT NULL,
29
- name TEXT NOT NULL,
30
- description TEXT,
26
+ cursor.execute('''
27
+ CREATE TABLE IF NOT EXISTS memory_tree (
28
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
29
+ node_type TEXT NOT NULL,
30
+ name TEXT NOT NULL,
31
+ description TEXT,
31
32
 
32
- parent_id INTEGER,
33
- tree_path TEXT NOT NULL,
34
- depth INTEGER DEFAULT 0,
33
+ parent_id INTEGER,
34
+ tree_path TEXT NOT NULL,
35
+ depth INTEGER DEFAULT 0,
35
36
 
36
- memory_count INTEGER DEFAULT 0,
37
- total_size INTEGER DEFAULT 0,
38
- last_updated TIMESTAMP,
37
+ memory_count INTEGER DEFAULT 0,
38
+ total_size INTEGER DEFAULT 0,
39
+ last_updated TIMESTAMP,
39
40
 
40
- memory_id INTEGER,
41
+ memory_id INTEGER,
41
42
 
42
- FOREIGN KEY (parent_id) REFERENCES memory_tree(id) ON DELETE CASCADE,
43
- FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
44
- )
45
- ''')
43
+ FOREIGN KEY (parent_id) REFERENCES memory_tree(id) ON DELETE CASCADE,
44
+ FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
45
+ )
46
+ ''')
46
47
 
47
- cursor.execute('CREATE INDEX IF NOT EXISTS idx_tree_path_layer2 ON memory_tree(tree_path)')
48
- cursor.execute('CREATE INDEX IF NOT EXISTS idx_node_type ON memory_tree(node_type)')
49
- cursor.execute('CREATE INDEX IF NOT EXISTS idx_parent_id_tree ON memory_tree(parent_id)')
48
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_tree_path_layer2 ON memory_tree(tree_path)')
49
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_node_type ON memory_tree(node_type)')
50
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_parent_id_tree ON memory_tree(parent_id)')
50
51
 
51
- conn.commit()
52
- conn.close()
52
+ conn.commit()
53
+ finally:
54
+ conn.close()
53
55
 
54
56
  def _ensure_root(self) -> int:
55
57
  """Ensure root node exists and return its ID."""
56
58
  conn = sqlite3.connect(self.db_path)
57
- cursor = conn.cursor()
58
-
59
- cursor.execute('SELECT id FROM memory_tree WHERE node_type = ? AND parent_id IS NULL', ('root',))
60
- result = cursor.fetchone()
61
-
62
- if result:
63
- root_id = result[0]
64
- else:
65
- cursor.execute('''
66
- INSERT INTO memory_tree (node_type, name, tree_path, depth, last_updated)
67
- VALUES (?, ?, ?, ?, ?)
68
- ''', ('root', 'Root', '1', 0, datetime.now().isoformat()))
69
- root_id = cursor.lastrowid
70
-
71
- # Update tree_path with actual ID
72
- cursor.execute('UPDATE memory_tree SET tree_path = ? WHERE id = ?', (str(root_id), root_id))
73
- conn.commit()
74
-
75
- conn.close()
59
+ try:
60
+ cursor = conn.cursor()
61
+
62
+ cursor.execute('SELECT id FROM memory_tree WHERE node_type = ? AND parent_id IS NULL', ('root',))
63
+ result = cursor.fetchone()
64
+
65
+ if result:
66
+ root_id = result[0]
67
+ else:
68
+ cursor.execute('''
69
+ INSERT INTO memory_tree (node_type, name, tree_path, depth, last_updated)
70
+ VALUES (?, ?, ?, ?, ?)
71
+ ''', ('root', 'Root', '1', 0, datetime.now().isoformat()))
72
+ root_id = cursor.lastrowid
73
+
74
+ # Update tree_path with actual ID
75
+ cursor.execute('UPDATE memory_tree SET tree_path = ? WHERE id = ?', (str(root_id), root_id))
76
+ conn.commit()
77
+
78
+ finally:
79
+ conn.close()
76
80
  return root_id