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.
- package/CHANGELOG.md +120 -155
- package/README.md +115 -89
- package/api_server.py +2 -12
- package/docs/PATTERN-LEARNING.md +64 -199
- package/docs/example_graph_usage.py +4 -6
- package/install.sh +59 -0
- package/mcp_server.py +83 -7
- package/package.json +1 -8
- package/scripts/generate-thumbnails.py +3 -5
- package/skills/slm-build-graph/SKILL.md +1 -1
- package/skills/slm-list-recent/SKILL.md +1 -1
- package/skills/slm-recall/SKILL.md +1 -1
- package/skills/slm-remember/SKILL.md +1 -1
- package/skills/slm-show-patterns/SKILL.md +1 -1
- package/skills/slm-status/SKILL.md +1 -1
- package/skills/slm-switch-profile/SKILL.md +1 -1
- package/src/agent_registry.py +7 -18
- package/src/auth_middleware.py +3 -5
- package/src/auto_backup.py +3 -7
- package/src/behavioral/__init__.py +49 -0
- package/src/behavioral/behavioral_listener.py +203 -0
- package/src/behavioral/behavioral_patterns.py +275 -0
- package/src/behavioral/cross_project_transfer.py +206 -0
- package/src/behavioral/outcome_inference.py +194 -0
- package/src/behavioral/outcome_tracker.py +193 -0
- package/src/behavioral/tests/__init__.py +4 -0
- package/src/behavioral/tests/test_behavioral_integration.py +108 -0
- package/src/behavioral/tests/test_behavioral_patterns.py +150 -0
- package/src/behavioral/tests/test_cross_project_transfer.py +142 -0
- package/src/behavioral/tests/test_mcp_behavioral.py +139 -0
- package/src/behavioral/tests/test_mcp_report_outcome.py +117 -0
- package/src/behavioral/tests/test_outcome_inference.py +107 -0
- package/src/behavioral/tests/test_outcome_tracker.py +96 -0
- package/src/cache_manager.py +4 -6
- package/src/compliance/__init__.py +48 -0
- package/src/compliance/abac_engine.py +149 -0
- package/src/compliance/abac_middleware.py +116 -0
- package/src/compliance/audit_db.py +215 -0
- package/src/compliance/audit_logger.py +148 -0
- package/src/compliance/retention_manager.py +289 -0
- package/src/compliance/retention_scheduler.py +186 -0
- package/src/compliance/tests/__init__.py +4 -0
- package/src/compliance/tests/test_abac_enforcement.py +95 -0
- package/src/compliance/tests/test_abac_engine.py +124 -0
- package/src/compliance/tests/test_abac_mcp_integration.py +118 -0
- package/src/compliance/tests/test_audit_db.py +123 -0
- package/src/compliance/tests/test_audit_logger.py +98 -0
- package/src/compliance/tests/test_mcp_audit.py +128 -0
- package/src/compliance/tests/test_mcp_retention_policy.py +125 -0
- package/src/compliance/tests/test_retention_manager.py +131 -0
- package/src/compliance/tests/test_retention_scheduler.py +99 -0
- package/src/db_connection_manager.py +2 -12
- package/src/embedding_engine.py +61 -669
- package/src/embeddings/__init__.py +47 -0
- package/src/embeddings/cache.py +70 -0
- package/src/embeddings/cli.py +113 -0
- package/src/embeddings/constants.py +47 -0
- package/src/embeddings/database.py +91 -0
- package/src/embeddings/engine.py +247 -0
- package/src/embeddings/model_loader.py +145 -0
- package/src/event_bus.py +3 -13
- package/src/graph/__init__.py +36 -0
- package/src/graph/build_helpers.py +74 -0
- package/src/graph/cli.py +87 -0
- package/src/graph/cluster_builder.py +188 -0
- package/src/graph/cluster_summary.py +148 -0
- package/src/graph/constants.py +47 -0
- package/src/graph/edge_builder.py +162 -0
- package/src/graph/entity_extractor.py +95 -0
- package/src/graph/graph_core.py +226 -0
- package/src/graph/graph_search.py +231 -0
- package/src/graph/hierarchical.py +207 -0
- package/src/graph/schema.py +99 -0
- package/src/graph_engine.py +45 -1451
- package/src/hnsw_index.py +3 -7
- package/src/hybrid_search.py +36 -683
- package/src/learning/__init__.py +27 -12
- package/src/learning/adaptive_ranker.py +50 -12
- package/src/learning/cross_project_aggregator.py +2 -12
- package/src/learning/engagement_tracker.py +2 -12
- package/src/learning/feature_extractor.py +175 -43
- package/src/learning/feedback_collector.py +7 -12
- package/src/learning/learning_db.py +180 -12
- package/src/learning/project_context_manager.py +2 -12
- package/src/learning/source_quality_scorer.py +2 -12
- package/src/learning/synthetic_bootstrap.py +2 -12
- package/src/learning/tests/__init__.py +2 -0
- package/src/learning/tests/test_adaptive_ranker.py +2 -6
- package/src/learning/tests/test_adaptive_ranker_v28.py +60 -0
- package/src/learning/tests/test_aggregator.py +2 -6
- package/src/learning/tests/test_auto_retrain_v28.py +35 -0
- package/src/learning/tests/test_e2e_ranking_v28.py +82 -0
- package/src/learning/tests/test_feature_extractor_v28.py +93 -0
- package/src/learning/tests/test_feedback_collector.py +2 -6
- package/src/learning/tests/test_learning_db.py +2 -6
- package/src/learning/tests/test_learning_db_v28.py +110 -0
- package/src/learning/tests/test_learning_init_v28.py +48 -0
- package/src/learning/tests/test_outcome_signals.py +48 -0
- package/src/learning/tests/test_project_context.py +2 -6
- package/src/learning/tests/test_schema_migration.py +319 -0
- package/src/learning/tests/test_signal_inference.py +11 -13
- package/src/learning/tests/test_source_quality.py +2 -6
- package/src/learning/tests/test_synthetic_bootstrap.py +3 -7
- package/src/learning/tests/test_workflow_miner.py +2 -6
- package/src/learning/workflow_pattern_miner.py +2 -12
- package/src/lifecycle/__init__.py +54 -0
- package/src/lifecycle/bounded_growth.py +239 -0
- package/src/lifecycle/compaction_engine.py +226 -0
- package/src/lifecycle/lifecycle_engine.py +302 -0
- package/src/lifecycle/lifecycle_evaluator.py +225 -0
- package/src/lifecycle/lifecycle_scheduler.py +130 -0
- package/src/lifecycle/retention_policy.py +285 -0
- package/src/lifecycle/tests/__init__.py +4 -0
- package/src/lifecycle/tests/test_bounded_growth.py +193 -0
- package/src/lifecycle/tests/test_compaction.py +179 -0
- package/src/lifecycle/tests/test_lifecycle_engine.py +137 -0
- package/src/lifecycle/tests/test_lifecycle_evaluation.py +177 -0
- package/src/lifecycle/tests/test_lifecycle_scheduler.py +127 -0
- package/src/lifecycle/tests/test_lifecycle_search.py +109 -0
- package/src/lifecycle/tests/test_mcp_compact.py +149 -0
- package/src/lifecycle/tests/test_mcp_lifecycle_status.py +114 -0
- package/src/lifecycle/tests/test_retention_policy.py +162 -0
- package/src/mcp_tools_v28.py +280 -0
- package/src/memory-profiles.py +2 -12
- package/src/memory-reset.py +2 -12
- package/src/memory_compression.py +2 -12
- package/src/memory_store_v2.py +76 -20
- package/src/migrate_v1_to_v2.py +2 -12
- package/src/pattern_learner.py +29 -975
- package/src/patterns/__init__.py +24 -0
- package/src/patterns/analyzers.py +247 -0
- package/src/patterns/learner.py +267 -0
- package/src/patterns/scoring.py +167 -0
- package/src/patterns/store.py +223 -0
- package/src/patterns/terminology.py +138 -0
- package/src/provenance_tracker.py +4 -14
- package/src/query_optimizer.py +4 -6
- package/src/rate_limiter.py +2 -6
- package/src/search/__init__.py +20 -0
- package/src/search/cli.py +77 -0
- package/src/search/constants.py +26 -0
- package/src/search/engine.py +239 -0
- package/src/search/fusion.py +122 -0
- package/src/search/index_loader.py +112 -0
- package/src/search/methods.py +162 -0
- package/src/search_engine_v2.py +4 -6
- package/src/setup_validator.py +7 -13
- package/src/subscription_manager.py +2 -12
- package/src/tree/__init__.py +59 -0
- package/src/tree/builder.py +183 -0
- package/src/tree/nodes.py +196 -0
- package/src/tree/queries.py +252 -0
- package/src/tree/schema.py +76 -0
- package/src/tree_manager.py +10 -711
- package/src/trust/__init__.py +45 -0
- package/src/trust/constants.py +66 -0
- package/src/trust/queries.py +157 -0
- package/src/trust/schema.py +95 -0
- package/src/trust/scorer.py +299 -0
- package/src/trust/signals.py +95 -0
- package/src/trust_scorer.py +39 -697
- package/src/webhook_dispatcher.py +2 -12
- package/ui/app.js +1 -1
- package/ui/js/agents.js +1 -1
- package/ui_server.py +2 -14
- package/ATTRIBUTION.md +0 -140
- package/docs/ARCHITECTURE-V2.5.md +0 -190
- package/docs/GRAPH-ENGINE.md +0 -503
- package/docs/architecture-diagram.drawio +0 -405
- package/docs/plans/2026-02-13-benchmark-suite.md +0 -1349
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|