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
@@ -1,16 +1,6 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- SuperLocalMemory V2 - Learning Database Manager (v2.7)
4
- Copyright (c) 2026 Varun Pratap Bhardwaj
5
- Licensed under MIT License
6
-
7
- Repository: https://github.com/varun369/SuperLocalMemoryV2
8
- Author: Varun Pratap Bhardwaj (Solution Architect)
9
-
10
- NOTICE: This software is protected by MIT License.
11
- Attribution must be preserved in all copies or derivatives.
12
- """
13
-
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
14
4
  """
15
5
  LearningDB — Manages the separate learning.db for behavioral data.
16
6
 
@@ -275,6 +265,63 @@ class LearningDB:
275
265
  'ON engagement_metrics(metric_date)'
276
266
  )
277
267
 
268
+ # ------------------------------------------------------------------
269
+ # v2.8.0: Behavioral learning tables
270
+ # ------------------------------------------------------------------
271
+ cursor.execute('''
272
+ CREATE TABLE IF NOT EXISTS action_outcomes (
273
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
274
+ memory_ids TEXT NOT NULL,
275
+ outcome TEXT NOT NULL,
276
+ action_type TEXT DEFAULT 'other',
277
+ context TEXT DEFAULT '{}',
278
+ confidence REAL DEFAULT 1.0,
279
+ agent_id TEXT DEFAULT 'user',
280
+ project TEXT,
281
+ profile TEXT DEFAULT 'default',
282
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
283
+ )
284
+ ''')
285
+
286
+ cursor.execute('''
287
+ CREATE TABLE IF NOT EXISTS behavioral_patterns (
288
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
289
+ pattern_type TEXT NOT NULL,
290
+ pattern_key TEXT NOT NULL,
291
+ success_rate REAL DEFAULT 0.0,
292
+ evidence_count INTEGER DEFAULT 0,
293
+ confidence REAL DEFAULT 0.0,
294
+ metadata TEXT DEFAULT '{}',
295
+ project TEXT,
296
+ profile TEXT DEFAULT 'default',
297
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
298
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
299
+ )
300
+ ''')
301
+
302
+ cursor.execute('''
303
+ CREATE TABLE IF NOT EXISTS cross_project_behaviors (
304
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
305
+ source_project TEXT NOT NULL,
306
+ target_project TEXT NOT NULL,
307
+ pattern_id INTEGER NOT NULL,
308
+ transfer_type TEXT DEFAULT 'metadata',
309
+ confidence REAL DEFAULT 0.0,
310
+ profile TEXT DEFAULT 'default',
311
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
312
+ FOREIGN KEY (pattern_id) REFERENCES behavioral_patterns(id)
313
+ )
314
+ ''')
315
+
316
+ # v2.8.0 indexes
317
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_outcomes_memory ON action_outcomes(memory_ids)')
318
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_outcomes_project ON action_outcomes(project)')
319
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_outcomes_profile ON action_outcomes(profile)')
320
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_bpatterns_type ON behavioral_patterns(pattern_type)')
321
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_bpatterns_project ON behavioral_patterns(project)')
322
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_xproject_source ON cross_project_behaviors(source_project)')
323
+ cursor.execute('CREATE INDEX IF NOT EXISTS idx_xproject_target ON cross_project_behaviors(target_project)')
324
+
278
325
  conn.commit()
279
326
  logger.info("Learning schema initialized successfully")
280
327
 
@@ -915,6 +962,127 @@ class LearningDB:
915
962
  finally:
916
963
  conn.close()
917
964
 
965
+ # ======================================================================
966
+ # v2.8.0: Action Outcomes CRUD
967
+ # ======================================================================
968
+
969
+ def store_outcome(self, memory_ids, outcome, action_type="other", context=None, confidence=1.0, agent_id="user", project=None, profile="default"):
970
+ """Store an action outcome for behavioral learning."""
971
+ memory_ids_str = json.dumps(memory_ids if isinstance(memory_ids, list) else [memory_ids])
972
+ context_str = json.dumps(context or {})
973
+ conn = self._get_connection()
974
+ try:
975
+ with self._write_lock:
976
+ cursor = conn.execute(
977
+ "INSERT INTO action_outcomes (memory_ids, outcome, action_type, context, confidence, agent_id, project, profile) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
978
+ (memory_ids_str, outcome, action_type, context_str, confidence, agent_id, project, profile),
979
+ )
980
+ conn.commit()
981
+ return cursor.lastrowid
982
+ finally:
983
+ conn.close()
984
+
985
+ def get_outcomes(self, memory_id=None, project=None, profile="default", limit=100):
986
+ """Get action outcomes, optionally filtered."""
987
+ conn = self._get_connection()
988
+ try:
989
+ query = "SELECT * FROM action_outcomes WHERE profile = ?"
990
+ params = [profile]
991
+ if project:
992
+ query += " AND project = ?"
993
+ params.append(project)
994
+ query += " ORDER BY created_at DESC LIMIT ?"
995
+ params.append(limit)
996
+ rows = conn.execute(query, params).fetchall()
997
+ results = []
998
+ for row in rows:
999
+ d = dict(row)
1000
+ d["memory_ids"] = json.loads(d["memory_ids"])
1001
+ d["context"] = json.loads(d["context"])
1002
+ if memory_id and memory_id not in d["memory_ids"]:
1003
+ continue
1004
+ results.append(d)
1005
+ return results
1006
+ finally:
1007
+ conn.close()
1008
+
1009
+ # ======================================================================
1010
+ # v2.8.0: Behavioral Patterns CRUD
1011
+ # ======================================================================
1012
+
1013
+ def store_behavioral_pattern(self, pattern_type, pattern_key, success_rate=0.0, evidence_count=0, confidence=0.0, metadata=None, project=None, profile="default"):
1014
+ """Store or update a behavioral pattern."""
1015
+ metadata_str = json.dumps(metadata or {})
1016
+ conn = self._get_connection()
1017
+ try:
1018
+ with self._write_lock:
1019
+ cursor = conn.execute(
1020
+ "INSERT INTO behavioral_patterns (pattern_type, pattern_key, success_rate, evidence_count, confidence, metadata, project, profile) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
1021
+ (pattern_type, pattern_key, success_rate, evidence_count, confidence, metadata_str, project, profile),
1022
+ )
1023
+ conn.commit()
1024
+ return cursor.lastrowid
1025
+ finally:
1026
+ conn.close()
1027
+
1028
+ def get_behavioral_patterns(self, pattern_type=None, project=None, min_confidence=0.0, profile="default"):
1029
+ """Get behavioral patterns, optionally filtered."""
1030
+ conn = self._get_connection()
1031
+ try:
1032
+ query = "SELECT * FROM behavioral_patterns WHERE profile = ? AND confidence >= ?"
1033
+ params = [profile, min_confidence]
1034
+ if pattern_type:
1035
+ query += " AND pattern_type = ?"
1036
+ params.append(pattern_type)
1037
+ if project:
1038
+ query += " AND project = ?"
1039
+ params.append(project)
1040
+ query += " ORDER BY confidence DESC"
1041
+ rows = conn.execute(query, params).fetchall()
1042
+ results = []
1043
+ for row in rows:
1044
+ d = dict(row)
1045
+ d["metadata"] = json.loads(d["metadata"])
1046
+ results.append(d)
1047
+ return results
1048
+ finally:
1049
+ conn.close()
1050
+
1051
+ # ======================================================================
1052
+ # v2.8.0: Cross-Project CRUD
1053
+ # ======================================================================
1054
+
1055
+ def store_cross_project(self, source_project, target_project, pattern_id, transfer_type="metadata", confidence=0.0, profile="default"):
1056
+ """Record a cross-project behavioral transfer."""
1057
+ conn = self._get_connection()
1058
+ try:
1059
+ with self._write_lock:
1060
+ cursor = conn.execute(
1061
+ "INSERT INTO cross_project_behaviors (source_project, target_project, pattern_id, transfer_type, confidence, profile) VALUES (?, ?, ?, ?, ?, ?)",
1062
+ (source_project, target_project, pattern_id, transfer_type, confidence, profile),
1063
+ )
1064
+ conn.commit()
1065
+ return cursor.lastrowid
1066
+ finally:
1067
+ conn.close()
1068
+
1069
+ def get_cross_project_transfers(self, source_project=None, target_project=None, profile="default"):
1070
+ """Get cross-project transfer records."""
1071
+ conn = self._get_connection()
1072
+ try:
1073
+ query = "SELECT * FROM cross_project_behaviors WHERE profile = ?"
1074
+ params = [profile]
1075
+ if source_project:
1076
+ query += " AND source_project = ?"
1077
+ params.append(source_project)
1078
+ if target_project:
1079
+ query += " AND target_project = ?"
1080
+ params.append(target_project)
1081
+ query += " ORDER BY created_at DESC"
1082
+ return [dict(row) for row in conn.execute(query, params).fetchall()]
1083
+ finally:
1084
+ conn.close()
1085
+
918
1086
  # ======================================================================
919
1087
  # Reset / Cleanup
920
1088
  # ======================================================================
@@ -1,16 +1,6 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- SuperLocalMemory V2 - Project Context Manager (v2.7)
4
- Copyright (c) 2026 Varun Pratap Bhardwaj
5
- Licensed under MIT License
6
-
7
- Repository: https://github.com/varun369/SuperLocalMemoryV2
8
- Author: Varun Pratap Bhardwaj (Solution Architect)
9
-
10
- NOTICE: This software is protected by MIT License.
11
- Attribution must be preserved in all copies or derivatives.
12
- """
13
-
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
14
4
  """
15
5
  ProjectContextManager — Layer 2: Multi-signal project detection.
16
6
 
@@ -1,16 +1,6 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- SuperLocalMemory V2 - Source Quality Scorer (v2.7)
4
- Copyright (c) 2026 Varun Pratap Bhardwaj
5
- Licensed under MIT License
6
-
7
- Repository: https://github.com/varun369/SuperLocalMemoryV2
8
- Author: Varun Pratap Bhardwaj (Solution Architect)
9
-
10
- NOTICE: This software is protected by MIT License.
11
- Attribution must be preserved in all copies or derivatives.
12
- """
13
-
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
14
4
  """
15
5
  SourceQualityScorer — Per-source quality learning.
16
6
 
@@ -1,16 +1,6 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- SuperLocalMemory V2 - Synthetic Bootstrap (v2.7)
4
- Copyright (c) 2026 Varun Pratap Bhardwaj
5
- Licensed under MIT License
6
-
7
- Repository: https://github.com/varun369/SuperLocalMemoryV2
8
- Author: Varun Pratap Bhardwaj (Solution Architect)
9
-
10
- NOTICE: This software is protected by MIT License.
11
- Attribution must be preserved in all copies or derivatives.
12
- """
13
-
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
14
4
  """
15
5
  SyntheticBootstrapper — Bootstrap ML model from existing data patterns.
16
6
 
@@ -0,0 +1,2 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
@@ -1,10 +1,6 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- SuperLocalMemory V2 - Tests for AdaptiveRanker (v2.7)
4
- Copyright (c) 2026 Varun Pratap Bhardwaj
5
- Licensed under MIT License
6
- """
7
-
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
8
4
  import pytest
9
5
 
10
6
 
@@ -0,0 +1,60 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
+ """Tests for v2.8 adaptive ranker — 20 features + lifecycle/behavioral boosts.
4
+ """
5
+ import sys
6
+ from pathlib import Path
7
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
8
+
9
+
10
+ class TestAdaptiveRankerV28:
11
+ def test_num_features_is_20(self):
12
+ from learning.feature_extractor import NUM_FEATURES
13
+ assert NUM_FEATURES == 20
14
+
15
+ def test_new_rule_boosts_exist(self):
16
+ from learning.adaptive_ranker import _RULE_BOOST
17
+ assert 'lifecycle_active' in _RULE_BOOST
18
+ assert 'outcome_success_high' in _RULE_BOOST
19
+ assert 'behavioral_match_strong' in _RULE_BOOST
20
+ assert 'cross_project_boost' in _RULE_BOOST
21
+
22
+ def test_lifecycle_active_boost(self):
23
+ from learning.adaptive_ranker import _RULE_BOOST
24
+ assert _RULE_BOOST['lifecycle_active'] == 1.0
25
+ assert _RULE_BOOST['lifecycle_warm'] == 0.85
26
+ assert _RULE_BOOST['lifecycle_cold'] == 0.6
27
+
28
+ def test_outcome_boosts(self):
29
+ from learning.adaptive_ranker import _RULE_BOOST
30
+ assert _RULE_BOOST['outcome_success_high'] == 1.3
31
+ assert _RULE_BOOST['outcome_failure_high'] == 0.7
32
+
33
+ def test_phase1_works_with_20_features(self):
34
+ """Phase 1 (rule-based) ranking should work with 20-feature vectors."""
35
+ from learning.adaptive_ranker import AdaptiveRanker
36
+ ranker = AdaptiveRanker()
37
+ results = [
38
+ {'id': 1, 'content': 'Python memory', 'score': 0.8, 'match_type': 'semantic',
39
+ 'importance': 7, 'lifecycle_state': 'active'},
40
+ {'id': 2, 'content': 'Old memory', 'score': 0.7, 'match_type': 'semantic',
41
+ 'importance': 3, 'lifecycle_state': 'cold'},
42
+ ]
43
+ ranked = ranker.rerank(results, "Python")
44
+ assert len(ranked) == 2
45
+ assert all('score' in r for r in ranked)
46
+
47
+ def test_active_memory_ranks_higher_than_cold(self):
48
+ """Active memories should score higher than cold with same base score."""
49
+ from learning.adaptive_ranker import AdaptiveRanker
50
+ ranker = AdaptiveRanker()
51
+ results = [
52
+ {'id': 1, 'content': 'same content', 'score': 0.8, 'match_type': 'semantic',
53
+ 'importance': 5, 'lifecycle_state': 'cold'},
54
+ {'id': 2, 'content': 'same content', 'score': 0.8, 'match_type': 'semantic',
55
+ 'importance': 5, 'lifecycle_state': 'active'},
56
+ ]
57
+ ranked = ranker.rerank(results, "test")
58
+ # Active (id=2) should rank higher than cold (id=1)
59
+ if len(ranked) >= 2:
60
+ assert ranked[0]['id'] == 2 or ranked[0]['score'] >= ranked[1]['score']
@@ -1,10 +1,6 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- SuperLocalMemory V2 - Tests for CrossProjectAggregator (v2.7)
4
- Copyright (c) 2026 Varun Pratap Bhardwaj
5
- Licensed under MIT License
6
- """
7
-
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
8
4
  import math
9
5
  import sqlite3
10
6
  from datetime import datetime, timedelta
@@ -0,0 +1,35 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
+ """Tests for auto-retrain mechanism when feature dimensions change 12->20.
4
+ """
5
+ import sys
6
+ from pathlib import Path
7
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
8
+
9
+
10
+ class TestAutoRetrain:
11
+ def test_feature_dimension_is_20(self):
12
+ from learning.feature_extractor import NUM_FEATURES
13
+ assert NUM_FEATURES == 20
14
+
15
+ def test_ranker_handles_dimension_mismatch(self):
16
+ """Ranker should not crash when loaded model has different dimensions."""
17
+ from learning.adaptive_ranker import AdaptiveRanker
18
+ ranker = AdaptiveRanker()
19
+ # Even without a trained model, phase 1 should work
20
+ results = [{'id': 1, 'content': 'test', 'score': 0.5, 'match_type': 'semantic', 'importance': 5}]
21
+ ranked = ranker.rerank(results, "test")
22
+ assert len(ranked) == 1
23
+
24
+ def test_phase1_immediate_during_retrain(self):
25
+ """Phase 1 (rule-based) should work immediately even during model retrain."""
26
+ from learning.adaptive_ranker import AdaptiveRanker
27
+ ranker = AdaptiveRanker()
28
+ results = [
29
+ {'id': 1, 'content': 'memory A', 'score': 0.9, 'match_type': 'semantic', 'importance': 8},
30
+ {'id': 2, 'content': 'memory B', 'score': 0.3, 'match_type': 'keyword', 'importance': 2},
31
+ ]
32
+ ranked = ranker.rerank(results, "test query")
33
+ assert len(ranked) == 2
34
+ # Higher base score should still rank first in phase 1
35
+ assert ranked[0]['score'] >= ranked[1]['score']
@@ -0,0 +1,82 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
+ """End-to-end tests for adaptive ranking with 20-feature vector.
4
+ """
5
+ import sys
6
+ from pathlib import Path
7
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
8
+
9
+
10
+ class TestE2ERankingV28:
11
+ def test_full_20_feature_pipeline(self):
12
+ """Full pipeline: extract 20 features -> rank results."""
13
+ from learning.feature_extractor import FeatureExtractor
14
+ from learning.adaptive_ranker import AdaptiveRanker
15
+
16
+ extractor = FeatureExtractor()
17
+ ranker = AdaptiveRanker()
18
+
19
+ memories = [
20
+ {'id': 1, 'content': 'Python best practices for production', 'score': 0.8,
21
+ 'match_type': 'semantic', 'importance': 8, 'lifecycle_state': 'active',
22
+ 'access_count': 15, 'created_at': '2026-02-01'},
23
+ {'id': 2, 'content': 'Old JavaScript patterns deprecated', 'score': 0.75,
24
+ 'match_type': 'semantic', 'importance': 3, 'lifecycle_state': 'cold',
25
+ 'access_count': 1, 'created_at': '2025-06-01'},
26
+ ]
27
+
28
+ # Extract features
29
+ for mem in memories:
30
+ features = extractor.extract_features(mem, "Python production")
31
+ assert len(features) == 20
32
+
33
+ # Rank
34
+ ranked = ranker.rerank(memories, "Python production")
35
+ assert len(ranked) == 2
36
+
37
+ def test_lifecycle_affects_ranking(self):
38
+ """Active memories should generally rank above cold ones."""
39
+ from learning.adaptive_ranker import AdaptiveRanker
40
+ ranker = AdaptiveRanker()
41
+
42
+ results = [
43
+ {'id': 1, 'content': 'cold memory', 'score': 0.85, 'match_type': 'semantic',
44
+ 'importance': 5, 'lifecycle_state': 'cold'},
45
+ {'id': 2, 'content': 'active memory', 'score': 0.80, 'match_type': 'semantic',
46
+ 'importance': 5, 'lifecycle_state': 'active'},
47
+ ]
48
+ ranked = ranker.rerank(results, "test")
49
+ # Even with slightly lower base score, active should win due to lifecycle boost
50
+ assert ranked[0]['id'] == 2
51
+
52
+ def test_high_outcome_success_boosts_ranking(self):
53
+ """Memories with high success rates should rank higher."""
54
+ from learning.adaptive_ranker import AdaptiveRanker
55
+ ranker = AdaptiveRanker()
56
+
57
+ results = [
58
+ {'id': 1, 'content': 'frequently failing', 'score': 0.8, 'match_type': 'semantic',
59
+ 'importance': 5, 'outcome_success_rate': 0.1},
60
+ {'id': 2, 'content': 'consistently useful', 'score': 0.8, 'match_type': 'semantic',
61
+ 'importance': 5, 'outcome_success_rate': 0.95},
62
+ ]
63
+ ranked = ranker.rerank(results, "test")
64
+ assert ranked[0]['id'] == 2
65
+
66
+ def test_result_ordering_reflects_all_signals(self):
67
+ """Result ordering should reflect lifecycle + behavioral + base score."""
68
+ from learning.adaptive_ranker import AdaptiveRanker
69
+ ranker = AdaptiveRanker()
70
+
71
+ results = [
72
+ {'id': 1, 'content': 'A', 'score': 0.7, 'match_type': 'semantic', 'importance': 5,
73
+ 'lifecycle_state': 'active', 'outcome_success_rate': 0.9},
74
+ {'id': 2, 'content': 'B', 'score': 0.9, 'match_type': 'semantic', 'importance': 5,
75
+ 'lifecycle_state': 'cold', 'outcome_success_rate': 0.1},
76
+ {'id': 3, 'content': 'C', 'score': 0.8, 'match_type': 'semantic', 'importance': 8,
77
+ 'lifecycle_state': 'active', 'outcome_success_rate': 0.5},
78
+ ]
79
+ ranked = ranker.rerank(results, "test")
80
+ assert len(ranked) == 3
81
+ # All should have final scores
82
+ assert all('score' in r for r in ranked)
@@ -0,0 +1,93 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
+ """Tests for v2.8 feature extractor — 20-dimensional feature vectors.
4
+ """
5
+ import sys
6
+ from pathlib import Path
7
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
8
+
9
+
10
+ class TestFeatureExtractorV28:
11
+ def test_feature_count_is_20(self):
12
+ from learning.feature_extractor import FEATURE_NAMES
13
+ assert len(FEATURE_NAMES) == 20
14
+
15
+ def test_new_feature_names_present(self):
16
+ from learning.feature_extractor import FEATURE_NAMES
17
+ new_features = ['lifecycle_state', 'outcome_success_rate', 'outcome_count',
18
+ 'behavioral_match', 'cross_project_score', 'retention_priority',
19
+ 'trust_at_creation', 'lifecycle_aware_decay']
20
+ for f in new_features:
21
+ assert f in FEATURE_NAMES
22
+
23
+ def test_extract_returns_20_features(self):
24
+ from learning.feature_extractor import FeatureExtractor
25
+ extractor = FeatureExtractor()
26
+ memory = {
27
+ 'id': 1, 'content': 'test memory about Python', 'importance': 5,
28
+ 'created_at': '2026-02-20T10:00:00', 'last_accessed': '2026-02-25T10:00:00',
29
+ 'access_count': 3, 'tags': ['python'], 'project_name': 'myproject',
30
+ 'lifecycle_state': 'active', 'score': 0.8, 'match_type': 'semantic',
31
+ }
32
+ features = extractor.extract_features(memory, "Python programming")
33
+ assert len(features) == 20
34
+ assert all(isinstance(f, (int, float)) for f in features)
35
+
36
+ def test_lifecycle_state_encoding(self):
37
+ from learning.feature_extractor import FeatureExtractor
38
+ extractor = FeatureExtractor()
39
+ base = {'id': 1, 'content': 'test', 'importance': 5, 'created_at': '2026-02-20',
40
+ 'access_count': 0, 'score': 0.5, 'match_type': 'semantic'}
41
+
42
+ active = extractor.extract_features({**base, 'lifecycle_state': 'active'}, "test")
43
+ warm = extractor.extract_features({**base, 'lifecycle_state': 'warm'}, "test")
44
+ cold = extractor.extract_features({**base, 'lifecycle_state': 'cold'}, "test")
45
+
46
+ assert active[12] > warm[12] > cold[12]
47
+
48
+ def test_default_values_when_data_missing(self):
49
+ """Features should return sensible defaults when data is unavailable."""
50
+ from learning.feature_extractor import FeatureExtractor
51
+ extractor = FeatureExtractor()
52
+ minimal = {'id': 1, 'content': 'test', 'importance': 5, 'score': 0.5, 'match_type': 'semantic'}
53
+ features = extractor.extract_features(minimal, "test")
54
+ assert len(features) == 20
55
+ # lifecycle_state default (active) = 1.0
56
+ assert features[12] == 1.0
57
+ # outcome_success_rate default = 0.5
58
+ assert features[13] == 0.5
59
+ # retention_priority default = 0.5
60
+ assert features[17] == 0.5
61
+ # trust_at_creation default = 0.8
62
+ assert features[18] == 0.8
63
+
64
+ def test_backward_compat_old_12_features_still_work(self):
65
+ """The first 12 features should still be computed correctly."""
66
+ from learning.feature_extractor import FeatureExtractor
67
+ extractor = FeatureExtractor()
68
+ memory = {'id': 1, 'content': 'Python is great', 'importance': 8,
69
+ 'created_at': '2026-02-20', 'access_count': 5, 'score': 0.9,
70
+ 'match_type': 'semantic', 'lifecycle_state': 'active'}
71
+ features = extractor.extract_features(memory, "Python")
72
+ # importance_norm (index 6) should be 0.8 (8/10)
73
+ assert abs(features[6] - 0.8) < 0.01
74
+
75
+ def test_extract_batch_returns_20_wide(self):
76
+ from learning.feature_extractor import FeatureExtractor
77
+ extractor = FeatureExtractor()
78
+ memories = [
79
+ {'id': 1, 'content': 'mem1', 'importance': 5, 'score': 0.5, 'match_type': 'semantic'},
80
+ {'id': 2, 'content': 'mem2', 'importance': 7, 'score': 0.7, 'match_type': 'semantic'},
81
+ ]
82
+ batch = extractor.extract_batch(memories, "test")
83
+ assert len(batch) == 2
84
+ assert all(len(v) == 20 for v in batch)
85
+
86
+ def test_outcome_success_rate_from_memory(self):
87
+ """If memory has outcome_success_rate in dict, use it."""
88
+ from learning.feature_extractor import FeatureExtractor
89
+ extractor = FeatureExtractor()
90
+ memory = {'id': 1, 'content': 'test', 'importance': 5, 'score': 0.5,
91
+ 'match_type': 'semantic', 'outcome_success_rate': 0.85}
92
+ features = extractor.extract_features(memory, "test")
93
+ assert abs(features[13] - 0.85) < 0.01
@@ -1,10 +1,6 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- SuperLocalMemory V2 - Tests for FeedbackCollector (v2.7)
4
- Copyright (c) 2026 Varun Pratap Bhardwaj
5
- Licensed under MIT License
6
- """
7
-
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
8
4
  import hashlib
9
5
  import time
10
6
 
@@ -1,10 +1,6 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- SuperLocalMemory V2 - Tests for LearningDB (v2.7)
4
- Copyright (c) 2026 Varun Pratap Bhardwaj
5
- Licensed under MIT License
6
- """
7
-
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
8
4
  import json
9
5
  import sqlite3
10
6
  import threading