superlocalmemory 2.8.6 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (431) hide show
  1. package/LICENSE +9 -1
  2. package/NOTICE +63 -0
  3. package/README.md +165 -480
  4. package/bin/slm +17 -449
  5. package/bin/slm-npm +62 -48
  6. package/conftest.py +5 -0
  7. package/docs/api-reference.md +284 -0
  8. package/docs/architecture.md +149 -0
  9. package/docs/auto-memory.md +150 -0
  10. package/docs/cli-reference.md +276 -0
  11. package/docs/compliance.md +191 -0
  12. package/docs/configuration.md +182 -0
  13. package/docs/getting-started.md +102 -0
  14. package/docs/ide-setup.md +261 -0
  15. package/docs/mcp-tools.md +220 -0
  16. package/docs/migration-from-v2.md +170 -0
  17. package/docs/profiles.md +173 -0
  18. package/docs/troubleshooting.md +310 -0
  19. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  20. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  21. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  22. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  23. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  24. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  25. package/ide/configs/cursor-mcp.json +15 -0
  26. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  27. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  28. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  29. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  30. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  31. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  32. package/{configs → ide/configs}/zed-mcp.json +2 -2
  33. package/{hooks → ide/hooks}/context-hook.js +9 -20
  34. package/ide/hooks/memory-list-skill.js +70 -0
  35. package/ide/hooks/memory-profile-skill.js +101 -0
  36. package/ide/hooks/memory-recall-skill.js +62 -0
  37. package/ide/hooks/memory-remember-skill.js +68 -0
  38. package/ide/hooks/memory-reset-skill.js +160 -0
  39. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  40. package/ide/integrations/langchain/README.md +106 -0
  41. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  42. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  43. package/ide/integrations/langchain/pyproject.toml +38 -0
  44. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  45. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  46. package/ide/integrations/langchain/tests/test_security.py +117 -0
  47. package/ide/integrations/llamaindex/README.md +81 -0
  48. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  49. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  50. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  51. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  52. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  53. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  54. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  55. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  56. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  57. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  58. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  59. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  60. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  61. package/package.json +13 -22
  62. package/pyproject.toml +85 -0
  63. package/scripts/build-dmg.sh +417 -0
  64. package/scripts/install-skills.ps1 +334 -0
  65. package/scripts/postinstall.js +2 -2
  66. package/scripts/start-dashboard.ps1 +52 -0
  67. package/scripts/start-dashboard.sh +41 -0
  68. package/scripts/sync-wiki.ps1 +127 -0
  69. package/scripts/sync-wiki.sh +82 -0
  70. package/scripts/test-dmg.sh +161 -0
  71. package/scripts/test-npm-package.ps1 +252 -0
  72. package/scripts/test-npm-package.sh +207 -0
  73. package/scripts/verify-install.ps1 +294 -0
  74. package/scripts/verify-install.sh +266 -0
  75. package/src/superlocalmemory/__init__.py +0 -0
  76. package/src/superlocalmemory/attribution/__init__.py +9 -0
  77. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  78. package/src/superlocalmemory/attribution/signer.py +153 -0
  79. package/src/superlocalmemory/attribution/watermark.py +189 -0
  80. package/src/superlocalmemory/cli/__init__.py +5 -0
  81. package/src/superlocalmemory/cli/commands.py +245 -0
  82. package/src/superlocalmemory/cli/main.py +89 -0
  83. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  84. package/src/superlocalmemory/cli/post_install.py +99 -0
  85. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  86. package/src/superlocalmemory/compliance/__init__.py +0 -0
  87. package/src/superlocalmemory/compliance/abac.py +204 -0
  88. package/src/superlocalmemory/compliance/audit.py +314 -0
  89. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  90. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  91. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  92. package/src/superlocalmemory/compliance/retention.py +232 -0
  93. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  94. package/src/superlocalmemory/core/__init__.py +0 -0
  95. package/src/superlocalmemory/core/config.py +391 -0
  96. package/src/superlocalmemory/core/embeddings.py +293 -0
  97. package/src/superlocalmemory/core/engine.py +701 -0
  98. package/src/superlocalmemory/core/hooks.py +65 -0
  99. package/src/superlocalmemory/core/maintenance.py +172 -0
  100. package/src/superlocalmemory/core/modes.py +140 -0
  101. package/src/superlocalmemory/core/profiles.py +234 -0
  102. package/src/superlocalmemory/core/registry.py +117 -0
  103. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  104. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  105. package/src/superlocalmemory/encoding/__init__.py +0 -0
  106. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  107. package/src/superlocalmemory/encoding/emotional.py +125 -0
  108. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  109. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  110. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  111. package/src/superlocalmemory/encoding/foresight.py +91 -0
  112. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  113. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  114. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  115. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  116. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  117. package/src/superlocalmemory/encoding/type_router.py +235 -0
  118. package/src/superlocalmemory/hooks/__init__.py +3 -0
  119. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  120. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  121. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  122. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  123. package/src/superlocalmemory/infra/__init__.py +3 -0
  124. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  125. package/src/superlocalmemory/infra/backup.py +317 -0
  126. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  127. package/src/superlocalmemory/infra/event_bus.py +381 -0
  128. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  129. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  130. package/src/superlocalmemory/learning/__init__.py +0 -0
  131. package/src/superlocalmemory/learning/adaptive.py +172 -0
  132. package/src/superlocalmemory/learning/behavioral.py +490 -0
  133. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  134. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  135. package/src/superlocalmemory/learning/cross_project.py +399 -0
  136. package/src/superlocalmemory/learning/database.py +376 -0
  137. package/src/superlocalmemory/learning/engagement.py +323 -0
  138. package/src/superlocalmemory/learning/features.py +138 -0
  139. package/src/superlocalmemory/learning/feedback.py +316 -0
  140. package/src/superlocalmemory/learning/outcomes.py +255 -0
  141. package/src/superlocalmemory/learning/project_context.py +366 -0
  142. package/src/superlocalmemory/learning/ranker.py +155 -0
  143. package/src/superlocalmemory/learning/source_quality.py +303 -0
  144. package/src/superlocalmemory/learning/workflows.py +309 -0
  145. package/src/superlocalmemory/llm/__init__.py +0 -0
  146. package/src/superlocalmemory/llm/backbone.py +316 -0
  147. package/src/superlocalmemory/math/__init__.py +0 -0
  148. package/src/superlocalmemory/math/fisher.py +356 -0
  149. package/src/superlocalmemory/math/langevin.py +398 -0
  150. package/src/superlocalmemory/math/sheaf.py +257 -0
  151. package/src/superlocalmemory/mcp/__init__.py +0 -0
  152. package/src/superlocalmemory/mcp/resources.py +245 -0
  153. package/src/superlocalmemory/mcp/server.py +61 -0
  154. package/src/superlocalmemory/mcp/tools.py +18 -0
  155. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  156. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  157. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  158. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  159. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  160. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  161. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  162. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  163. package/src/superlocalmemory/retrieval/engine.py +390 -0
  164. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  165. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  166. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  167. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  168. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  169. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  170. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  171. package/src/superlocalmemory/server/__init__.py +1 -0
  172. package/src/superlocalmemory/server/api.py +248 -0
  173. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  174. package/src/superlocalmemory/server/routes/agents.py +107 -0
  175. package/src/superlocalmemory/server/routes/backup.py +91 -0
  176. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  177. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  178. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  179. package/src/superlocalmemory/server/routes/events.py +183 -0
  180. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  181. package/src/superlocalmemory/server/routes/learning.py +273 -0
  182. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  183. package/src/superlocalmemory/server/routes/memories.py +399 -0
  184. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  185. package/src/superlocalmemory/server/routes/stats.py +346 -0
  186. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  187. package/src/superlocalmemory/server/routes/ws.py +82 -0
  188. package/src/superlocalmemory/server/security_middleware.py +57 -0
  189. package/src/superlocalmemory/server/ui.py +245 -0
  190. package/src/superlocalmemory/storage/__init__.py +0 -0
  191. package/src/superlocalmemory/storage/access_control.py +182 -0
  192. package/src/superlocalmemory/storage/database.py +594 -0
  193. package/src/superlocalmemory/storage/migrations.py +303 -0
  194. package/src/superlocalmemory/storage/models.py +406 -0
  195. package/src/superlocalmemory/storage/schema.py +726 -0
  196. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  197. package/src/superlocalmemory/trust/__init__.py +0 -0
  198. package/src/superlocalmemory/trust/gate.py +130 -0
  199. package/src/superlocalmemory/trust/provenance.py +124 -0
  200. package/src/superlocalmemory/trust/scorer.py +347 -0
  201. package/src/superlocalmemory/trust/signals.py +153 -0
  202. package/ui/index.html +278 -5
  203. package/ui/js/auto-settings.js +70 -0
  204. package/ui/js/dashboard.js +90 -0
  205. package/ui/js/fact-detail.js +92 -0
  206. package/ui/js/feedback.js +2 -2
  207. package/ui/js/ide-status.js +102 -0
  208. package/ui/js/math-health.js +98 -0
  209. package/ui/js/recall-lab.js +127 -0
  210. package/ui/js/settings.js +2 -2
  211. package/ui/js/trust-dashboard.js +73 -0
  212. package/api_server.py +0 -724
  213. package/bin/aider-smart +0 -72
  214. package/bin/superlocalmemoryv2-learning +0 -4
  215. package/bin/superlocalmemoryv2-list +0 -3
  216. package/bin/superlocalmemoryv2-patterns +0 -4
  217. package/bin/superlocalmemoryv2-profile +0 -3
  218. package/bin/superlocalmemoryv2-recall +0 -3
  219. package/bin/superlocalmemoryv2-remember +0 -3
  220. package/bin/superlocalmemoryv2-reset +0 -3
  221. package/bin/superlocalmemoryv2-status +0 -3
  222. package/configs/chatgpt-desktop-mcp.json +0 -16
  223. package/configs/cursor-mcp.json +0 -15
  224. package/hooks/memory-list-skill.js +0 -139
  225. package/hooks/memory-profile-skill.js +0 -273
  226. package/hooks/memory-recall-skill.js +0 -114
  227. package/hooks/memory-remember-skill.js +0 -127
  228. package/hooks/memory-reset-skill.js +0 -274
  229. package/mcp_server.py +0 -1808
  230. package/requirements-core.txt +0 -22
  231. package/requirements-learning.txt +0 -12
  232. package/requirements.txt +0 -12
  233. package/src/agent_registry.py +0 -411
  234. package/src/auth_middleware.py +0 -61
  235. package/src/auto_backup.py +0 -459
  236. package/src/behavioral/__init__.py +0 -49
  237. package/src/behavioral/behavioral_listener.py +0 -203
  238. package/src/behavioral/behavioral_patterns.py +0 -275
  239. package/src/behavioral/cross_project_transfer.py +0 -206
  240. package/src/behavioral/outcome_inference.py +0 -194
  241. package/src/behavioral/outcome_tracker.py +0 -193
  242. package/src/behavioral/tests/__init__.py +0 -4
  243. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  244. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  245. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  246. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  247. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  248. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  249. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  250. package/src/cache_manager.py +0 -518
  251. package/src/compliance/__init__.py +0 -48
  252. package/src/compliance/abac_engine.py +0 -149
  253. package/src/compliance/abac_middleware.py +0 -116
  254. package/src/compliance/audit_db.py +0 -215
  255. package/src/compliance/audit_logger.py +0 -148
  256. package/src/compliance/retention_manager.py +0 -289
  257. package/src/compliance/retention_scheduler.py +0 -186
  258. package/src/compliance/tests/__init__.py +0 -4
  259. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  260. package/src/compliance/tests/test_abac_engine.py +0 -124
  261. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  262. package/src/compliance/tests/test_audit_db.py +0 -123
  263. package/src/compliance/tests/test_audit_logger.py +0 -98
  264. package/src/compliance/tests/test_mcp_audit.py +0 -128
  265. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  266. package/src/compliance/tests/test_retention_manager.py +0 -131
  267. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  268. package/src/compression/__init__.py +0 -25
  269. package/src/compression/cli.py +0 -150
  270. package/src/compression/cold_storage.py +0 -217
  271. package/src/compression/config.py +0 -72
  272. package/src/compression/orchestrator.py +0 -133
  273. package/src/compression/tier2_compressor.py +0 -228
  274. package/src/compression/tier3_compressor.py +0 -153
  275. package/src/compression/tier_classifier.py +0 -148
  276. package/src/db_connection_manager.py +0 -536
  277. package/src/embedding_engine.py +0 -63
  278. package/src/embeddings/__init__.py +0 -47
  279. package/src/embeddings/cache.py +0 -70
  280. package/src/embeddings/cli.py +0 -113
  281. package/src/embeddings/constants.py +0 -47
  282. package/src/embeddings/database.py +0 -91
  283. package/src/embeddings/engine.py +0 -247
  284. package/src/embeddings/model_loader.py +0 -145
  285. package/src/event_bus.py +0 -562
  286. package/src/graph/__init__.py +0 -36
  287. package/src/graph/build_helpers.py +0 -74
  288. package/src/graph/cli.py +0 -87
  289. package/src/graph/cluster_builder.py +0 -188
  290. package/src/graph/cluster_summary.py +0 -148
  291. package/src/graph/constants.py +0 -47
  292. package/src/graph/edge_builder.py +0 -162
  293. package/src/graph/entity_extractor.py +0 -95
  294. package/src/graph/graph_core.py +0 -226
  295. package/src/graph/graph_search.py +0 -231
  296. package/src/graph/hierarchical.py +0 -207
  297. package/src/graph/schema.py +0 -99
  298. package/src/graph_engine.py +0 -52
  299. package/src/hnsw_index.py +0 -628
  300. package/src/hybrid_search.py +0 -46
  301. package/src/learning/__init__.py +0 -217
  302. package/src/learning/adaptive_ranker.py +0 -682
  303. package/src/learning/bootstrap/__init__.py +0 -69
  304. package/src/learning/bootstrap/constants.py +0 -93
  305. package/src/learning/bootstrap/db_queries.py +0 -316
  306. package/src/learning/bootstrap/sampling.py +0 -82
  307. package/src/learning/bootstrap/text_utils.py +0 -71
  308. package/src/learning/cross_project_aggregator.py +0 -857
  309. package/src/learning/db/__init__.py +0 -40
  310. package/src/learning/db/constants.py +0 -44
  311. package/src/learning/db/schema.py +0 -279
  312. package/src/learning/engagement_tracker.py +0 -628
  313. package/src/learning/feature_extractor.py +0 -708
  314. package/src/learning/feedback_collector.py +0 -806
  315. package/src/learning/learning_db.py +0 -915
  316. package/src/learning/project_context_manager.py +0 -572
  317. package/src/learning/ranking/__init__.py +0 -33
  318. package/src/learning/ranking/constants.py +0 -84
  319. package/src/learning/ranking/helpers.py +0 -278
  320. package/src/learning/source_quality_scorer.py +0 -676
  321. package/src/learning/synthetic_bootstrap.py +0 -755
  322. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  323. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  324. package/src/learning/tests/test_aggregator.py +0 -306
  325. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  326. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  327. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  328. package/src/learning/tests/test_feedback_collector.py +0 -294
  329. package/src/learning/tests/test_learning_db.py +0 -602
  330. package/src/learning/tests/test_learning_db_v28.py +0 -110
  331. package/src/learning/tests/test_learning_init_v28.py +0 -48
  332. package/src/learning/tests/test_outcome_signals.py +0 -48
  333. package/src/learning/tests/test_project_context.py +0 -292
  334. package/src/learning/tests/test_schema_migration.py +0 -319
  335. package/src/learning/tests/test_signal_inference.py +0 -397
  336. package/src/learning/tests/test_source_quality.py +0 -351
  337. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  338. package/src/learning/tests/test_workflow_miner.py +0 -318
  339. package/src/learning/workflow_pattern_miner.py +0 -655
  340. package/src/lifecycle/__init__.py +0 -54
  341. package/src/lifecycle/bounded_growth.py +0 -239
  342. package/src/lifecycle/compaction_engine.py +0 -226
  343. package/src/lifecycle/lifecycle_engine.py +0 -355
  344. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  345. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  346. package/src/lifecycle/retention_policy.py +0 -285
  347. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  348. package/src/lifecycle/tests/test_compaction.py +0 -179
  349. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  350. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  351. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  352. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  353. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  354. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  355. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  356. package/src/mcp_tools_v28.py +0 -281
  357. package/src/memory/__init__.py +0 -36
  358. package/src/memory/cli.py +0 -205
  359. package/src/memory/constants.py +0 -39
  360. package/src/memory/helpers.py +0 -28
  361. package/src/memory/schema.py +0 -166
  362. package/src/memory-profiles.py +0 -595
  363. package/src/memory-reset.py +0 -491
  364. package/src/memory_compression.py +0 -989
  365. package/src/memory_store_v2.py +0 -1155
  366. package/src/migrate_v1_to_v2.py +0 -629
  367. package/src/pattern_learner.py +0 -34
  368. package/src/patterns/__init__.py +0 -24
  369. package/src/patterns/analyzers.py +0 -251
  370. package/src/patterns/learner.py +0 -271
  371. package/src/patterns/scoring.py +0 -171
  372. package/src/patterns/store.py +0 -225
  373. package/src/patterns/terminology.py +0 -140
  374. package/src/provenance_tracker.py +0 -312
  375. package/src/qualixar_attribution.py +0 -139
  376. package/src/qualixar_watermark.py +0 -78
  377. package/src/query_optimizer.py +0 -511
  378. package/src/rate_limiter.py +0 -83
  379. package/src/search/__init__.py +0 -20
  380. package/src/search/cli.py +0 -77
  381. package/src/search/constants.py +0 -26
  382. package/src/search/engine.py +0 -241
  383. package/src/search/fusion.py +0 -122
  384. package/src/search/index_loader.py +0 -114
  385. package/src/search/methods.py +0 -162
  386. package/src/search_engine_v2.py +0 -401
  387. package/src/setup_validator.py +0 -482
  388. package/src/subscription_manager.py +0 -391
  389. package/src/tree/__init__.py +0 -59
  390. package/src/tree/builder.py +0 -185
  391. package/src/tree/nodes.py +0 -202
  392. package/src/tree/queries.py +0 -257
  393. package/src/tree/schema.py +0 -80
  394. package/src/tree_manager.py +0 -19
  395. package/src/trust/__init__.py +0 -45
  396. package/src/trust/constants.py +0 -66
  397. package/src/trust/queries.py +0 -157
  398. package/src/trust/schema.py +0 -95
  399. package/src/trust/scorer.py +0 -299
  400. package/src/trust/signals.py +0 -95
  401. package/src/trust_scorer.py +0 -44
  402. package/ui/app.js +0 -1588
  403. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  404. package/ui/js/graph-cytoscape.js +0 -1168
  405. package/ui/js/graph-d3-backup.js +0 -32
  406. package/ui/js/graph.js +0 -32
  407. package/ui_server.py +0 -286
  408. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  409. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  410. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  411. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  412. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  413. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  414. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  415. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  416. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  417. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  418. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  419. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  420. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  421. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  422. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  423. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  424. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  425. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  426. /package/{completions → ide/completions}/slm.bash +0 -0
  427. /package/{completions → ide/completions}/slm.zsh +0 -0
  428. /package/{configs → ide/configs}/cody-commands.json +0 -0
  429. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
  430. /package/{install.ps1 → scripts/install.ps1} +0 -0
  431. /package/{install.sh → scripts/install.sh} +0 -0
@@ -1,676 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: MIT
3
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
4
- """
5
- SourceQualityScorer — Per-source quality learning.
6
-
7
- Learns which memory sources (tools/agents) produce memories that users
8
- actually find useful. If memories from 'mcp:claude-desktop' get positive
9
- feedback (via memory_used) 3x more often than memories from 'cli:terminal',
10
- then Claude Desktop memories receive a quality boost in the adaptive ranker.
11
-
12
- Data Sources:
13
- - memory.db `created_by` column (set by ProvenanceTracker in v2.5)
14
- Values like: 'mcp:claude-desktop', 'mcp:cursor', 'cli:terminal',
15
- 'rest:api', 'user', etc.
16
- - learning.db `ranking_feedback` table (positive signals from FeedbackCollector)
17
- Signal types: 'mcp_used', 'cli_useful', 'dashboard_click'
18
-
19
- Scoring Algorithm (Beta-Binomial Smoothing):
20
- quality_score = (alpha + positive_signals) / (alpha + beta + total_memories)
21
-
22
- With alpha=1, beta=1 (Laplace smoothing / uniform prior):
23
- - Unknown source with 0 feedback: 1/(2+0) = 0.50 (neutral)
24
- - Source with 5 positives out of 10 total: 6/12 = 0.50 (average)
25
- - Source with 8 positives out of 10 total: 9/12 = 0.75 (good)
26
- - Source with 1 positive out of 10 total: 2/12 = 0.17 (poor)
27
-
28
- This naturally handles:
29
- - Cold start: new sources get 0.5 (neutral) until evidence accumulates
30
- - Low sample: smoothing prevents extreme scores from few observations
31
- - Convergence: scores stabilize as evidence grows
32
-
33
- Storage:
34
- Results stored in learning.db `source_quality` table via LearningDB.
35
- The adaptive ranker reads source_quality at query time to boost/penalize
36
- memories based on their source.
37
-
38
- Thread Safety:
39
- - All writes protected by LearningDB's internal write lock
40
- - Reads to memory.db use per-call connections (safe with WAL mode)
41
- - compute_source_scores() is idempotent — safe to call concurrently
42
-
43
- Graceful Degradation:
44
- - If memory.db lacks `created_by` column: all memories grouped as 'unknown'
45
- - If learning.db unavailable: scores computed but not persisted
46
- - If ranking_feedback is empty: all sources get 0.5 (neutral)
47
-
48
- Research Backing:
49
- - Beta-Binomial smoothing: Standard Bayesian approach (matches trust_scorer.py)
50
- - Source reliability learning: ADPMF (IPM 2024) privacy-preserving feedback
51
- - FCS LREC 2024: cold-start handling via smoothing priors
52
- """
53
-
54
- import json
55
- import logging
56
- import sqlite3
57
- import threading
58
- from datetime import datetime
59
- from pathlib import Path
60
- from typing import Dict, List, Optional, Any
61
-
62
- logger = logging.getLogger("superlocalmemory.learning.source_quality")
63
-
64
- # ---------------------------------------------------------------------------
65
- # Import LearningDB (sibling module in src/learning/)
66
- # ---------------------------------------------------------------------------
67
- try:
68
- from .learning_db import LearningDB
69
- except ImportError:
70
- try:
71
- from learning_db import LearningDB
72
- except ImportError:
73
- LearningDB = None
74
- logger.warning(
75
- "LearningDB not available — source quality scores will not persist."
76
- )
77
-
78
- # ---------------------------------------------------------------------------
79
- # Constants
80
- # ---------------------------------------------------------------------------
81
-
82
- MEMORY_DIR = Path.home() / ".claude-memory"
83
- DEFAULT_MEMORY_DB = MEMORY_DIR / "memory.db"
84
-
85
- # Beta-Binomial prior parameters (Laplace smoothing)
86
- ALPHA = 1.0 # Prior successes
87
- BETA = 1.0 # Prior failures
88
-
89
- # Default score for unknown sources (= alpha / (alpha + beta))
90
- DEFAULT_QUALITY_SCORE = ALPHA / (ALPHA + BETA)
91
-
92
- # Minimum total memories from a source before we trust its score
93
- # Below this, the score is blended toward the default
94
- MIN_EVIDENCE_THRESHOLD = 5
95
-
96
- # Positive feedback signal types from ranking_feedback table
97
- POSITIVE_SIGNAL_TYPES = ("mcp_used", "cli_useful", "dashboard_click")
98
-
99
-
100
- class SourceQualityScorer:
101
- """
102
- Learns which memory sources produce higher-quality memories.
103
-
104
- Computes a quality score per source using Beta-Binomial smoothing
105
- over positive feedback signals. Stores results in learning.db for
106
- use by the adaptive ranker.
107
-
108
- Usage:
109
- scorer = SourceQualityScorer()
110
- scores = scorer.compute_source_scores()
111
- # scores = {'mcp:claude-desktop': 0.72, 'cli:terminal': 0.45, ...}
112
-
113
- boost = scorer.get_source_boost(memory_dict)
114
- # boost = 0.72 (for a memory from claude-desktop)
115
- """
116
-
117
- def __init__(
118
- self,
119
- memory_db_path: Optional[Path] = None,
120
- learning_db: Optional[Any] = None,
121
- ):
122
- """
123
- Initialize the source quality scorer.
124
-
125
- Args:
126
- memory_db_path: Path to memory.db (READ-ONLY). Defaults to
127
- ~/.claude-memory/memory.db.
128
- learning_db: A LearningDB instance for reading feedback and
129
- storing scores. If None, one is created.
130
- """
131
- self.memory_db_path = Path(memory_db_path) if memory_db_path else DEFAULT_MEMORY_DB
132
- self._lock = threading.Lock()
133
-
134
- # In-memory cache of source scores (refreshed by compute_source_scores)
135
- self._cached_scores: Dict[str, float] = {}
136
-
137
- # Initialize LearningDB
138
- if learning_db is not None:
139
- self._learning_db = learning_db
140
- elif LearningDB is not None:
141
- try:
142
- self._learning_db = LearningDB.get_instance()
143
- except Exception as e:
144
- logger.error("Failed to initialize LearningDB: %s", e)
145
- self._learning_db = None
146
- else:
147
- self._learning_db = None
148
-
149
- # Pre-load cached scores from learning.db if available
150
- self._load_cached_scores()
151
-
152
- logger.info(
153
- "SourceQualityScorer initialized: memory_db=%s, learning_db=%s, "
154
- "cached_sources=%d",
155
- self.memory_db_path,
156
- "available" if self._learning_db else "unavailable",
157
- len(self._cached_scores),
158
- )
159
-
160
- # ======================================================================
161
- # Core Scoring
162
- # ======================================================================
163
-
164
- def compute_source_scores(self) -> Dict[str, float]:
165
- """
166
- Compute quality scores for all memory sources.
167
-
168
- Workflow:
169
- 1. Get total memories per source from memory.db (created_by column)
170
- 2. Get positive feedback count per source by joining
171
- learning.db ranking_feedback with memory.db memories
172
- 3. Compute Beta-Binomial smoothed score per source
173
- 4. Store results in learning.db source_quality table
174
- 5. Update in-memory cache
175
-
176
- Returns:
177
- Dict mapping source_id -> quality_score (0.0 to 1.0)
178
- """
179
- # Step 1: Count total memories per source from memory.db
180
- source_totals = self._get_memory_counts_by_source()
181
-
182
- if not source_totals:
183
- logger.info("No source data found in memory.db.")
184
- return {}
185
-
186
- # Step 2: Count positive signals per source
187
- source_positives = self._get_positive_signal_counts(
188
- set(source_totals.keys())
189
- )
190
-
191
- # Step 3: Compute Beta-Binomial scores
192
- scores = {}
193
- for source_id, total in source_totals.items():
194
- positives = source_positives.get(source_id, 0)
195
- score = self._beta_binomial_score(positives, total)
196
- scores[source_id] = round(score, 4)
197
-
198
- # Step 4: Store in learning.db
199
- self._store_scores(scores, source_totals, source_positives)
200
-
201
- # Step 5: Update cache
202
- with self._lock:
203
- self._cached_scores = dict(scores)
204
-
205
- logger.info(
206
- "Source quality scores computed for %d sources: %s",
207
- len(scores),
208
- ", ".join(
209
- "%s=%.3f" % (s, sc) for s, sc in sorted(
210
- scores.items(), key=lambda x: -x[1]
211
- )[:5]
212
- ) + ("..." if len(scores) > 5 else ""),
213
- )
214
-
215
- return scores
216
-
217
- def get_source_boost(
218
- self,
219
- memory: dict,
220
- source_scores: Optional[Dict[str, float]] = None,
221
- ) -> float:
222
- """
223
- Get the ranking boost for a memory based on its source quality.
224
-
225
- This is called by the adaptive ranker at query time for each
226
- candidate memory. The boost is a float in [0.0, 1.0] that
227
- represents how trustworthy/useful this source tends to be.
228
-
229
- Args:
230
- memory: A memory dict. Must have 'created_by' key, or will
231
- fall back to DEFAULT_QUALITY_SCORE.
232
- source_scores: Optional pre-computed scores dict. If None,
233
- uses the internal cache. Pass this to avoid
234
- repeated cache reads in a tight loop.
235
-
236
- Returns:
237
- Quality score (0.0 to 1.0). 0.5 for unknown sources.
238
- """
239
- scores = source_scores if source_scores is not None else self._cached_scores
240
-
241
- # Extract source identifier from the memory
242
- source_id = self._extract_source_id(memory)
243
-
244
- if not source_id or source_id not in scores:
245
- return DEFAULT_QUALITY_SCORE
246
-
247
- return scores[source_id]
248
-
249
- def refresh(self):
250
- """
251
- Recompute all source scores.
252
-
253
- Convenience wrapper for compute_source_scores(). Called periodically
254
- by the engagement tracker or on explicit user request.
255
- """
256
- return self.compute_source_scores()
257
-
258
- # ======================================================================
259
- # Data Extraction (memory.db — READ-ONLY)
260
- # ======================================================================
261
-
262
- def _get_memory_counts_by_source(self) -> Dict[str, int]:
263
- """
264
- Count total memories per source from memory.db's `created_by` column.
265
-
266
- Handles the case where the `created_by` column does not exist
267
- (older databases pre-v2.5). In that case, all memories are
268
- grouped under 'unknown'.
269
-
270
- Returns:
271
- Dict mapping source_id -> total memory count.
272
- """
273
- counts: Dict[str, int] = {}
274
-
275
- try:
276
- conn = sqlite3.connect(str(self.memory_db_path), timeout=10)
277
- try:
278
- conn.execute("PRAGMA busy_timeout=5000")
279
- cursor = conn.cursor()
280
-
281
- # Check if created_by column exists
282
- cursor.execute("PRAGMA table_info(memories)")
283
- columns = {row[1] for row in cursor.fetchall()}
284
-
285
- if "created_by" in columns:
286
- cursor.execute("""
287
- SELECT
288
- COALESCE(created_by, 'unknown') AS source,
289
- COUNT(*) AS cnt
290
- FROM memories
291
- GROUP BY source
292
- ORDER BY cnt DESC
293
- """)
294
- for row in cursor.fetchall():
295
- source_id = row[0] if row[0] else "unknown"
296
- counts[source_id] = row[1]
297
- else:
298
- # Column doesn't exist — count all as 'unknown'
299
- cursor.execute("SELECT COUNT(*) FROM memories")
300
- total = cursor.fetchone()[0]
301
- if total > 0:
302
- counts["unknown"] = total
303
- logger.debug(
304
- "created_by column not in memory.db — "
305
- "all %d memories grouped as 'unknown'.",
306
- total,
307
- )
308
- finally:
309
- conn.close()
310
-
311
- except sqlite3.OperationalError as e:
312
- logger.warning("Error reading memory counts by source: %s", e)
313
- except Exception as e:
314
- logger.error("Unexpected error reading memory.db: %s", e)
315
-
316
- return counts
317
-
318
- def _get_positive_signal_counts(
319
- self,
320
- known_sources: set,
321
- ) -> Dict[str, int]:
322
- """
323
- Count positive feedback signals per source.
324
-
325
- Joins learning.db's ranking_feedback (positive signals) with
326
- memory.db's memories (to get created_by) on memory_id.
327
-
328
- This requires reading from BOTH databases. We do a two-step approach:
329
- 1. Get all memory_ids with positive feedback from learning.db
330
- 2. Look up their created_by from memory.db
331
-
332
- This avoids ATTACH DATABASE which can have locking issues.
333
-
334
- Returns:
335
- Dict mapping source_id -> positive signal count.
336
- """
337
- positives: Dict[str, int] = {}
338
-
339
- if self._learning_db is None:
340
- return positives
341
-
342
- # Step 1: Get memory_ids with positive feedback from learning.db
343
- feedback_memory_ids: Dict[int, int] = {} # memory_id -> count
344
-
345
- try:
346
- feedback_rows = self._learning_db.get_feedback_for_training(limit=50000)
347
- for row in feedback_rows:
348
- signal_type = row.get("signal_type", "")
349
- if signal_type in POSITIVE_SIGNAL_TYPES:
350
- mem_id = row.get("memory_id")
351
- if mem_id is not None:
352
- feedback_memory_ids[mem_id] = (
353
- feedback_memory_ids.get(mem_id, 0) + 1
354
- )
355
- except Exception as e:
356
- logger.warning("Could not read feedback from learning.db: %s", e)
357
- return positives
358
-
359
- if not feedback_memory_ids:
360
- return positives
361
-
362
- # Step 2: Look up created_by for each feedback memory_id in memory.db
363
- try:
364
- conn = sqlite3.connect(str(self.memory_db_path), timeout=10)
365
- try:
366
- conn.execute("PRAGMA busy_timeout=5000")
367
- cursor = conn.cursor()
368
-
369
- # Check if created_by column exists
370
- cursor.execute("PRAGMA table_info(memories)")
371
- columns = {row[1] for row in cursor.fetchall()}
372
-
373
- if "created_by" not in columns:
374
- # All positives go to 'unknown'
375
- total_positives = sum(feedback_memory_ids.values())
376
- if total_positives > 0:
377
- positives["unknown"] = total_positives
378
- return positives
379
-
380
- # Batch lookup in chunks to avoid SQLite variable limit
381
- mem_ids = list(feedback_memory_ids.keys())
382
- chunk_size = 500 # SQLite max variables is 999
383
-
384
- for i in range(0, len(mem_ids), chunk_size):
385
- chunk = mem_ids[i:i + chunk_size]
386
- placeholders = ",".join("?" * len(chunk))
387
- cursor.execute(
388
- "SELECT id, COALESCE(created_by, 'unknown') "
389
- "FROM memories WHERE id IN (%s)" % placeholders,
390
- chunk,
391
- )
392
- for row in cursor.fetchall():
393
- mem_id = row[0]
394
- source_id = row[1] if row[1] else "unknown"
395
- count = feedback_memory_ids.get(mem_id, 0)
396
- positives[source_id] = positives.get(source_id, 0) + count
397
- finally:
398
- conn.close()
399
-
400
- except sqlite3.OperationalError as e:
401
- logger.warning("Error looking up memory sources: %s", e)
402
- except Exception as e:
403
- logger.error("Unexpected error in positive signal lookup: %s", e)
404
-
405
- return positives
406
-
407
- # ======================================================================
408
- # Scoring Math
409
- # ======================================================================
410
-
411
- @staticmethod
412
- def _beta_binomial_score(positive_count: int, total_count: int) -> float:
413
- """
414
- Compute Beta-Binomial smoothed quality score.
415
-
416
- Formula: (alpha + positive) / (alpha + beta + total)
417
-
418
- With alpha=1, beta=1 (uniform prior / Laplace smoothing):
419
- - 0 positives, 0 total = 0.50 (neutral)
420
- - 5 positives, 10 total = 0.50
421
- - 8 positives, 10 total = 0.75
422
- - 1 positive, 10 total = 0.17
423
- - 50 positives, 100 total = 0.50
424
-
425
- This converges to the true rate as evidence grows, while being
426
- conservative (pulled toward 0.5) with limited data.
427
-
428
- Args:
429
- positive_count: Number of positive feedback signals.
430
- total_count: Total number of memories from this source.
431
-
432
- Returns:
433
- Quality score in [0.0, 1.0].
434
- """
435
- score = (ALPHA + positive_count) / (ALPHA + BETA + total_count)
436
- return max(0.0, min(1.0, score))
437
-
438
- # ======================================================================
439
- # Storage (learning.db)
440
- # ======================================================================
441
-
442
- def _store_scores(
443
- self,
444
- scores: Dict[str, float],
445
- totals: Dict[str, int],
446
- positives: Dict[str, int],
447
- ):
448
- """
449
- Store computed scores in learning.db's source_quality table.
450
-
451
- Uses LearningDB.update_source_quality() which handles UPSERT
452
- internally with its own write lock.
453
- """
454
- if self._learning_db is None:
455
- logger.debug(
456
- "LearningDB unavailable — scores computed but not stored."
457
- )
458
- return
459
-
460
- stored = 0
461
- for source_id, score in scores.items():
462
- try:
463
- self._learning_db.update_source_quality(
464
- source_id=source_id,
465
- positive_signals=positives.get(source_id, 0),
466
- total_memories=totals.get(source_id, 0),
467
- )
468
- stored += 1
469
- except Exception as e:
470
- logger.error(
471
- "Failed to store score for source '%s': %s",
472
- source_id, e,
473
- )
474
-
475
- logger.debug("Stored %d/%d source quality scores.", stored, len(scores))
476
-
477
- def _load_cached_scores(self):
478
- """
479
- Load source quality scores from learning.db into the in-memory cache.
480
-
481
- Called on initialization so that get_source_boost() works immediately
482
- without requiring a compute_source_scores() call first.
483
- """
484
- if self._learning_db is None:
485
- return
486
-
487
- try:
488
- db_scores = self._learning_db.get_source_scores()
489
- with self._lock:
490
- self._cached_scores = dict(db_scores)
491
- if db_scores:
492
- logger.debug(
493
- "Loaded %d cached source scores from learning.db.",
494
- len(db_scores),
495
- )
496
- except Exception as e:
497
- logger.debug("Could not load cached source scores: %s", e)
498
-
499
- # ======================================================================
500
- # Utility Methods
501
- # ======================================================================
502
-
503
- @staticmethod
504
- def _extract_source_id(memory: dict) -> Optional[str]:
505
- """
506
- Extract the source identifier from a memory dict.
507
-
508
- Checks 'created_by' first (set by ProvenanceTracker), then
509
- falls back to 'source_protocol' if available.
510
-
511
- Args:
512
- memory: A memory dict (from search results or direct DB query).
513
-
514
- Returns:
515
- Source identifier string, or None if not available.
516
- """
517
- # Primary: created_by (e.g., 'mcp:claude-desktop', 'cli:terminal')
518
- source = memory.get("created_by")
519
- if source and source != "user":
520
- return source
521
-
522
- # Fallback: source_protocol (e.g., 'mcp', 'cli', 'rest')
523
- protocol = memory.get("source_protocol")
524
- if protocol:
525
- return protocol
526
-
527
- # Last resort: the 'user' default from provenance_tracker
528
- if source == "user":
529
- return "user"
530
-
531
- return None
532
-
533
- def get_all_scores(self) -> Dict[str, dict]:
534
- """
535
- Get detailed quality information for all tracked sources.
536
-
537
- Returns full details including positive signals, total memories,
538
- and computed score for diagnostic/dashboard display.
539
-
540
- Returns:
541
- Dict mapping source_id -> {quality_score, positive_signals,
542
- total_memories, last_updated}
543
- """
544
- if self._learning_db is None:
545
- # Return from cache with minimal info
546
- with self._lock:
547
- return {
548
- source_id: {
549
- "quality_score": score,
550
- "positive_signals": None,
551
- "total_memories": None,
552
- "last_updated": None,
553
- }
554
- for source_id, score in self._cached_scores.items()
555
- }
556
-
557
- try:
558
- conn = self._learning_db._get_connection()
559
- cursor = conn.cursor()
560
- cursor.execute("""
561
- SELECT source_id, quality_score, positive_signals,
562
- total_memories, last_updated
563
- FROM source_quality
564
- ORDER BY quality_score DESC
565
- """)
566
- results = {}
567
- for row in cursor.fetchall():
568
- results[row["source_id"]] = {
569
- "quality_score": row["quality_score"],
570
- "positive_signals": row["positive_signals"],
571
- "total_memories": row["total_memories"],
572
- "last_updated": row["last_updated"],
573
- }
574
- conn.close()
575
- return results
576
- except Exception as e:
577
- logger.error("Failed to read detailed source scores: %s", e)
578
- return {}
579
-
580
- def get_source_summary(self) -> str:
581
- """
582
- Get a human-readable summary of source quality scores.
583
-
584
- Returns:
585
- Formatted multi-line string for diagnostics or dashboard.
586
- """
587
- all_scores = self.get_all_scores()
588
-
589
- if not all_scores:
590
- return "No source quality data available. Run refresh() first."
591
-
592
- lines = ["Source Quality Scores:", ""]
593
- lines.append(
594
- " %-30s %8s %8s %8s"
595
- % ("Source", "Score", "Positive", "Total")
596
- )
597
- lines.append(" " + "-" * 62)
598
-
599
- for source_id, data in sorted(
600
- all_scores.items(), key=lambda x: -x[1]["quality_score"]
601
- ):
602
- pos = data["positive_signals"]
603
- tot = data["total_memories"]
604
- lines.append(
605
- " %-30s %8.3f %8s %8s"
606
- % (
607
- source_id,
608
- data["quality_score"],
609
- str(pos) if pos is not None else "?",
610
- str(tot) if tot is not None else "?",
611
- )
612
- )
613
-
614
- return "\n".join(lines)
615
-
616
-
617
- # ===========================================================================
618
- # CLI Interface
619
- # ===========================================================================
620
-
621
- if __name__ == "__main__":
622
- import sys as _sys
623
-
624
- logging.basicConfig(
625
- level=logging.INFO,
626
- format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
627
- )
628
-
629
- scorer = SourceQualityScorer()
630
-
631
- if len(_sys.argv) < 2:
632
- print("SourceQualityScorer — Per-Source Quality Learning")
633
- print()
634
- print("Usage:")
635
- print(" python source_quality_scorer.py compute # Compute all source scores")
636
- print(" python source_quality_scorer.py show # Show current scores")
637
- print(" python source_quality_scorer.py summary # Human-readable summary")
638
- _sys.exit(0)
639
-
640
- command = _sys.argv[1]
641
-
642
- if command == "compute":
643
- scores = scorer.compute_source_scores()
644
- if scores:
645
- print("\nComputed quality scores for %d sources:" % len(scores))
646
- for source_id, score in sorted(scores.items(), key=lambda x: -x[1]):
647
- bar = "#" * int(score * 20)
648
- print(" %-30s %.3f [%-20s]" % (source_id, score, bar))
649
- else:
650
- print("No sources found. Add memories with provenance tracking first.")
651
-
652
- elif command == "show":
653
- all_scores = scorer.get_all_scores()
654
- if all_scores:
655
- print("\nStored source quality scores:")
656
- for source_id, data in sorted(
657
- all_scores.items(), key=lambda x: -x[1]["quality_score"]
658
- ):
659
- print(
660
- " %-30s score=%.3f positives=%s total=%s"
661
- % (
662
- source_id,
663
- data["quality_score"],
664
- data["positive_signals"],
665
- data["total_memories"],
666
- )
667
- )
668
- else:
669
- print("No scores stored. Run 'compute' first.")
670
-
671
- elif command == "summary":
672
- print(scorer.get_source_summary())
673
-
674
- else:
675
- print("Unknown command: %s" % command)
676
- _sys.exit(1)