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,628 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: MIT
3
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
4
- """
5
- EngagementTracker — Local-only engagement metrics.
6
-
7
- Measures how actively the user interacts with the memory system.
8
- All data stays local — NEVER transmitted anywhere.
9
-
10
- Capabilities:
11
- - Comprehensive engagement stats (days active, staleness, per-day rates)
12
- - Health status classification (HEALTHY / DECLINING / AT_RISK / INACTIVE)
13
- - Activity recording (delegates to LearningDB.increment_engagement)
14
- - Weekly summary aggregation
15
- - CLI-friendly formatted output for `slm engagement`
16
- - MCP resource exposure (read-only stats)
17
-
18
- Data sources:
19
- - memory.db (read-only) — creation dates, total count, source agents
20
- - learning.db (read/write via LearningDB) — feedback counts, patterns,
21
- engagement_metrics daily rows
22
-
23
- Design:
24
- - Thread-safe: each method opens/closes its own connection
25
- - Division-by-zero safe: all ratios default to 0.0 for empty databases
26
- - Graceful degradation: works even if learning.db does not exist yet
27
- """
28
-
29
- import json
30
- import logging
31
- import sqlite3
32
- import threading
33
- from datetime import datetime, date, timedelta
34
- from pathlib import Path
35
- from typing import Optional, List, Dict, Any
36
-
37
- logger = logging.getLogger("superlocalmemory.learning.engagement")
38
-
39
- MEMORY_DIR = Path.home() / ".claude-memory"
40
- MEMORY_DB_PATH = MEMORY_DIR / "memory.db"
41
-
42
-
43
- class EngagementTracker:
44
- """
45
- Local-only engagement metrics for the SuperLocalMemory system.
46
-
47
- Usage:
48
- tracker = EngagementTracker()
49
- stats = tracker.get_engagement_stats()
50
- print(tracker.format_for_cli())
51
-
52
- Thread-safe: all methods use per-call connections.
53
- """
54
-
55
- def __init__(
56
- self,
57
- memory_db_path: Optional[Path] = None,
58
- learning_db: Optional[Any] = None,
59
- ):
60
- """
61
- Initialize EngagementTracker.
62
-
63
- Args:
64
- memory_db_path: Path to memory.db. Defaults to
65
- ~/.claude-memory/memory.db. Opened read-only.
66
- learning_db: A LearningDB instance for reading/writing
67
- engagement metrics. If None, lazily created on first use.
68
- """
69
- self._memory_db_path = (
70
- Path(memory_db_path) if memory_db_path else MEMORY_DB_PATH
71
- )
72
- self._learning_db = learning_db
73
- self._lock = threading.Lock()
74
- logger.info(
75
- "EngagementTracker initialized: memory_db=%s",
76
- self._memory_db_path,
77
- )
78
-
79
- # ------------------------------------------------------------------
80
- # LearningDB access (lazy)
81
- # ------------------------------------------------------------------
82
-
83
- def _get_learning_db(self):
84
- """
85
- Get or lazily create the LearningDB instance.
86
-
87
- Returns None if LearningDB cannot be imported or initialized.
88
- """
89
- if self._learning_db is not None:
90
- return self._learning_db
91
-
92
- try:
93
- from .learning_db import LearningDB
94
- self._learning_db = LearningDB()
95
- return self._learning_db
96
- except Exception as e:
97
- logger.warning("Failed to initialize LearningDB: %s", e)
98
- return None
99
-
100
- # ------------------------------------------------------------------
101
- # Memory.db read-only access
102
- # ------------------------------------------------------------------
103
-
104
- def _open_memory_db(self) -> sqlite3.Connection:
105
- """
106
- Open a read-only connection to memory.db.
107
-
108
- Uses URI mode=ro when supported; falls back to regular connection.
109
- """
110
- db_str = str(self._memory_db_path)
111
- try:
112
- uri = f"file:{db_str}?mode=ro"
113
- conn = sqlite3.connect(uri, uri=True, timeout=5)
114
- except (sqlite3.OperationalError, sqlite3.NotSupportedError):
115
- conn = sqlite3.connect(db_str, timeout=5)
116
- conn.execute("PRAGMA busy_timeout=3000")
117
- return conn
118
-
119
- def _get_memory_db_columns(self) -> set:
120
- """Get available columns in the memories table."""
121
- if not self._memory_db_path.exists():
122
- return set()
123
- try:
124
- conn = self._open_memory_db()
125
- try:
126
- cursor = conn.cursor()
127
- cursor.execute("PRAGMA table_info(memories)")
128
- return {row[1] for row in cursor.fetchall()}
129
- finally:
130
- conn.close()
131
- except sqlite3.Error:
132
- return set()
133
-
134
- # ------------------------------------------------------------------
135
- # Core stats
136
- # ------------------------------------------------------------------
137
-
138
- def get_engagement_stats(self) -> Dict[str, Any]:
139
- """
140
- Return a comprehensive engagement report.
141
-
142
- Returns:
143
- Dict with keys:
144
- days_active — Days since first memory was created
145
- days_since_last — Days since most recent activity
146
- staleness_ratio — days_since_last / days_active (0=active, 1=abandoned)
147
- total_memories — Total memory count
148
- memories_per_day — Average memories created per active day
149
- recalls_per_day — Average recalls per active day (from feedback)
150
- patterns_learned — Transferable patterns with confidence > 0.6
151
- feedback_signals — Total feedback count
152
- health_status — 'HEALTHY', 'DECLINING', 'AT_RISK', 'INACTIVE'
153
- active_sources — List of tool sources used recently
154
- """
155
- # --- Memory.db stats ---
156
- mem_stats = self._get_memory_stats()
157
-
158
- # --- Learning.db stats ---
159
- learn_stats = self._get_learning_stats()
160
-
161
- # --- Derived metrics ---
162
- days_active = mem_stats['days_active']
163
- days_since_last = mem_stats['days_since_last']
164
- total_memories = mem_stats['total_memories']
165
-
166
- # Staleness: 0.0 = used today, 1.0 = abandoned
167
- if days_active > 0:
168
- staleness_ratio = round(days_since_last / days_active, 4)
169
- else:
170
- staleness_ratio = 0.0 # Brand-new user — not stale
171
-
172
- # Cap staleness at 1.0 (can exceed if days_since_last > days_active
173
- # due to timezone edge cases)
174
- staleness_ratio = min(staleness_ratio, 1.0)
175
-
176
- # Per-day rates
177
- if days_active > 0:
178
- memories_per_day = round(total_memories / days_active, 2)
179
- recalls_per_day = round(
180
- learn_stats['feedback_signals'] / days_active, 2
181
- )
182
- else:
183
- memories_per_day = float(total_memories) # Day 0 — show raw count
184
- recalls_per_day = 0.0
185
-
186
- health_status = self._compute_health_status(
187
- staleness_ratio, recalls_per_day
188
- )
189
-
190
- return {
191
- 'days_active': days_active,
192
- 'days_since_last': days_since_last,
193
- 'staleness_ratio': staleness_ratio,
194
- 'total_memories': total_memories,
195
- 'memories_per_day': memories_per_day,
196
- 'recalls_per_day': recalls_per_day,
197
- 'patterns_learned': learn_stats['patterns_learned'],
198
- 'feedback_signals': learn_stats['feedback_signals'],
199
- 'health_status': health_status,
200
- 'active_sources': mem_stats['active_sources'],
201
- }
202
-
203
- def _get_memory_stats(self) -> Dict[str, Any]:
204
- """
205
- Gather stats from memory.db (read-only).
206
-
207
- Returns dict with: days_active, days_since_last, total_memories,
208
- active_sources.
209
- """
210
- default = {
211
- 'days_active': 0,
212
- 'days_since_last': 0,
213
- 'total_memories': 0,
214
- 'active_sources': [],
215
- }
216
-
217
- if not self._memory_db_path.exists():
218
- return default
219
-
220
- available = self._get_memory_db_columns()
221
-
222
- try:
223
- conn = self._open_memory_db()
224
- try:
225
- cursor = conn.cursor()
226
-
227
- # Total memories
228
- cursor.execute("SELECT COUNT(*) FROM memories")
229
- total = cursor.fetchone()[0]
230
- if total == 0:
231
- return default
232
-
233
- # Date range
234
- if 'created_at' in available:
235
- cursor.execute(
236
- "SELECT MIN(created_at), MAX(created_at) "
237
- "FROM memories"
238
- )
239
- row = cursor.fetchone()
240
- first_ts, last_ts = row[0], row[1]
241
-
242
- first_date = self._parse_date(first_ts)
243
- last_date = self._parse_date(last_ts)
244
- today = date.today()
245
-
246
- if first_date and last_date:
247
- days_active = max((today - first_date).days, 1)
248
- days_since_last = max((today - last_date).days, 0)
249
- else:
250
- days_active = 1
251
- days_since_last = 0
252
- else:
253
- days_active = 1
254
- days_since_last = 0
255
-
256
- # Active sources (created_by field, v2.5+)
257
- active_sources = []
258
- if 'created_by' in available:
259
- try:
260
- cursor.execute(
261
- "SELECT DISTINCT created_by FROM memories "
262
- "WHERE created_by IS NOT NULL "
263
- "AND created_by != '' "
264
- "ORDER BY created_by"
265
- )
266
- active_sources = [
267
- row[0] for row in cursor.fetchall()
268
- ]
269
- except sqlite3.OperationalError:
270
- pass # Column might not be queryable
271
-
272
- return {
273
- 'days_active': days_active,
274
- 'days_since_last': days_since_last,
275
- 'total_memories': total,
276
- 'active_sources': active_sources,
277
- }
278
- finally:
279
- conn.close()
280
- except sqlite3.Error as e:
281
- logger.warning("Failed to read memory stats: %s", e)
282
- return default
283
-
284
- def _get_learning_stats(self) -> Dict[str, Any]:
285
- """
286
- Gather stats from learning.db via LearningDB.
287
-
288
- Returns dict with: patterns_learned, feedback_signals.
289
- """
290
- default = {
291
- 'patterns_learned': 0,
292
- 'feedback_signals': 0,
293
- }
294
-
295
- ldb = self._get_learning_db()
296
- if ldb is None:
297
- return default
298
-
299
- try:
300
- # Feedback signals
301
- feedback_count = ldb.get_feedback_count()
302
-
303
- # High-confidence patterns
304
- patterns = ldb.get_transferable_patterns(min_confidence=0.6)
305
- patterns_count = len(patterns)
306
-
307
- return {
308
- 'patterns_learned': patterns_count,
309
- 'feedback_signals': feedback_count,
310
- }
311
- except Exception as e:
312
- logger.warning("Failed to read learning stats: %s", e)
313
- return default
314
-
315
- # ------------------------------------------------------------------
316
- # Health classification
317
- # ------------------------------------------------------------------
318
-
319
- @staticmethod
320
- def _compute_health_status(
321
- staleness_ratio: float,
322
- recalls_per_day: float,
323
- ) -> str:
324
- """
325
- Classify engagement health.
326
-
327
- Tiers:
328
- HEALTHY — staleness < 0.1 AND recalls > 0.5/day
329
- DECLINING — staleness < 0.3 OR recalls > 0.2/day
330
- AT_RISK — staleness < 0.5
331
- INACTIVE — staleness >= 0.5
332
-
333
- Args:
334
- staleness_ratio: 0.0 (active) to 1.0 (abandoned).
335
- recalls_per_day: Average recall operations per day.
336
-
337
- Returns:
338
- One of 'HEALTHY', 'DECLINING', 'AT_RISK', 'INACTIVE'.
339
- """
340
- if staleness_ratio < 0.1 and recalls_per_day > 0.5:
341
- return 'HEALTHY'
342
- if staleness_ratio < 0.3 or recalls_per_day > 0.2:
343
- return 'DECLINING'
344
- if staleness_ratio < 0.5:
345
- return 'AT_RISK'
346
- return 'INACTIVE'
347
-
348
- # ------------------------------------------------------------------
349
- # Activity recording
350
- # ------------------------------------------------------------------
351
-
352
- def record_activity(
353
- self,
354
- activity_type: str,
355
- source: Optional[str] = None,
356
- ):
357
- """
358
- Record an engagement activity event.
359
-
360
- Delegates to LearningDB.increment_engagement() which maintains
361
- daily engagement_metrics rows.
362
-
363
- Args:
364
- activity_type: One of 'memory_created', 'recall_performed',
365
- 'feedback_given', 'pattern_updated'.
366
- source: Source tool identifier (e.g., "claude-desktop",
367
- "cursor", "cli").
368
- """
369
- # Map activity_type to LearningDB metric column names
370
- metric_map = {
371
- 'memory_created': 'memories_created',
372
- 'recall_performed': 'recalls_performed',
373
- 'feedback_given': 'feedback_signals',
374
- 'pattern_updated': 'patterns_updated',
375
- }
376
-
377
- metric_type = metric_map.get(activity_type)
378
- if metric_type is None:
379
- logger.warning(
380
- "Unknown activity type: %r (expected one of %s)",
381
- activity_type,
382
- list(metric_map.keys()),
383
- )
384
- return
385
-
386
- ldb = self._get_learning_db()
387
- if ldb is None:
388
- logger.debug(
389
- "LearningDB unavailable — cannot record activity '%s'",
390
- activity_type,
391
- )
392
- return
393
-
394
- try:
395
- ldb.increment_engagement(
396
- metric_type=metric_type,
397
- count=1,
398
- source=source,
399
- )
400
- logger.debug(
401
- "Recorded activity: type=%s, source=%s",
402
- activity_type, source,
403
- )
404
- except Exception as e:
405
- logger.warning("Failed to record activity: %s", e)
406
-
407
- # ------------------------------------------------------------------
408
- # Weekly summary
409
- # ------------------------------------------------------------------
410
-
411
- def get_weekly_summary(self) -> Dict[str, Any]:
412
- """
413
- Aggregate the last 7 days of engagement_metrics.
414
-
415
- Returns:
416
- Dict with:
417
- period_start — ISO date string
418
- period_end — ISO date string
419
- days_with_data — Number of days that had engagement rows
420
- total_memories_created
421
- total_recalls
422
- total_feedback
423
- total_patterns_updated
424
- avg_memories_per_day
425
- avg_recalls_per_day
426
- all_sources — Unique tools used across the week
427
- """
428
- ldb = self._get_learning_db()
429
- default = {
430
- 'period_start': (date.today() - timedelta(days=6)).isoformat(),
431
- 'period_end': date.today().isoformat(),
432
- 'days_with_data': 0,
433
- 'total_memories_created': 0,
434
- 'total_recalls': 0,
435
- 'total_feedback': 0,
436
- 'total_patterns_updated': 0,
437
- 'avg_memories_per_day': 0.0,
438
- 'avg_recalls_per_day': 0.0,
439
- 'all_sources': [],
440
- }
441
-
442
- if ldb is None:
443
- return default
444
-
445
- try:
446
- history = ldb.get_engagement_history(days=7)
447
- except Exception as e:
448
- logger.warning("Failed to get engagement history: %s", e)
449
- return default
450
-
451
- if not history:
452
- return default
453
-
454
- total_mem = 0
455
- total_rec = 0
456
- total_fb = 0
457
- total_pat = 0
458
- all_sources: set = set()
459
-
460
- for row in history:
461
- total_mem += row.get('memories_created', 0) or 0
462
- total_rec += row.get('recalls_performed', 0) or 0
463
- total_fb += row.get('feedback_signals', 0) or 0
464
- total_pat += row.get('patterns_updated', 0) or 0
465
-
466
- sources_raw = row.get('active_sources', '[]')
467
- if isinstance(sources_raw, str):
468
- try:
469
- sources = json.loads(sources_raw)
470
- all_sources.update(sources)
471
- except (json.JSONDecodeError, TypeError):
472
- pass
473
-
474
- days_with_data = len(history)
475
-
476
- return {
477
- 'period_start': (date.today() - timedelta(days=6)).isoformat(),
478
- 'period_end': date.today().isoformat(),
479
- 'days_with_data': days_with_data,
480
- 'total_memories_created': total_mem,
481
- 'total_recalls': total_rec,
482
- 'total_feedback': total_fb,
483
- 'total_patterns_updated': total_pat,
484
- 'avg_memories_per_day': (
485
- round(total_mem / days_with_data, 1)
486
- if days_with_data > 0 else 0.0
487
- ),
488
- 'avg_recalls_per_day': (
489
- round(total_rec / days_with_data, 1)
490
- if days_with_data > 0 else 0.0
491
- ),
492
- 'all_sources': sorted(all_sources),
493
- }
494
-
495
- # ------------------------------------------------------------------
496
- # CLI formatting
497
- # ------------------------------------------------------------------
498
-
499
- def format_for_cli(self) -> str:
500
- """
501
- Format engagement stats as human-readable CLI output.
502
-
503
- Example:
504
- Active for: 94 days
505
- Last activity: 2 days ago
506
- Memories per day: 3.2
507
- Recalls per day: 1.8
508
- Patterns learned: 23
509
- Engagement: HEALTHY (staleness: 0.02)
510
- """
511
- try:
512
- stats = self.get_engagement_stats()
513
- except Exception as e:
514
- return f"Error computing engagement stats: {e}"
515
-
516
- # Format "last activity" human-friendly
517
- days_since = stats['days_since_last']
518
- if days_since == 0:
519
- last_activity = "today"
520
- elif days_since == 1:
521
- last_activity = "yesterday"
522
- else:
523
- last_activity = f"{days_since} days ago"
524
-
525
- # Health status with optional color hint for terminals
526
- health = stats['health_status']
527
- staleness = stats['staleness_ratio']
528
-
529
- lines = [
530
- f"Active for: {stats['days_active']} days",
531
- f"Last activity: {last_activity}",
532
- f"Total memories: {stats['total_memories']}",
533
- f"Memories per day: {stats['memories_per_day']}",
534
- f"Recalls per day: {stats['recalls_per_day']}",
535
- f"Patterns learned: {stats['patterns_learned']}",
536
- f"Feedback signals: {stats['feedback_signals']}",
537
- f"Engagement: {health} (staleness: {staleness:.2f})",
538
- ]
539
-
540
- if stats['active_sources']:
541
- lines.append(f"Active sources: {', '.join(stats['active_sources'])}")
542
-
543
- return "\n".join(lines)
544
-
545
- # ------------------------------------------------------------------
546
- # Utilities
547
- # ------------------------------------------------------------------
548
-
549
- @staticmethod
550
- def _parse_date(timestamp: Any) -> Optional[date]:
551
- """
552
- Parse a timestamp string into a date object.
553
-
554
- Handles multiple formats from SQLite:
555
- - '2026-02-16 14:30:00'
556
- - '2026-02-16T14:30:00'
557
- - '2026-02-16'
558
- """
559
- if timestamp is None:
560
- return None
561
-
562
- ts = str(timestamp).strip()
563
- if not ts:
564
- return None
565
-
566
- # Try ISO formats
567
- for fmt in (
568
- "%Y-%m-%d %H:%M:%S",
569
- "%Y-%m-%dT%H:%M:%S",
570
- "%Y-%m-%d %H:%M:%S.%f",
571
- "%Y-%m-%dT%H:%M:%S.%f",
572
- "%Y-%m-%d",
573
- ):
574
- try:
575
- return datetime.strptime(ts, fmt).date()
576
- except ValueError:
577
- continue
578
-
579
- # Last resort: try to parse just the date portion
580
- try:
581
- return datetime.strptime(ts[:10], "%Y-%m-%d").date()
582
- except (ValueError, IndexError):
583
- logger.debug("Unparseable timestamp: %r", timestamp)
584
- return None
585
-
586
-
587
- # ======================================================================
588
- # Standalone testing
589
- # ======================================================================
590
-
591
- if __name__ == "__main__":
592
- logging.basicConfig(
593
- level=logging.DEBUG,
594
- format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
595
- )
596
-
597
- tracker = EngagementTracker()
598
-
599
- print("=== Engagement Stats ===")
600
- stats = tracker.get_engagement_stats()
601
- for k, v in stats.items():
602
- print(f" {k}: {v}")
603
-
604
- print("\n=== CLI Output ===")
605
- print(tracker.format_for_cli())
606
-
607
- print("\n=== Weekly Summary ===")
608
- weekly = tracker.get_weekly_summary()
609
- for k, v in weekly.items():
610
- print(f" {k}: {v}")
611
-
612
- print("\n=== Health Classification Tests ===")
613
- test_cases = [
614
- (0.02, 1.5, "HEALTHY"),
615
- (0.05, 0.3, "DECLINING"),
616
- (0.25, 0.3, "DECLINING"),
617
- (0.35, 0.1, "AT_RISK"),
618
- (0.60, 0.0, "INACTIVE"),
619
- (0.0, 0.0, "DECLINING"), # Active but no recalls
620
- (0.99, 5.0, "DECLINING"), # High staleness but high recall
621
- ]
622
- for staleness, recalls, expected in test_cases:
623
- actual = EngagementTracker._compute_health_status(staleness, recalls)
624
- status = "PASS" if actual == expected else "FAIL"
625
- print(
626
- f" [{status}] staleness={staleness:.2f}, recalls={recalls:.1f}"
627
- f" -> {actual} (expected {expected})"
628
- )