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
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
4
+ """SuperLocalMemory V2 - LlamaIndex Chat Store Backend
5
+
6
+ Implements LlamaIndex's BaseChatStore backed by SuperLocalMemory V2's
7
+ local SQLite storage. All data stays on-device — zero cloud, zero telemetry.
8
+
9
+ Usage:
10
+ from llama_index.storage.chat_store.superlocalmemory import SuperLocalMemoryChatStore
11
+ from llama_index.core.memory import ChatMemoryBuffer
12
+
13
+ chat_store = SuperLocalMemoryChatStore()
14
+ memory = ChatMemoryBuffer.from_defaults(chat_store=chat_store, chat_store_key="user-123")
15
+ """
16
+ import hashlib
17
+ import json
18
+ import sys
19
+ from pathlib import Path
20
+ from typing import Any, Dict, List, Optional
21
+
22
+ from llama_index.core.base.llms.types import ChatMessage, MessageRole
23
+ from llama_index.core.storage.chat_store.base import BaseChatStore
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Locate and import SuperLocalMemory V2's MemoryStoreV2
27
+ # ---------------------------------------------------------------------------
28
+ _SLM_PATH = Path.home() / ".claude-memory"
29
+ if str(_SLM_PATH) not in sys.path:
30
+ sys.path.insert(0, str(_SLM_PATH))
31
+
32
+ # Also support the source tree layout (for development / tests that pass db_path)
33
+ _SLM_SRC_PATH = Path(__file__).resolve().parents[6] / "src"
34
+ if _SLM_SRC_PATH.exists() and str(_SLM_SRC_PATH) not in sys.path:
35
+ sys.path.insert(0, str(_SLM_SRC_PATH))
36
+
37
+ try:
38
+ from memory_store_v2 import MemoryStoreV2
39
+ except ImportError as exc:
40
+ raise ImportError(
41
+ "SuperLocalMemory V2 is not installed. "
42
+ "Run: curl -fsSL https://raw.githubusercontent.com/varun369/SuperLocalMemoryV2/main/install.sh | bash\n"
43
+ "Or visit: https://github.com/varun369/SuperLocalMemoryV2"
44
+ ) from exc
45
+
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Constants
49
+ # ---------------------------------------------------------------------------
50
+ # Tag format: "li:chat:<hash>" where hash is a 24-char SHA-256 prefix of the key.
51
+ # Total tag length = 8 + 24 = 32 chars, well under SLM's 50-char MAX_TAG_LENGTH.
52
+ # The full session key is stored inside the serialized content JSON so get_keys()
53
+ # can reconstruct the original key even when the tag is hash-based.
54
+ _TAG_PREFIX = "li:chat:"
55
+ _PROJECT_NAME = "llamaindex"
56
+ _IMPORTANCE = 3
57
+ _LIST_LIMIT = 10000 # Upper bound when listing all memories for filtering
58
+ _HASH_LEN = 24 # Characters of SHA-256 hex digest to use in tags
59
+
60
+
61
+ def _key_hash(key: str) -> str:
62
+ """Produce a deterministic short hash of a session key for use in SLM tags."""
63
+ return hashlib.sha256(key.encode("utf-8")).hexdigest()[:_HASH_LEN]
64
+
65
+
66
+ def _make_tag(key: str) -> str:
67
+ """Build the SLM tag for a chat session key.
68
+
69
+ Uses a hash of the key to guarantee the tag never exceeds SLM's
70
+ 50-character limit regardless of key length or content.
71
+ """
72
+ return f"{_TAG_PREFIX}{_key_hash(key)}"
73
+
74
+
75
+ def _serialize_message(key: str, message: ChatMessage) -> str:
76
+ """Serialize a ChatMessage to JSON string for SLM content storage.
77
+
78
+ The session *key* is embedded in the payload so that ``get_keys()`` can
79
+ reconstruct the original key from stored memories (the tag only contains
80
+ a hash).
81
+ """
82
+ return json.dumps(
83
+ {
84
+ "key": key,
85
+ "role": message.role.value,
86
+ "content": message.content or "",
87
+ "additional_kwargs": message.additional_kwargs,
88
+ },
89
+ ensure_ascii=False,
90
+ )
91
+
92
+
93
+ def _deserialize_message(content: str) -> Optional[ChatMessage]:
94
+ """Deserialize a JSON string back to a ChatMessage.
95
+
96
+ Returns None if the content is not valid chat message JSON.
97
+ """
98
+ try:
99
+ data = json.loads(content)
100
+ except (json.JSONDecodeError, TypeError):
101
+ return None
102
+
103
+ if not isinstance(data, dict) or "role" not in data:
104
+ return None
105
+
106
+ try:
107
+ role = MessageRole(data["role"])
108
+ except ValueError:
109
+ role = MessageRole.USER
110
+
111
+ return ChatMessage(
112
+ role=role,
113
+ content=data.get("content", ""),
114
+ additional_kwargs=data.get("additional_kwargs", {}),
115
+ )
116
+
117
+
118
+ def _extract_key(content: str) -> Optional[str]:
119
+ """Extract the session key from a serialized memory content string."""
120
+ try:
121
+ data = json.loads(content)
122
+ except (json.JSONDecodeError, TypeError):
123
+ return None
124
+ if isinstance(data, dict):
125
+ return data.get("key")
126
+ return None
127
+
128
+
129
+ class SuperLocalMemoryChatStore(BaseChatStore):
130
+ """LlamaIndex chat store backed by SuperLocalMemory V2.
131
+
132
+ Stores chat messages in SuperLocalMemory's local SQLite database,
133
+ keeping all data on-device with zero cloud calls.
134
+
135
+ Each message is stored as a separate SLM memory entry tagged with
136
+ ``li:chat:<hash>`` so different conversation sessions are cleanly
137
+ isolated. The full session key is preserved inside the serialized
138
+ content JSON for lossless round-tripping.
139
+
140
+ Args:
141
+ db_path: Optional path to the SQLite database file.
142
+ Defaults to ``~/.claude-memory/memory.db``.
143
+ """
144
+
145
+ # Pydantic fields ---------------------------------------------------
146
+ # We store the db_path as a string so the model stays JSON-serializable.
147
+ _db_path: Optional[str] = None
148
+ _store: Any = None # MemoryStoreV2 — not serializable, set in __init__
149
+
150
+ model_config = {"arbitrary_types_allowed": True}
151
+
152
+ def __init__(self, db_path: Optional[str] = None, **kwargs: Any) -> None:
153
+ """Initialize SuperLocalMemoryChatStore.
154
+
155
+ Args:
156
+ db_path: Optional path to the SLM SQLite database.
157
+ If None, uses the default ``~/.claude-memory/memory.db``.
158
+ """
159
+ super().__init__(**kwargs)
160
+ self._db_path = db_path
161
+ if db_path:
162
+ self._store = MemoryStoreV2(db_path=Path(db_path))
163
+ else:
164
+ self._store = MemoryStoreV2()
165
+
166
+ @classmethod
167
+ def class_name(cls) -> str:
168
+ """Get class name."""
169
+ return "SuperLocalMemoryChatStore"
170
+
171
+ # ------------------------------------------------------------------
172
+ # Internal helpers
173
+ # ------------------------------------------------------------------
174
+
175
+ def _get_memories_for_key(self, key: str) -> List[Dict[str, Any]]:
176
+ """Retrieve all SLM memories that belong to a chat session *key*.
177
+
178
+ Returns memories sorted by ``created_at`` ascending (oldest first)
179
+ to preserve conversation order.
180
+ """
181
+ tag = _make_tag(key)
182
+ all_memories = self._store.list_all(limit=_LIST_LIMIT)
183
+
184
+ matched: List[Dict[str, Any]] = []
185
+ for mem in all_memories:
186
+ mem_tags = mem.get("tags", [])
187
+ if isinstance(mem_tags, str):
188
+ try:
189
+ mem_tags = json.loads(mem_tags)
190
+ except (json.JSONDecodeError, TypeError):
191
+ mem_tags = [t.strip() for t in mem_tags.split(",") if t.strip()]
192
+ if tag in (mem_tags or []):
193
+ matched.append(mem)
194
+
195
+ # Sort by created_at ascending for correct conversation order
196
+ matched.sort(key=lambda m: m.get("created_at", ""))
197
+ return matched
198
+
199
+ def _memories_to_messages(
200
+ self, memories: List[Dict[str, Any]]
201
+ ) -> List[ChatMessage]:
202
+ """Convert a list of SLM memory dicts to ChatMessage objects."""
203
+ messages: List[ChatMessage] = []
204
+ for mem in memories:
205
+ msg = _deserialize_message(mem.get("content", ""))
206
+ if msg is not None:
207
+ messages.append(msg)
208
+ return messages
209
+
210
+ # ------------------------------------------------------------------
211
+ # BaseChatStore abstract method implementations
212
+ # ------------------------------------------------------------------
213
+
214
+ def set_messages(self, key: str, messages: List[ChatMessage]) -> None:
215
+ """Set messages for a key (replaces any existing messages)."""
216
+ self.delete_messages(key)
217
+ for message in messages:
218
+ self.add_message(key, message)
219
+
220
+ def get_messages(self, key: str) -> List[ChatMessage]:
221
+ """Get messages for a key, ordered by creation time."""
222
+ memories = self._get_memories_for_key(key)
223
+ return self._memories_to_messages(memories)
224
+
225
+ def add_message(self, key: str, message: ChatMessage) -> None:
226
+ """Add a single message for a key."""
227
+ content = _serialize_message(key, message)
228
+ tag = _make_tag(key)
229
+ self._store.add_memory(
230
+ content=content,
231
+ tags=[tag],
232
+ project_name=_PROJECT_NAME,
233
+ importance=_IMPORTANCE,
234
+ )
235
+
236
+ def delete_messages(self, key: str) -> Optional[List[ChatMessage]]:
237
+ """Delete all messages for a key.
238
+
239
+ Returns the deleted messages, or None if the key had no messages.
240
+ """
241
+ memories = self._get_memories_for_key(key)
242
+ if not memories:
243
+ return None
244
+
245
+ messages = self._memories_to_messages(memories)
246
+
247
+ for mem in memories:
248
+ self._store.delete_memory(mem["id"])
249
+
250
+ return messages
251
+
252
+ def delete_message(self, key: str, idx: int) -> Optional[ChatMessage]:
253
+ """Delete specific message for a key by index.
254
+
255
+ Args:
256
+ key: The session key.
257
+ idx: Zero-based index of the message to delete.
258
+
259
+ Returns:
260
+ The deleted ChatMessage, or None if index is out of range.
261
+ """
262
+ memories = self._get_memories_for_key(key)
263
+ if not memories or idx < 0 or idx >= len(memories):
264
+ return None
265
+
266
+ target = memories[idx]
267
+ msg = _deserialize_message(target.get("content", ""))
268
+ self._store.delete_memory(target["id"])
269
+ return msg
270
+
271
+ def delete_last_message(self, key: str) -> Optional[ChatMessage]:
272
+ """Delete the last (most recent) message for a key.
273
+
274
+ Returns:
275
+ The deleted ChatMessage, or None if the key has no messages.
276
+ """
277
+ memories = self._get_memories_for_key(key)
278
+ if not memories:
279
+ return None
280
+
281
+ last = memories[-1]
282
+ msg = _deserialize_message(last.get("content", ""))
283
+ self._store.delete_memory(last["id"])
284
+ return msg
285
+
286
+ def get_keys(self) -> List[str]:
287
+ """Get all unique session keys that have stored messages.
288
+
289
+ Keys are extracted from the serialized content JSON (the ``key``
290
+ field) rather than from tags, because tags contain only a hash of
291
+ the key for length-safety.
292
+ """
293
+ all_memories = self._store.list_all(limit=_LIST_LIMIT)
294
+ keys_seen: set[str] = set()
295
+
296
+ for mem in all_memories:
297
+ # Only consider memories whose tags indicate they belong to us
298
+ mem_tags = mem.get("tags", [])
299
+ if isinstance(mem_tags, str):
300
+ try:
301
+ mem_tags = json.loads(mem_tags)
302
+ except (json.JSONDecodeError, TypeError):
303
+ mem_tags = [t.strip() for t in mem_tags.split(",") if t.strip()]
304
+
305
+ is_ours = any(
306
+ isinstance(t, str) and t.startswith(_TAG_PREFIX)
307
+ for t in (mem_tags or [])
308
+ )
309
+ if not is_ours:
310
+ continue
311
+
312
+ key = _extract_key(mem.get("content", ""))
313
+ if key is not None:
314
+ keys_seen.add(key)
315
+
316
+ return sorted(keys_seen)
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "llama-index-storage-chat-store-superlocalmemory"
7
+ version = "0.1.0"
8
+ description = "LlamaIndex chat store backed by SuperLocalMemory — 100% local, zero cloud"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "Varun Pratap Bhardwaj" },
14
+ ]
15
+ keywords = ["llamaindex", "llama-index", "memory", "local-first", "privacy", "sqlite", "chat-store", "mcp"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
22
+ ]
23
+ dependencies = [
24
+ "llama-index-core>=0.13.0,<0.15",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/varun369/SuperLocalMemoryV2"
29
+ Documentation = "https://superlocalmemory.com/"
30
+ Repository = "https://github.com/varun369/SuperLocalMemoryV2"
31
+
32
+ [tool.hatch.build.targets.wheel]
33
+ packages = ["llama_index"]
34
+
35
+ [tool.llamahub]
36
+ contains_example = false
37
+ import_path = "llama_index.storage.chat_store.superlocalmemory"
38
+
39
+ [tool.llamahub.class_authors]
40
+ SuperLocalMemoryChatStore = "varun369"
41
+
42
+ [tool.pytest.ini_options]
43
+ testpaths = ["tests"]
@@ -1,4 +1,3 @@
1
+ #!/usr/bin/env python3
1
2
  # SPDX-License-Identifier: MIT
2
3
  # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for lifecycle engine.
4
- """
@@ -0,0 +1,294 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
4
+ """SuperLocalMemory V2 - LlamaIndex Chat Store Tests
5
+
6
+ Comprehensive tests for the SuperLocalMemoryChatStore implementation.
7
+ All tests use temporary databases — no production data is touched.
8
+ """
9
+ import os
10
+ import sys
11
+ import tempfile
12
+
13
+ import pytest
14
+
15
+ # Ensure the SLM source tree is importable for tests
16
+ _SLM_SRC = os.path.join(
17
+ os.path.dirname(__file__), os.pardir, os.pardir, os.pardir, "src"
18
+ )
19
+ _SLM_SRC = os.path.abspath(_SLM_SRC)
20
+ if _SLM_SRC not in sys.path:
21
+ sys.path.insert(0, _SLM_SRC)
22
+
23
+ from llama_index.core.base.llms.types import ChatMessage, MessageRole
24
+ from llama_index.storage.chat_store.superlocalmemory import SuperLocalMemoryChatStore
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Fixtures
29
+ # ---------------------------------------------------------------------------
30
+
31
+
32
+ @pytest.fixture
33
+ def tmp_db():
34
+ """Provide a path to a temporary database file that is cleaned up after use."""
35
+ with tempfile.TemporaryDirectory() as d:
36
+ yield os.path.join(d, "test_memory.db")
37
+
38
+
39
+ @pytest.fixture
40
+ def store(tmp_db):
41
+ """Provide a fresh SuperLocalMemoryChatStore backed by a temp database."""
42
+ return SuperLocalMemoryChatStore(db_path=tmp_db)
43
+
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Helpers
47
+ # ---------------------------------------------------------------------------
48
+
49
+
50
+ def _make_messages(
51
+ texts: list[str], roles: list[MessageRole] | None = None
52
+ ) -> list[ChatMessage]:
53
+ """Create a list of ChatMessages from plain text strings."""
54
+ if roles is None:
55
+ # Alternate user / assistant
56
+ roles = [
57
+ MessageRole.USER if i % 2 == 0 else MessageRole.ASSISTANT
58
+ for i in range(len(texts))
59
+ ]
60
+ return [ChatMessage(role=r, content=t) for r, t in zip(roles, texts)]
61
+
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # Tests
65
+ # ---------------------------------------------------------------------------
66
+
67
+
68
+ class TestSetAndGetMessages:
69
+ """Tests for set_messages / get_messages round-trip."""
70
+
71
+ def test_set_and_get_messages(self, store):
72
+ """Set 3 messages and retrieve them in order."""
73
+ msgs = _make_messages(["Hello", "Hi there", "How are you?"])
74
+ store.set_messages("session-1", msgs)
75
+
76
+ retrieved = store.get_messages("session-1")
77
+ assert len(retrieved) == 3
78
+ assert retrieved[0].content == "Hello"
79
+ assert retrieved[1].content == "Hi there"
80
+ assert retrieved[2].content == "How are you?"
81
+
82
+ def test_get_messages_empty(self, store):
83
+ """Getting messages for a non-existent key returns an empty list."""
84
+ assert store.get_messages("nonexistent") == []
85
+
86
+
87
+ class TestAddMessage:
88
+ """Tests for add_message."""
89
+
90
+ def test_add_single_message(self, store):
91
+ """Add one message and verify it appears in get_messages."""
92
+ msg = ChatMessage(role=MessageRole.USER, content="solo message")
93
+ store.add_message("key-a", msg)
94
+
95
+ retrieved = store.get_messages("key-a")
96
+ assert len(retrieved) == 1
97
+ assert retrieved[0].content == "solo message"
98
+ assert retrieved[0].role == MessageRole.USER
99
+
100
+
101
+ class TestDeleteMessages:
102
+ """Tests for delete_messages (all messages for a key)."""
103
+
104
+ def test_delete_messages(self, store):
105
+ """Set, delete, verify empty and check return value."""
106
+ msgs = _make_messages(["A", "B", "C"])
107
+ store.set_messages("del-key", msgs)
108
+
109
+ deleted = store.delete_messages("del-key")
110
+ assert deleted is not None
111
+ assert len(deleted) == 3
112
+ assert deleted[0].content == "A"
113
+
114
+ # After deletion, key should be empty
115
+ assert store.get_messages("del-key") == []
116
+
117
+ def test_delete_messages_nonexistent(self, store):
118
+ """Deleting messages for a key with no data returns None."""
119
+ assert store.delete_messages("ghost-key") is None
120
+
121
+
122
+ class TestDeleteMessageByIndex:
123
+ """Tests for delete_message (single message by index)."""
124
+
125
+ def test_delete_message_by_index(self, store):
126
+ """Set 3 messages, delete index 1, verify 2 remain in correct order."""
127
+ msgs = _make_messages(["first", "second", "third"])
128
+ store.set_messages("idx-key", msgs)
129
+
130
+ deleted = store.delete_message("idx-key", 1)
131
+ assert deleted is not None
132
+ assert deleted.content == "second"
133
+
134
+ remaining = store.get_messages("idx-key")
135
+ assert len(remaining) == 2
136
+ assert remaining[0].content == "first"
137
+ assert remaining[1].content == "third"
138
+
139
+ def test_delete_message_out_of_range(self, store):
140
+ """Deleting an out-of-range index returns None."""
141
+ msgs = _make_messages(["only"])
142
+ store.set_messages("range-key", msgs)
143
+ assert store.delete_message("range-key", 5) is None
144
+
145
+ def test_delete_message_negative_index(self, store):
146
+ """Deleting a negative index returns None."""
147
+ msgs = _make_messages(["x"])
148
+ store.set_messages("neg-key", msgs)
149
+ assert store.delete_message("neg-key", -1) is None
150
+
151
+
152
+ class TestDeleteLastMessage:
153
+ """Tests for delete_last_message."""
154
+
155
+ def test_delete_last_message(self, store):
156
+ """Set 3 messages, delete last, verify 2 remain."""
157
+ msgs = _make_messages(["alpha", "beta", "gamma"])
158
+ store.set_messages("last-key", msgs)
159
+
160
+ deleted = store.delete_last_message("last-key")
161
+ assert deleted is not None
162
+ assert deleted.content == "gamma"
163
+
164
+ remaining = store.get_messages("last-key")
165
+ assert len(remaining) == 2
166
+ assert remaining[-1].content == "beta"
167
+
168
+ def test_delete_last_message_empty(self, store):
169
+ """Deleting last message on an empty key returns None."""
170
+ assert store.delete_last_message("empty-key") is None
171
+
172
+
173
+ class TestGetKeys:
174
+ """Tests for get_keys."""
175
+
176
+ def test_get_keys(self, store):
177
+ """Set messages for 3 different keys and verify get_keys returns all 3."""
178
+ for key in ["alice", "bob", "carol"]:
179
+ store.add_message(key, ChatMessage(role=MessageRole.USER, content=f"hi from {key}"))
180
+
181
+ keys = store.get_keys()
182
+ assert sorted(keys) == ["alice", "bob", "carol"]
183
+
184
+ def test_get_keys_empty(self, store):
185
+ """No data returns empty list."""
186
+ assert store.get_keys() == []
187
+
188
+
189
+ class TestSessionIsolation:
190
+ """Tests that different keys don't interfere with each other."""
191
+
192
+ def test_session_isolation(self, store):
193
+ """Messages in key A should not appear in key B."""
194
+ store.set_messages("sess-A", _make_messages(["A1", "A2"]))
195
+ store.set_messages("sess-B", _make_messages(["B1", "B2", "B3"]))
196
+
197
+ a_msgs = store.get_messages("sess-A")
198
+ b_msgs = store.get_messages("sess-B")
199
+
200
+ assert len(a_msgs) == 2
201
+ assert len(b_msgs) == 3
202
+ assert a_msgs[0].content == "A1"
203
+ assert b_msgs[0].content == "B1"
204
+
205
+
206
+ class TestSetOverwrites:
207
+ """Tests that set_messages replaces previous messages."""
208
+
209
+ def test_set_overwrites(self, store):
210
+ """Setting messages twice replaces the first batch entirely."""
211
+ store.set_messages("ow-key", _make_messages(["old1", "old2", "old3"]))
212
+ store.set_messages("ow-key", _make_messages(["new1"]))
213
+
214
+ msgs = store.get_messages("ow-key")
215
+ assert len(msgs) == 1
216
+ assert msgs[0].content == "new1"
217
+
218
+
219
+ class TestPersistence:
220
+ """Tests that data survives across store instances."""
221
+
222
+ def test_persistence(self, tmp_db):
223
+ """Create store, add data, create NEW store on same db, verify data."""
224
+ store1 = SuperLocalMemoryChatStore(db_path=tmp_db)
225
+ store1.set_messages("persist-key", _make_messages(["remember me"]))
226
+
227
+ # New store instance pointing at same database
228
+ store2 = SuperLocalMemoryChatStore(db_path=tmp_db)
229
+ msgs = store2.get_messages("persist-key")
230
+ assert len(msgs) == 1
231
+ assert msgs[0].content == "remember me"
232
+
233
+
234
+ class TestContentEdgeCases:
235
+ """Tests for special content: unicode, long text, multiple roles."""
236
+
237
+ def test_unicode_content(self, store):
238
+ """Unicode characters (CJK, emoji, diacritics) round-trip correctly."""
239
+ texts = [
240
+ "Hello, world!",
241
+ "Hej varlden!",
242
+ "Bonjour le monde!",
243
+ ]
244
+ store.set_messages("unicode-key", _make_messages(texts))
245
+ msgs = store.get_messages("unicode-key")
246
+ assert len(msgs) == 3
247
+ assert msgs[1].content == "Hej varlden!"
248
+
249
+ def test_long_content(self, store):
250
+ """A 10K character message round-trips correctly."""
251
+ long_text = "x" * 10_000
252
+ store.add_message(
253
+ "long-key", ChatMessage(role=MessageRole.USER, content=long_text)
254
+ )
255
+ msgs = store.get_messages("long-key")
256
+ assert len(msgs) == 1
257
+ assert len(msgs[0].content) == 10_000
258
+
259
+ def test_message_roles(self, store):
260
+ """User, assistant, and system roles all round-trip correctly."""
261
+ msgs = [
262
+ ChatMessage(role=MessageRole.SYSTEM, content="You are helpful."),
263
+ ChatMessage(role=MessageRole.USER, content="Hi"),
264
+ ChatMessage(role=MessageRole.ASSISTANT, content="Hello!"),
265
+ ]
266
+ store.set_messages("roles-key", msgs)
267
+ retrieved = store.get_messages("roles-key")
268
+
269
+ assert retrieved[0].role == MessageRole.SYSTEM
270
+ assert retrieved[1].role == MessageRole.USER
271
+ assert retrieved[2].role == MessageRole.ASSISTANT
272
+
273
+ def test_empty_content_message(self, store):
274
+ """A message with empty content still round-trips."""
275
+ store.add_message(
276
+ "empty-msg", ChatMessage(role=MessageRole.ASSISTANT, content="")
277
+ )
278
+ msgs = store.get_messages("empty-msg")
279
+ assert len(msgs) == 1
280
+ # Empty content serializes as "" and deserializes back
281
+ assert msgs[0].content == "" or msgs[0].content is None
282
+
283
+ def test_additional_kwargs(self, store):
284
+ """additional_kwargs round-trip correctly."""
285
+ msg = ChatMessage(
286
+ role=MessageRole.USER,
287
+ content="with metadata",
288
+ additional_kwargs={"source": "test", "count": 42},
289
+ )
290
+ store.add_message("kwargs-key", msg)
291
+ msgs = store.get_messages("kwargs-key")
292
+ assert len(msgs) == 1
293
+ assert msgs[0].additional_kwargs.get("source") == "test"
294
+ assert msgs[0].additional_kwargs.get("count") == 42