superlocalmemory 2.8.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +9 -1
  3. package/NOTICE +63 -0
  4. package/README.md +165 -480
  5. package/bin/slm +17 -449
  6. package/bin/slm-npm +2 -2
  7. package/bin/slm.bat +4 -2
  8. package/conftest.py +5 -0
  9. package/docs/api-reference.md +284 -0
  10. package/docs/architecture.md +149 -0
  11. package/docs/auto-memory.md +150 -0
  12. package/docs/cli-reference.md +276 -0
  13. package/docs/compliance.md +191 -0
  14. package/docs/configuration.md +182 -0
  15. package/docs/getting-started.md +102 -0
  16. package/docs/ide-setup.md +261 -0
  17. package/docs/mcp-tools.md +220 -0
  18. package/docs/migration-from-v2.md +170 -0
  19. package/docs/profiles.md +173 -0
  20. package/docs/troubleshooting.md +310 -0
  21. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  22. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  23. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  24. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  25. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  26. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  27. package/ide/configs/cursor-mcp.json +15 -0
  28. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  29. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  30. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  31. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  32. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  33. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  34. package/{configs → ide/configs}/zed-mcp.json +2 -2
  35. package/{hooks → ide/hooks}/context-hook.js +9 -20
  36. package/ide/hooks/memory-list-skill.js +70 -0
  37. package/ide/hooks/memory-profile-skill.js +101 -0
  38. package/ide/hooks/memory-recall-skill.js +62 -0
  39. package/ide/hooks/memory-remember-skill.js +68 -0
  40. package/ide/hooks/memory-reset-skill.js +160 -0
  41. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  42. package/ide/integrations/langchain/README.md +106 -0
  43. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  44. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  45. package/ide/integrations/langchain/pyproject.toml +38 -0
  46. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  47. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  48. package/ide/integrations/langchain/tests/test_security.py +117 -0
  49. package/ide/integrations/llamaindex/README.md +81 -0
  50. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  51. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  52. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  53. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  54. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  55. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  56. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  57. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  58. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  59. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  60. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  61. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  62. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  63. package/package.json +13 -22
  64. package/pyproject.toml +85 -0
  65. package/scripts/build-dmg.sh +417 -0
  66. package/scripts/install-skills.ps1 +334 -0
  67. package/{install.ps1 → scripts/install.ps1} +36 -4
  68. package/{install.sh → scripts/install.sh} +14 -13
  69. package/scripts/postinstall.js +2 -2
  70. package/scripts/start-dashboard.ps1 +52 -0
  71. package/scripts/start-dashboard.sh +41 -0
  72. package/scripts/sync-wiki.ps1 +127 -0
  73. package/scripts/sync-wiki.sh +82 -0
  74. package/scripts/test-dmg.sh +161 -0
  75. package/scripts/test-npm-package.ps1 +252 -0
  76. package/scripts/test-npm-package.sh +207 -0
  77. package/scripts/verify-install.ps1 +294 -0
  78. package/scripts/verify-install.sh +266 -0
  79. package/src/superlocalmemory/__init__.py +0 -0
  80. package/src/superlocalmemory/attribution/__init__.py +9 -0
  81. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  82. package/src/superlocalmemory/attribution/signer.py +153 -0
  83. package/src/superlocalmemory/attribution/watermark.py +189 -0
  84. package/src/superlocalmemory/cli/__init__.py +5 -0
  85. package/src/superlocalmemory/cli/commands.py +245 -0
  86. package/src/superlocalmemory/cli/main.py +89 -0
  87. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  88. package/src/superlocalmemory/cli/post_install.py +99 -0
  89. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  90. package/src/superlocalmemory/compliance/__init__.py +0 -0
  91. package/src/superlocalmemory/compliance/abac.py +204 -0
  92. package/src/superlocalmemory/compliance/audit.py +314 -0
  93. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  94. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  95. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  96. package/src/superlocalmemory/compliance/retention.py +232 -0
  97. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  98. package/src/superlocalmemory/core/__init__.py +0 -0
  99. package/src/superlocalmemory/core/config.py +391 -0
  100. package/src/superlocalmemory/core/embeddings.py +293 -0
  101. package/src/superlocalmemory/core/engine.py +701 -0
  102. package/src/superlocalmemory/core/hooks.py +65 -0
  103. package/src/superlocalmemory/core/maintenance.py +172 -0
  104. package/src/superlocalmemory/core/modes.py +140 -0
  105. package/src/superlocalmemory/core/profiles.py +234 -0
  106. package/src/superlocalmemory/core/registry.py +117 -0
  107. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  108. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  109. package/src/superlocalmemory/encoding/__init__.py +0 -0
  110. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  111. package/src/superlocalmemory/encoding/emotional.py +125 -0
  112. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  113. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  114. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  115. package/src/superlocalmemory/encoding/foresight.py +91 -0
  116. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  117. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  118. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  119. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  120. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  121. package/src/superlocalmemory/encoding/type_router.py +235 -0
  122. package/src/superlocalmemory/hooks/__init__.py +3 -0
  123. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  124. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  125. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  126. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  127. package/src/superlocalmemory/infra/__init__.py +3 -0
  128. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  129. package/src/superlocalmemory/infra/backup.py +317 -0
  130. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  131. package/src/superlocalmemory/infra/event_bus.py +381 -0
  132. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  133. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  134. package/src/superlocalmemory/learning/__init__.py +0 -0
  135. package/src/superlocalmemory/learning/adaptive.py +172 -0
  136. package/src/superlocalmemory/learning/behavioral.py +490 -0
  137. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  138. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  139. package/src/superlocalmemory/learning/cross_project.py +399 -0
  140. package/src/superlocalmemory/learning/database.py +376 -0
  141. package/src/superlocalmemory/learning/engagement.py +323 -0
  142. package/src/superlocalmemory/learning/features.py +138 -0
  143. package/src/superlocalmemory/learning/feedback.py +316 -0
  144. package/src/superlocalmemory/learning/outcomes.py +255 -0
  145. package/src/superlocalmemory/learning/project_context.py +366 -0
  146. package/src/superlocalmemory/learning/ranker.py +155 -0
  147. package/src/superlocalmemory/learning/source_quality.py +303 -0
  148. package/src/superlocalmemory/learning/workflows.py +309 -0
  149. package/src/superlocalmemory/llm/__init__.py +0 -0
  150. package/src/superlocalmemory/llm/backbone.py +316 -0
  151. package/src/superlocalmemory/math/__init__.py +0 -0
  152. package/src/superlocalmemory/math/fisher.py +356 -0
  153. package/src/superlocalmemory/math/langevin.py +398 -0
  154. package/src/superlocalmemory/math/sheaf.py +257 -0
  155. package/src/superlocalmemory/mcp/__init__.py +0 -0
  156. package/src/superlocalmemory/mcp/resources.py +245 -0
  157. package/src/superlocalmemory/mcp/server.py +61 -0
  158. package/src/superlocalmemory/mcp/tools.py +18 -0
  159. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  160. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  161. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  162. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  163. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  164. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  165. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  166. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  167. package/src/superlocalmemory/retrieval/engine.py +390 -0
  168. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  169. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  170. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  171. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  172. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  173. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  174. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  175. package/src/superlocalmemory/server/__init__.py +1 -0
  176. package/src/superlocalmemory/server/api.py +248 -0
  177. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  178. package/src/superlocalmemory/server/routes/agents.py +107 -0
  179. package/src/superlocalmemory/server/routes/backup.py +91 -0
  180. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  181. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  182. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  183. package/src/superlocalmemory/server/routes/events.py +183 -0
  184. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  185. package/src/superlocalmemory/server/routes/learning.py +273 -0
  186. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  187. package/src/superlocalmemory/server/routes/memories.py +399 -0
  188. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  189. package/src/superlocalmemory/server/routes/stats.py +346 -0
  190. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  191. package/src/superlocalmemory/server/routes/ws.py +82 -0
  192. package/src/superlocalmemory/server/security_middleware.py +57 -0
  193. package/src/superlocalmemory/server/ui.py +245 -0
  194. package/src/superlocalmemory/storage/__init__.py +0 -0
  195. package/src/superlocalmemory/storage/access_control.py +182 -0
  196. package/src/superlocalmemory/storage/database.py +594 -0
  197. package/src/superlocalmemory/storage/migrations.py +303 -0
  198. package/src/superlocalmemory/storage/models.py +406 -0
  199. package/src/superlocalmemory/storage/schema.py +726 -0
  200. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  201. package/src/superlocalmemory/trust/__init__.py +0 -0
  202. package/src/superlocalmemory/trust/gate.py +130 -0
  203. package/src/superlocalmemory/trust/provenance.py +124 -0
  204. package/src/superlocalmemory/trust/scorer.py +347 -0
  205. package/src/superlocalmemory/trust/signals.py +153 -0
  206. package/ui/index.html +278 -5
  207. package/ui/js/auto-settings.js +70 -0
  208. package/ui/js/dashboard.js +90 -0
  209. package/ui/js/fact-detail.js +92 -0
  210. package/ui/js/feedback.js +2 -2
  211. package/ui/js/ide-status.js +102 -0
  212. package/ui/js/math-health.js +98 -0
  213. package/ui/js/recall-lab.js +127 -0
  214. package/ui/js/settings.js +2 -2
  215. package/ui/js/trust-dashboard.js +73 -0
  216. package/api_server.py +0 -724
  217. package/bin/aider-smart +0 -72
  218. package/bin/superlocalmemoryv2-learning +0 -4
  219. package/bin/superlocalmemoryv2-list +0 -3
  220. package/bin/superlocalmemoryv2-patterns +0 -4
  221. package/bin/superlocalmemoryv2-profile +0 -3
  222. package/bin/superlocalmemoryv2-recall +0 -3
  223. package/bin/superlocalmemoryv2-remember +0 -3
  224. package/bin/superlocalmemoryv2-reset +0 -3
  225. package/bin/superlocalmemoryv2-status +0 -3
  226. package/configs/chatgpt-desktop-mcp.json +0 -16
  227. package/configs/cursor-mcp.json +0 -15
  228. package/docs/SECURITY-QUICK-REFERENCE.md +0 -214
  229. package/hooks/memory-list-skill.js +0 -139
  230. package/hooks/memory-profile-skill.js +0 -273
  231. package/hooks/memory-recall-skill.js +0 -114
  232. package/hooks/memory-remember-skill.js +0 -127
  233. package/hooks/memory-reset-skill.js +0 -274
  234. package/mcp_server.py +0 -1800
  235. package/requirements-core.txt +0 -22
  236. package/requirements-learning.txt +0 -12
  237. package/requirements.txt +0 -12
  238. package/src/agent_registry.py +0 -411
  239. package/src/auth_middleware.py +0 -61
  240. package/src/auto_backup.py +0 -459
  241. package/src/behavioral/__init__.py +0 -49
  242. package/src/behavioral/behavioral_listener.py +0 -203
  243. package/src/behavioral/behavioral_patterns.py +0 -275
  244. package/src/behavioral/cross_project_transfer.py +0 -206
  245. package/src/behavioral/outcome_inference.py +0 -194
  246. package/src/behavioral/outcome_tracker.py +0 -193
  247. package/src/behavioral/tests/__init__.py +0 -4
  248. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  249. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  250. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  251. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  252. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  253. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  254. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  255. package/src/cache_manager.py +0 -518
  256. package/src/compliance/__init__.py +0 -48
  257. package/src/compliance/abac_engine.py +0 -149
  258. package/src/compliance/abac_middleware.py +0 -116
  259. package/src/compliance/audit_db.py +0 -215
  260. package/src/compliance/audit_logger.py +0 -148
  261. package/src/compliance/retention_manager.py +0 -289
  262. package/src/compliance/retention_scheduler.py +0 -186
  263. package/src/compliance/tests/__init__.py +0 -4
  264. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  265. package/src/compliance/tests/test_abac_engine.py +0 -124
  266. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  267. package/src/compliance/tests/test_audit_db.py +0 -123
  268. package/src/compliance/tests/test_audit_logger.py +0 -98
  269. package/src/compliance/tests/test_mcp_audit.py +0 -128
  270. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  271. package/src/compliance/tests/test_retention_manager.py +0 -131
  272. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  273. package/src/compression/__init__.py +0 -25
  274. package/src/compression/cli.py +0 -150
  275. package/src/compression/cold_storage.py +0 -217
  276. package/src/compression/config.py +0 -72
  277. package/src/compression/orchestrator.py +0 -133
  278. package/src/compression/tier2_compressor.py +0 -228
  279. package/src/compression/tier3_compressor.py +0 -153
  280. package/src/compression/tier_classifier.py +0 -148
  281. package/src/db_connection_manager.py +0 -536
  282. package/src/embedding_engine.py +0 -63
  283. package/src/embeddings/__init__.py +0 -47
  284. package/src/embeddings/cache.py +0 -70
  285. package/src/embeddings/cli.py +0 -113
  286. package/src/embeddings/constants.py +0 -47
  287. package/src/embeddings/database.py +0 -91
  288. package/src/embeddings/engine.py +0 -247
  289. package/src/embeddings/model_loader.py +0 -145
  290. package/src/event_bus.py +0 -562
  291. package/src/graph/__init__.py +0 -36
  292. package/src/graph/build_helpers.py +0 -74
  293. package/src/graph/cli.py +0 -87
  294. package/src/graph/cluster_builder.py +0 -188
  295. package/src/graph/cluster_summary.py +0 -148
  296. package/src/graph/constants.py +0 -47
  297. package/src/graph/edge_builder.py +0 -162
  298. package/src/graph/entity_extractor.py +0 -95
  299. package/src/graph/graph_core.py +0 -226
  300. package/src/graph/graph_search.py +0 -231
  301. package/src/graph/hierarchical.py +0 -207
  302. package/src/graph/schema.py +0 -99
  303. package/src/graph_engine.py +0 -52
  304. package/src/hnsw_index.py +0 -628
  305. package/src/hybrid_search.py +0 -46
  306. package/src/learning/__init__.py +0 -217
  307. package/src/learning/adaptive_ranker.py +0 -682
  308. package/src/learning/bootstrap/__init__.py +0 -69
  309. package/src/learning/bootstrap/constants.py +0 -93
  310. package/src/learning/bootstrap/db_queries.py +0 -316
  311. package/src/learning/bootstrap/sampling.py +0 -82
  312. package/src/learning/bootstrap/text_utils.py +0 -71
  313. package/src/learning/cross_project_aggregator.py +0 -857
  314. package/src/learning/db/__init__.py +0 -40
  315. package/src/learning/db/constants.py +0 -44
  316. package/src/learning/db/schema.py +0 -279
  317. package/src/learning/engagement_tracker.py +0 -628
  318. package/src/learning/feature_extractor.py +0 -708
  319. package/src/learning/feedback_collector.py +0 -806
  320. package/src/learning/learning_db.py +0 -915
  321. package/src/learning/project_context_manager.py +0 -572
  322. package/src/learning/ranking/__init__.py +0 -33
  323. package/src/learning/ranking/constants.py +0 -84
  324. package/src/learning/ranking/helpers.py +0 -278
  325. package/src/learning/source_quality_scorer.py +0 -676
  326. package/src/learning/synthetic_bootstrap.py +0 -755
  327. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  328. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  329. package/src/learning/tests/test_aggregator.py +0 -306
  330. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  331. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  332. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  333. package/src/learning/tests/test_feedback_collector.py +0 -294
  334. package/src/learning/tests/test_learning_db.py +0 -602
  335. package/src/learning/tests/test_learning_db_v28.py +0 -110
  336. package/src/learning/tests/test_learning_init_v28.py +0 -48
  337. package/src/learning/tests/test_outcome_signals.py +0 -48
  338. package/src/learning/tests/test_project_context.py +0 -292
  339. package/src/learning/tests/test_schema_migration.py +0 -319
  340. package/src/learning/tests/test_signal_inference.py +0 -397
  341. package/src/learning/tests/test_source_quality.py +0 -351
  342. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  343. package/src/learning/tests/test_workflow_miner.py +0 -318
  344. package/src/learning/workflow_pattern_miner.py +0 -655
  345. package/src/lifecycle/__init__.py +0 -54
  346. package/src/lifecycle/bounded_growth.py +0 -239
  347. package/src/lifecycle/compaction_engine.py +0 -226
  348. package/src/lifecycle/lifecycle_engine.py +0 -355
  349. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  350. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  351. package/src/lifecycle/retention_policy.py +0 -285
  352. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  353. package/src/lifecycle/tests/test_compaction.py +0 -179
  354. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  355. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  356. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  357. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  358. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  359. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  360. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  361. package/src/mcp_tools_v28.py +0 -281
  362. package/src/memory/__init__.py +0 -36
  363. package/src/memory/cli.py +0 -205
  364. package/src/memory/constants.py +0 -39
  365. package/src/memory/helpers.py +0 -28
  366. package/src/memory/schema.py +0 -166
  367. package/src/memory-profiles.py +0 -595
  368. package/src/memory-reset.py +0 -491
  369. package/src/memory_compression.py +0 -989
  370. package/src/memory_store_v2.py +0 -1155
  371. package/src/migrate_v1_to_v2.py +0 -629
  372. package/src/pattern_learner.py +0 -34
  373. package/src/patterns/__init__.py +0 -24
  374. package/src/patterns/analyzers.py +0 -251
  375. package/src/patterns/learner.py +0 -271
  376. package/src/patterns/scoring.py +0 -171
  377. package/src/patterns/store.py +0 -225
  378. package/src/patterns/terminology.py +0 -140
  379. package/src/provenance_tracker.py +0 -312
  380. package/src/qualixar_attribution.py +0 -139
  381. package/src/qualixar_watermark.py +0 -78
  382. package/src/query_optimizer.py +0 -511
  383. package/src/rate_limiter.py +0 -83
  384. package/src/search/__init__.py +0 -20
  385. package/src/search/cli.py +0 -77
  386. package/src/search/constants.py +0 -26
  387. package/src/search/engine.py +0 -241
  388. package/src/search/fusion.py +0 -122
  389. package/src/search/index_loader.py +0 -114
  390. package/src/search/methods.py +0 -162
  391. package/src/search_engine_v2.py +0 -401
  392. package/src/setup_validator.py +0 -482
  393. package/src/subscription_manager.py +0 -391
  394. package/src/tree/__init__.py +0 -59
  395. package/src/tree/builder.py +0 -185
  396. package/src/tree/nodes.py +0 -202
  397. package/src/tree/queries.py +0 -257
  398. package/src/tree/schema.py +0 -80
  399. package/src/tree_manager.py +0 -19
  400. package/src/trust/__init__.py +0 -45
  401. package/src/trust/constants.py +0 -66
  402. package/src/trust/queries.py +0 -157
  403. package/src/trust/schema.py +0 -95
  404. package/src/trust/scorer.py +0 -299
  405. package/src/trust/signals.py +0 -95
  406. package/src/trust_scorer.py +0 -44
  407. package/ui/app.js +0 -1588
  408. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  409. package/ui/js/graph-cytoscape.js +0 -1168
  410. package/ui/js/graph-d3-backup.js +0 -32
  411. package/ui/js/graph.js +0 -32
  412. package/ui_server.py +0 -266
  413. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  414. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  415. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  416. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  417. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  418. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  419. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  420. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  421. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  422. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  423. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  424. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  425. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  426. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  427. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  428. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  429. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  430. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  431. /package/{completions → ide/completions}/slm.bash +0 -0
  432. /package/{completions → ide/completions}/slm.zsh +0 -0
  433. /package/{configs → ide/configs}/cody-commands.json +0 -0
  434. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
@@ -1,806 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: MIT
3
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
4
- """
5
- FeedbackCollector -- Multi-channel feedback collection for the LightGBM re-ranker.
6
-
7
- Collects implicit and explicit relevance signals from three channels:
8
-
9
- 1. MCP -- ``memory_used`` tool with usefulness level (high/medium/low).
10
- 2. CLI -- ``slm useful <id> [<id>...]`` marks memories as helpful.
11
- 3. Dashboard -- click events with optional dwell-time tracking.
12
-
13
- Additionally tracks *passive decay*: memories that are repeatedly returned
14
- by recall but never receive a positive signal are assigned a 0.0 (negative)
15
- feedback entry, teaching the re-ranker to demote them.
16
-
17
- Privacy:
18
- - Full query text is NEVER stored.
19
- - Queries are hashed to SHA-256[:16] for grouping.
20
- - Top 3 keywords are extracted for loose thematic grouping only.
21
-
22
- All data is written to the ``ranking_feedback`` table in learning.db via
23
- the shared LearningDB instance.
24
-
25
- Research backing:
26
- - ADPMF (IPM 2024): privacy-preserving feedback for recommendation.
27
- - FCS LREC 2024: cold-start feedback bootstrapping.
28
- """
29
-
30
- import hashlib
31
- import logging
32
- import re
33
- import threading
34
- from collections import Counter
35
- from datetime import datetime
36
- from typing import Dict, List, Optional, Any
37
-
38
- logger = logging.getLogger("superlocalmemory.learning.feedback")
39
-
40
- # ---------------------------------------------------------------------------
41
- # Stopwords for keyword extraction (small, curated list -- no NLTK needed)
42
- # ---------------------------------------------------------------------------
43
- _STOPWORDS = frozenset({
44
- "a", "an", "the", "is", "are", "was", "were", "be", "been", "being",
45
- "have", "has", "had", "do", "does", "did", "will", "would", "shall",
46
- "should", "may", "might", "must", "can", "could",
47
- "i", "me", "my", "we", "our", "you", "your", "he", "she", "it",
48
- "they", "them", "their", "its", "this", "that", "these", "those",
49
- "what", "which", "who", "whom", "how", "when", "where", "why",
50
- "not", "no", "nor", "but", "or", "and", "if", "then", "so",
51
- "of", "in", "on", "at", "to", "for", "with", "from", "by",
52
- "about", "into", "through", "during", "before", "after",
53
- "above", "below", "between", "out", "off", "up", "down",
54
- "all", "each", "every", "both", "few", "more", "most", "some", "any",
55
- "such", "only", "same", "than", "too", "very",
56
- "just", "also", "now", "here", "there",
57
- })
58
-
59
- # Regex to split on non-alphanumeric (keeps words and numbers)
60
- _WORD_SPLIT = re.compile(r"[^a-zA-Z0-9]+")
61
-
62
-
63
- class FeedbackCollector:
64
- """
65
- Collects multi-channel relevance feedback for the adaptive re-ranker.
66
-
67
- Each signal maps to a numeric value used as a training label:
68
-
69
- mcp_used_high = 1.0 (strong positive)
70
- mcp_used_medium = 0.7
71
- mcp_used_low = 0.4
72
- cli_useful = 0.9
73
- dashboard_click = 0.8
74
- passive_decay = 0.0 (negative signal)
75
-
76
- Usage:
77
- from learning.learning_db import LearningDB
78
- collector = FeedbackCollector(learning_db=LearningDB())
79
-
80
- # MCP channel
81
- collector.record_memory_used(42, "how to deploy FastAPI", usefulness="high")
82
-
83
- # CLI channel
84
- collector.record_cli_useful([42, 87], "deploy fastapi")
85
-
86
- # Dashboard channel
87
- collector.record_dashboard_click(42, "deploy fastapi", dwell_time=12.5)
88
-
89
- # Passive decay (call periodically)
90
- collector.record_recall_results("deploy fastapi", [42, 87, 91])
91
- collector.compute_passive_decay()
92
- """
93
-
94
- # Signal type -> numeric label for re-ranker training
95
- SIGNAL_VALUES: Dict[str, float] = {
96
- "mcp_used_high": 1.0,
97
- "mcp_used_medium": 0.7,
98
- "mcp_used_low": 0.4,
99
- "cli_useful": 0.9,
100
- "dashboard_click": 0.8,
101
- "dashboard_thumbs_up": 1.0,
102
- "dashboard_thumbs_down": 0.0,
103
- "dashboard_pin": 1.0,
104
- "dashboard_dwell_positive": 0.7,
105
- "dashboard_dwell_negative": 0.1,
106
- "implicit_positive_timegap": 0.6,
107
- "implicit_negative_requick": 0.1,
108
- "implicit_positive_reaccess": 0.7,
109
- "implicit_positive_post_update": 0.8,
110
- "implicit_negative_post_delete": 0.0,
111
- "implicit_positive_cross_tool": 0.8,
112
- "passive_decay": 0.0,
113
- # v2.8.0: Outcome-based signals from behavioral learning
114
- "outcome_success": 1.0,
115
- "outcome_partial": 0.5,
116
- "outcome_failure": 0.0,
117
- "outcome_retry": 0.2,
118
- }
119
-
120
- # Usefulness string -> signal type mapping
121
- _USEFULNESS_MAP: Dict[str, str] = {
122
- "high": "mcp_used_high",
123
- "medium": "mcp_used_medium",
124
- "low": "mcp_used_low",
125
- }
126
-
127
- def __init__(self, learning_db: Optional[Any] = None):
128
- """
129
- Args:
130
- learning_db: LearningDB instance for persisting feedback.
131
- If None, auto-creates a LearningDB instance.
132
- """
133
- if learning_db is None:
134
- try:
135
- from .learning_db import LearningDB
136
- self.learning_db = LearningDB()
137
- except Exception:
138
- self.learning_db = None
139
- else:
140
- self.learning_db = learning_db
141
-
142
- # In-memory buffer for passive decay tracking.
143
- # Structure: {query_hash: {memory_id: times_returned_count}}
144
- # Protected by a lock since MCP/CLI/API may call concurrently.
145
- self._recall_buffer: Dict[str, Dict[int, int]] = {}
146
- self._recall_buffer_lock = threading.Lock()
147
-
148
- # Counter: total recall operations tracked (for decay threshold)
149
- self._recall_count: int = 0
150
-
151
- # ======================================================================
152
- # Channel 1: MCP -- memory_used tool
153
- # ======================================================================
154
-
155
- def record_memory_used(
156
- self,
157
- memory_id: int,
158
- query: str,
159
- usefulness: str = "high",
160
- source_tool: Optional[str] = None,
161
- rank_position: Optional[int] = None,
162
- ) -> Optional[int]:
163
- """
164
- Record that a memory was explicitly used after an MCP recall.
165
-
166
- Called by the ``memory_used`` MCP tool. This is the highest-quality
167
- feedback signal because the AI agent explicitly indicates it found
168
- the memory useful.
169
-
170
- Args:
171
- memory_id: ID of the used memory in memory.db.
172
- query: The original recall query (hashed, not stored raw).
173
- usefulness: "high", "medium", or "low".
174
- source_tool: Which tool originated the query (e.g. 'claude-desktop').
175
- rank_position: Position of the memory in the recall results (1-based).
176
-
177
- Returns:
178
- Row ID of the feedback record, or None on error.
179
- """
180
- if not query:
181
- logger.warning("record_memory_used called with empty query")
182
- return None
183
-
184
- # Validate usefulness level
185
- usefulness = usefulness.lower().strip()
186
- if usefulness not in self._USEFULNESS_MAP:
187
- logger.warning(
188
- "Invalid usefulness level '%s', defaulting to 'high'",
189
- usefulness,
190
- )
191
- usefulness = "high"
192
-
193
- signal_type = self._USEFULNESS_MAP[usefulness]
194
- signal_value = self.SIGNAL_VALUES[signal_type]
195
- query_hash = self._hash_query(query)
196
- keywords = self._extract_keywords(query)
197
-
198
- return self._store_feedback(
199
- query_hash=query_hash,
200
- query_keywords=keywords,
201
- memory_id=memory_id,
202
- signal_type=signal_type,
203
- signal_value=signal_value,
204
- channel="mcp",
205
- source_tool=source_tool,
206
- rank_position=rank_position,
207
- )
208
-
209
- # ======================================================================
210
- # Channel 2: CLI -- slm useful <id> [<id>...]
211
- # ======================================================================
212
-
213
- def record_cli_useful(
214
- self,
215
- memory_ids: List[int],
216
- query: str,
217
- ) -> List[Optional[int]]:
218
- """
219
- Record positive feedback from the CLI ``slm useful`` command.
220
-
221
- Stores a positive signal for each memory_id. The CLI typically
222
- captures the most recent recall query, so all IDs share the same
223
- query hash.
224
-
225
- Args:
226
- memory_ids: List of memory IDs the user marked as useful.
227
- query: The recall query that surfaced these memories.
228
-
229
- Returns:
230
- List of row IDs (one per memory_id), or None entries on error.
231
- """
232
- if not query:
233
- logger.warning("record_cli_useful called with empty query")
234
- return [None] * len(memory_ids)
235
-
236
- query_hash = self._hash_query(query)
237
- keywords = self._extract_keywords(query)
238
- signal_value = self.SIGNAL_VALUES["cli_useful"]
239
- row_ids: List[Optional[int]] = []
240
-
241
- for mid in memory_ids:
242
- row_id = self._store_feedback(
243
- query_hash=query_hash,
244
- query_keywords=keywords,
245
- memory_id=mid,
246
- signal_type="cli_useful",
247
- signal_value=signal_value,
248
- channel="cli",
249
- )
250
- row_ids.append(row_id)
251
-
252
- logger.info(
253
- "CLI useful: %d memories marked for query_hash=%s",
254
- len(memory_ids),
255
- query_hash,
256
- )
257
- return row_ids
258
-
259
- # ======================================================================
260
- # Channel 3: Dashboard -- click events
261
- # ======================================================================
262
-
263
- def record_dashboard_click(
264
- self,
265
- memory_id: int,
266
- query: str,
267
- dwell_time: Optional[float] = None,
268
- ) -> Optional[int]:
269
- """
270
- Record a dashboard click on a memory card in search results.
271
-
272
- Optionally includes dwell time (seconds the user spent viewing
273
- the expanded memory). Longer dwell times indicate higher relevance
274
- but this is captured as metadata, not reflected in signal_value
275
- (the re-ranker can learn from dwell_time as a feature).
276
-
277
- Args:
278
- memory_id: ID of the clicked memory.
279
- query: The search query active when the click happened.
280
- dwell_time: Seconds spent viewing the memory (optional).
281
-
282
- Returns:
283
- Row ID of the feedback record, or None on error.
284
- """
285
- if not query:
286
- logger.warning("record_dashboard_click called with empty query")
287
- return None
288
-
289
- query_hash = self._hash_query(query)
290
- keywords = self._extract_keywords(query)
291
- signal_value = self.SIGNAL_VALUES["dashboard_click"]
292
-
293
- return self._store_feedback(
294
- query_hash=query_hash,
295
- query_keywords=keywords,
296
- memory_id=memory_id,
297
- signal_type="dashboard_click",
298
- signal_value=signal_value,
299
- channel="dashboard",
300
- dwell_time=dwell_time,
301
- )
302
-
303
- # ======================================================================
304
- # Channel 4: Implicit Signals (v2.7.4 — auto-collected, zero user effort)
305
- # ======================================================================
306
-
307
- def record_implicit_signal(
308
- self,
309
- memory_id: int,
310
- query: str,
311
- signal_type: str,
312
- source_tool: Optional[str] = None,
313
- rank_position: Optional[int] = None,
314
- ) -> Optional[int]:
315
- """
316
- Record an implicit feedback signal inferred from user behavior.
317
-
318
- Called by the signal inference engine in mcp_server.py when it
319
- detects behavioral patterns (time gaps, re-queries, re-access, etc.).
320
-
321
- Args:
322
- memory_id: ID of the memory.
323
- query: The recall query (hashed, not stored raw).
324
- signal_type: One of the implicit_* signal types.
325
- source_tool: Which tool originated the query.
326
- rank_position: Where the memory appeared in results.
327
-
328
- Returns:
329
- Row ID of the feedback record, or None on error.
330
- """
331
- if not query or signal_type not in self.SIGNAL_VALUES:
332
- logger.warning(
333
- "record_implicit_signal: invalid query or signal_type=%s",
334
- signal_type,
335
- )
336
- return None
337
-
338
- signal_value = self.SIGNAL_VALUES[signal_type]
339
- query_hash = self._hash_query(query)
340
- keywords = self._extract_keywords(query)
341
-
342
- return self._store_feedback(
343
- query_hash=query_hash,
344
- query_keywords=keywords,
345
- memory_id=memory_id,
346
- signal_type=signal_type,
347
- signal_value=signal_value,
348
- channel="implicit",
349
- source_tool=source_tool,
350
- rank_position=rank_position,
351
- )
352
-
353
- def record_dashboard_feedback(
354
- self,
355
- memory_id: int,
356
- query: str,
357
- feedback_type: str,
358
- dwell_time: Optional[float] = None,
359
- ) -> Optional[int]:
360
- """
361
- Record explicit dashboard feedback (thumbs up/down, pin, dwell).
362
-
363
- Args:
364
- memory_id: ID of the memory.
365
- query: The search query active when feedback given.
366
- feedback_type: One of 'thumbs_up', 'thumbs_down', 'pin',
367
- 'dwell_positive', 'dwell_negative'.
368
- dwell_time: Seconds spent viewing (for dwell signals).
369
-
370
- Returns:
371
- Row ID of the feedback record, or None on error.
372
- """
373
- type_map = {
374
- "thumbs_up": "dashboard_thumbs_up",
375
- "thumbs_down": "dashboard_thumbs_down",
376
- "pin": "dashboard_pin",
377
- "dwell_positive": "dashboard_dwell_positive",
378
- "dwell_negative": "dashboard_dwell_negative",
379
- }
380
-
381
- signal_type = type_map.get(feedback_type)
382
- if not signal_type or signal_type not in self.SIGNAL_VALUES:
383
- logger.warning(
384
- "record_dashboard_feedback: invalid feedback_type=%s",
385
- feedback_type,
386
- )
387
- return None
388
-
389
- if not query:
390
- query = f"__dashboard__:{memory_id}"
391
-
392
- signal_value = self.SIGNAL_VALUES[signal_type]
393
- query_hash = self._hash_query(query)
394
- keywords = self._extract_keywords(query)
395
-
396
- return self._store_feedback(
397
- query_hash=query_hash,
398
- query_keywords=keywords,
399
- memory_id=memory_id,
400
- signal_type=signal_type,
401
- signal_value=signal_value,
402
- channel="dashboard",
403
- dwell_time=dwell_time,
404
- )
405
-
406
- # ======================================================================
407
- # Passive Decay Tracking
408
- # ======================================================================
409
-
410
- def record_recall_results(
411
- self,
412
- query: str,
413
- returned_ids: List[int],
414
- ) -> None:
415
- """
416
- Track which memories were returned in a recall operation.
417
-
418
- This does NOT create feedback records immediately. Instead, it
419
- populates an in-memory buffer. When ``compute_passive_decay()``
420
- is called (periodically), memories that were returned in 5+
421
- distinct queries but never received a positive signal get a
422
- passive_decay (0.0) feedback entry.
423
-
424
- Args:
425
- query: The recall query (hashed for grouping).
426
- returned_ids: Memory IDs returned by the recall.
427
- """
428
- if not query or not returned_ids:
429
- return
430
-
431
- query_hash = self._hash_query(query)
432
-
433
- with self._recall_buffer_lock:
434
- if query_hash not in self._recall_buffer:
435
- self._recall_buffer[query_hash] = {}
436
-
437
- for mid in returned_ids:
438
- self._recall_buffer[query_hash][mid] = (
439
- self._recall_buffer[query_hash].get(mid, 0) + 1
440
- )
441
-
442
- self._recall_count += 1
443
-
444
- def compute_passive_decay(self, threshold: int = 10) -> int:
445
- """
446
- Emit passive decay signals for memories that appear in results
447
- but are never explicitly marked as useful.
448
-
449
- Algorithm:
450
- 1. Only runs after *threshold* recall operations are tracked.
451
- 2. For each memory that appeared in 5+ distinct query hashes:
452
- a. Check if it has ANY positive feedback in ranking_feedback.
453
- b. If not, insert a passive_decay signal (value=0.0).
454
- 3. Clear the recall buffer after processing.
455
-
456
- This teaches the re-ranker: "this memory keeps showing up but
457
- nobody ever finds it useful -- demote it."
458
-
459
- Args:
460
- threshold: Minimum number of tracked recalls before running.
461
-
462
- Returns:
463
- Number of passive decay signals emitted.
464
- """
465
- with self._recall_buffer_lock:
466
- if self._recall_count < threshold:
467
- logger.debug(
468
- "Passive decay skipped: %d/%d recalls tracked",
469
- self._recall_count,
470
- threshold,
471
- )
472
- return 0
473
-
474
- # Build a map: memory_id -> set of distinct query_hashes it appeared in
475
- memory_query_counts: Dict[int, int] = {}
476
- for query_hash, mem_counts in self._recall_buffer.items():
477
- for mid in mem_counts:
478
- memory_query_counts[mid] = memory_query_counts.get(mid, 0) + 1
479
-
480
- # Find candidates: appeared in 5+ distinct queries
481
- candidates = [
482
- mid for mid, qcount in memory_query_counts.items()
483
- if qcount >= 5
484
- ]
485
-
486
- # Snapshot and clear buffer
487
- buffer_snapshot = dict(self._recall_buffer)
488
- self._recall_buffer.clear()
489
- self._recall_count = 0
490
-
491
- if not candidates:
492
- logger.debug("No passive decay candidates found")
493
- return 0
494
-
495
- # Check which candidates have positive feedback already
496
- decay_count = 0
497
- for mid in candidates:
498
- if self._has_positive_feedback(mid):
499
- continue
500
-
501
- # Emit passive decay signal. Use a synthetic query hash
502
- # derived from the memory_id to group decay signals.
503
- decay_hash = self._hash_query(f"__passive_decay__:{mid}")
504
- self._store_feedback(
505
- query_hash=decay_hash,
506
- query_keywords=None,
507
- memory_id=mid,
508
- signal_type="passive_decay",
509
- signal_value=self.SIGNAL_VALUES["passive_decay"],
510
- channel="system",
511
- )
512
- decay_count += 1
513
-
514
- if decay_count > 0:
515
- logger.info(
516
- "Passive decay: emitted %d signals for %d candidates",
517
- decay_count,
518
- len(candidates),
519
- )
520
-
521
- return decay_count
522
-
523
- # ======================================================================
524
- # Summary & Diagnostics
525
- # ======================================================================
526
-
527
- def get_feedback_summary(self) -> dict:
528
- """
529
- Return summary statistics for display in CLI or dashboard.
530
-
531
- Returns:
532
- {
533
- 'total_signals': 142,
534
- 'unique_queries': 38,
535
- 'by_channel': {'mcp': 80, 'cli': 35, 'dashboard': 20, 'system': 7},
536
- 'by_type': {'mcp_used_high': 50, 'cli_useful': 35, ...},
537
- 'passive_decay_pending': 12,
538
- 'recall_buffer_size': 45,
539
- }
540
- """
541
- summary: Dict[str, Any] = {
542
- "total_signals": 0,
543
- "unique_queries": 0,
544
- "by_channel": {},
545
- "by_type": {},
546
- "passive_decay_pending": 0,
547
- "recall_buffer_size": 0,
548
- }
549
-
550
- # Buffer stats (always available, even without DB)
551
- with self._recall_buffer_lock:
552
- summary["recall_buffer_size"] = self._recall_count
553
- # Count memories that would be decay candidates
554
- memory_query_counts: Dict[int, int] = {}
555
- for _qh, mem_counts in self._recall_buffer.items():
556
- for mid in mem_counts:
557
- memory_query_counts[mid] = memory_query_counts.get(mid, 0) + 1
558
- summary["passive_decay_pending"] = sum(
559
- 1 for qcount in memory_query_counts.values() if qcount >= 5
560
- )
561
-
562
- if self.learning_db is None:
563
- summary["error"] = "No learning database connected"
564
- return summary
565
-
566
- try:
567
- conn = self.learning_db._get_connection()
568
- try:
569
- cursor = conn.cursor()
570
-
571
- # Total signals
572
- cursor.execute("SELECT COUNT(*) FROM ranking_feedback")
573
- summary["total_signals"] = cursor.fetchone()[0]
574
-
575
- # Unique queries
576
- cursor.execute(
577
- "SELECT COUNT(DISTINCT query_hash) FROM ranking_feedback"
578
- )
579
- summary["unique_queries"] = cursor.fetchone()[0]
580
-
581
- # By channel
582
- cursor.execute(
583
- "SELECT channel, COUNT(*) as cnt "
584
- "FROM ranking_feedback GROUP BY channel"
585
- )
586
- summary["by_channel"] = {
587
- row["channel"]: row["cnt"] for row in cursor.fetchall()
588
- }
589
-
590
- # By signal type
591
- cursor.execute(
592
- "SELECT signal_type, COUNT(*) as cnt "
593
- "FROM ranking_feedback GROUP BY signal_type"
594
- )
595
- summary["by_type"] = {
596
- row["signal_type"]: row["cnt"] for row in cursor.fetchall()
597
- }
598
-
599
- finally:
600
- conn.close()
601
-
602
- except Exception as e:
603
- logger.error("Failed to get feedback summary: %s", e)
604
- summary["error"] = str(e)
605
-
606
- return summary
607
-
608
- # ======================================================================
609
- # Internal helpers
610
- # ======================================================================
611
-
612
- def _hash_query(self, query: str) -> str:
613
- """
614
- Privacy-preserving query hash.
615
-
616
- Returns the first 16 hex characters of the SHA-256 digest.
617
- This is sufficient for grouping without being reversible.
618
- """
619
- return hashlib.sha256(query.encode("utf-8")).hexdigest()[:16]
620
-
621
- def _extract_keywords(self, query: str, top_n: int = 3) -> str:
622
- """
623
- Extract the top N meaningful words from a query string.
624
-
625
- Removes stopwords and short tokens (< 2 chars), then returns
626
- the most frequent remaining words as a comma-separated string.
627
-
628
- Args:
629
- query: Raw query text.
630
- top_n: Number of keywords to extract.
631
-
632
- Returns:
633
- Comma-separated keyword string (e.g. "deploy,fastapi,docker").
634
- Empty string if no keywords extracted.
635
- """
636
- if not query:
637
- return ""
638
-
639
- words = _WORD_SPLIT.split(query.lower())
640
- # Filter stopwords and short tokens
641
- meaningful = [w for w in words if w and len(w) >= 2 and w not in _STOPWORDS]
642
-
643
- if not meaningful:
644
- return ""
645
-
646
- # Most common N words (preserves order of first occurrence for ties)
647
- counts = Counter(meaningful)
648
- top_words = [word for word, _count in counts.most_common(top_n)]
649
- return ",".join(top_words)
650
-
651
- def _store_feedback(
652
- self,
653
- query_hash: str,
654
- query_keywords: Optional[str],
655
- memory_id: int,
656
- signal_type: str,
657
- signal_value: float,
658
- channel: str,
659
- source_tool: Optional[str] = None,
660
- rank_position: Optional[int] = None,
661
- dwell_time: Optional[float] = None,
662
- ) -> Optional[int]:
663
- """
664
- Persist a single feedback record via LearningDB.
665
-
666
- Also updates the daily engagement metric counter.
667
-
668
- Returns:
669
- Row ID on success, None on failure or if no DB is available.
670
- """
671
- if self.learning_db is None:
672
- logger.debug(
673
- "Feedback not stored (no DB): memory=%d, type=%s",
674
- memory_id,
675
- signal_type,
676
- )
677
- return None
678
-
679
- try:
680
- row_id = self.learning_db.store_feedback(
681
- query_hash=query_hash,
682
- memory_id=memory_id,
683
- signal_type=signal_type,
684
- signal_value=signal_value,
685
- channel=channel,
686
- query_keywords=query_keywords,
687
- rank_position=rank_position,
688
- source_tool=source_tool,
689
- dwell_time=dwell_time,
690
- )
691
-
692
- # Update daily engagement counter (best-effort)
693
- try:
694
- self.learning_db.increment_engagement(
695
- "feedback_signals",
696
- count=1,
697
- source=source_tool,
698
- )
699
- except Exception:
700
- pass
701
-
702
- return row_id
703
-
704
- except Exception as e:
705
- logger.error(
706
- "Failed to store feedback for memory %d: %s",
707
- memory_id,
708
- e,
709
- )
710
- return None
711
-
712
- def _has_positive_feedback(self, memory_id: int) -> bool:
713
- """
714
- Check if a memory has ANY positive feedback in learning.db.
715
-
716
- Positive = signal_value > 0.0 (anything above passive_decay).
717
- Used by passive decay to avoid penalising memories that were
718
- actually found useful at some point.
719
-
720
- Args:
721
- memory_id: Memory ID to check.
722
-
723
- Returns:
724
- True if at least one positive feedback record exists.
725
- """
726
- if self.learning_db is None:
727
- return False
728
-
729
- try:
730
- conn = self.learning_db._get_connection()
731
- try:
732
- cursor = conn.cursor()
733
- cursor.execute(
734
- """
735
- SELECT COUNT(*) FROM ranking_feedback
736
- WHERE memory_id = ? AND signal_value > 0.0
737
- """,
738
- (memory_id,),
739
- )
740
- count = cursor.fetchone()[0]
741
- return count > 0
742
- finally:
743
- conn.close()
744
- except Exception as e:
745
- logger.error(
746
- "Failed to check positive feedback for memory %d: %s",
747
- memory_id,
748
- e,
749
- )
750
- # If we can't check, assume positive to avoid false penalisation
751
- return True
752
-
753
-
754
- # ======================================================================
755
- # Standalone execution (for diagnostics: python3 feedback_collector.py)
756
- # ======================================================================
757
-
758
- def main():
759
- """Print feedback summary from CLI."""
760
- import sys
761
-
762
- logging.basicConfig(
763
- level=logging.INFO,
764
- format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
765
- )
766
-
767
- # Try to get LearningDB
768
- learning_db = None
769
- try:
770
- sys.path.insert(0, str(__import__("pathlib").Path(__file__).parent))
771
- from learning_db import LearningDB
772
- learning_db = LearningDB()
773
- except ImportError:
774
- logger.warning("LearningDB not available")
775
-
776
- collector = FeedbackCollector(learning_db=learning_db)
777
- summary = collector.get_feedback_summary()
778
-
779
- print(f"\n{'='*60}")
780
- print(f" Feedback Summary")
781
- print(f"{'='*60}")
782
- print(f" Total signals: {summary.get('total_signals', 0)}")
783
- print(f" Unique queries: {summary.get('unique_queries', 0)}")
784
- print(f" Recall buffer size: {summary.get('recall_buffer_size', 0)}")
785
- print(f" Decay pending: {summary.get('passive_decay_pending', 0)}")
786
-
787
- by_channel = summary.get("by_channel", {})
788
- if by_channel:
789
- print(f"\n By Channel:")
790
- for ch, cnt in sorted(by_channel.items()):
791
- print(f" {ch:>12s}: {cnt}")
792
-
793
- by_type = summary.get("by_type", {})
794
- if by_type:
795
- print(f"\n By Signal Type:")
796
- for st, cnt in sorted(by_type.items()):
797
- print(f" {st:>18s}: {cnt}")
798
-
799
- if "error" in summary:
800
- print(f"\n Error: {summary['error']}")
801
-
802
- print()
803
-
804
-
805
- if __name__ == "__main__":
806
- main()