superlocalmemory 2.7.6 → 2.8.0

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 (170) hide show
  1. package/CHANGELOG.md +120 -155
  2. package/README.md +115 -89
  3. package/api_server.py +2 -12
  4. package/docs/PATTERN-LEARNING.md +64 -199
  5. package/docs/example_graph_usage.py +4 -6
  6. package/install.sh +59 -0
  7. package/mcp_server.py +83 -7
  8. package/package.json +1 -8
  9. package/scripts/generate-thumbnails.py +3 -5
  10. package/skills/slm-build-graph/SKILL.md +1 -1
  11. package/skills/slm-list-recent/SKILL.md +1 -1
  12. package/skills/slm-recall/SKILL.md +1 -1
  13. package/skills/slm-remember/SKILL.md +1 -1
  14. package/skills/slm-show-patterns/SKILL.md +1 -1
  15. package/skills/slm-status/SKILL.md +1 -1
  16. package/skills/slm-switch-profile/SKILL.md +1 -1
  17. package/src/agent_registry.py +7 -18
  18. package/src/auth_middleware.py +3 -5
  19. package/src/auto_backup.py +3 -7
  20. package/src/behavioral/__init__.py +49 -0
  21. package/src/behavioral/behavioral_listener.py +203 -0
  22. package/src/behavioral/behavioral_patterns.py +275 -0
  23. package/src/behavioral/cross_project_transfer.py +206 -0
  24. package/src/behavioral/outcome_inference.py +194 -0
  25. package/src/behavioral/outcome_tracker.py +193 -0
  26. package/src/behavioral/tests/__init__.py +4 -0
  27. package/src/behavioral/tests/test_behavioral_integration.py +108 -0
  28. package/src/behavioral/tests/test_behavioral_patterns.py +150 -0
  29. package/src/behavioral/tests/test_cross_project_transfer.py +142 -0
  30. package/src/behavioral/tests/test_mcp_behavioral.py +139 -0
  31. package/src/behavioral/tests/test_mcp_report_outcome.py +117 -0
  32. package/src/behavioral/tests/test_outcome_inference.py +107 -0
  33. package/src/behavioral/tests/test_outcome_tracker.py +96 -0
  34. package/src/cache_manager.py +4 -6
  35. package/src/compliance/__init__.py +48 -0
  36. package/src/compliance/abac_engine.py +149 -0
  37. package/src/compliance/abac_middleware.py +116 -0
  38. package/src/compliance/audit_db.py +215 -0
  39. package/src/compliance/audit_logger.py +148 -0
  40. package/src/compliance/retention_manager.py +289 -0
  41. package/src/compliance/retention_scheduler.py +186 -0
  42. package/src/compliance/tests/__init__.py +4 -0
  43. package/src/compliance/tests/test_abac_enforcement.py +95 -0
  44. package/src/compliance/tests/test_abac_engine.py +124 -0
  45. package/src/compliance/tests/test_abac_mcp_integration.py +118 -0
  46. package/src/compliance/tests/test_audit_db.py +123 -0
  47. package/src/compliance/tests/test_audit_logger.py +98 -0
  48. package/src/compliance/tests/test_mcp_audit.py +128 -0
  49. package/src/compliance/tests/test_mcp_retention_policy.py +125 -0
  50. package/src/compliance/tests/test_retention_manager.py +131 -0
  51. package/src/compliance/tests/test_retention_scheduler.py +99 -0
  52. package/src/db_connection_manager.py +2 -12
  53. package/src/embedding_engine.py +61 -669
  54. package/src/embeddings/__init__.py +47 -0
  55. package/src/embeddings/cache.py +70 -0
  56. package/src/embeddings/cli.py +113 -0
  57. package/src/embeddings/constants.py +47 -0
  58. package/src/embeddings/database.py +91 -0
  59. package/src/embeddings/engine.py +247 -0
  60. package/src/embeddings/model_loader.py +145 -0
  61. package/src/event_bus.py +3 -13
  62. package/src/graph/__init__.py +36 -0
  63. package/src/graph/build_helpers.py +74 -0
  64. package/src/graph/cli.py +87 -0
  65. package/src/graph/cluster_builder.py +188 -0
  66. package/src/graph/cluster_summary.py +148 -0
  67. package/src/graph/constants.py +47 -0
  68. package/src/graph/edge_builder.py +162 -0
  69. package/src/graph/entity_extractor.py +95 -0
  70. package/src/graph/graph_core.py +226 -0
  71. package/src/graph/graph_search.py +231 -0
  72. package/src/graph/hierarchical.py +207 -0
  73. package/src/graph/schema.py +99 -0
  74. package/src/graph_engine.py +45 -1451
  75. package/src/hnsw_index.py +3 -7
  76. package/src/hybrid_search.py +36 -683
  77. package/src/learning/__init__.py +27 -12
  78. package/src/learning/adaptive_ranker.py +50 -12
  79. package/src/learning/cross_project_aggregator.py +2 -12
  80. package/src/learning/engagement_tracker.py +2 -12
  81. package/src/learning/feature_extractor.py +175 -43
  82. package/src/learning/feedback_collector.py +7 -12
  83. package/src/learning/learning_db.py +180 -12
  84. package/src/learning/project_context_manager.py +2 -12
  85. package/src/learning/source_quality_scorer.py +2 -12
  86. package/src/learning/synthetic_bootstrap.py +2 -12
  87. package/src/learning/tests/__init__.py +2 -0
  88. package/src/learning/tests/test_adaptive_ranker.py +2 -6
  89. package/src/learning/tests/test_adaptive_ranker_v28.py +60 -0
  90. package/src/learning/tests/test_aggregator.py +2 -6
  91. package/src/learning/tests/test_auto_retrain_v28.py +35 -0
  92. package/src/learning/tests/test_e2e_ranking_v28.py +82 -0
  93. package/src/learning/tests/test_feature_extractor_v28.py +93 -0
  94. package/src/learning/tests/test_feedback_collector.py +2 -6
  95. package/src/learning/tests/test_learning_db.py +2 -6
  96. package/src/learning/tests/test_learning_db_v28.py +110 -0
  97. package/src/learning/tests/test_learning_init_v28.py +48 -0
  98. package/src/learning/tests/test_outcome_signals.py +48 -0
  99. package/src/learning/tests/test_project_context.py +2 -6
  100. package/src/learning/tests/test_schema_migration.py +319 -0
  101. package/src/learning/tests/test_signal_inference.py +11 -13
  102. package/src/learning/tests/test_source_quality.py +2 -6
  103. package/src/learning/tests/test_synthetic_bootstrap.py +3 -7
  104. package/src/learning/tests/test_workflow_miner.py +2 -6
  105. package/src/learning/workflow_pattern_miner.py +2 -12
  106. package/src/lifecycle/__init__.py +54 -0
  107. package/src/lifecycle/bounded_growth.py +239 -0
  108. package/src/lifecycle/compaction_engine.py +226 -0
  109. package/src/lifecycle/lifecycle_engine.py +302 -0
  110. package/src/lifecycle/lifecycle_evaluator.py +225 -0
  111. package/src/lifecycle/lifecycle_scheduler.py +130 -0
  112. package/src/lifecycle/retention_policy.py +285 -0
  113. package/src/lifecycle/tests/__init__.py +4 -0
  114. package/src/lifecycle/tests/test_bounded_growth.py +193 -0
  115. package/src/lifecycle/tests/test_compaction.py +179 -0
  116. package/src/lifecycle/tests/test_lifecycle_engine.py +137 -0
  117. package/src/lifecycle/tests/test_lifecycle_evaluation.py +177 -0
  118. package/src/lifecycle/tests/test_lifecycle_scheduler.py +127 -0
  119. package/src/lifecycle/tests/test_lifecycle_search.py +109 -0
  120. package/src/lifecycle/tests/test_mcp_compact.py +149 -0
  121. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +114 -0
  122. package/src/lifecycle/tests/test_retention_policy.py +162 -0
  123. package/src/mcp_tools_v28.py +280 -0
  124. package/src/memory-profiles.py +2 -12
  125. package/src/memory-reset.py +2 -12
  126. package/src/memory_compression.py +2 -12
  127. package/src/memory_store_v2.py +76 -20
  128. package/src/migrate_v1_to_v2.py +2 -12
  129. package/src/pattern_learner.py +29 -975
  130. package/src/patterns/__init__.py +24 -0
  131. package/src/patterns/analyzers.py +247 -0
  132. package/src/patterns/learner.py +267 -0
  133. package/src/patterns/scoring.py +167 -0
  134. package/src/patterns/store.py +223 -0
  135. package/src/patterns/terminology.py +138 -0
  136. package/src/provenance_tracker.py +4 -14
  137. package/src/query_optimizer.py +4 -6
  138. package/src/rate_limiter.py +2 -6
  139. package/src/search/__init__.py +20 -0
  140. package/src/search/cli.py +77 -0
  141. package/src/search/constants.py +26 -0
  142. package/src/search/engine.py +239 -0
  143. package/src/search/fusion.py +122 -0
  144. package/src/search/index_loader.py +112 -0
  145. package/src/search/methods.py +162 -0
  146. package/src/search_engine_v2.py +4 -6
  147. package/src/setup_validator.py +7 -13
  148. package/src/subscription_manager.py +2 -12
  149. package/src/tree/__init__.py +59 -0
  150. package/src/tree/builder.py +183 -0
  151. package/src/tree/nodes.py +196 -0
  152. package/src/tree/queries.py +252 -0
  153. package/src/tree/schema.py +76 -0
  154. package/src/tree_manager.py +10 -711
  155. package/src/trust/__init__.py +45 -0
  156. package/src/trust/constants.py +66 -0
  157. package/src/trust/queries.py +157 -0
  158. package/src/trust/schema.py +95 -0
  159. package/src/trust/scorer.py +299 -0
  160. package/src/trust/signals.py +95 -0
  161. package/src/trust_scorer.py +39 -697
  162. package/src/webhook_dispatcher.py +2 -12
  163. package/ui/app.js +1 -1
  164. package/ui/js/agents.js +1 -1
  165. package/ui_server.py +2 -14
  166. package/ATTRIBUTION.md +0 -140
  167. package/docs/ARCHITECTURE-V2.5.md +0 -190
  168. package/docs/GRAPH-ENGINE.md +0 -503
  169. package/docs/architecture-diagram.drawio +0 -405
  170. package/docs/plans/2026-02-13-benchmark-suite.md +0 -1349
@@ -0,0 +1,252 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
+ """Tree Queries — Read-only tree traversal and statistics.
4
+
5
+ Provides TreeQueriesMixin with get_tree, get_subtree,
6
+ get_path_to_root, and get_stats.
7
+ """
8
+ import sqlite3
9
+ from typing import Optional, List, Dict, Any
10
+
11
+
12
+ class TreeQueriesMixin:
13
+ """Read-only query methods for the memory tree."""
14
+
15
+ def get_tree(
16
+ self,
17
+ project_name: Optional[str] = None,
18
+ max_depth: Optional[int] = None
19
+ ) -> Dict[str, Any]:
20
+ """
21
+ Get tree structure as nested dictionary.
22
+
23
+ Args:
24
+ project_name: Filter by specific project
25
+ max_depth: Maximum depth to retrieve
26
+
27
+ Returns:
28
+ Nested dictionary representing tree structure
29
+ """
30
+ conn = sqlite3.connect(self.db_path)
31
+ cursor = conn.cursor()
32
+
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:
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}.%"))
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()
85
+
86
+ if not rows:
87
+ return {'error': 'No tree nodes found'}
88
+
89
+ # Build nested structure
90
+ nodes = {}
91
+ root = None
92
+
93
+ for row in rows:
94
+ node = {
95
+ 'id': row[0],
96
+ 'type': row[1],
97
+ 'name': row[2],
98
+ 'description': row[3],
99
+ 'parent_id': row[4],
100
+ 'tree_path': row[5],
101
+ 'depth': row[6],
102
+ 'memory_count': row[7],
103
+ 'total_size': row[8],
104
+ 'last_updated': row[9],
105
+ 'memory_id': row[10],
106
+ 'children': []
107
+ }
108
+ nodes[node['id']] = node
109
+
110
+ if node['parent_id'] is None or (project_name and node['type'] == 'project'):
111
+ root = node
112
+ elif node['parent_id'] in nodes:
113
+ nodes[node['parent_id']]['children'].append(node)
114
+
115
+ return root or {'error': 'Root node not found'}
116
+
117
+ def get_subtree(self, node_id: int) -> List[Dict[str, Any]]:
118
+ """
119
+ Get all descendants of a specific node (flat list).
120
+
121
+ Args:
122
+ node_id: Node ID to get subtree for
123
+
124
+ Returns:
125
+ List of descendant nodes
126
+ """
127
+ conn = sqlite3.connect(self.db_path)
128
+ cursor = conn.cursor()
129
+
130
+ # Get node's tree_path
131
+ cursor.execute('SELECT tree_path FROM memory_tree WHERE id = ?', (node_id,))
132
+ result = cursor.fetchone()
133
+
134
+ if not result:
135
+ 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
+
165
+ conn.close()
166
+ return results
167
+
168
+ def get_path_to_root(self, node_id: int) -> List[Dict[str, Any]]:
169
+ """
170
+ Get path from node to root (breadcrumb trail).
171
+
172
+ Args:
173
+ node_id: Starting node ID
174
+
175
+ Returns:
176
+ List of nodes from root to target node
177
+ """
178
+ conn = sqlite3.connect(self.db_path)
179
+ cursor = conn.cursor()
180
+
181
+ # Get node's tree_path
182
+ cursor.execute('SELECT tree_path FROM memory_tree WHERE id = ?', (node_id,))
183
+ result = cursor.fetchone()
184
+
185
+ if not result:
186
+ 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
+
220
+ conn.close()
221
+ return results
222
+
223
+ def get_stats(self) -> Dict[str, Any]:
224
+ """Get tree statistics."""
225
+ conn = sqlite3.connect(self.db_path)
226
+ cursor = conn.cursor()
227
+
228
+ cursor.execute('SELECT COUNT(*) FROM memory_tree')
229
+ total_nodes = cursor.fetchone()[0]
230
+
231
+ cursor.execute('SELECT node_type, COUNT(*) FROM memory_tree GROUP BY node_type')
232
+ by_type = dict(cursor.fetchall())
233
+
234
+ cursor.execute('SELECT MAX(depth) FROM memory_tree')
235
+ max_depth = cursor.fetchone()[0] or 0
236
+
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()
243
+
244
+ conn.close()
245
+
246
+ return {
247
+ 'total_nodes': total_nodes,
248
+ 'by_type': by_type,
249
+ 'max_depth': max_depth,
250
+ 'total_memories': total_memories or 0,
251
+ 'total_size_bytes': total_size or 0
252
+ }
@@ -0,0 +1,76 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
+ """Tree Schema — Database initialization and root node management.
4
+
5
+ Provides the TreeSchemaMixin with DB init and root-node bootstrap logic
6
+ for the materialized-path tree structure.
7
+ """
8
+ import sqlite3
9
+ from pathlib import Path
10
+ from typing import Optional
11
+ from datetime import datetime
12
+
13
+ MEMORY_DIR = Path.home() / ".claude-memory"
14
+ DB_PATH = MEMORY_DIR / "memory.db"
15
+
16
+
17
+ class TreeSchemaMixin:
18
+ """Database schema and root-node management for the memory tree."""
19
+
20
+ def _init_db(self):
21
+ """Initialize memory_tree table."""
22
+ conn = sqlite3.connect(self.db_path)
23
+ cursor = conn.cursor()
24
+
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,
31
+
32
+ parent_id INTEGER,
33
+ tree_path TEXT NOT NULL,
34
+ depth INTEGER DEFAULT 0,
35
+
36
+ memory_count INTEGER DEFAULT 0,
37
+ total_size INTEGER DEFAULT 0,
38
+ last_updated TIMESTAMP,
39
+
40
+ memory_id INTEGER,
41
+
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
+ ''')
46
+
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)')
50
+
51
+ conn.commit()
52
+ conn.close()
53
+
54
+ def _ensure_root(self) -> int:
55
+ """Ensure root node exists and return its ID."""
56
+ 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()
76
+ return root_id