superlocalmemory 2.8.5 → 3.0.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 (434) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +9 -1
  3. package/NOTICE +63 -0
  4. package/README.md +165 -480
  5. package/bin/slm +17 -449
  6. package/bin/slm-npm +2 -2
  7. package/bin/slm.bat +4 -2
  8. package/conftest.py +5 -0
  9. package/docs/api-reference.md +284 -0
  10. package/docs/architecture.md +149 -0
  11. package/docs/auto-memory.md +150 -0
  12. package/docs/cli-reference.md +276 -0
  13. package/docs/compliance.md +191 -0
  14. package/docs/configuration.md +182 -0
  15. package/docs/getting-started.md +102 -0
  16. package/docs/ide-setup.md +261 -0
  17. package/docs/mcp-tools.md +220 -0
  18. package/docs/migration-from-v2.md +170 -0
  19. package/docs/profiles.md +173 -0
  20. package/docs/troubleshooting.md +310 -0
  21. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  22. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  23. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  24. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  25. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  26. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  27. package/ide/configs/cursor-mcp.json +15 -0
  28. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  29. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  30. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  31. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  32. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  33. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  34. package/{configs → ide/configs}/zed-mcp.json +2 -2
  35. package/{hooks → ide/hooks}/context-hook.js +9 -20
  36. package/ide/hooks/memory-list-skill.js +70 -0
  37. package/ide/hooks/memory-profile-skill.js +101 -0
  38. package/ide/hooks/memory-recall-skill.js +62 -0
  39. package/ide/hooks/memory-remember-skill.js +68 -0
  40. package/ide/hooks/memory-reset-skill.js +160 -0
  41. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  42. package/ide/integrations/langchain/README.md +106 -0
  43. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  44. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  45. package/ide/integrations/langchain/pyproject.toml +38 -0
  46. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  47. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  48. package/ide/integrations/langchain/tests/test_security.py +117 -0
  49. package/ide/integrations/llamaindex/README.md +81 -0
  50. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  51. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  52. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  53. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  54. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  55. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  56. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  57. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  58. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  59. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  60. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  61. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  62. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  63. package/package.json +13 -22
  64. package/pyproject.toml +85 -0
  65. package/scripts/build-dmg.sh +417 -0
  66. package/scripts/install-skills.ps1 +334 -0
  67. package/{install.ps1 → scripts/install.ps1} +36 -4
  68. package/{install.sh → scripts/install.sh} +14 -13
  69. package/scripts/postinstall.js +2 -2
  70. package/scripts/start-dashboard.ps1 +52 -0
  71. package/scripts/start-dashboard.sh +41 -0
  72. package/scripts/sync-wiki.ps1 +127 -0
  73. package/scripts/sync-wiki.sh +82 -0
  74. package/scripts/test-dmg.sh +161 -0
  75. package/scripts/test-npm-package.ps1 +252 -0
  76. package/scripts/test-npm-package.sh +207 -0
  77. package/scripts/verify-install.ps1 +294 -0
  78. package/scripts/verify-install.sh +266 -0
  79. package/src/superlocalmemory/__init__.py +0 -0
  80. package/src/superlocalmemory/attribution/__init__.py +9 -0
  81. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  82. package/src/superlocalmemory/attribution/signer.py +153 -0
  83. package/src/superlocalmemory/attribution/watermark.py +189 -0
  84. package/src/superlocalmemory/cli/__init__.py +5 -0
  85. package/src/superlocalmemory/cli/commands.py +245 -0
  86. package/src/superlocalmemory/cli/main.py +89 -0
  87. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  88. package/src/superlocalmemory/cli/post_install.py +99 -0
  89. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  90. package/src/superlocalmemory/compliance/__init__.py +0 -0
  91. package/src/superlocalmemory/compliance/abac.py +204 -0
  92. package/src/superlocalmemory/compliance/audit.py +314 -0
  93. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  94. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  95. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  96. package/src/superlocalmemory/compliance/retention.py +232 -0
  97. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  98. package/src/superlocalmemory/core/__init__.py +0 -0
  99. package/src/superlocalmemory/core/config.py +391 -0
  100. package/src/superlocalmemory/core/embeddings.py +293 -0
  101. package/src/superlocalmemory/core/engine.py +701 -0
  102. package/src/superlocalmemory/core/hooks.py +65 -0
  103. package/src/superlocalmemory/core/maintenance.py +172 -0
  104. package/src/superlocalmemory/core/modes.py +140 -0
  105. package/src/superlocalmemory/core/profiles.py +234 -0
  106. package/src/superlocalmemory/core/registry.py +117 -0
  107. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  108. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  109. package/src/superlocalmemory/encoding/__init__.py +0 -0
  110. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  111. package/src/superlocalmemory/encoding/emotional.py +125 -0
  112. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  113. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  114. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  115. package/src/superlocalmemory/encoding/foresight.py +91 -0
  116. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  117. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  118. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  119. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  120. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  121. package/src/superlocalmemory/encoding/type_router.py +235 -0
  122. package/src/superlocalmemory/hooks/__init__.py +3 -0
  123. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  124. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  125. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  126. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  127. package/src/superlocalmemory/infra/__init__.py +3 -0
  128. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  129. package/src/superlocalmemory/infra/backup.py +317 -0
  130. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  131. package/src/superlocalmemory/infra/event_bus.py +381 -0
  132. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  133. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  134. package/src/superlocalmemory/learning/__init__.py +0 -0
  135. package/src/superlocalmemory/learning/adaptive.py +172 -0
  136. package/src/superlocalmemory/learning/behavioral.py +490 -0
  137. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  138. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  139. package/src/superlocalmemory/learning/cross_project.py +399 -0
  140. package/src/superlocalmemory/learning/database.py +376 -0
  141. package/src/superlocalmemory/learning/engagement.py +323 -0
  142. package/src/superlocalmemory/learning/features.py +138 -0
  143. package/src/superlocalmemory/learning/feedback.py +316 -0
  144. package/src/superlocalmemory/learning/outcomes.py +255 -0
  145. package/src/superlocalmemory/learning/project_context.py +366 -0
  146. package/src/superlocalmemory/learning/ranker.py +155 -0
  147. package/src/superlocalmemory/learning/source_quality.py +303 -0
  148. package/src/superlocalmemory/learning/workflows.py +309 -0
  149. package/src/superlocalmemory/llm/__init__.py +0 -0
  150. package/src/superlocalmemory/llm/backbone.py +316 -0
  151. package/src/superlocalmemory/math/__init__.py +0 -0
  152. package/src/superlocalmemory/math/fisher.py +356 -0
  153. package/src/superlocalmemory/math/langevin.py +398 -0
  154. package/src/superlocalmemory/math/sheaf.py +257 -0
  155. package/src/superlocalmemory/mcp/__init__.py +0 -0
  156. package/src/superlocalmemory/mcp/resources.py +245 -0
  157. package/src/superlocalmemory/mcp/server.py +61 -0
  158. package/src/superlocalmemory/mcp/tools.py +18 -0
  159. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  160. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  161. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  162. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  163. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  164. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  165. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  166. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  167. package/src/superlocalmemory/retrieval/engine.py +390 -0
  168. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  169. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  170. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  171. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  172. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  173. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  174. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  175. package/src/superlocalmemory/server/__init__.py +1 -0
  176. package/src/superlocalmemory/server/api.py +248 -0
  177. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  178. package/src/superlocalmemory/server/routes/agents.py +107 -0
  179. package/src/superlocalmemory/server/routes/backup.py +91 -0
  180. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  181. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  182. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  183. package/src/superlocalmemory/server/routes/events.py +183 -0
  184. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  185. package/src/superlocalmemory/server/routes/learning.py +273 -0
  186. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  187. package/src/superlocalmemory/server/routes/memories.py +399 -0
  188. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  189. package/src/superlocalmemory/server/routes/stats.py +346 -0
  190. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  191. package/src/superlocalmemory/server/routes/ws.py +82 -0
  192. package/src/superlocalmemory/server/security_middleware.py +57 -0
  193. package/src/superlocalmemory/server/ui.py +245 -0
  194. package/src/superlocalmemory/storage/__init__.py +0 -0
  195. package/src/superlocalmemory/storage/access_control.py +182 -0
  196. package/src/superlocalmemory/storage/database.py +594 -0
  197. package/src/superlocalmemory/storage/migrations.py +303 -0
  198. package/src/superlocalmemory/storage/models.py +406 -0
  199. package/src/superlocalmemory/storage/schema.py +726 -0
  200. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  201. package/src/superlocalmemory/trust/__init__.py +0 -0
  202. package/src/superlocalmemory/trust/gate.py +130 -0
  203. package/src/superlocalmemory/trust/provenance.py +124 -0
  204. package/src/superlocalmemory/trust/scorer.py +347 -0
  205. package/src/superlocalmemory/trust/signals.py +153 -0
  206. package/ui/index.html +278 -5
  207. package/ui/js/auto-settings.js +70 -0
  208. package/ui/js/dashboard.js +90 -0
  209. package/ui/js/fact-detail.js +92 -0
  210. package/ui/js/feedback.js +2 -2
  211. package/ui/js/ide-status.js +102 -0
  212. package/ui/js/math-health.js +98 -0
  213. package/ui/js/recall-lab.js +127 -0
  214. package/ui/js/settings.js +2 -2
  215. package/ui/js/trust-dashboard.js +73 -0
  216. package/api_server.py +0 -724
  217. package/bin/aider-smart +0 -72
  218. package/bin/superlocalmemoryv2-learning +0 -4
  219. package/bin/superlocalmemoryv2-list +0 -3
  220. package/bin/superlocalmemoryv2-patterns +0 -4
  221. package/bin/superlocalmemoryv2-profile +0 -3
  222. package/bin/superlocalmemoryv2-recall +0 -3
  223. package/bin/superlocalmemoryv2-remember +0 -3
  224. package/bin/superlocalmemoryv2-reset +0 -3
  225. package/bin/superlocalmemoryv2-status +0 -3
  226. package/configs/chatgpt-desktop-mcp.json +0 -16
  227. package/configs/cursor-mcp.json +0 -15
  228. package/docs/SECURITY-QUICK-REFERENCE.md +0 -214
  229. package/hooks/memory-list-skill.js +0 -139
  230. package/hooks/memory-profile-skill.js +0 -273
  231. package/hooks/memory-recall-skill.js +0 -114
  232. package/hooks/memory-remember-skill.js +0 -127
  233. package/hooks/memory-reset-skill.js +0 -274
  234. package/mcp_server.py +0 -1800
  235. package/requirements-core.txt +0 -22
  236. package/requirements-learning.txt +0 -12
  237. package/requirements.txt +0 -12
  238. package/src/agent_registry.py +0 -411
  239. package/src/auth_middleware.py +0 -61
  240. package/src/auto_backup.py +0 -459
  241. package/src/behavioral/__init__.py +0 -49
  242. package/src/behavioral/behavioral_listener.py +0 -203
  243. package/src/behavioral/behavioral_patterns.py +0 -275
  244. package/src/behavioral/cross_project_transfer.py +0 -206
  245. package/src/behavioral/outcome_inference.py +0 -194
  246. package/src/behavioral/outcome_tracker.py +0 -193
  247. package/src/behavioral/tests/__init__.py +0 -4
  248. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  249. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  250. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  251. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  252. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  253. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  254. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  255. package/src/cache_manager.py +0 -518
  256. package/src/compliance/__init__.py +0 -48
  257. package/src/compliance/abac_engine.py +0 -149
  258. package/src/compliance/abac_middleware.py +0 -116
  259. package/src/compliance/audit_db.py +0 -215
  260. package/src/compliance/audit_logger.py +0 -148
  261. package/src/compliance/retention_manager.py +0 -289
  262. package/src/compliance/retention_scheduler.py +0 -186
  263. package/src/compliance/tests/__init__.py +0 -4
  264. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  265. package/src/compliance/tests/test_abac_engine.py +0 -124
  266. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  267. package/src/compliance/tests/test_audit_db.py +0 -123
  268. package/src/compliance/tests/test_audit_logger.py +0 -98
  269. package/src/compliance/tests/test_mcp_audit.py +0 -128
  270. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  271. package/src/compliance/tests/test_retention_manager.py +0 -131
  272. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  273. package/src/compression/__init__.py +0 -25
  274. package/src/compression/cli.py +0 -150
  275. package/src/compression/cold_storage.py +0 -217
  276. package/src/compression/config.py +0 -72
  277. package/src/compression/orchestrator.py +0 -133
  278. package/src/compression/tier2_compressor.py +0 -228
  279. package/src/compression/tier3_compressor.py +0 -153
  280. package/src/compression/tier_classifier.py +0 -148
  281. package/src/db_connection_manager.py +0 -536
  282. package/src/embedding_engine.py +0 -63
  283. package/src/embeddings/__init__.py +0 -47
  284. package/src/embeddings/cache.py +0 -70
  285. package/src/embeddings/cli.py +0 -113
  286. package/src/embeddings/constants.py +0 -47
  287. package/src/embeddings/database.py +0 -91
  288. package/src/embeddings/engine.py +0 -247
  289. package/src/embeddings/model_loader.py +0 -145
  290. package/src/event_bus.py +0 -562
  291. package/src/graph/__init__.py +0 -36
  292. package/src/graph/build_helpers.py +0 -74
  293. package/src/graph/cli.py +0 -87
  294. package/src/graph/cluster_builder.py +0 -188
  295. package/src/graph/cluster_summary.py +0 -148
  296. package/src/graph/constants.py +0 -47
  297. package/src/graph/edge_builder.py +0 -162
  298. package/src/graph/entity_extractor.py +0 -95
  299. package/src/graph/graph_core.py +0 -226
  300. package/src/graph/graph_search.py +0 -231
  301. package/src/graph/hierarchical.py +0 -207
  302. package/src/graph/schema.py +0 -99
  303. package/src/graph_engine.py +0 -52
  304. package/src/hnsw_index.py +0 -628
  305. package/src/hybrid_search.py +0 -46
  306. package/src/learning/__init__.py +0 -217
  307. package/src/learning/adaptive_ranker.py +0 -682
  308. package/src/learning/bootstrap/__init__.py +0 -69
  309. package/src/learning/bootstrap/constants.py +0 -93
  310. package/src/learning/bootstrap/db_queries.py +0 -316
  311. package/src/learning/bootstrap/sampling.py +0 -82
  312. package/src/learning/bootstrap/text_utils.py +0 -71
  313. package/src/learning/cross_project_aggregator.py +0 -857
  314. package/src/learning/db/__init__.py +0 -40
  315. package/src/learning/db/constants.py +0 -44
  316. package/src/learning/db/schema.py +0 -279
  317. package/src/learning/engagement_tracker.py +0 -628
  318. package/src/learning/feature_extractor.py +0 -708
  319. package/src/learning/feedback_collector.py +0 -806
  320. package/src/learning/learning_db.py +0 -915
  321. package/src/learning/project_context_manager.py +0 -572
  322. package/src/learning/ranking/__init__.py +0 -33
  323. package/src/learning/ranking/constants.py +0 -84
  324. package/src/learning/ranking/helpers.py +0 -278
  325. package/src/learning/source_quality_scorer.py +0 -676
  326. package/src/learning/synthetic_bootstrap.py +0 -755
  327. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  328. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  329. package/src/learning/tests/test_aggregator.py +0 -306
  330. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  331. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  332. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  333. package/src/learning/tests/test_feedback_collector.py +0 -294
  334. package/src/learning/tests/test_learning_db.py +0 -602
  335. package/src/learning/tests/test_learning_db_v28.py +0 -110
  336. package/src/learning/tests/test_learning_init_v28.py +0 -48
  337. package/src/learning/tests/test_outcome_signals.py +0 -48
  338. package/src/learning/tests/test_project_context.py +0 -292
  339. package/src/learning/tests/test_schema_migration.py +0 -319
  340. package/src/learning/tests/test_signal_inference.py +0 -397
  341. package/src/learning/tests/test_source_quality.py +0 -351
  342. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  343. package/src/learning/tests/test_workflow_miner.py +0 -318
  344. package/src/learning/workflow_pattern_miner.py +0 -655
  345. package/src/lifecycle/__init__.py +0 -54
  346. package/src/lifecycle/bounded_growth.py +0 -239
  347. package/src/lifecycle/compaction_engine.py +0 -226
  348. package/src/lifecycle/lifecycle_engine.py +0 -355
  349. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  350. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  351. package/src/lifecycle/retention_policy.py +0 -285
  352. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  353. package/src/lifecycle/tests/test_compaction.py +0 -179
  354. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  355. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  356. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  357. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  358. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  359. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  360. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  361. package/src/mcp_tools_v28.py +0 -281
  362. package/src/memory/__init__.py +0 -36
  363. package/src/memory/cli.py +0 -205
  364. package/src/memory/constants.py +0 -39
  365. package/src/memory/helpers.py +0 -28
  366. package/src/memory/schema.py +0 -166
  367. package/src/memory-profiles.py +0 -595
  368. package/src/memory-reset.py +0 -491
  369. package/src/memory_compression.py +0 -989
  370. package/src/memory_store_v2.py +0 -1155
  371. package/src/migrate_v1_to_v2.py +0 -629
  372. package/src/pattern_learner.py +0 -34
  373. package/src/patterns/__init__.py +0 -24
  374. package/src/patterns/analyzers.py +0 -251
  375. package/src/patterns/learner.py +0 -271
  376. package/src/patterns/scoring.py +0 -171
  377. package/src/patterns/store.py +0 -225
  378. package/src/patterns/terminology.py +0 -140
  379. package/src/provenance_tracker.py +0 -312
  380. package/src/qualixar_attribution.py +0 -139
  381. package/src/qualixar_watermark.py +0 -78
  382. package/src/query_optimizer.py +0 -511
  383. package/src/rate_limiter.py +0 -83
  384. package/src/search/__init__.py +0 -20
  385. package/src/search/cli.py +0 -77
  386. package/src/search/constants.py +0 -26
  387. package/src/search/engine.py +0 -241
  388. package/src/search/fusion.py +0 -122
  389. package/src/search/index_loader.py +0 -114
  390. package/src/search/methods.py +0 -162
  391. package/src/search_engine_v2.py +0 -401
  392. package/src/setup_validator.py +0 -482
  393. package/src/subscription_manager.py +0 -391
  394. package/src/tree/__init__.py +0 -59
  395. package/src/tree/builder.py +0 -185
  396. package/src/tree/nodes.py +0 -202
  397. package/src/tree/queries.py +0 -257
  398. package/src/tree/schema.py +0 -80
  399. package/src/tree_manager.py +0 -19
  400. package/src/trust/__init__.py +0 -45
  401. package/src/trust/constants.py +0 -66
  402. package/src/trust/queries.py +0 -157
  403. package/src/trust/schema.py +0 -95
  404. package/src/trust/scorer.py +0 -299
  405. package/src/trust/signals.py +0 -95
  406. package/src/trust_scorer.py +0 -44
  407. package/ui/app.js +0 -1588
  408. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  409. package/ui/js/graph-cytoscape.js +0 -1168
  410. package/ui/js/graph-d3-backup.js +0 -32
  411. package/ui/js/graph.js +0 -32
  412. package/ui_server.py +0 -266
  413. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  414. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  415. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  416. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  417. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  418. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  419. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  420. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  421. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  422. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  423. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  424. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  425. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  426. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  427. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  428. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  429. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  430. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  431. /package/{completions → ide/completions}/slm.bash +0 -0
  432. /package/{completions → ide/completions}/slm.zsh +0 -0
  433. /package/{configs → ide/configs}/cody-commands.json +0 -0
  434. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
@@ -1,602 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: MIT
3
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
4
- import json
5
- import sqlite3
6
- import threading
7
- import time
8
- from pathlib import Path
9
-
10
- import pytest
11
-
12
-
13
- # ---------------------------------------------------------------------------
14
- # Fixtures
15
- # ---------------------------------------------------------------------------
16
-
17
- @pytest.fixture(autouse=True)
18
- def reset_singleton():
19
- """Reset LearningDB singleton between tests to avoid cross-test pollution."""
20
- from src.learning.learning_db import LearningDB
21
- LearningDB.reset_instance()
22
- yield
23
- LearningDB.reset_instance()
24
-
25
-
26
- @pytest.fixture
27
- def learning_db(tmp_path):
28
- """Create a fresh LearningDB backed by a temp directory."""
29
- from src.learning.learning_db import LearningDB
30
- db_path = tmp_path / "learning.db"
31
- db = LearningDB(db_path=db_path)
32
- return db
33
-
34
-
35
- # ---------------------------------------------------------------------------
36
- # Schema Initialisation
37
- # ---------------------------------------------------------------------------
38
-
39
- class TestSchema:
40
- """Verify all 6 tables and indexes are created correctly."""
41
-
42
- def test_all_tables_exist(self, learning_db):
43
- conn = learning_db._get_connection()
44
- cursor = conn.cursor()
45
- cursor.execute(
46
- "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
47
- )
48
- tables = {row[0] for row in cursor.fetchall()}
49
- conn.close()
50
-
51
- expected = {
52
- "transferable_patterns",
53
- "workflow_patterns",
54
- "ranking_feedback",
55
- "ranking_models",
56
- "source_quality",
57
- "engagement_metrics",
58
- }
59
- assert expected.issubset(tables), f"Missing tables: {expected - tables}"
60
-
61
- def test_indexes_exist(self, learning_db):
62
- conn = learning_db._get_connection()
63
- cursor = conn.cursor()
64
- cursor.execute(
65
- "SELECT name FROM sqlite_master WHERE type='index' ORDER BY name"
66
- )
67
- indexes = {row[0] for row in cursor.fetchall()}
68
- conn.close()
69
-
70
- expected_indexes = {
71
- "idx_feedback_query",
72
- "idx_feedback_memory",
73
- "idx_feedback_channel",
74
- "idx_feedback_created",
75
- "idx_patterns_type",
76
- "idx_workflow_type",
77
- "idx_engagement_date",
78
- }
79
- assert expected_indexes.issubset(indexes), (
80
- f"Missing indexes: {expected_indexes - indexes}"
81
- )
82
-
83
- def test_wal_mode_enabled(self, learning_db):
84
- conn = learning_db._get_connection()
85
- cursor = conn.cursor()
86
- cursor.execute("PRAGMA journal_mode")
87
- mode = cursor.fetchone()[0]
88
- conn.close()
89
- assert mode.lower() == "wal"
90
-
91
-
92
- # ---------------------------------------------------------------------------
93
- # Feedback Operations
94
- # ---------------------------------------------------------------------------
95
-
96
- class TestFeedback:
97
- """Tests for store_feedback / get_feedback_count / get_unique_query_count."""
98
-
99
- def test_store_feedback_basic(self, learning_db):
100
- row_id = learning_db.store_feedback(
101
- query_hash="abc123",
102
- memory_id=42,
103
- signal_type="mcp_used",
104
- signal_value=1.0,
105
- channel="mcp",
106
- )
107
- assert row_id is not None
108
- assert row_id >= 1
109
-
110
- def test_store_feedback_with_all_fields(self, learning_db):
111
- row_id = learning_db.store_feedback(
112
- query_hash="def456",
113
- memory_id=99,
114
- signal_type="dashboard_click",
115
- signal_value=0.8,
116
- channel="dashboard",
117
- query_keywords="deploy,fastapi",
118
- rank_position=3,
119
- source_tool="cursor",
120
- dwell_time=12.5,
121
- )
122
- assert row_id is not None
123
-
124
- # Verify all fields were stored
125
- rows = learning_db.get_feedback_for_training()
126
- assert len(rows) == 1
127
- row = rows[0]
128
- assert row["query_hash"] == "def456"
129
- assert row["memory_id"] == 99
130
- assert row["signal_type"] == "dashboard_click"
131
- assert row["signal_value"] == 0.8
132
- assert row["channel"] == "dashboard"
133
- assert row["query_keywords"] == "deploy,fastapi"
134
- assert row["rank_position"] == 3
135
- assert row["source_tool"] == "cursor"
136
-
137
- def test_feedback_count(self, learning_db):
138
- assert learning_db.get_feedback_count() == 0
139
-
140
- for i in range(5):
141
- learning_db.store_feedback(
142
- query_hash=f"q{i}",
143
- memory_id=i,
144
- signal_type="mcp_used",
145
- )
146
- assert learning_db.get_feedback_count() == 5
147
-
148
- def test_unique_query_count(self, learning_db):
149
- # 3 feedback entries across 2 distinct queries
150
- learning_db.store_feedback(query_hash="q1", memory_id=1, signal_type="mcp_used")
151
- learning_db.store_feedback(query_hash="q1", memory_id=2, signal_type="mcp_used")
152
- learning_db.store_feedback(query_hash="q2", memory_id=3, signal_type="cli_useful")
153
-
154
- assert learning_db.get_unique_query_count() == 2
155
-
156
- def test_feedback_for_training_limit(self, learning_db):
157
- for i in range(15):
158
- learning_db.store_feedback(
159
- query_hash=f"q{i % 5}",
160
- memory_id=i,
161
- signal_type="mcp_used",
162
- )
163
-
164
- # Fetch with limit
165
- rows = learning_db.get_feedback_for_training(limit=5)
166
- assert len(rows) == 5
167
-
168
- def test_feedback_for_training_order(self, learning_db):
169
- """Newest first ordering."""
170
- learning_db.store_feedback(query_hash="old", memory_id=1, signal_type="mcp_used")
171
- time.sleep(0.05)
172
- learning_db.store_feedback(query_hash="new", memory_id=2, signal_type="cli_useful")
173
-
174
- rows = learning_db.get_feedback_for_training()
175
- assert len(rows) == 2
176
- assert rows[0]["query_hash"] == "new"
177
-
178
- def test_signal_value_variations(self, learning_db):
179
- """Various signal values: 0.0, 0.5, 1.0."""
180
- for sv in [0.0, 0.4, 0.7, 1.0]:
181
- learning_db.store_feedback(
182
- query_hash="q",
183
- memory_id=1,
184
- signal_type="mcp_used",
185
- signal_value=sv,
186
- )
187
- rows = learning_db.get_feedback_for_training()
188
- values = sorted(r["signal_value"] for r in rows)
189
- assert values == [0.0, 0.4, 0.7, 1.0]
190
-
191
-
192
- # ---------------------------------------------------------------------------
193
- # Transferable Patterns
194
- # ---------------------------------------------------------------------------
195
-
196
- class TestTransferablePatterns:
197
- def test_upsert_insert(self, learning_db):
198
- row_id = learning_db.upsert_transferable_pattern(
199
- pattern_type="preference",
200
- key="frontend_framework",
201
- value="react",
202
- confidence=0.85,
203
- evidence_count=12,
204
- )
205
- assert row_id >= 1
206
-
207
- patterns = learning_db.get_transferable_patterns()
208
- assert len(patterns) == 1
209
- assert patterns[0]["key"] == "frontend_framework"
210
- assert patterns[0]["value"] == "react"
211
-
212
- def test_upsert_update(self, learning_db):
213
- """Second upsert with same type+key should UPDATE, not insert."""
214
- learning_db.upsert_transferable_pattern(
215
- pattern_type="preference",
216
- key="lang",
217
- value="python",
218
- confidence=0.6,
219
- evidence_count=5,
220
- )
221
- learning_db.upsert_transferable_pattern(
222
- pattern_type="preference",
223
- key="lang",
224
- value="typescript",
225
- confidence=0.8,
226
- evidence_count=10,
227
- )
228
-
229
- patterns = learning_db.get_transferable_patterns()
230
- assert len(patterns) == 1
231
- assert patterns[0]["value"] == "typescript"
232
- assert patterns[0]["confidence"] == 0.8
233
- assert patterns[0]["evidence_count"] == 10
234
-
235
- def test_get_with_confidence_filter(self, learning_db):
236
- learning_db.upsert_transferable_pattern(
237
- pattern_type="preference", key="a", value="v1",
238
- confidence=0.3, evidence_count=1,
239
- )
240
- learning_db.upsert_transferable_pattern(
241
- pattern_type="preference", key="b", value="v2",
242
- confidence=0.9, evidence_count=10,
243
- )
244
-
245
- high = learning_db.get_transferable_patterns(min_confidence=0.6)
246
- assert len(high) == 1
247
- assert high[0]["key"] == "b"
248
-
249
- def test_get_with_type_filter(self, learning_db):
250
- learning_db.upsert_transferable_pattern(
251
- pattern_type="preference", key="k1", value="v1",
252
- confidence=0.7, evidence_count=5,
253
- )
254
- learning_db.upsert_transferable_pattern(
255
- pattern_type="style", key="k2", value="v2",
256
- confidence=0.8, evidence_count=8,
257
- )
258
-
259
- prefs = learning_db.get_transferable_patterns(pattern_type="preference")
260
- assert len(prefs) == 1
261
- assert prefs[0]["key"] == "k1"
262
-
263
- def test_contradictions_stored_as_json(self, learning_db):
264
- learning_db.upsert_transferable_pattern(
265
- pattern_type="preference",
266
- key="db",
267
- value="postgres",
268
- confidence=0.7,
269
- evidence_count=5,
270
- contradictions=["Profile 'work' prefers 'mysql'"],
271
- )
272
- patterns = learning_db.get_transferable_patterns()
273
- raw = patterns[0]["contradictions"]
274
- parsed = json.loads(raw) if isinstance(raw, str) else raw
275
- assert parsed == ["Profile 'work' prefers 'mysql'"]
276
-
277
-
278
- # ---------------------------------------------------------------------------
279
- # Workflow Patterns
280
- # ---------------------------------------------------------------------------
281
-
282
- class TestWorkflowPatterns:
283
- def test_store_and_get(self, learning_db):
284
- learning_db.store_workflow_pattern(
285
- pattern_type="sequence",
286
- pattern_key="docs -> code -> test",
287
- pattern_value='{"sequence": ["docs", "code", "test"]}',
288
- confidence=0.45,
289
- evidence_count=12,
290
- )
291
-
292
- patterns = learning_db.get_workflow_patterns()
293
- assert len(patterns) == 1
294
- assert patterns[0]["pattern_key"] == "docs -> code -> test"
295
-
296
- def test_get_with_type_filter(self, learning_db):
297
- learning_db.store_workflow_pattern(
298
- pattern_type="sequence", pattern_key="a", pattern_value="v",
299
- confidence=0.5, evidence_count=5,
300
- )
301
- learning_db.store_workflow_pattern(
302
- pattern_type="temporal", pattern_key="morning", pattern_value="{}",
303
- confidence=0.6, evidence_count=8,
304
- )
305
-
306
- seq = learning_db.get_workflow_patterns(pattern_type="sequence")
307
- assert len(seq) == 1
308
- assert seq[0]["pattern_type"] == "sequence"
309
-
310
- def test_clear_all(self, learning_db):
311
- for i in range(3):
312
- learning_db.store_workflow_pattern(
313
- pattern_type="sequence",
314
- pattern_key=f"p{i}",
315
- pattern_value="{}",
316
- )
317
- learning_db.clear_workflow_patterns()
318
- assert learning_db.get_workflow_patterns() == []
319
-
320
- def test_clear_by_type(self, learning_db):
321
- learning_db.store_workflow_pattern(
322
- pattern_type="sequence", pattern_key="a", pattern_value="{}",
323
- )
324
- learning_db.store_workflow_pattern(
325
- pattern_type="temporal", pattern_key="b", pattern_value="{}",
326
- )
327
- learning_db.clear_workflow_patterns(pattern_type="sequence")
328
-
329
- remaining = learning_db.get_workflow_patterns()
330
- assert len(remaining) == 1
331
- assert remaining[0]["pattern_type"] == "temporal"
332
-
333
- def test_confidence_filter(self, learning_db):
334
- learning_db.store_workflow_pattern(
335
- pattern_type="sequence", pattern_key="low",
336
- pattern_value="{}", confidence=0.2,
337
- )
338
- learning_db.store_workflow_pattern(
339
- pattern_type="sequence", pattern_key="high",
340
- pattern_value="{}", confidence=0.8,
341
- )
342
-
343
- high = learning_db.get_workflow_patterns(min_confidence=0.5)
344
- assert len(high) == 1
345
- assert high[0]["pattern_key"] == "high"
346
-
347
-
348
- # ---------------------------------------------------------------------------
349
- # Source Quality
350
- # ---------------------------------------------------------------------------
351
-
352
- class TestSourceQuality:
353
- def test_update_and_get(self, learning_db):
354
- learning_db.update_source_quality("mcp:claude", 8, 10)
355
-
356
- scores = learning_db.get_source_scores()
357
- assert "mcp:claude" in scores
358
- # Beta-Binomial: (1 + 8) / (2 + 10) = 0.75
359
- assert abs(scores["mcp:claude"] - 0.75) < 0.001
360
-
361
- def test_beta_binomial_calculation(self, learning_db):
362
- """Verify the Beta-Binomial formula: (1+pos)/(2+total)."""
363
- cases = [
364
- (0, 0, 0.5), # No data = neutral
365
- (5, 10, 0.5), # 50% positive = 0.5
366
- (1, 10, 2.0 / 12.0), # Low positive
367
- (9, 10, 10.0 / 12.0), # High positive
368
- ]
369
- for pos, total, expected in cases:
370
- learning_db.update_source_quality(f"src_{pos}_{total}", pos, total)
371
- scores = learning_db.get_source_scores()
372
- actual = scores[f"src_{pos}_{total}"]
373
- assert abs(actual - expected) < 0.001, (
374
- f"pos={pos}, total={total}: expected {expected}, got {actual}"
375
- )
376
-
377
- def test_upsert_on_conflict(self, learning_db):
378
- """Updating same source_id should overwrite, not duplicate."""
379
- learning_db.update_source_quality("mcp:cursor", 2, 10)
380
- learning_db.update_source_quality("mcp:cursor", 8, 10)
381
-
382
- scores = learning_db.get_source_scores()
383
- assert abs(scores["mcp:cursor"] - 0.75) < 0.001
384
-
385
- def test_empty_scores(self, learning_db):
386
- scores = learning_db.get_source_scores()
387
- assert scores == {}
388
-
389
-
390
- # ---------------------------------------------------------------------------
391
- # Model Metadata
392
- # ---------------------------------------------------------------------------
393
-
394
- class TestModelMetadata:
395
- def test_record_and_get_latest(self, learning_db):
396
- learning_db.record_model_training(
397
- model_version="v1",
398
- training_samples=500,
399
- synthetic_samples=200,
400
- real_samples=300,
401
- ndcg_at_10=0.85,
402
- model_path="/tmp/model.txt",
403
- )
404
- latest = learning_db.get_latest_model()
405
- assert latest is not None
406
- assert latest["model_version"] == "v1"
407
- assert latest["training_samples"] == 500
408
- assert latest["ndcg_at_10"] == 0.85
409
-
410
- def test_latest_model_ordering(self, learning_db):
411
- """Latest model should be the one with the highest rowid."""
412
- learning_db.record_model_training("v1", 100)
413
- # SQLite CURRENT_TIMESTAMP has second precision, so sleep long enough
414
- time.sleep(1.1)
415
- learning_db.record_model_training("v2", 200)
416
-
417
- latest = learning_db.get_latest_model()
418
- assert latest["model_version"] == "v2"
419
-
420
- def test_no_models(self, learning_db):
421
- assert learning_db.get_latest_model() is None
422
-
423
-
424
- # ---------------------------------------------------------------------------
425
- # Engagement Metrics
426
- # ---------------------------------------------------------------------------
427
-
428
- class TestEngagement:
429
- def test_increment_memories_created(self, learning_db):
430
- learning_db.increment_engagement("memories_created", count=3)
431
- history = learning_db.get_engagement_history(days=1)
432
- assert len(history) >= 1
433
- assert history[0]["memories_created"] == 3
434
-
435
- def test_increment_multiple_types(self, learning_db):
436
- learning_db.increment_engagement("memories_created", count=2)
437
- learning_db.increment_engagement("recalls_performed", count=5)
438
- learning_db.increment_engagement("feedback_signals", count=1)
439
-
440
- history = learning_db.get_engagement_history(days=1)
441
- row = history[0]
442
- assert row["memories_created"] == 2
443
- assert row["recalls_performed"] == 5
444
- assert row["feedback_signals"] == 1
445
-
446
- def test_invalid_metric_type_ignored(self, learning_db):
447
- """Invalid metric types should be silently ignored."""
448
- learning_db.increment_engagement("invalid_metric", count=1)
449
- # No row created if no valid metric incremented
450
- history = learning_db.get_engagement_history(days=1)
451
- assert len(history) == 0
452
-
453
- def test_source_tracking(self, learning_db):
454
- learning_db.increment_engagement(
455
- "memories_created", count=1, source="claude-desktop"
456
- )
457
- learning_db.increment_engagement(
458
- "recalls_performed", count=1, source="cursor"
459
- )
460
-
461
- history = learning_db.get_engagement_history(days=1)
462
- sources = json.loads(history[0]["active_sources"])
463
- assert "claude-desktop" in sources
464
- assert "cursor" in sources
465
-
466
- def test_source_deduplication(self, learning_db):
467
- """Same source added twice should appear only once."""
468
- learning_db.increment_engagement("memories_created", count=1, source="cli")
469
- learning_db.increment_engagement("recalls_performed", count=1, source="cli")
470
-
471
- history = learning_db.get_engagement_history(days=1)
472
- sources = json.loads(history[0]["active_sources"])
473
- assert sources.count("cli") == 1
474
-
475
-
476
- # ---------------------------------------------------------------------------
477
- # Stats & Reset
478
- # ---------------------------------------------------------------------------
479
-
480
- class TestStatsAndReset:
481
- def test_get_stats_empty(self, learning_db):
482
- stats = learning_db.get_stats()
483
- assert stats["feedback_count"] == 0
484
- assert stats["unique_queries"] == 0
485
- assert stats["transferable_patterns"] == 0
486
- assert stats["high_confidence_patterns"] == 0
487
- assert stats["workflow_patterns"] == 0
488
- assert stats["tracked_sources"] == 0
489
- assert stats["models_trained"] == 0
490
- assert stats["latest_model_version"] is None
491
- assert stats["db_size_bytes"] > 0 # DB file exists
492
-
493
- def test_get_stats_populated(self, learning_db):
494
- learning_db.store_feedback(
495
- query_hash="q1", memory_id=1, signal_type="mcp_used",
496
- )
497
- learning_db.upsert_transferable_pattern(
498
- pattern_type="preference", key="lang", value="python",
499
- confidence=0.9, evidence_count=10,
500
- )
501
- learning_db.store_workflow_pattern(
502
- pattern_type="sequence", pattern_key="a -> b",
503
- pattern_value="{}", confidence=0.5, evidence_count=5,
504
- )
505
- learning_db.update_source_quality("cli", 3, 5)
506
- learning_db.record_model_training("v1", 100, ndcg_at_10=0.8)
507
-
508
- stats = learning_db.get_stats()
509
- assert stats["feedback_count"] == 1
510
- assert stats["transferable_patterns"] == 1
511
- assert stats["high_confidence_patterns"] == 1
512
- assert stats["workflow_patterns"] == 1
513
- assert stats["tracked_sources"] == 1
514
- assert stats["models_trained"] == 1
515
- assert stats["latest_model_version"] == "v1"
516
- assert stats["latest_model_ndcg"] == 0.8
517
-
518
- def test_reset_clears_all(self, learning_db):
519
- learning_db.store_feedback(query_hash="q", memory_id=1, signal_type="x")
520
- learning_db.upsert_transferable_pattern(
521
- pattern_type="p", key="k", value="v", confidence=0.5, evidence_count=1,
522
- )
523
- learning_db.store_workflow_pattern(
524
- pattern_type="s", pattern_key="k", pattern_value="v",
525
- )
526
- learning_db.update_source_quality("src", 1, 1)
527
- learning_db.record_model_training("v1", 10)
528
- learning_db.increment_engagement("memories_created", count=1)
529
-
530
- learning_db.reset()
531
-
532
- stats = learning_db.get_stats()
533
- assert stats["feedback_count"] == 0
534
- assert stats["transferable_patterns"] == 0
535
- assert stats["workflow_patterns"] == 0
536
- assert stats["tracked_sources"] == 0
537
- assert stats["models_trained"] == 0
538
-
539
-
540
- # ---------------------------------------------------------------------------
541
- # Concurrency
542
- # ---------------------------------------------------------------------------
543
-
544
- class TestConcurrency:
545
- def test_concurrent_writes(self, learning_db):
546
- """10 threads writing simultaneously should produce zero errors."""
547
- errors = []
548
-
549
- def writer(thread_id):
550
- try:
551
- for i in range(10):
552
- learning_db.store_feedback(
553
- query_hash=f"q_t{thread_id}_{i}",
554
- memory_id=thread_id * 100 + i,
555
- signal_type="mcp_used",
556
- signal_value=1.0,
557
- channel="mcp",
558
- )
559
- except Exception as e:
560
- errors.append(str(e))
561
-
562
- threads = [threading.Thread(target=writer, args=(tid,)) for tid in range(10)]
563
- for t in threads:
564
- t.start()
565
- for t in threads:
566
- t.join(timeout=30)
567
-
568
- assert errors == [], f"Concurrent write errors: {errors}"
569
- assert learning_db.get_feedback_count() == 100
570
-
571
-
572
- # ---------------------------------------------------------------------------
573
- # Singleton Pattern
574
- # ---------------------------------------------------------------------------
575
-
576
- class TestSingleton:
577
- def test_get_instance_returns_same_object(self, tmp_path):
578
- from src.learning.learning_db import LearningDB
579
- LearningDB.reset_instance()
580
-
581
- db_path = tmp_path / "singleton_test.db"
582
- a = LearningDB.get_instance(db_path)
583
- b = LearningDB.get_instance(db_path)
584
- assert a is b
585
-
586
- def test_different_paths_different_instances(self, tmp_path):
587
- from src.learning.learning_db import LearningDB
588
- LearningDB.reset_instance()
589
-
590
- a = LearningDB.get_instance(tmp_path / "a.db")
591
- b = LearningDB.get_instance(tmp_path / "b.db")
592
- assert a is not b
593
-
594
- def test_reset_instance_clears(self, tmp_path):
595
- from src.learning.learning_db import LearningDB
596
- LearningDB.reset_instance()
597
-
598
- db_path = tmp_path / "reset_test.db"
599
- a = LearningDB.get_instance(db_path)
600
- LearningDB.reset_instance(db_path)
601
- b = LearningDB.get_instance(db_path)
602
- assert a is not b