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