superlocalmemory 2.8.6 → 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 (431) hide show
  1. package/LICENSE +9 -1
  2. package/NOTICE +63 -0
  3. package/README.md +165 -480
  4. package/bin/slm +17 -449
  5. package/bin/slm-npm +1 -1
  6. package/conftest.py +5 -0
  7. package/docs/api-reference.md +284 -0
  8. package/docs/architecture.md +149 -0
  9. package/docs/auto-memory.md +150 -0
  10. package/docs/cli-reference.md +276 -0
  11. package/docs/compliance.md +191 -0
  12. package/docs/configuration.md +182 -0
  13. package/docs/getting-started.md +102 -0
  14. package/docs/ide-setup.md +261 -0
  15. package/docs/mcp-tools.md +220 -0
  16. package/docs/migration-from-v2.md +170 -0
  17. package/docs/profiles.md +173 -0
  18. package/docs/troubleshooting.md +310 -0
  19. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  20. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  21. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  22. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  23. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  24. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  25. package/ide/configs/cursor-mcp.json +15 -0
  26. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  27. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  28. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  29. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  30. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  31. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  32. package/{configs → ide/configs}/zed-mcp.json +2 -2
  33. package/{hooks → ide/hooks}/context-hook.js +9 -20
  34. package/ide/hooks/memory-list-skill.js +70 -0
  35. package/ide/hooks/memory-profile-skill.js +101 -0
  36. package/ide/hooks/memory-recall-skill.js +62 -0
  37. package/ide/hooks/memory-remember-skill.js +68 -0
  38. package/ide/hooks/memory-reset-skill.js +160 -0
  39. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  40. package/ide/integrations/langchain/README.md +106 -0
  41. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  42. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  43. package/ide/integrations/langchain/pyproject.toml +38 -0
  44. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  45. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  46. package/ide/integrations/langchain/tests/test_security.py +117 -0
  47. package/ide/integrations/llamaindex/README.md +81 -0
  48. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  49. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  50. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  51. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  52. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  53. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  54. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  55. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  56. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  57. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  58. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  59. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  60. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  61. package/package.json +13 -22
  62. package/pyproject.toml +85 -0
  63. package/scripts/build-dmg.sh +417 -0
  64. package/scripts/install-skills.ps1 +334 -0
  65. package/scripts/postinstall.js +2 -2
  66. package/scripts/start-dashboard.ps1 +52 -0
  67. package/scripts/start-dashboard.sh +41 -0
  68. package/scripts/sync-wiki.ps1 +127 -0
  69. package/scripts/sync-wiki.sh +82 -0
  70. package/scripts/test-dmg.sh +161 -0
  71. package/scripts/test-npm-package.ps1 +252 -0
  72. package/scripts/test-npm-package.sh +207 -0
  73. package/scripts/verify-install.ps1 +294 -0
  74. package/scripts/verify-install.sh +266 -0
  75. package/src/superlocalmemory/__init__.py +0 -0
  76. package/src/superlocalmemory/attribution/__init__.py +9 -0
  77. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  78. package/src/superlocalmemory/attribution/signer.py +153 -0
  79. package/src/superlocalmemory/attribution/watermark.py +189 -0
  80. package/src/superlocalmemory/cli/__init__.py +5 -0
  81. package/src/superlocalmemory/cli/commands.py +245 -0
  82. package/src/superlocalmemory/cli/main.py +89 -0
  83. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  84. package/src/superlocalmemory/cli/post_install.py +99 -0
  85. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  86. package/src/superlocalmemory/compliance/__init__.py +0 -0
  87. package/src/superlocalmemory/compliance/abac.py +204 -0
  88. package/src/superlocalmemory/compliance/audit.py +314 -0
  89. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  90. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  91. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  92. package/src/superlocalmemory/compliance/retention.py +232 -0
  93. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  94. package/src/superlocalmemory/core/__init__.py +0 -0
  95. package/src/superlocalmemory/core/config.py +391 -0
  96. package/src/superlocalmemory/core/embeddings.py +293 -0
  97. package/src/superlocalmemory/core/engine.py +701 -0
  98. package/src/superlocalmemory/core/hooks.py +65 -0
  99. package/src/superlocalmemory/core/maintenance.py +172 -0
  100. package/src/superlocalmemory/core/modes.py +140 -0
  101. package/src/superlocalmemory/core/profiles.py +234 -0
  102. package/src/superlocalmemory/core/registry.py +117 -0
  103. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  104. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  105. package/src/superlocalmemory/encoding/__init__.py +0 -0
  106. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  107. package/src/superlocalmemory/encoding/emotional.py +125 -0
  108. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  109. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  110. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  111. package/src/superlocalmemory/encoding/foresight.py +91 -0
  112. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  113. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  114. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  115. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  116. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  117. package/src/superlocalmemory/encoding/type_router.py +235 -0
  118. package/src/superlocalmemory/hooks/__init__.py +3 -0
  119. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  120. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  121. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  122. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  123. package/src/superlocalmemory/infra/__init__.py +3 -0
  124. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  125. package/src/superlocalmemory/infra/backup.py +317 -0
  126. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  127. package/src/superlocalmemory/infra/event_bus.py +381 -0
  128. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  129. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  130. package/src/superlocalmemory/learning/__init__.py +0 -0
  131. package/src/superlocalmemory/learning/adaptive.py +172 -0
  132. package/src/superlocalmemory/learning/behavioral.py +490 -0
  133. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  134. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  135. package/src/superlocalmemory/learning/cross_project.py +399 -0
  136. package/src/superlocalmemory/learning/database.py +376 -0
  137. package/src/superlocalmemory/learning/engagement.py +323 -0
  138. package/src/superlocalmemory/learning/features.py +138 -0
  139. package/src/superlocalmemory/learning/feedback.py +316 -0
  140. package/src/superlocalmemory/learning/outcomes.py +255 -0
  141. package/src/superlocalmemory/learning/project_context.py +366 -0
  142. package/src/superlocalmemory/learning/ranker.py +155 -0
  143. package/src/superlocalmemory/learning/source_quality.py +303 -0
  144. package/src/superlocalmemory/learning/workflows.py +309 -0
  145. package/src/superlocalmemory/llm/__init__.py +0 -0
  146. package/src/superlocalmemory/llm/backbone.py +316 -0
  147. package/src/superlocalmemory/math/__init__.py +0 -0
  148. package/src/superlocalmemory/math/fisher.py +356 -0
  149. package/src/superlocalmemory/math/langevin.py +398 -0
  150. package/src/superlocalmemory/math/sheaf.py +257 -0
  151. package/src/superlocalmemory/mcp/__init__.py +0 -0
  152. package/src/superlocalmemory/mcp/resources.py +245 -0
  153. package/src/superlocalmemory/mcp/server.py +61 -0
  154. package/src/superlocalmemory/mcp/tools.py +18 -0
  155. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  156. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  157. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  158. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  159. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  160. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  161. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  162. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  163. package/src/superlocalmemory/retrieval/engine.py +390 -0
  164. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  165. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  166. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  167. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  168. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  169. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  170. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  171. package/src/superlocalmemory/server/__init__.py +1 -0
  172. package/src/superlocalmemory/server/api.py +248 -0
  173. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  174. package/src/superlocalmemory/server/routes/agents.py +107 -0
  175. package/src/superlocalmemory/server/routes/backup.py +91 -0
  176. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  177. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  178. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  179. package/src/superlocalmemory/server/routes/events.py +183 -0
  180. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  181. package/src/superlocalmemory/server/routes/learning.py +273 -0
  182. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  183. package/src/superlocalmemory/server/routes/memories.py +399 -0
  184. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  185. package/src/superlocalmemory/server/routes/stats.py +346 -0
  186. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  187. package/src/superlocalmemory/server/routes/ws.py +82 -0
  188. package/src/superlocalmemory/server/security_middleware.py +57 -0
  189. package/src/superlocalmemory/server/ui.py +245 -0
  190. package/src/superlocalmemory/storage/__init__.py +0 -0
  191. package/src/superlocalmemory/storage/access_control.py +182 -0
  192. package/src/superlocalmemory/storage/database.py +594 -0
  193. package/src/superlocalmemory/storage/migrations.py +303 -0
  194. package/src/superlocalmemory/storage/models.py +406 -0
  195. package/src/superlocalmemory/storage/schema.py +726 -0
  196. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  197. package/src/superlocalmemory/trust/__init__.py +0 -0
  198. package/src/superlocalmemory/trust/gate.py +130 -0
  199. package/src/superlocalmemory/trust/provenance.py +124 -0
  200. package/src/superlocalmemory/trust/scorer.py +347 -0
  201. package/src/superlocalmemory/trust/signals.py +153 -0
  202. package/ui/index.html +278 -5
  203. package/ui/js/auto-settings.js +70 -0
  204. package/ui/js/dashboard.js +90 -0
  205. package/ui/js/fact-detail.js +92 -0
  206. package/ui/js/feedback.js +2 -2
  207. package/ui/js/ide-status.js +102 -0
  208. package/ui/js/math-health.js +98 -0
  209. package/ui/js/recall-lab.js +127 -0
  210. package/ui/js/settings.js +2 -2
  211. package/ui/js/trust-dashboard.js +73 -0
  212. package/api_server.py +0 -724
  213. package/bin/aider-smart +0 -72
  214. package/bin/superlocalmemoryv2-learning +0 -4
  215. package/bin/superlocalmemoryv2-list +0 -3
  216. package/bin/superlocalmemoryv2-patterns +0 -4
  217. package/bin/superlocalmemoryv2-profile +0 -3
  218. package/bin/superlocalmemoryv2-recall +0 -3
  219. package/bin/superlocalmemoryv2-remember +0 -3
  220. package/bin/superlocalmemoryv2-reset +0 -3
  221. package/bin/superlocalmemoryv2-status +0 -3
  222. package/configs/chatgpt-desktop-mcp.json +0 -16
  223. package/configs/cursor-mcp.json +0 -15
  224. package/hooks/memory-list-skill.js +0 -139
  225. package/hooks/memory-profile-skill.js +0 -273
  226. package/hooks/memory-recall-skill.js +0 -114
  227. package/hooks/memory-remember-skill.js +0 -127
  228. package/hooks/memory-reset-skill.js +0 -274
  229. package/mcp_server.py +0 -1808
  230. package/requirements-core.txt +0 -22
  231. package/requirements-learning.txt +0 -12
  232. package/requirements.txt +0 -12
  233. package/src/agent_registry.py +0 -411
  234. package/src/auth_middleware.py +0 -61
  235. package/src/auto_backup.py +0 -459
  236. package/src/behavioral/__init__.py +0 -49
  237. package/src/behavioral/behavioral_listener.py +0 -203
  238. package/src/behavioral/behavioral_patterns.py +0 -275
  239. package/src/behavioral/cross_project_transfer.py +0 -206
  240. package/src/behavioral/outcome_inference.py +0 -194
  241. package/src/behavioral/outcome_tracker.py +0 -193
  242. package/src/behavioral/tests/__init__.py +0 -4
  243. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  244. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  245. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  246. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  247. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  248. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  249. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  250. package/src/cache_manager.py +0 -518
  251. package/src/compliance/__init__.py +0 -48
  252. package/src/compliance/abac_engine.py +0 -149
  253. package/src/compliance/abac_middleware.py +0 -116
  254. package/src/compliance/audit_db.py +0 -215
  255. package/src/compliance/audit_logger.py +0 -148
  256. package/src/compliance/retention_manager.py +0 -289
  257. package/src/compliance/retention_scheduler.py +0 -186
  258. package/src/compliance/tests/__init__.py +0 -4
  259. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  260. package/src/compliance/tests/test_abac_engine.py +0 -124
  261. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  262. package/src/compliance/tests/test_audit_db.py +0 -123
  263. package/src/compliance/tests/test_audit_logger.py +0 -98
  264. package/src/compliance/tests/test_mcp_audit.py +0 -128
  265. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  266. package/src/compliance/tests/test_retention_manager.py +0 -131
  267. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  268. package/src/compression/__init__.py +0 -25
  269. package/src/compression/cli.py +0 -150
  270. package/src/compression/cold_storage.py +0 -217
  271. package/src/compression/config.py +0 -72
  272. package/src/compression/orchestrator.py +0 -133
  273. package/src/compression/tier2_compressor.py +0 -228
  274. package/src/compression/tier3_compressor.py +0 -153
  275. package/src/compression/tier_classifier.py +0 -148
  276. package/src/db_connection_manager.py +0 -536
  277. package/src/embedding_engine.py +0 -63
  278. package/src/embeddings/__init__.py +0 -47
  279. package/src/embeddings/cache.py +0 -70
  280. package/src/embeddings/cli.py +0 -113
  281. package/src/embeddings/constants.py +0 -47
  282. package/src/embeddings/database.py +0 -91
  283. package/src/embeddings/engine.py +0 -247
  284. package/src/embeddings/model_loader.py +0 -145
  285. package/src/event_bus.py +0 -562
  286. package/src/graph/__init__.py +0 -36
  287. package/src/graph/build_helpers.py +0 -74
  288. package/src/graph/cli.py +0 -87
  289. package/src/graph/cluster_builder.py +0 -188
  290. package/src/graph/cluster_summary.py +0 -148
  291. package/src/graph/constants.py +0 -47
  292. package/src/graph/edge_builder.py +0 -162
  293. package/src/graph/entity_extractor.py +0 -95
  294. package/src/graph/graph_core.py +0 -226
  295. package/src/graph/graph_search.py +0 -231
  296. package/src/graph/hierarchical.py +0 -207
  297. package/src/graph/schema.py +0 -99
  298. package/src/graph_engine.py +0 -52
  299. package/src/hnsw_index.py +0 -628
  300. package/src/hybrid_search.py +0 -46
  301. package/src/learning/__init__.py +0 -217
  302. package/src/learning/adaptive_ranker.py +0 -682
  303. package/src/learning/bootstrap/__init__.py +0 -69
  304. package/src/learning/bootstrap/constants.py +0 -93
  305. package/src/learning/bootstrap/db_queries.py +0 -316
  306. package/src/learning/bootstrap/sampling.py +0 -82
  307. package/src/learning/bootstrap/text_utils.py +0 -71
  308. package/src/learning/cross_project_aggregator.py +0 -857
  309. package/src/learning/db/__init__.py +0 -40
  310. package/src/learning/db/constants.py +0 -44
  311. package/src/learning/db/schema.py +0 -279
  312. package/src/learning/engagement_tracker.py +0 -628
  313. package/src/learning/feature_extractor.py +0 -708
  314. package/src/learning/feedback_collector.py +0 -806
  315. package/src/learning/learning_db.py +0 -915
  316. package/src/learning/project_context_manager.py +0 -572
  317. package/src/learning/ranking/__init__.py +0 -33
  318. package/src/learning/ranking/constants.py +0 -84
  319. package/src/learning/ranking/helpers.py +0 -278
  320. package/src/learning/source_quality_scorer.py +0 -676
  321. package/src/learning/synthetic_bootstrap.py +0 -755
  322. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  323. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  324. package/src/learning/tests/test_aggregator.py +0 -306
  325. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  326. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  327. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  328. package/src/learning/tests/test_feedback_collector.py +0 -294
  329. package/src/learning/tests/test_learning_db.py +0 -602
  330. package/src/learning/tests/test_learning_db_v28.py +0 -110
  331. package/src/learning/tests/test_learning_init_v28.py +0 -48
  332. package/src/learning/tests/test_outcome_signals.py +0 -48
  333. package/src/learning/tests/test_project_context.py +0 -292
  334. package/src/learning/tests/test_schema_migration.py +0 -319
  335. package/src/learning/tests/test_signal_inference.py +0 -397
  336. package/src/learning/tests/test_source_quality.py +0 -351
  337. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  338. package/src/learning/tests/test_workflow_miner.py +0 -318
  339. package/src/learning/workflow_pattern_miner.py +0 -655
  340. package/src/lifecycle/__init__.py +0 -54
  341. package/src/lifecycle/bounded_growth.py +0 -239
  342. package/src/lifecycle/compaction_engine.py +0 -226
  343. package/src/lifecycle/lifecycle_engine.py +0 -355
  344. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  345. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  346. package/src/lifecycle/retention_policy.py +0 -285
  347. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  348. package/src/lifecycle/tests/test_compaction.py +0 -179
  349. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  350. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  351. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  352. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  353. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  354. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  355. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  356. package/src/mcp_tools_v28.py +0 -281
  357. package/src/memory/__init__.py +0 -36
  358. package/src/memory/cli.py +0 -205
  359. package/src/memory/constants.py +0 -39
  360. package/src/memory/helpers.py +0 -28
  361. package/src/memory/schema.py +0 -166
  362. package/src/memory-profiles.py +0 -595
  363. package/src/memory-reset.py +0 -491
  364. package/src/memory_compression.py +0 -989
  365. package/src/memory_store_v2.py +0 -1155
  366. package/src/migrate_v1_to_v2.py +0 -629
  367. package/src/pattern_learner.py +0 -34
  368. package/src/patterns/__init__.py +0 -24
  369. package/src/patterns/analyzers.py +0 -251
  370. package/src/patterns/learner.py +0 -271
  371. package/src/patterns/scoring.py +0 -171
  372. package/src/patterns/store.py +0 -225
  373. package/src/patterns/terminology.py +0 -140
  374. package/src/provenance_tracker.py +0 -312
  375. package/src/qualixar_attribution.py +0 -139
  376. package/src/qualixar_watermark.py +0 -78
  377. package/src/query_optimizer.py +0 -511
  378. package/src/rate_limiter.py +0 -83
  379. package/src/search/__init__.py +0 -20
  380. package/src/search/cli.py +0 -77
  381. package/src/search/constants.py +0 -26
  382. package/src/search/engine.py +0 -241
  383. package/src/search/fusion.py +0 -122
  384. package/src/search/index_loader.py +0 -114
  385. package/src/search/methods.py +0 -162
  386. package/src/search_engine_v2.py +0 -401
  387. package/src/setup_validator.py +0 -482
  388. package/src/subscription_manager.py +0 -391
  389. package/src/tree/__init__.py +0 -59
  390. package/src/tree/builder.py +0 -185
  391. package/src/tree/nodes.py +0 -202
  392. package/src/tree/queries.py +0 -257
  393. package/src/tree/schema.py +0 -80
  394. package/src/tree_manager.py +0 -19
  395. package/src/trust/__init__.py +0 -45
  396. package/src/trust/constants.py +0 -66
  397. package/src/trust/queries.py +0 -157
  398. package/src/trust/schema.py +0 -95
  399. package/src/trust/scorer.py +0 -299
  400. package/src/trust/signals.py +0 -95
  401. package/src/trust_scorer.py +0 -44
  402. package/ui/app.js +0 -1588
  403. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  404. package/ui/js/graph-cytoscape.js +0 -1168
  405. package/ui/js/graph-d3-backup.js +0 -32
  406. package/ui/js/graph.js +0 -32
  407. package/ui_server.py +0 -286
  408. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  409. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  410. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  411. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  412. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  413. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  414. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  415. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  416. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  417. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  418. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  419. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  420. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  421. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  422. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  423. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  424. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  425. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  426. /package/{completions → ide/completions}/slm.bash +0 -0
  427. /package/{completions → ide/completions}/slm.zsh +0 -0
  428. /package/{configs → ide/configs}/cody-commands.json +0 -0
  429. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
  430. /package/{install.ps1 → scripts/install.ps1} +0 -0
  431. /package/{install.sh → scripts/install.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