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,485 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under the MIT License - see LICENSE file
3
+ # Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
4
+
5
+ """SuperLocalMemory V3 — Memory Consolidator.
6
+
7
+ Mem0-style ADD/UPDATE/SUPERSEDE/NOOP logic for incoming facts.
8
+ V1 was append-only (never updated, never deleted, never merged).
9
+ This module gives a ~26% uplift by deduplicating, updating, and
10
+ resolving contradictions at encoding time.
11
+
12
+ Mode A: keyword-based contradiction detection (zero LLM).
13
+ Mode B/C: LLM-assisted contradiction detection when available.
14
+
15
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
16
+ License: MIT
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+ import math
23
+ from typing import Any, Protocol
24
+
25
+ from superlocalmemory.core.config import EncodingConfig
26
+ from superlocalmemory.storage.database import DatabaseManager
27
+ from superlocalmemory.storage.models import (
28
+ AtomicFact,
29
+ ConsolidationAction,
30
+ ConsolidationActionType,
31
+ EdgeType,
32
+ GraphEdge,
33
+ MemoryLifecycle,
34
+ )
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+ # ---------------------------------------------------------------------------
39
+ # Protocols (avoid tight coupling to concrete classes)
40
+ # ---------------------------------------------------------------------------
41
+
42
+ class Embedder(Protocol):
43
+ """Anything that produces an embedding vector from text."""
44
+
45
+ def encode(self, text: str) -> list[float]: ...
46
+
47
+
48
+ class LLM(Protocol):
49
+ """Anything that can generate text from a prompt."""
50
+
51
+ def generate(self, prompt: str, system: str = "") -> str: ...
52
+
53
+ def is_available(self) -> bool: ...
54
+
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # Negation patterns for Mode A keyword contradiction detection
58
+ # ---------------------------------------------------------------------------
59
+
60
+ _NEGATION_MARKERS: frozenset[str] = frozenset({
61
+ "not", "no longer", "never", "stopped", "quit", "left",
62
+ "changed", "moved", "divorced", "fired", "resigned",
63
+ "broke up", "ended", "cancelled", "dropped", "switched",
64
+ "former", "ex-", "previously", "used to", "no more",
65
+ })
66
+
67
+ # Score thresholds (match EncodingConfig defaults)
68
+ _NOOP_THRESHOLD = 0.95
69
+ _MATCH_THRESHOLD = 0.85
70
+
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # MemoryConsolidator
74
+ # ---------------------------------------------------------------------------
75
+
76
+ class MemoryConsolidator:
77
+ """Decides ADD/UPDATE/SUPERSEDE/NOOP for each incoming fact.
78
+
79
+ For each new fact the consolidator:
80
+ 1. Finds candidate matches via entity overlap + semantic similarity.
81
+ 2. Scores each candidate (Jaccard entity + cosine embedding).
82
+ 3. Classifies the relationship and executes the action.
83
+ 4. Logs every decision in ``consolidation_log``.
84
+
85
+ Thread-safe — delegates all DB ops to DatabaseManager which holds a lock.
86
+ """
87
+
88
+ def __init__(
89
+ self,
90
+ db: DatabaseManager,
91
+ embedder: Embedder | None = None,
92
+ llm: LLM | None = None,
93
+ config: EncodingConfig | None = None,
94
+ ) -> None:
95
+ self._db = db
96
+ self._embedder = embedder
97
+ self._llm = llm
98
+ self._cfg = config or EncodingConfig()
99
+
100
+ # -- Public API ---------------------------------------------------------
101
+
102
+ def consolidate(
103
+ self, new_fact: AtomicFact, profile_id: str,
104
+ ) -> ConsolidationAction:
105
+ """Consolidate *new_fact* against existing knowledge.
106
+
107
+ Returns a ``ConsolidationAction`` describing what was done.
108
+ """
109
+ candidates = self._find_candidates(new_fact, profile_id)
110
+
111
+ if not candidates:
112
+ return self._execute_add(new_fact, profile_id, reason="no matching facts")
113
+
114
+ best_fact, best_score = candidates[0]
115
+
116
+ if best_score > _NOOP_THRESHOLD:
117
+ # Even near-duplicates might be contradictions (e.g. negation).
118
+ # Check contradiction BEFORE declaring NOOP.
119
+ if self._is_contradicting(new_fact, best_fact):
120
+ return self._execute_supersede(
121
+ new_fact, best_fact, profile_id,
122
+ reason=f"contradiction detected (score={best_score:.3f})",
123
+ )
124
+ return self._execute_noop(
125
+ new_fact, best_fact, profile_id,
126
+ reason=f"near-duplicate (score={best_score:.3f})",
127
+ )
128
+
129
+ if best_score > _MATCH_THRESHOLD:
130
+ if self._is_contradicting(new_fact, best_fact):
131
+ return self._execute_supersede(
132
+ new_fact, best_fact, profile_id,
133
+ reason=f"contradiction detected (score={best_score:.3f})",
134
+ )
135
+ if new_fact.fact_type == best_fact.fact_type:
136
+ return self._execute_update(
137
+ new_fact, best_fact, profile_id,
138
+ reason=f"refines existing (score={best_score:.3f})",
139
+ )
140
+
141
+ return self._execute_add(
142
+ new_fact, profile_id,
143
+ reason=f"new information (best_score={best_score:.3f})",
144
+ )
145
+
146
+ def get_consolidation_history(
147
+ self, profile_id: str, limit: int = 50,
148
+ ) -> list[ConsolidationAction]:
149
+ """Recent consolidation actions for audit/debugging."""
150
+ rows = self._db.execute(
151
+ "SELECT * FROM consolidation_log WHERE profile_id = ? "
152
+ "ORDER BY timestamp DESC LIMIT ?",
153
+ (profile_id, limit),
154
+ )
155
+ return [
156
+ ConsolidationAction(
157
+ action_id=(d := dict(r))["action_id"],
158
+ profile_id=d["profile_id"],
159
+ action_type=ConsolidationActionType(d["action_type"]),
160
+ new_fact_id=d["new_fact_id"],
161
+ existing_fact_id=d["existing_fact_id"],
162
+ reason=d["reason"],
163
+ timestamp=d["timestamp"],
164
+ )
165
+ for r in rows
166
+ ]
167
+
168
+ # -- Candidate search ---------------------------------------------------
169
+
170
+ def _find_candidates(
171
+ self, new_fact: AtomicFact, profile_id: str,
172
+ ) -> list[tuple[AtomicFact, float]]:
173
+ """Find and score candidate matches from existing facts.
174
+
175
+ Uses two signals:
176
+ 1. Entity overlap — ``get_facts_by_entity`` for each canonical entity.
177
+ 2. Semantic similarity — cosine of embedding vectors.
178
+
179
+ Returns sorted list of (fact, combined_score), descending.
180
+ """
181
+ seen_ids: set[str] = set()
182
+ candidate_facts: list[AtomicFact] = []
183
+
184
+ # --- entity-based candidates ---
185
+ for entity in new_fact.canonical_entities:
186
+ for fact in self._db.get_facts_by_entity(entity, profile_id):
187
+ if fact.fact_id not in seen_ids:
188
+ seen_ids.add(fact.fact_id)
189
+ candidate_facts.append(fact)
190
+
191
+ # --- semantic candidates (top-K by embedding) ---
192
+ if new_fact.embedding is not None and self._embedder is not None:
193
+ all_facts = self._db.get_all_facts(profile_id)
194
+ semantic_scored: list[tuple[AtomicFact, float]] = []
195
+ for fact in all_facts:
196
+ if fact.fact_id in seen_ids:
197
+ continue
198
+ sim = _compute_similarity(new_fact.embedding, fact.embedding)
199
+ if sim > 0.5:
200
+ semantic_scored.append((fact, sim))
201
+ semantic_scored.sort(key=lambda t: t[1], reverse=True)
202
+ for fact, _ in semantic_scored[: self._cfg.max_consolidation_candidates]:
203
+ if fact.fact_id not in seen_ids:
204
+ seen_ids.add(fact.fact_id)
205
+ candidate_facts.append(fact)
206
+
207
+ # --- score all candidates ---
208
+ scored: list[tuple[AtomicFact, float]] = []
209
+ for cand in candidate_facts:
210
+ entity_overlap = _jaccard(
211
+ set(new_fact.canonical_entities),
212
+ set(cand.canonical_entities),
213
+ )
214
+ semantic_sim = _compute_similarity(
215
+ new_fact.embedding, cand.embedding,
216
+ )
217
+ combined = 0.4 * entity_overlap + 0.6 * semantic_sim
218
+ scored.append((cand, combined))
219
+
220
+ scored.sort(key=lambda t: t[1], reverse=True)
221
+ return scored
222
+
223
+ # -- Contradiction detection --------------------------------------------
224
+
225
+ def _is_contradicting(
226
+ self, fact_a: AtomicFact, fact_b: AtomicFact,
227
+ ) -> bool:
228
+ """Detect if *fact_a* contradicts *fact_b*.
229
+
230
+ Mode B/C: delegates to LLM for nuanced judgment.
231
+ Mode A: keyword-based negation detection.
232
+ """
233
+ if self._llm is not None and self._llm.is_available():
234
+ return self._llm_contradiction_check(fact_a, fact_b)
235
+ return self._keyword_contradiction_check(fact_a, fact_b)
236
+
237
+ def _keyword_contradiction_check(
238
+ self, fact_a: AtomicFact, fact_b: AtomicFact,
239
+ ) -> bool:
240
+ """Heuristic: check negation markers in either fact's content."""
241
+ text_a = fact_a.content.lower()
242
+ text_b = fact_b.content.lower()
243
+ for marker in _NEGATION_MARKERS:
244
+ if marker in text_a and marker not in text_b:
245
+ return True
246
+ if marker in text_b and marker not in text_a:
247
+ return True
248
+
249
+ # Opposing emotional valence with same entities
250
+ shared_entities = set(fact_a.canonical_entities) & set(fact_b.canonical_entities)
251
+ if shared_entities:
252
+ valence_diff = abs(fact_a.emotional_valence - fact_b.emotional_valence)
253
+ if valence_diff > 1.2:
254
+ return True
255
+
256
+ return False
257
+
258
+ def _llm_contradiction_check(
259
+ self, fact_a: AtomicFact, fact_b: AtomicFact,
260
+ ) -> bool:
261
+ """Ask the LLM whether two facts contradict each other."""
262
+ assert self._llm is not None # guarded by caller
263
+ prompt = (
264
+ "Do these two statements contradict each other?\n\n"
265
+ f"Statement A: {fact_a.content}\n"
266
+ f"Statement B: {fact_b.content}\n\n"
267
+ "Answer ONLY 'yes' or 'no'."
268
+ )
269
+ response = self._llm.generate(
270
+ prompt, system="You are a precise fact-checker.",
271
+ )
272
+ return response.strip().lower().startswith("yes")
273
+
274
+ # -- Action executors ---------------------------------------------------
275
+
276
+ def _execute_add(
277
+ self, new_fact: AtomicFact, profile_id: str, *, reason: str,
278
+ ) -> ConsolidationAction:
279
+ """Store the new fact and link to related facts via semantic edges."""
280
+ self._db.store_fact(new_fact)
281
+ self._create_semantic_edges(new_fact, profile_id)
282
+ action = self._log_action(
283
+ ConsolidationActionType.ADD, new_fact.fact_id, "", profile_id, reason,
284
+ )
285
+ logger.debug("ADD fact %s: %s", new_fact.fact_id, reason)
286
+ return action
287
+
288
+ def _execute_update(
289
+ self,
290
+ new_fact: AtomicFact,
291
+ existing: AtomicFact,
292
+ profile_id: str,
293
+ *,
294
+ reason: str,
295
+ ) -> ConsolidationAction:
296
+ """Update existing fact: bump evidence, optionally merge content."""
297
+ new_evidence = existing.evidence_count + 1
298
+ new_confidence = min(1.0, existing.confidence + 0.05)
299
+ updates: dict[str, Any] = {
300
+ "evidence_count": new_evidence,
301
+ "confidence": new_confidence,
302
+ }
303
+
304
+ # If LLM available, merge content for a richer fact
305
+ if self._llm is not None and self._llm.is_available():
306
+ merged = self._merge_facts(existing.content, new_fact.content)
307
+ if merged:
308
+ updates["content"] = merged
309
+
310
+ self._db.update_fact(existing.fact_id, updates)
311
+ action = self._log_action(
312
+ ConsolidationActionType.UPDATE,
313
+ new_fact.fact_id, existing.fact_id,
314
+ profile_id, reason,
315
+ )
316
+ logger.debug(
317
+ "UPDATE fact %s (evidence=%d): %s",
318
+ existing.fact_id, new_evidence, reason,
319
+ )
320
+ return action
321
+
322
+ def _execute_supersede(
323
+ self,
324
+ new_fact: AtomicFact,
325
+ existing: AtomicFact,
326
+ profile_id: str,
327
+ *,
328
+ reason: str,
329
+ ) -> ConsolidationAction:
330
+ """Archive old fact, store new, create contradiction edge."""
331
+ # Archive old fact (keep for history but deprioritize in retrieval)
332
+ self._db.update_fact(
333
+ existing.fact_id,
334
+ {"lifecycle": MemoryLifecycle.ARCHIVED},
335
+ )
336
+ # Store new fact
337
+ self._db.store_fact(new_fact)
338
+ # Create contradiction + supersedes edges
339
+ self._db.store_edge(GraphEdge(
340
+ profile_id=profile_id,
341
+ source_id=new_fact.fact_id,
342
+ target_id=existing.fact_id,
343
+ edge_type=EdgeType.CONTRADICTION,
344
+ weight=1.0,
345
+ ))
346
+ self._db.store_edge(GraphEdge(
347
+ profile_id=profile_id,
348
+ source_id=new_fact.fact_id,
349
+ target_id=existing.fact_id,
350
+ edge_type=EdgeType.SUPERSEDES,
351
+ weight=1.0,
352
+ ))
353
+ action = self._log_action(
354
+ ConsolidationActionType.SUPERSEDE,
355
+ new_fact.fact_id, existing.fact_id,
356
+ profile_id, reason,
357
+ )
358
+ logger.debug(
359
+ "SUPERSEDE %s → %s: %s",
360
+ new_fact.fact_id, existing.fact_id, reason,
361
+ )
362
+ return action
363
+
364
+ def _execute_noop(
365
+ self,
366
+ new_fact: AtomicFact,
367
+ existing: AtomicFact,
368
+ profile_id: str,
369
+ *,
370
+ reason: str,
371
+ ) -> ConsolidationAction:
372
+ """Near-duplicate — just bump access count on existing."""
373
+ self._db.update_fact(
374
+ existing.fact_id,
375
+ {"access_count": existing.access_count + 1},
376
+ )
377
+ action = self._log_action(
378
+ ConsolidationActionType.NOOP,
379
+ new_fact.fact_id, existing.fact_id,
380
+ profile_id, reason,
381
+ )
382
+ logger.debug("NOOP for %s (dup of %s): %s", new_fact.fact_id, existing.fact_id, reason)
383
+ return action
384
+
385
+ # -- Helpers ------------------------------------------------------------
386
+
387
+ def _create_semantic_edges(
388
+ self, new_fact: AtomicFact, profile_id: str,
389
+ ) -> None:
390
+ """Link new fact to top-K most similar existing facts."""
391
+ if new_fact.embedding is None:
392
+ return
393
+ all_facts = self._db.get_all_facts(profile_id)
394
+ scored: list[tuple[str, float]] = []
395
+ for fact in all_facts:
396
+ if fact.fact_id == new_fact.fact_id:
397
+ continue
398
+ sim = _compute_similarity(new_fact.embedding, fact.embedding)
399
+ if sim > 0.5:
400
+ scored.append((fact.fact_id, sim))
401
+ scored.sort(key=lambda t: t[1], reverse=True)
402
+ for target_id, weight in scored[: self._cfg.semantic_edge_top_k]:
403
+ self._db.store_edge(GraphEdge(
404
+ profile_id=profile_id,
405
+ source_id=new_fact.fact_id,
406
+ target_id=target_id,
407
+ edge_type=EdgeType.SEMANTIC,
408
+ weight=weight,
409
+ ))
410
+
411
+ def _merge_facts(self, existing_text: str, new_text: str) -> str:
412
+ """Use LLM to merge two fact statements into one richer fact."""
413
+ assert self._llm is not None
414
+ prompt = (
415
+ "Merge these two statements about the same topic into one "
416
+ "concise, accurate fact. Keep all unique information.\n\n"
417
+ f"Existing: {existing_text}\n"
418
+ f"New: {new_text}\n\n"
419
+ "Merged statement:"
420
+ )
421
+ result = self._llm.generate(prompt, system="You are a precise editor.")
422
+ return result.strip() if result.strip() else ""
423
+
424
+ def _log_action(
425
+ self,
426
+ action_type: ConsolidationActionType,
427
+ new_fact_id: str,
428
+ existing_fact_id: str,
429
+ profile_id: str,
430
+ reason: str,
431
+ ) -> ConsolidationAction:
432
+ """Persist action to consolidation_log and return the model."""
433
+ action = ConsolidationAction(
434
+ profile_id=profile_id,
435
+ action_type=action_type,
436
+ new_fact_id=new_fact_id,
437
+ existing_fact_id=existing_fact_id,
438
+ reason=reason,
439
+ )
440
+ self._db.execute(
441
+ "INSERT INTO consolidation_log "
442
+ "(action_id, profile_id, action_type, new_fact_id, "
443
+ " existing_fact_id, reason, timestamp) "
444
+ "VALUES (?,?,?,?,?,?,?)",
445
+ (
446
+ action.action_id, action.profile_id,
447
+ action.action_type.value, action.new_fact_id,
448
+ action.existing_fact_id, action.reason, action.timestamp,
449
+ ),
450
+ )
451
+ return action
452
+
453
+
454
+ # ---------------------------------------------------------------------------
455
+ # Module-level helpers (pure functions, no side effects)
456
+ # ---------------------------------------------------------------------------
457
+
458
+ def _compute_similarity(
459
+ emb_a: list[float] | None, emb_b: list[float] | None,
460
+ ) -> float:
461
+ """Cosine similarity between two embedding vectors.
462
+
463
+ Returns 0.0 if either embedding is None or empty.
464
+ """
465
+ if not emb_a or not emb_b:
466
+ return 0.0
467
+ if len(emb_a) != len(emb_b):
468
+ return 0.0
469
+
470
+ dot = sum(a * b for a, b in zip(emb_a, emb_b))
471
+ norm_a = math.sqrt(sum(a * a for a in emb_a))
472
+ norm_b = math.sqrt(sum(b * b for b in emb_b))
473
+
474
+ if norm_a < 1e-12 or norm_b < 1e-12:
475
+ return 0.0
476
+ return dot / (norm_a * norm_b)
477
+
478
+
479
+ def _jaccard(set_a: set[str], set_b: set[str]) -> float:
480
+ """Jaccard similarity of two string sets. Returns 0.0 if both empty."""
481
+ if not set_a and not set_b:
482
+ return 0.0
483
+ intersection = len(set_a & set_b)
484
+ union = len(set_a | set_b)
485
+ return intersection / union if union > 0 else 0.0
@@ -0,0 +1,125 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under the MIT License - see LICENSE file
3
+ # Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
4
+
5
+ """SuperLocalMemory V3 — Emotional Tagging (VADER).
6
+
7
+ Extracts emotional valence and arousal from text.
8
+ Emotionally charged memories are stored more strongly and retrieved more easily
9
+ (amygdala tagging principle from neuroscience).
10
+
11
+ Ported from V1 — VADER-based, zero-LLM, works in all modes.
12
+
13
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ import math
20
+ from dataclasses import dataclass
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ _vader_analyzer = None
25
+
26
+
27
+ def _get_vader():
28
+ """Lazy-load VADER to avoid import cost on startup."""
29
+ global _vader_analyzer
30
+ if _vader_analyzer is not None:
31
+ return _vader_analyzer
32
+ try:
33
+ from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
34
+ _vader_analyzer = SentimentIntensityAnalyzer()
35
+ except ImportError:
36
+ logger.warning("vaderSentiment not installed — emotional tagging disabled")
37
+ _vader_analyzer = None
38
+ return _vader_analyzer
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class EmotionalTag:
43
+ """Emotional metadata for a memory or fact."""
44
+
45
+ valence: float # -1.0 (negative) to +1.0 (positive)
46
+ arousal: float # 0.0 (calm) to 1.0 (intense)
47
+
48
+
49
+ def tag_emotion(text: str) -> EmotionalTag:
50
+ """Extract emotional valence and arousal from text.
51
+
52
+ Valence: VADER compound score [-1, +1].
53
+ Arousal: absolute compound + max(pos, neg) — higher = more emotional intensity.
54
+ Falls back to keyword heuristic when VADER is unavailable.
55
+ """
56
+ analyzer = _get_vader()
57
+ if analyzer is None:
58
+ return _keyword_fallback(text)
59
+
60
+ scores = analyzer.polarity_scores(text)
61
+ compound = scores["compound"] # -1 to +1
62
+ pos = scores["pos"] # 0 to 1
63
+ neg = scores["neg"] # 0 to 1
64
+
65
+ valence = compound
66
+ # Arousal = emotional intensity regardless of direction
67
+ arousal = min(1.0, abs(compound) * 0.6 + max(pos, neg) * 0.4)
68
+
69
+ return EmotionalTag(valence=round(valence, 4), arousal=round(arousal, 4))
70
+
71
+
72
+ _POSITIVE_WORDS: frozenset[str] = frozenset({
73
+ "love", "amazing", "wonderful", "great", "happy", "fantastic",
74
+ "excellent", "beautiful", "awesome", "brilliant", "incredible",
75
+ "joy", "thrilled", "grateful", "delighted", "superb",
76
+ })
77
+
78
+ _NEGATIVE_WORDS: frozenset[str] = frozenset({
79
+ "hate", "terrible", "horrible", "awful", "bad", "worst",
80
+ "angry", "frustrated", "disappointed", "sad", "miserable",
81
+ "disgusting", "dreadful", "pathetic", "furious", "outraged",
82
+ })
83
+
84
+
85
+ def _keyword_fallback(text: str) -> EmotionalTag:
86
+ """Lightweight sentiment heuristic when VADER is unavailable.
87
+
88
+ Counts positive/negative keywords and derives approximate valence/arousal.
89
+ """
90
+ if not text.strip():
91
+ return EmotionalTag(valence=0.0, arousal=0.0)
92
+
93
+ words = set(text.lower().split())
94
+ pos_count = len(words & _POSITIVE_WORDS)
95
+ neg_count = len(words & _NEGATIVE_WORDS)
96
+ total = pos_count + neg_count
97
+
98
+ if total == 0:
99
+ return EmotionalTag(valence=0.0, arousal=0.0)
100
+
101
+ # Valence: positive - negative, normalised to [-1, 1]
102
+ raw_valence = (pos_count - neg_count) / total
103
+ valence = max(-1.0, min(1.0, raw_valence))
104
+
105
+ # Arousal: how many emotional words relative to total word count
106
+ word_count = max(len(words), 1)
107
+ arousal = min(1.0, total / word_count * 2.0)
108
+
109
+ return EmotionalTag(valence=round(valence, 4), arousal=round(arousal, 4))
110
+
111
+
112
+ def is_emotionally_significant(tag: EmotionalTag, threshold: float = 0.3) -> bool:
113
+ """Check if the emotional signal is strong enough to boost importance."""
114
+ return tag.arousal >= threshold
115
+
116
+
117
+ def emotional_importance_boost(tag: EmotionalTag) -> float:
118
+ """Compute importance boost from emotional signal.
119
+
120
+ Returns 0.0-0.3 boost. High arousal memories get stored more strongly
121
+ (amygdala-inspired encoding enhancement).
122
+ """
123
+ if tag.arousal <= 0.2:
124
+ return 0.0
125
+ return min(0.3, tag.arousal * 0.3)