superlocalmemory 2.8.6 → 3.0.1

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