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,35 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for auto-retrain mechanism when feature dimensions change 12->20.
4
- """
5
- import sys
6
- from pathlib import Path
7
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
8
-
9
-
10
- class TestAutoRetrain:
11
- def test_feature_dimension_is_20(self):
12
- from learning.feature_extractor import NUM_FEATURES
13
- assert NUM_FEATURES == 20
14
-
15
- def test_ranker_handles_dimension_mismatch(self):
16
- """Ranker should not crash when loaded model has different dimensions."""
17
- from learning.adaptive_ranker import AdaptiveRanker
18
- ranker = AdaptiveRanker()
19
- # Even without a trained model, phase 1 should work
20
- results = [{'id': 1, 'content': 'test', 'score': 0.5, 'match_type': 'semantic', 'importance': 5}]
21
- ranked = ranker.rerank(results, "test")
22
- assert len(ranked) == 1
23
-
24
- def test_phase1_immediate_during_retrain(self):
25
- """Phase 1 (rule-based) should work immediately even during model retrain."""
26
- from learning.adaptive_ranker import AdaptiveRanker
27
- ranker = AdaptiveRanker()
28
- results = [
29
- {'id': 1, 'content': 'memory A', 'score': 0.9, 'match_type': 'semantic', 'importance': 8},
30
- {'id': 2, 'content': 'memory B', 'score': 0.3, 'match_type': 'keyword', 'importance': 2},
31
- ]
32
- ranked = ranker.rerank(results, "test query")
33
- assert len(ranked) == 2
34
- # Higher base score should still rank first in phase 1
35
- assert ranked[0]['score'] >= ranked[1]['score']
@@ -1,82 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """End-to-end tests for adaptive ranking with 20-feature vector.
4
- """
5
- import sys
6
- from pathlib import Path
7
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
8
-
9
-
10
- class TestE2ERankingV28:
11
- def test_full_20_feature_pipeline(self):
12
- """Full pipeline: extract 20 features -> rank results."""
13
- from learning.feature_extractor import FeatureExtractor
14
- from learning.adaptive_ranker import AdaptiveRanker
15
-
16
- extractor = FeatureExtractor()
17
- ranker = AdaptiveRanker()
18
-
19
- memories = [
20
- {'id': 1, 'content': 'Python best practices for production', 'score': 0.8,
21
- 'match_type': 'semantic', 'importance': 8, 'lifecycle_state': 'active',
22
- 'access_count': 15, 'created_at': '2026-02-01'},
23
- {'id': 2, 'content': 'Old JavaScript patterns deprecated', 'score': 0.75,
24
- 'match_type': 'semantic', 'importance': 3, 'lifecycle_state': 'cold',
25
- 'access_count': 1, 'created_at': '2025-06-01'},
26
- ]
27
-
28
- # Extract features
29
- for mem in memories:
30
- features = extractor.extract_features(mem, "Python production")
31
- assert len(features) == 20
32
-
33
- # Rank
34
- ranked = ranker.rerank(memories, "Python production")
35
- assert len(ranked) == 2
36
-
37
- def test_lifecycle_affects_ranking(self):
38
- """Active memories should generally rank above cold ones."""
39
- from learning.adaptive_ranker import AdaptiveRanker
40
- ranker = AdaptiveRanker()
41
-
42
- results = [
43
- {'id': 1, 'content': 'cold memory', 'score': 0.85, 'match_type': 'semantic',
44
- 'importance': 5, 'lifecycle_state': 'cold'},
45
- {'id': 2, 'content': 'active memory', 'score': 0.80, 'match_type': 'semantic',
46
- 'importance': 5, 'lifecycle_state': 'active'},
47
- ]
48
- ranked = ranker.rerank(results, "test")
49
- # Even with slightly lower base score, active should win due to lifecycle boost
50
- assert ranked[0]['id'] == 2
51
-
52
- def test_high_outcome_success_boosts_ranking(self):
53
- """Memories with high success rates should rank higher."""
54
- from learning.adaptive_ranker import AdaptiveRanker
55
- ranker = AdaptiveRanker()
56
-
57
- results = [
58
- {'id': 1, 'content': 'frequently failing', 'score': 0.8, 'match_type': 'semantic',
59
- 'importance': 5, 'outcome_success_rate': 0.1},
60
- {'id': 2, 'content': 'consistently useful', 'score': 0.8, 'match_type': 'semantic',
61
- 'importance': 5, 'outcome_success_rate': 0.95},
62
- ]
63
- ranked = ranker.rerank(results, "test")
64
- assert ranked[0]['id'] == 2
65
-
66
- def test_result_ordering_reflects_all_signals(self):
67
- """Result ordering should reflect lifecycle + behavioral + base score."""
68
- from learning.adaptive_ranker import AdaptiveRanker
69
- ranker = AdaptiveRanker()
70
-
71
- results = [
72
- {'id': 1, 'content': 'A', 'score': 0.7, 'match_type': 'semantic', 'importance': 5,
73
- 'lifecycle_state': 'active', 'outcome_success_rate': 0.9},
74
- {'id': 2, 'content': 'B', 'score': 0.9, 'match_type': 'semantic', 'importance': 5,
75
- 'lifecycle_state': 'cold', 'outcome_success_rate': 0.1},
76
- {'id': 3, 'content': 'C', 'score': 0.8, 'match_type': 'semantic', 'importance': 8,
77
- 'lifecycle_state': 'active', 'outcome_success_rate': 0.5},
78
- ]
79
- ranked = ranker.rerank(results, "test")
80
- assert len(ranked) == 3
81
- # All should have final scores
82
- assert all('score' in r for r in ranked)
@@ -1,93 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for v2.8 feature extractor — 20-dimensional feature vectors.
4
- """
5
- import sys
6
- from pathlib import Path
7
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
8
-
9
-
10
- class TestFeatureExtractorV28:
11
- def test_feature_count_is_20(self):
12
- from learning.feature_extractor import FEATURE_NAMES
13
- assert len(FEATURE_NAMES) == 20
14
-
15
- def test_new_feature_names_present(self):
16
- from learning.feature_extractor import FEATURE_NAMES
17
- new_features = ['lifecycle_state', 'outcome_success_rate', 'outcome_count',
18
- 'behavioral_match', 'cross_project_score', 'retention_priority',
19
- 'trust_at_creation', 'lifecycle_aware_decay']
20
- for f in new_features:
21
- assert f in FEATURE_NAMES
22
-
23
- def test_extract_returns_20_features(self):
24
- from learning.feature_extractor import FeatureExtractor
25
- extractor = FeatureExtractor()
26
- memory = {
27
- 'id': 1, 'content': 'test memory about Python', 'importance': 5,
28
- 'created_at': '2026-02-20T10:00:00', 'last_accessed': '2026-02-25T10:00:00',
29
- 'access_count': 3, 'tags': ['python'], 'project_name': 'myproject',
30
- 'lifecycle_state': 'active', 'score': 0.8, 'match_type': 'semantic',
31
- }
32
- features = extractor.extract_features(memory, "Python programming")
33
- assert len(features) == 20
34
- assert all(isinstance(f, (int, float)) for f in features)
35
-
36
- def test_lifecycle_state_encoding(self):
37
- from learning.feature_extractor import FeatureExtractor
38
- extractor = FeatureExtractor()
39
- base = {'id': 1, 'content': 'test', 'importance': 5, 'created_at': '2026-02-20',
40
- 'access_count': 0, 'score': 0.5, 'match_type': 'semantic'}
41
-
42
- active = extractor.extract_features({**base, 'lifecycle_state': 'active'}, "test")
43
- warm = extractor.extract_features({**base, 'lifecycle_state': 'warm'}, "test")
44
- cold = extractor.extract_features({**base, 'lifecycle_state': 'cold'}, "test")
45
-
46
- assert active[12] > warm[12] > cold[12]
47
-
48
- def test_default_values_when_data_missing(self):
49
- """Features should return sensible defaults when data is unavailable."""
50
- from learning.feature_extractor import FeatureExtractor
51
- extractor = FeatureExtractor()
52
- minimal = {'id': 1, 'content': 'test', 'importance': 5, 'score': 0.5, 'match_type': 'semantic'}
53
- features = extractor.extract_features(minimal, "test")
54
- assert len(features) == 20
55
- # lifecycle_state default (active) = 1.0
56
- assert features[12] == 1.0
57
- # outcome_success_rate default = 0.5
58
- assert features[13] == 0.5
59
- # retention_priority default = 0.5
60
- assert features[17] == 0.5
61
- # trust_at_creation default = 0.8
62
- assert features[18] == 0.8
63
-
64
- def test_backward_compat_old_12_features_still_work(self):
65
- """The first 12 features should still be computed correctly."""
66
- from learning.feature_extractor import FeatureExtractor
67
- extractor = FeatureExtractor()
68
- memory = {'id': 1, 'content': 'Python is great', 'importance': 8,
69
- 'created_at': '2026-02-20', 'access_count': 5, 'score': 0.9,
70
- 'match_type': 'semantic', 'lifecycle_state': 'active'}
71
- features = extractor.extract_features(memory, "Python")
72
- # importance_norm (index 6) should be 0.8 (8/10)
73
- assert abs(features[6] - 0.8) < 0.01
74
-
75
- def test_extract_batch_returns_20_wide(self):
76
- from learning.feature_extractor import FeatureExtractor
77
- extractor = FeatureExtractor()
78
- memories = [
79
- {'id': 1, 'content': 'mem1', 'importance': 5, 'score': 0.5, 'match_type': 'semantic'},
80
- {'id': 2, 'content': 'mem2', 'importance': 7, 'score': 0.7, 'match_type': 'semantic'},
81
- ]
82
- batch = extractor.extract_batch(memories, "test")
83
- assert len(batch) == 2
84
- assert all(len(v) == 20 for v in batch)
85
-
86
- def test_outcome_success_rate_from_memory(self):
87
- """If memory has outcome_success_rate in dict, use it."""
88
- from learning.feature_extractor import FeatureExtractor
89
- extractor = FeatureExtractor()
90
- memory = {'id': 1, 'content': 'test', 'importance': 5, 'score': 0.5,
91
- 'match_type': 'semantic', 'outcome_success_rate': 0.85}
92
- features = extractor.extract_features(memory, "test")
93
- assert abs(features[13] - 0.85) < 0.01
@@ -1,294 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: MIT
3
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
4
- import hashlib
5
- import time
6
-
7
- import pytest
8
-
9
-
10
- # ---------------------------------------------------------------------------
11
- # Fixtures
12
- # ---------------------------------------------------------------------------
13
-
14
- @pytest.fixture(autouse=True)
15
- def reset_singleton():
16
- from src.learning.learning_db import LearningDB
17
- LearningDB.reset_instance()
18
- yield
19
- LearningDB.reset_instance()
20
-
21
-
22
- @pytest.fixture
23
- def learning_db(tmp_path):
24
- from src.learning.learning_db import LearningDB
25
- db_path = tmp_path / "learning.db"
26
- return LearningDB(db_path=db_path)
27
-
28
-
29
- @pytest.fixture
30
- def collector(learning_db):
31
- from src.learning.feedback_collector import FeedbackCollector
32
- return FeedbackCollector(learning_db=learning_db)
33
-
34
-
35
- @pytest.fixture
36
- def collector_no_db():
37
- """Collector with no database — tests graceful degradation."""
38
- from src.learning.feedback_collector import FeedbackCollector
39
- return FeedbackCollector(learning_db=None)
40
-
41
-
42
- # ---------------------------------------------------------------------------
43
- # Channel 1: MCP memory_used
44
- # ---------------------------------------------------------------------------
45
-
46
- class TestRecordMemoryUsed:
47
- def test_high_usefulness(self, collector, learning_db):
48
- row_id = collector.record_memory_used(42, "deploy fastapi", usefulness="high")
49
- assert row_id is not None
50
-
51
- rows = learning_db.get_feedback_for_training()
52
- assert len(rows) == 1
53
- assert rows[0]["signal_type"] == "mcp_used_high"
54
- assert rows[0]["signal_value"] == 1.0
55
- assert rows[0]["channel"] == "mcp"
56
-
57
- def test_medium_usefulness(self, collector, learning_db):
58
- collector.record_memory_used(42, "deploy fastapi", usefulness="medium")
59
- rows = learning_db.get_feedback_for_training()
60
- assert rows[0]["signal_type"] == "mcp_used_medium"
61
- assert rows[0]["signal_value"] == 0.7
62
-
63
- def test_low_usefulness(self, collector, learning_db):
64
- collector.record_memory_used(42, "deploy fastapi", usefulness="low")
65
- rows = learning_db.get_feedback_for_training()
66
- assert rows[0]["signal_type"] == "mcp_used_low"
67
- assert rows[0]["signal_value"] == 0.4
68
-
69
- def test_invalid_usefulness_defaults_to_high(self, collector, learning_db):
70
- collector.record_memory_used(42, "test query", usefulness="INVALID")
71
- rows = learning_db.get_feedback_for_training()
72
- assert rows[0]["signal_type"] == "mcp_used_high"
73
-
74
- def test_empty_query_returns_none(self, collector):
75
- result = collector.record_memory_used(42, "")
76
- assert result is None
77
-
78
- def test_source_tool_recorded(self, collector, learning_db):
79
- collector.record_memory_used(
80
- 42, "test query", source_tool="claude-desktop", rank_position=2,
81
- )
82
- rows = learning_db.get_feedback_for_training()
83
- assert rows[0]["source_tool"] == "claude-desktop"
84
- assert rows[0]["rank_position"] == 2
85
-
86
- def test_no_db_auto_creates(self, collector_no_db):
87
- """v2.7.2+: FeedbackCollector auto-creates LearningDB when None passed."""
88
- result = collector_no_db.record_memory_used(42, "test query")
89
- # Auto-created DB means this succeeds (returns row ID)
90
- assert result is not None
91
-
92
-
93
- # ---------------------------------------------------------------------------
94
- # Channel 2: CLI slm useful
95
- # ---------------------------------------------------------------------------
96
-
97
- class TestRecordCliUseful:
98
- def test_batch_ids(self, collector, learning_db):
99
- row_ids = collector.record_cli_useful([10, 20, 30], "deploy fastapi")
100
- assert len(row_ids) == 3
101
- assert all(rid is not None for rid in row_ids)
102
- assert learning_db.get_feedback_count() == 3
103
-
104
- def test_signal_value(self, collector, learning_db):
105
- collector.record_cli_useful([42], "query")
106
- rows = learning_db.get_feedback_for_training()
107
- assert rows[0]["signal_value"] == 0.9
108
- assert rows[0]["signal_type"] == "cli_useful"
109
- assert rows[0]["channel"] == "cli"
110
-
111
- def test_all_share_same_query_hash(self, collector, learning_db):
112
- collector.record_cli_useful([1, 2, 3], "same query")
113
- rows = learning_db.get_feedback_for_training()
114
- hashes = {r["query_hash"] for r in rows}
115
- assert len(hashes) == 1
116
-
117
- def test_empty_query(self, collector):
118
- result = collector.record_cli_useful([1, 2], "")
119
- assert result == [None, None]
120
-
121
-
122
- # ---------------------------------------------------------------------------
123
- # Channel 3: Dashboard click
124
- # ---------------------------------------------------------------------------
125
-
126
- class TestRecordDashboardClick:
127
- def test_basic_click(self, collector, learning_db):
128
- row_id = collector.record_dashboard_click(42, "test query")
129
- assert row_id is not None
130
- rows = learning_db.get_feedback_for_training()
131
- assert rows[0]["signal_type"] == "dashboard_click"
132
- assert rows[0]["signal_value"] == 0.8
133
- assert rows[0]["channel"] == "dashboard"
134
-
135
- def test_with_dwell_time(self, collector, learning_db):
136
- collector.record_dashboard_click(42, "test query", dwell_time=15.3)
137
- # dwell_time is stored in ranking_feedback but not in training export
138
- # Verify via direct DB query
139
- conn = learning_db._get_connection()
140
- cursor = conn.cursor()
141
- cursor.execute("SELECT dwell_time FROM ranking_feedback WHERE memory_id = 42")
142
- row = cursor.fetchone()
143
- conn.close()
144
- assert row is not None
145
- assert abs(row[0] - 15.3) < 0.01
146
-
147
- def test_empty_query_returns_none(self, collector):
148
- result = collector.record_dashboard_click(42, "")
149
- assert result is None
150
-
151
-
152
- # ---------------------------------------------------------------------------
153
- # Query Hashing
154
- # ---------------------------------------------------------------------------
155
-
156
- class TestHashQuery:
157
- def test_deterministic(self, collector):
158
- h1 = collector._hash_query("deploy fastapi")
159
- h2 = collector._hash_query("deploy fastapi")
160
- assert h1 == h2
161
-
162
- def test_sha256_first_16(self, collector):
163
- query = "deploy fastapi"
164
- expected = hashlib.sha256(query.encode("utf-8")).hexdigest()[:16]
165
- assert collector._hash_query(query) == expected
166
-
167
- def test_length_is_16(self, collector):
168
- result = collector._hash_query("any string")
169
- assert len(result) == 16
170
-
171
- def test_different_queries_different_hashes(self, collector):
172
- h1 = collector._hash_query("query one")
173
- h2 = collector._hash_query("query two")
174
- assert h1 != h2
175
-
176
-
177
- # ---------------------------------------------------------------------------
178
- # Keyword Extraction
179
- # ---------------------------------------------------------------------------
180
-
181
- class TestExtractKeywords:
182
- def test_stopword_removal(self, collector):
183
- kw = collector._extract_keywords("how to deploy the app")
184
- assert "how" not in kw
185
- assert "to" not in kw
186
- assert "the" not in kw
187
- assert "deploy" in kw
188
-
189
- def test_top_n_limit(self, collector):
190
- kw = collector._extract_keywords(
191
- "python fastapi docker kubernetes deployment pipeline"
192
- )
193
- assert len(kw.split(",")) <= 3
194
-
195
- def test_empty_query(self, collector):
196
- assert collector._extract_keywords("") == ""
197
-
198
- def test_only_stopwords(self, collector):
199
- assert collector._extract_keywords("the and or but") == ""
200
-
201
- def test_comma_separated_output(self, collector):
202
- kw = collector._extract_keywords("deploy fastapi docker")
203
- assert "," in kw or len(kw.split(",")) == 1 # single keyword = no comma
204
-
205
-
206
- # ---------------------------------------------------------------------------
207
- # Passive Decay
208
- # ---------------------------------------------------------------------------
209
-
210
- class TestPassiveDecay:
211
- def test_record_recall_results(self, collector):
212
- collector.record_recall_results("test query", [1, 2, 3])
213
- assert collector._recall_count == 1
214
-
215
- def test_compute_passive_decay_below_threshold(self, collector):
216
- """Should return 0 if below threshold."""
217
- collector.record_recall_results("test query", [1, 2, 3])
218
- result = collector.compute_passive_decay(threshold=10)
219
- assert result == 0
220
-
221
- def test_compute_passive_decay_with_candidates(self, collector, learning_db):
222
- """Memory appearing in 5+ distinct queries should get decay signal."""
223
- # Create 10+ recall operations with memory_id=99 appearing in 6 distinct queries
224
- for i in range(10):
225
- collector.record_recall_results(f"query_{i}", [99, 100 + i])
226
-
227
- decay_count = collector.compute_passive_decay(threshold=10)
228
- # memory 99 appeared in 10 distinct queries, no positive feedback
229
- assert decay_count >= 1
230
-
231
- def test_no_decay_for_positively_rated(self, collector, learning_db):
232
- """Memories with positive feedback should NOT get passive decay."""
233
- # Give memory 99 positive feedback first
234
- learning_db.store_feedback(
235
- query_hash="q", memory_id=99, signal_type="mcp_used",
236
- signal_value=1.0, channel="mcp",
237
- )
238
-
239
- # Record 10+ recall operations with memory 99
240
- for i in range(12):
241
- collector.record_recall_results(f"query_{i}", [99])
242
-
243
- decay_count = collector.compute_passive_decay(threshold=10)
244
- assert decay_count == 0
245
-
246
- def test_buffer_cleared_after_decay(self, collector):
247
- """Recall buffer should be cleared after computing decay."""
248
- for i in range(10):
249
- collector.record_recall_results(f"q{i}", [1])
250
- collector.compute_passive_decay(threshold=10)
251
- assert collector._recall_count == 0
252
-
253
- def test_empty_query_ignored(self, collector):
254
- collector.record_recall_results("", [1, 2, 3])
255
- assert collector._recall_count == 0
256
-
257
- def test_empty_ids_ignored(self, collector):
258
- collector.record_recall_results("test query", [])
259
- assert collector._recall_count == 0
260
-
261
-
262
- # ---------------------------------------------------------------------------
263
- # Summary
264
- # ---------------------------------------------------------------------------
265
-
266
- class TestFeedbackSummary:
267
- def test_summary_with_data(self, collector, learning_db):
268
- collector.record_memory_used(1, "q1", usefulness="high")
269
- collector.record_cli_useful([2], "q2")
270
- collector.record_dashboard_click(3, "q3")
271
-
272
- summary = collector.get_feedback_summary()
273
- assert summary["total_signals"] == 3
274
- assert summary["unique_queries"] == 3
275
- assert "mcp" in summary["by_channel"]
276
- assert "cli" in summary["by_channel"]
277
- assert "dashboard" in summary["by_channel"]
278
-
279
- def test_summary_empty_db(self, collector):
280
- summary = collector.get_feedback_summary()
281
- assert summary["total_signals"] == 0
282
- assert summary["unique_queries"] == 0
283
-
284
- def test_summary_no_db_auto_creates(self, collector_no_db):
285
- """v2.7.2+: Auto-created DB returns valid summary, not error."""
286
- summary = collector_no_db.get_feedback_summary()
287
- assert "total_signals" in summary
288
-
289
- def test_summary_buffer_stats(self, collector):
290
- collector.record_recall_results("q1", [1, 2, 3])
291
- collector.record_recall_results("q2", [1, 4, 5])
292
-
293
- summary = collector.get_feedback_summary()
294
- assert summary["recall_buffer_size"] == 2