superlocalmemory 2.8.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +9 -1
  3. package/NOTICE +63 -0
  4. package/README.md +165 -480
  5. package/bin/slm +17 -449
  6. package/bin/slm-npm +2 -2
  7. package/bin/slm.bat +4 -2
  8. package/conftest.py +5 -0
  9. package/docs/api-reference.md +284 -0
  10. package/docs/architecture.md +149 -0
  11. package/docs/auto-memory.md +150 -0
  12. package/docs/cli-reference.md +276 -0
  13. package/docs/compliance.md +191 -0
  14. package/docs/configuration.md +182 -0
  15. package/docs/getting-started.md +102 -0
  16. package/docs/ide-setup.md +261 -0
  17. package/docs/mcp-tools.md +220 -0
  18. package/docs/migration-from-v2.md +170 -0
  19. package/docs/profiles.md +173 -0
  20. package/docs/troubleshooting.md +310 -0
  21. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  22. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  23. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  24. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  25. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  26. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  27. package/ide/configs/cursor-mcp.json +15 -0
  28. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  29. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  30. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  31. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  32. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  33. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  34. package/{configs → ide/configs}/zed-mcp.json +2 -2
  35. package/{hooks → ide/hooks}/context-hook.js +9 -20
  36. package/ide/hooks/memory-list-skill.js +70 -0
  37. package/ide/hooks/memory-profile-skill.js +101 -0
  38. package/ide/hooks/memory-recall-skill.js +62 -0
  39. package/ide/hooks/memory-remember-skill.js +68 -0
  40. package/ide/hooks/memory-reset-skill.js +160 -0
  41. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  42. package/ide/integrations/langchain/README.md +106 -0
  43. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  44. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  45. package/ide/integrations/langchain/pyproject.toml +38 -0
  46. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  47. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  48. package/ide/integrations/langchain/tests/test_security.py +117 -0
  49. package/ide/integrations/llamaindex/README.md +81 -0
  50. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  51. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  52. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  53. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  54. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  55. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  56. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  57. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  58. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  59. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  60. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  61. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  62. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  63. package/package.json +13 -22
  64. package/pyproject.toml +85 -0
  65. package/scripts/build-dmg.sh +417 -0
  66. package/scripts/install-skills.ps1 +334 -0
  67. package/{install.ps1 → scripts/install.ps1} +36 -4
  68. package/{install.sh → scripts/install.sh} +14 -13
  69. package/scripts/postinstall.js +2 -2
  70. package/scripts/start-dashboard.ps1 +52 -0
  71. package/scripts/start-dashboard.sh +41 -0
  72. package/scripts/sync-wiki.ps1 +127 -0
  73. package/scripts/sync-wiki.sh +82 -0
  74. package/scripts/test-dmg.sh +161 -0
  75. package/scripts/test-npm-package.ps1 +252 -0
  76. package/scripts/test-npm-package.sh +207 -0
  77. package/scripts/verify-install.ps1 +294 -0
  78. package/scripts/verify-install.sh +266 -0
  79. package/src/superlocalmemory/__init__.py +0 -0
  80. package/src/superlocalmemory/attribution/__init__.py +9 -0
  81. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  82. package/src/superlocalmemory/attribution/signer.py +153 -0
  83. package/src/superlocalmemory/attribution/watermark.py +189 -0
  84. package/src/superlocalmemory/cli/__init__.py +5 -0
  85. package/src/superlocalmemory/cli/commands.py +245 -0
  86. package/src/superlocalmemory/cli/main.py +89 -0
  87. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  88. package/src/superlocalmemory/cli/post_install.py +99 -0
  89. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  90. package/src/superlocalmemory/compliance/__init__.py +0 -0
  91. package/src/superlocalmemory/compliance/abac.py +204 -0
  92. package/src/superlocalmemory/compliance/audit.py +314 -0
  93. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  94. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  95. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  96. package/src/superlocalmemory/compliance/retention.py +232 -0
  97. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  98. package/src/superlocalmemory/core/__init__.py +0 -0
  99. package/src/superlocalmemory/core/config.py +391 -0
  100. package/src/superlocalmemory/core/embeddings.py +293 -0
  101. package/src/superlocalmemory/core/engine.py +701 -0
  102. package/src/superlocalmemory/core/hooks.py +65 -0
  103. package/src/superlocalmemory/core/maintenance.py +172 -0
  104. package/src/superlocalmemory/core/modes.py +140 -0
  105. package/src/superlocalmemory/core/profiles.py +234 -0
  106. package/src/superlocalmemory/core/registry.py +117 -0
  107. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  108. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  109. package/src/superlocalmemory/encoding/__init__.py +0 -0
  110. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  111. package/src/superlocalmemory/encoding/emotional.py +125 -0
  112. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  113. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  114. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  115. package/src/superlocalmemory/encoding/foresight.py +91 -0
  116. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  117. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  118. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  119. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  120. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  121. package/src/superlocalmemory/encoding/type_router.py +235 -0
  122. package/src/superlocalmemory/hooks/__init__.py +3 -0
  123. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  124. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  125. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  126. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  127. package/src/superlocalmemory/infra/__init__.py +3 -0
  128. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  129. package/src/superlocalmemory/infra/backup.py +317 -0
  130. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  131. package/src/superlocalmemory/infra/event_bus.py +381 -0
  132. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  133. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  134. package/src/superlocalmemory/learning/__init__.py +0 -0
  135. package/src/superlocalmemory/learning/adaptive.py +172 -0
  136. package/src/superlocalmemory/learning/behavioral.py +490 -0
  137. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  138. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  139. package/src/superlocalmemory/learning/cross_project.py +399 -0
  140. package/src/superlocalmemory/learning/database.py +376 -0
  141. package/src/superlocalmemory/learning/engagement.py +323 -0
  142. package/src/superlocalmemory/learning/features.py +138 -0
  143. package/src/superlocalmemory/learning/feedback.py +316 -0
  144. package/src/superlocalmemory/learning/outcomes.py +255 -0
  145. package/src/superlocalmemory/learning/project_context.py +366 -0
  146. package/src/superlocalmemory/learning/ranker.py +155 -0
  147. package/src/superlocalmemory/learning/source_quality.py +303 -0
  148. package/src/superlocalmemory/learning/workflows.py +309 -0
  149. package/src/superlocalmemory/llm/__init__.py +0 -0
  150. package/src/superlocalmemory/llm/backbone.py +316 -0
  151. package/src/superlocalmemory/math/__init__.py +0 -0
  152. package/src/superlocalmemory/math/fisher.py +356 -0
  153. package/src/superlocalmemory/math/langevin.py +398 -0
  154. package/src/superlocalmemory/math/sheaf.py +257 -0
  155. package/src/superlocalmemory/mcp/__init__.py +0 -0
  156. package/src/superlocalmemory/mcp/resources.py +245 -0
  157. package/src/superlocalmemory/mcp/server.py +61 -0
  158. package/src/superlocalmemory/mcp/tools.py +18 -0
  159. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  160. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  161. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  162. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  163. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  164. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  165. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  166. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  167. package/src/superlocalmemory/retrieval/engine.py +390 -0
  168. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  169. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  170. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  171. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  172. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  173. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  174. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  175. package/src/superlocalmemory/server/__init__.py +1 -0
  176. package/src/superlocalmemory/server/api.py +248 -0
  177. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  178. package/src/superlocalmemory/server/routes/agents.py +107 -0
  179. package/src/superlocalmemory/server/routes/backup.py +91 -0
  180. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  181. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  182. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  183. package/src/superlocalmemory/server/routes/events.py +183 -0
  184. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  185. package/src/superlocalmemory/server/routes/learning.py +273 -0
  186. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  187. package/src/superlocalmemory/server/routes/memories.py +399 -0
  188. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  189. package/src/superlocalmemory/server/routes/stats.py +346 -0
  190. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  191. package/src/superlocalmemory/server/routes/ws.py +82 -0
  192. package/src/superlocalmemory/server/security_middleware.py +57 -0
  193. package/src/superlocalmemory/server/ui.py +245 -0
  194. package/src/superlocalmemory/storage/__init__.py +0 -0
  195. package/src/superlocalmemory/storage/access_control.py +182 -0
  196. package/src/superlocalmemory/storage/database.py +594 -0
  197. package/src/superlocalmemory/storage/migrations.py +303 -0
  198. package/src/superlocalmemory/storage/models.py +406 -0
  199. package/src/superlocalmemory/storage/schema.py +726 -0
  200. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  201. package/src/superlocalmemory/trust/__init__.py +0 -0
  202. package/src/superlocalmemory/trust/gate.py +130 -0
  203. package/src/superlocalmemory/trust/provenance.py +124 -0
  204. package/src/superlocalmemory/trust/scorer.py +347 -0
  205. package/src/superlocalmemory/trust/signals.py +153 -0
  206. package/ui/index.html +278 -5
  207. package/ui/js/auto-settings.js +70 -0
  208. package/ui/js/dashboard.js +90 -0
  209. package/ui/js/fact-detail.js +92 -0
  210. package/ui/js/feedback.js +2 -2
  211. package/ui/js/ide-status.js +102 -0
  212. package/ui/js/math-health.js +98 -0
  213. package/ui/js/recall-lab.js +127 -0
  214. package/ui/js/settings.js +2 -2
  215. package/ui/js/trust-dashboard.js +73 -0
  216. package/api_server.py +0 -724
  217. package/bin/aider-smart +0 -72
  218. package/bin/superlocalmemoryv2-learning +0 -4
  219. package/bin/superlocalmemoryv2-list +0 -3
  220. package/bin/superlocalmemoryv2-patterns +0 -4
  221. package/bin/superlocalmemoryv2-profile +0 -3
  222. package/bin/superlocalmemoryv2-recall +0 -3
  223. package/bin/superlocalmemoryv2-remember +0 -3
  224. package/bin/superlocalmemoryv2-reset +0 -3
  225. package/bin/superlocalmemoryv2-status +0 -3
  226. package/configs/chatgpt-desktop-mcp.json +0 -16
  227. package/configs/cursor-mcp.json +0 -15
  228. package/docs/SECURITY-QUICK-REFERENCE.md +0 -214
  229. package/hooks/memory-list-skill.js +0 -139
  230. package/hooks/memory-profile-skill.js +0 -273
  231. package/hooks/memory-recall-skill.js +0 -114
  232. package/hooks/memory-remember-skill.js +0 -127
  233. package/hooks/memory-reset-skill.js +0 -274
  234. package/mcp_server.py +0 -1800
  235. package/requirements-core.txt +0 -22
  236. package/requirements-learning.txt +0 -12
  237. package/requirements.txt +0 -12
  238. package/src/agent_registry.py +0 -411
  239. package/src/auth_middleware.py +0 -61
  240. package/src/auto_backup.py +0 -459
  241. package/src/behavioral/__init__.py +0 -49
  242. package/src/behavioral/behavioral_listener.py +0 -203
  243. package/src/behavioral/behavioral_patterns.py +0 -275
  244. package/src/behavioral/cross_project_transfer.py +0 -206
  245. package/src/behavioral/outcome_inference.py +0 -194
  246. package/src/behavioral/outcome_tracker.py +0 -193
  247. package/src/behavioral/tests/__init__.py +0 -4
  248. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  249. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  250. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  251. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  252. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  253. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  254. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  255. package/src/cache_manager.py +0 -518
  256. package/src/compliance/__init__.py +0 -48
  257. package/src/compliance/abac_engine.py +0 -149
  258. package/src/compliance/abac_middleware.py +0 -116
  259. package/src/compliance/audit_db.py +0 -215
  260. package/src/compliance/audit_logger.py +0 -148
  261. package/src/compliance/retention_manager.py +0 -289
  262. package/src/compliance/retention_scheduler.py +0 -186
  263. package/src/compliance/tests/__init__.py +0 -4
  264. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  265. package/src/compliance/tests/test_abac_engine.py +0 -124
  266. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  267. package/src/compliance/tests/test_audit_db.py +0 -123
  268. package/src/compliance/tests/test_audit_logger.py +0 -98
  269. package/src/compliance/tests/test_mcp_audit.py +0 -128
  270. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  271. package/src/compliance/tests/test_retention_manager.py +0 -131
  272. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  273. package/src/compression/__init__.py +0 -25
  274. package/src/compression/cli.py +0 -150
  275. package/src/compression/cold_storage.py +0 -217
  276. package/src/compression/config.py +0 -72
  277. package/src/compression/orchestrator.py +0 -133
  278. package/src/compression/tier2_compressor.py +0 -228
  279. package/src/compression/tier3_compressor.py +0 -153
  280. package/src/compression/tier_classifier.py +0 -148
  281. package/src/db_connection_manager.py +0 -536
  282. package/src/embedding_engine.py +0 -63
  283. package/src/embeddings/__init__.py +0 -47
  284. package/src/embeddings/cache.py +0 -70
  285. package/src/embeddings/cli.py +0 -113
  286. package/src/embeddings/constants.py +0 -47
  287. package/src/embeddings/database.py +0 -91
  288. package/src/embeddings/engine.py +0 -247
  289. package/src/embeddings/model_loader.py +0 -145
  290. package/src/event_bus.py +0 -562
  291. package/src/graph/__init__.py +0 -36
  292. package/src/graph/build_helpers.py +0 -74
  293. package/src/graph/cli.py +0 -87
  294. package/src/graph/cluster_builder.py +0 -188
  295. package/src/graph/cluster_summary.py +0 -148
  296. package/src/graph/constants.py +0 -47
  297. package/src/graph/edge_builder.py +0 -162
  298. package/src/graph/entity_extractor.py +0 -95
  299. package/src/graph/graph_core.py +0 -226
  300. package/src/graph/graph_search.py +0 -231
  301. package/src/graph/hierarchical.py +0 -207
  302. package/src/graph/schema.py +0 -99
  303. package/src/graph_engine.py +0 -52
  304. package/src/hnsw_index.py +0 -628
  305. package/src/hybrid_search.py +0 -46
  306. package/src/learning/__init__.py +0 -217
  307. package/src/learning/adaptive_ranker.py +0 -682
  308. package/src/learning/bootstrap/__init__.py +0 -69
  309. package/src/learning/bootstrap/constants.py +0 -93
  310. package/src/learning/bootstrap/db_queries.py +0 -316
  311. package/src/learning/bootstrap/sampling.py +0 -82
  312. package/src/learning/bootstrap/text_utils.py +0 -71
  313. package/src/learning/cross_project_aggregator.py +0 -857
  314. package/src/learning/db/__init__.py +0 -40
  315. package/src/learning/db/constants.py +0 -44
  316. package/src/learning/db/schema.py +0 -279
  317. package/src/learning/engagement_tracker.py +0 -628
  318. package/src/learning/feature_extractor.py +0 -708
  319. package/src/learning/feedback_collector.py +0 -806
  320. package/src/learning/learning_db.py +0 -915
  321. package/src/learning/project_context_manager.py +0 -572
  322. package/src/learning/ranking/__init__.py +0 -33
  323. package/src/learning/ranking/constants.py +0 -84
  324. package/src/learning/ranking/helpers.py +0 -278
  325. package/src/learning/source_quality_scorer.py +0 -676
  326. package/src/learning/synthetic_bootstrap.py +0 -755
  327. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  328. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  329. package/src/learning/tests/test_aggregator.py +0 -306
  330. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  331. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  332. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  333. package/src/learning/tests/test_feedback_collector.py +0 -294
  334. package/src/learning/tests/test_learning_db.py +0 -602
  335. package/src/learning/tests/test_learning_db_v28.py +0 -110
  336. package/src/learning/tests/test_learning_init_v28.py +0 -48
  337. package/src/learning/tests/test_outcome_signals.py +0 -48
  338. package/src/learning/tests/test_project_context.py +0 -292
  339. package/src/learning/tests/test_schema_migration.py +0 -319
  340. package/src/learning/tests/test_signal_inference.py +0 -397
  341. package/src/learning/tests/test_source_quality.py +0 -351
  342. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  343. package/src/learning/tests/test_workflow_miner.py +0 -318
  344. package/src/learning/workflow_pattern_miner.py +0 -655
  345. package/src/lifecycle/__init__.py +0 -54
  346. package/src/lifecycle/bounded_growth.py +0 -239
  347. package/src/lifecycle/compaction_engine.py +0 -226
  348. package/src/lifecycle/lifecycle_engine.py +0 -355
  349. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  350. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  351. package/src/lifecycle/retention_policy.py +0 -285
  352. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  353. package/src/lifecycle/tests/test_compaction.py +0 -179
  354. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  355. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  356. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  357. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  358. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  359. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  360. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  361. package/src/mcp_tools_v28.py +0 -281
  362. package/src/memory/__init__.py +0 -36
  363. package/src/memory/cli.py +0 -205
  364. package/src/memory/constants.py +0 -39
  365. package/src/memory/helpers.py +0 -28
  366. package/src/memory/schema.py +0 -166
  367. package/src/memory-profiles.py +0 -595
  368. package/src/memory-reset.py +0 -491
  369. package/src/memory_compression.py +0 -989
  370. package/src/memory_store_v2.py +0 -1155
  371. package/src/migrate_v1_to_v2.py +0 -629
  372. package/src/pattern_learner.py +0 -34
  373. package/src/patterns/__init__.py +0 -24
  374. package/src/patterns/analyzers.py +0 -251
  375. package/src/patterns/learner.py +0 -271
  376. package/src/patterns/scoring.py +0 -171
  377. package/src/patterns/store.py +0 -225
  378. package/src/patterns/terminology.py +0 -140
  379. package/src/provenance_tracker.py +0 -312
  380. package/src/qualixar_attribution.py +0 -139
  381. package/src/qualixar_watermark.py +0 -78
  382. package/src/query_optimizer.py +0 -511
  383. package/src/rate_limiter.py +0 -83
  384. package/src/search/__init__.py +0 -20
  385. package/src/search/cli.py +0 -77
  386. package/src/search/constants.py +0 -26
  387. package/src/search/engine.py +0 -241
  388. package/src/search/fusion.py +0 -122
  389. package/src/search/index_loader.py +0 -114
  390. package/src/search/methods.py +0 -162
  391. package/src/search_engine_v2.py +0 -401
  392. package/src/setup_validator.py +0 -482
  393. package/src/subscription_manager.py +0 -391
  394. package/src/tree/__init__.py +0 -59
  395. package/src/tree/builder.py +0 -185
  396. package/src/tree/nodes.py +0 -202
  397. package/src/tree/queries.py +0 -257
  398. package/src/tree/schema.py +0 -80
  399. package/src/tree_manager.py +0 -19
  400. package/src/trust/__init__.py +0 -45
  401. package/src/trust/constants.py +0 -66
  402. package/src/trust/queries.py +0 -157
  403. package/src/trust/schema.py +0 -95
  404. package/src/trust/scorer.py +0 -299
  405. package/src/trust/signals.py +0 -95
  406. package/src/trust_scorer.py +0 -44
  407. package/ui/app.js +0 -1588
  408. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  409. package/ui/js/graph-cytoscape.js +0 -1168
  410. package/ui/js/graph-d3-backup.js +0 -32
  411. package/ui/js/graph.js +0 -32
  412. package/ui_server.py +0 -266
  413. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  414. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  415. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  416. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  417. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  418. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  419. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  420. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  421. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  422. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  423. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  424. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  425. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  426. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  427. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  428. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  429. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  430. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  431. /package/{completions → ide/completions}/slm.bash +0 -0
  432. /package/{completions → ide/completions}/slm.zsh +0 -0
  433. /package/{configs → ide/configs}/cody-commands.json +0 -0
  434. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
@@ -1,179 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for compaction engine — content archival and restoration.
4
- """
5
- import sqlite3
6
- import tempfile
7
- import os
8
- import sys
9
- import json
10
- from pathlib import Path
11
-
12
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
13
-
14
-
15
- class TestCompactionEngine:
16
- """Test memory compaction and restoration."""
17
-
18
- def setup_method(self):
19
- self.tmp_dir = tempfile.mkdtemp()
20
- self.db_path = os.path.join(self.tmp_dir, "test.db")
21
- conn = sqlite3.connect(self.db_path)
22
- conn.execute("""
23
- CREATE TABLE memories (
24
- id INTEGER PRIMARY KEY AUTOINCREMENT,
25
- content TEXT NOT NULL,
26
- importance INTEGER DEFAULT 5,
27
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
28
- last_accessed TIMESTAMP,
29
- access_count INTEGER DEFAULT 0,
30
- lifecycle_state TEXT DEFAULT 'active',
31
- lifecycle_updated_at TIMESTAMP,
32
- lifecycle_history TEXT DEFAULT '[]',
33
- access_level TEXT DEFAULT 'public',
34
- profile TEXT DEFAULT 'default',
35
- tags TEXT DEFAULT '[]',
36
- summary TEXT
37
- )
38
- """)
39
- conn.execute("""
40
- CREATE TABLE IF NOT EXISTS memory_archive (
41
- id INTEGER PRIMARY KEY AUTOINCREMENT,
42
- memory_id INTEGER UNIQUE NOT NULL,
43
- full_content TEXT NOT NULL,
44
- archived_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
45
- FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
46
- )
47
- """)
48
- conn.execute("CREATE INDEX IF NOT EXISTS idx_archive_memory ON memory_archive(memory_id)")
49
-
50
- # Memory 1: Long content suitable for compaction
51
- long_content = (
52
- "The Python programming language is widely used for machine learning and data science. "
53
- "It provides libraries like scikit-learn, TensorFlow, and PyTorch for building models. "
54
- "Python's simplicity and readability make it ideal for rapid prototyping. "
55
- "The ecosystem includes tools for data preprocessing, visualization, and deployment. "
56
- "Many enterprise applications use Python for backend services and API development."
57
- )
58
- conn.execute(
59
- "INSERT INTO memories (content, importance, lifecycle_state, tags) VALUES (?, ?, ?, ?)",
60
- (long_content, 5, "cold", '["python","ml"]'),
61
- )
62
- # Memory 2: Short content
63
- conn.execute(
64
- "INSERT INTO memories (content, importance, lifecycle_state) VALUES (?, ?, ?)",
65
- ("brief note about testing", 3, "cold"),
66
- )
67
- # Memory 3: Already archived
68
- conn.execute(
69
- "INSERT INTO memories (content, importance, lifecycle_state) VALUES (?, ?, ?)",
70
- ("[COMPACTED] Key entities: database, SQL", 5, "archived"),
71
- )
72
- conn.execute(
73
- "INSERT INTO memory_archive (memory_id, full_content) VALUES (?, ?)",
74
- (3, "The database management system uses SQL for querying and PostgreSQL for storage."),
75
- )
76
- conn.commit()
77
- conn.close()
78
-
79
- def teardown_method(self):
80
- import shutil
81
- shutil.rmtree(self.tmp_dir, ignore_errors=True)
82
-
83
- def test_compact_memory_archives_content(self):
84
- """Compaction stores full content in memory_archive."""
85
- from lifecycle.compaction_engine import CompactionEngine
86
- engine = CompactionEngine(self.db_path)
87
- result = engine.compact_memory(1)
88
- assert result["success"] is True
89
- # Verify archive has full content
90
- conn = sqlite3.connect(self.db_path)
91
- row = conn.execute("SELECT full_content FROM memory_archive WHERE memory_id=1").fetchone()
92
- conn.close()
93
- assert row is not None
94
- assert "Python programming" in row[0]
95
-
96
- def test_compact_memory_replaces_content(self):
97
- """Compacted memory content is replaced with summary + entities."""
98
- from lifecycle.compaction_engine import CompactionEngine
99
- engine = CompactionEngine(self.db_path)
100
- engine.compact_memory(1)
101
- conn = sqlite3.connect(self.db_path)
102
- row = conn.execute("SELECT content FROM memories WHERE id=1").fetchone()
103
- conn.close()
104
- # Content should be shorter than original
105
- assert len(row[0]) < 300
106
- assert "[COMPACTED]" in row[0]
107
-
108
- def test_compact_preserves_key_entities(self):
109
- """Compacted content preserves key entities/terms."""
110
- from lifecycle.compaction_engine import CompactionEngine
111
- engine = CompactionEngine(self.db_path)
112
- result = engine.compact_memory(1)
113
- assert "entities" in result
114
- assert len(result["entities"]) >= 3
115
- # Should extract key terms like "python", "learning", "data"
116
- entities_lower = [e.lower() for e in result["entities"]]
117
- assert any("python" in e for e in entities_lower)
118
-
119
- def test_compact_preserves_tags(self):
120
- """Compaction does NOT remove tags from the memory."""
121
- from lifecycle.compaction_engine import CompactionEngine
122
- engine = CompactionEngine(self.db_path)
123
- engine.compact_memory(1)
124
- conn = sqlite3.connect(self.db_path)
125
- row = conn.execute("SELECT tags FROM memories WHERE id=1").fetchone()
126
- conn.close()
127
- assert row[0] is not None
128
- tags = json.loads(row[0])
129
- assert "python" in tags
130
-
131
- def test_restore_memory_from_archive(self):
132
- """Restoring a compacted memory brings back full content."""
133
- from lifecycle.compaction_engine import CompactionEngine
134
- engine = CompactionEngine(self.db_path)
135
- result = engine.restore_memory(3) # Already archived memory
136
- assert result["success"] is True
137
- conn = sqlite3.connect(self.db_path)
138
- row = conn.execute("SELECT content FROM memories WHERE id=3").fetchone()
139
- conn.close()
140
- assert "database management" in row[0]
141
-
142
- def test_restore_cleans_archive(self):
143
- """After restoration, the archive entry is removed."""
144
- from lifecycle.compaction_engine import CompactionEngine
145
- engine = CompactionEngine(self.db_path)
146
- engine.restore_memory(3)
147
- conn = sqlite3.connect(self.db_path)
148
- row = conn.execute("SELECT * FROM memory_archive WHERE memory_id=3").fetchone()
149
- conn.close()
150
- assert row is None
151
-
152
- def test_dry_run_no_changes(self):
153
- """dry_run mode shows what would happen without modifying DB."""
154
- from lifecycle.compaction_engine import CompactionEngine
155
- engine = CompactionEngine(self.db_path)
156
- result = engine.compact_memory(1, dry_run=True)
157
- assert result["success"] is True
158
- assert result["dry_run"] is True
159
- # Verify DB was NOT modified
160
- conn = sqlite3.connect(self.db_path)
161
- row = conn.execute("SELECT content FROM memories WHERE id=1").fetchone()
162
- archive = conn.execute("SELECT * FROM memory_archive WHERE memory_id=1").fetchone()
163
- conn.close()
164
- assert "Python programming" in row[0] # Original content still there
165
- assert archive is None # No archive entry created
166
-
167
- def test_compact_nonexistent_memory(self):
168
- """Compacting nonexistent memory returns failure."""
169
- from lifecycle.compaction_engine import CompactionEngine
170
- engine = CompactionEngine(self.db_path)
171
- result = engine.compact_memory(999)
172
- assert result["success"] is False
173
-
174
- def test_restore_nonexistent_archive(self):
175
- """Restoring memory without archive entry returns failure."""
176
- from lifecycle.compaction_engine import CompactionEngine
177
- engine = CompactionEngine(self.db_path)
178
- result = engine.restore_memory(1) # Memory 1 has no archive
179
- assert result["success"] is False
@@ -1,137 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for lifecycle state machine transitions.
4
- """
5
- import sqlite3
6
- import tempfile
7
- import os
8
- import sys
9
- import json
10
- import pytest
11
-
12
- # Ensure src/ is importable and takes precedence (matches existing test pattern)
13
- from pathlib import Path
14
- SRC_DIR = Path(__file__).resolve().parent.parent.parent # src/
15
- _src_str = str(SRC_DIR)
16
- if _src_str not in sys.path:
17
- sys.path.insert(0, _src_str)
18
-
19
-
20
- class TestLifecycleStates:
21
- """Test state definitions and valid transitions."""
22
-
23
- def setup_method(self):
24
- self.db_fd, self.db_path = tempfile.mkstemp(suffix=".db")
25
- conn = sqlite3.connect(self.db_path)
26
- conn.execute("""
27
- CREATE TABLE memories (
28
- id INTEGER PRIMARY KEY AUTOINCREMENT,
29
- content TEXT NOT NULL,
30
- importance INTEGER DEFAULT 5,
31
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
32
- last_accessed TIMESTAMP,
33
- access_count INTEGER DEFAULT 0,
34
- lifecycle_state TEXT DEFAULT 'active',
35
- lifecycle_updated_at TIMESTAMP,
36
- lifecycle_history TEXT DEFAULT '[]',
37
- access_level TEXT DEFAULT 'public',
38
- profile TEXT DEFAULT 'default'
39
- )
40
- """)
41
- conn.execute("""
42
- INSERT INTO memories (content, importance, lifecycle_state)
43
- VALUES ('test memory', 5, 'active')
44
- """)
45
- conn.commit()
46
- conn.close()
47
-
48
- def teardown_method(self):
49
- os.close(self.db_fd)
50
- os.unlink(self.db_path)
51
-
52
- def test_valid_states(self):
53
- from lifecycle.lifecycle_engine import LifecycleEngine
54
- engine = LifecycleEngine(self.db_path)
55
- assert set(engine.STATES) == {"active", "warm", "cold", "archived", "tombstoned"}
56
-
57
- def test_valid_transition_active_to_warm(self):
58
- from lifecycle.lifecycle_engine import LifecycleEngine
59
- engine = LifecycleEngine(self.db_path)
60
- assert engine.is_valid_transition("active", "warm") is True
61
-
62
- def test_invalid_transition_active_to_archived(self):
63
- from lifecycle.lifecycle_engine import LifecycleEngine
64
- engine = LifecycleEngine(self.db_path)
65
- assert engine.is_valid_transition("active", "archived") is False
66
-
67
- def test_reactivation_always_valid(self):
68
- from lifecycle.lifecycle_engine import LifecycleEngine
69
- engine = LifecycleEngine(self.db_path)
70
- for state in ["warm", "cold", "archived"]:
71
- assert engine.is_valid_transition(state, "active") is True
72
-
73
- def test_tombstoned_is_terminal(self):
74
- from lifecycle.lifecycle_engine import LifecycleEngine
75
- engine = LifecycleEngine(self.db_path)
76
- for state in engine.STATES:
77
- if state != "tombstoned":
78
- assert engine.is_valid_transition("tombstoned", state) is False
79
-
80
- def test_transition_memory(self):
81
- from lifecycle.lifecycle_engine import LifecycleEngine
82
- engine = LifecycleEngine(self.db_path)
83
- result = engine.transition_memory(1, "warm", reason="no_access_30d")
84
- assert result["success"] is True
85
- assert result["from_state"] == "active"
86
- assert result["to_state"] == "warm"
87
-
88
- def test_transition_updates_db(self):
89
- from lifecycle.lifecycle_engine import LifecycleEngine
90
- engine = LifecycleEngine(self.db_path)
91
- engine.transition_memory(1, "warm", reason="no_access_30d")
92
- conn = sqlite3.connect(self.db_path)
93
- row = conn.execute("SELECT lifecycle_state FROM memories WHERE id=1").fetchone()
94
- conn.close()
95
- assert row[0] == "warm"
96
-
97
- def test_transition_records_history(self):
98
- from lifecycle.lifecycle_engine import LifecycleEngine
99
- engine = LifecycleEngine(self.db_path)
100
- engine.transition_memory(1, "warm", reason="no_access_30d")
101
- conn = sqlite3.connect(self.db_path)
102
- row = conn.execute("SELECT lifecycle_history FROM memories WHERE id=1").fetchone()
103
- conn.close()
104
- history = json.loads(row[0])
105
- assert len(history) == 1
106
- assert history[0]["from"] == "active"
107
- assert history[0]["to"] == "warm"
108
- assert history[0]["reason"] == "no_access_30d"
109
-
110
- def test_invalid_transition_rejected(self):
111
- from lifecycle.lifecycle_engine import LifecycleEngine
112
- engine = LifecycleEngine(self.db_path)
113
- result = engine.transition_memory(1, "archived", reason="skip")
114
- assert result["success"] is False
115
- assert "invalid" in result["error"].lower()
116
-
117
- def test_get_memory_state(self):
118
- from lifecycle.lifecycle_engine import LifecycleEngine
119
- engine = LifecycleEngine(self.db_path)
120
- state = engine.get_memory_state(1)
121
- assert state == "active"
122
-
123
- def test_get_state_distribution(self):
124
- from lifecycle.lifecycle_engine import LifecycleEngine
125
- engine = LifecycleEngine(self.db_path)
126
- dist = engine.get_state_distribution()
127
- assert dist["active"] >= 1
128
- assert dist["warm"] == 0
129
-
130
- def test_reactivation_on_access(self):
131
- from lifecycle.lifecycle_engine import LifecycleEngine
132
- engine = LifecycleEngine(self.db_path)
133
- engine.transition_memory(1, "warm", reason="aged")
134
- result = engine.reactivate_memory(1, trigger="recall")
135
- assert result["success"] is True
136
- assert result["from_state"] == "warm"
137
- assert result["to_state"] == "active"
@@ -1,177 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for lifecycle evaluation rules — which memories should transition.
4
- """
5
- import sqlite3
6
- import tempfile
7
- import os
8
- import sys
9
- import json
10
- from datetime import datetime, timedelta
11
- from pathlib import Path
12
-
13
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
14
-
15
-
16
- class TestLifecycleEvaluation:
17
- """Test evaluation rules for memory lifecycle transitions."""
18
-
19
- def setup_method(self):
20
- # Create temp dir for DB + config isolation
21
- self.tmp_dir = tempfile.mkdtemp()
22
- self.db_path = os.path.join(self.tmp_dir, "test.db")
23
- conn = sqlite3.connect(self.db_path)
24
- conn.execute("""
25
- CREATE TABLE memories (
26
- id INTEGER PRIMARY KEY AUTOINCREMENT,
27
- content TEXT NOT NULL,
28
- importance INTEGER DEFAULT 5,
29
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
30
- last_accessed TIMESTAMP,
31
- access_count INTEGER DEFAULT 0,
32
- lifecycle_state TEXT DEFAULT 'active',
33
- lifecycle_updated_at TIMESTAMP,
34
- lifecycle_history TEXT DEFAULT '[]',
35
- access_level TEXT DEFAULT 'public',
36
- profile TEXT DEFAULT 'default'
37
- )
38
- """)
39
- now = datetime.now()
40
-
41
- # Memory 1: Active, stale (35d), low importance (5) → should recommend WARM
42
- conn.execute(
43
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
44
- ("stale low importance", 5, "active", (now - timedelta(days=35)).isoformat(), (now - timedelta(days=100)).isoformat()),
45
- )
46
- # Memory 2: Active, recent (10d), low importance (5) → should STAY
47
- conn.execute(
48
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
49
- ("recent access", 5, "active", (now - timedelta(days=10)).isoformat(), (now - timedelta(days=100)).isoformat()),
50
- )
51
- # Memory 3: Active, stale (35d), HIGH importance (8) → should STAY (importance resists)
52
- conn.execute(
53
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
54
- ("stale high importance", 8, "active", (now - timedelta(days=35)).isoformat(), (now - timedelta(days=100)).isoformat()),
55
- )
56
- # Memory 4: Warm, stale (95d), low importance (3) → should recommend COLD
57
- conn.execute(
58
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
59
- ("warm stale", 3, "warm", (now - timedelta(days=95)).isoformat(), (now - timedelta(days=200)).isoformat()),
60
- )
61
- # Memory 5: Cold, very stale (200d), importance 5 → should recommend ARCHIVED
62
- conn.execute(
63
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
64
- ("cold very stale", 5, "cold", (now - timedelta(days=200)).isoformat(), (now - timedelta(days=300)).isoformat()),
65
- )
66
- # Memory 6: Active, NULL last_accessed, created 40d ago, importance 4 → WARM (uses created_at)
67
- conn.execute(
68
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
69
- ("never accessed", 4, "active", None, (now - timedelta(days=40)).isoformat()),
70
- )
71
- conn.commit()
72
- conn.close()
73
-
74
- def teardown_method(self):
75
- import shutil
76
- shutil.rmtree(self.tmp_dir, ignore_errors=True)
77
-
78
- def test_active_to_warm_stale_low_importance(self):
79
- """Memory 1: stale 35d, importance 5 → recommend ACTIVE→WARM."""
80
- from lifecycle.lifecycle_evaluator import LifecycleEvaluator
81
- evaluator = LifecycleEvaluator(self.db_path)
82
- rec = evaluator.evaluate_single(1)
83
- assert rec is not None
84
- assert rec["from_state"] == "active"
85
- assert rec["to_state"] == "warm"
86
- assert rec["memory_id"] == 1
87
-
88
- def test_active_stays_recent_access(self):
89
- """Memory 2: accessed 10d ago → no transition recommended."""
90
- from lifecycle.lifecycle_evaluator import LifecycleEvaluator
91
- evaluator = LifecycleEvaluator(self.db_path)
92
- rec = evaluator.evaluate_single(2)
93
- assert rec is None
94
-
95
- def test_active_stays_high_importance(self):
96
- """Memory 3: importance 8 resists transition even when stale."""
97
- from lifecycle.lifecycle_evaluator import LifecycleEvaluator
98
- evaluator = LifecycleEvaluator(self.db_path)
99
- rec = evaluator.evaluate_single(3)
100
- assert rec is None
101
-
102
- def test_warm_to_cold_stale(self):
103
- """Memory 4: warm, stale 95d, importance 3 → recommend COLD."""
104
- from lifecycle.lifecycle_evaluator import LifecycleEvaluator
105
- evaluator = LifecycleEvaluator(self.db_path)
106
- rec = evaluator.evaluate_single(4)
107
- assert rec is not None
108
- assert rec["from_state"] == "warm"
109
- assert rec["to_state"] == "cold"
110
-
111
- def test_cold_to_archived(self):
112
- """Memory 5: cold, stale 200d → recommend ARCHIVED."""
113
- from lifecycle.lifecycle_evaluator import LifecycleEvaluator
114
- evaluator = LifecycleEvaluator(self.db_path)
115
- rec = evaluator.evaluate_single(5)
116
- assert rec is not None
117
- assert rec["from_state"] == "cold"
118
- assert rec["to_state"] == "archived"
119
-
120
- def test_never_accessed_uses_created_at(self):
121
- """Memory 6: NULL last_accessed, created 40d ago → recommend WARM."""
122
- from lifecycle.lifecycle_evaluator import LifecycleEvaluator
123
- evaluator = LifecycleEvaluator(self.db_path)
124
- rec = evaluator.evaluate_single(6)
125
- assert rec is not None
126
- assert rec["to_state"] == "warm"
127
-
128
- def test_retention_override_skips_memory(self):
129
- """Memory 1 should be skipped when in retention_overrides set."""
130
- from lifecycle.lifecycle_evaluator import LifecycleEvaluator
131
- evaluator = LifecycleEvaluator(self.db_path)
132
- rec = evaluator.evaluate_single(1, retention_overrides={1})
133
- assert rec is None
134
-
135
- def test_evaluate_memories_returns_recommendations(self):
136
- """Full scan should return list with recommendations for eligible memories."""
137
- from lifecycle.lifecycle_evaluator import LifecycleEvaluator
138
- evaluator = LifecycleEvaluator(self.db_path)
139
- recs = evaluator.evaluate_memories()
140
- # Should recommend: Memory 1 (active→warm), 4 (warm→cold), 5 (cold→archived), 6 (active→warm)
141
- assert isinstance(recs, list)
142
- assert len(recs) >= 3 # At least memories 1, 4, 5
143
- rec_ids = {r["memory_id"] for r in recs}
144
- assert 1 in rec_ids # stale active
145
- assert 4 in rec_ids # stale warm
146
- assert 5 in rec_ids # stale cold
147
-
148
- def test_evaluate_memories_excludes_retained(self):
149
- """evaluate_memories with retention_overrides skips those memory IDs."""
150
- from lifecycle.lifecycle_evaluator import LifecycleEvaluator
151
- evaluator = LifecycleEvaluator(self.db_path)
152
- recs = evaluator.evaluate_memories(retention_overrides={1, 4})
153
- rec_ids = {r["memory_id"] for r in recs}
154
- assert 1 not in rec_ids
155
- assert 4 not in rec_ids
156
- assert 5 in rec_ids # cold→archived not overridden
157
-
158
- def test_custom_config_thresholds(self):
159
- """Custom config should override default thresholds."""
160
- from lifecycle.lifecycle_evaluator import LifecycleEvaluator
161
- # Write custom config: raise active_to_warm threshold to 50 days
162
- config_path = os.path.join(self.tmp_dir, "lifecycle_config.json")
163
- with open(config_path, "w") as f:
164
- json.dump({
165
- "active_to_warm": {"no_access_days": 50, "max_importance": 6}
166
- }, f)
167
- evaluator = LifecycleEvaluator(self.db_path, config_path=config_path)
168
- # Memory 1 is stale 35d — below new 50d threshold → no recommendation
169
- rec = evaluator.evaluate_single(1)
170
- assert rec is None
171
-
172
- def test_evaluate_single_nonexistent_memory(self):
173
- """Evaluating a nonexistent memory returns None."""
174
- from lifecycle.lifecycle_evaluator import LifecycleEvaluator
175
- evaluator = LifecycleEvaluator(self.db_path)
176
- rec = evaluator.evaluate_single(999)
177
- assert rec is None
@@ -1,127 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for lifecycle background scheduler.
4
- """
5
- import sqlite3
6
- import tempfile
7
- import os
8
- import sys
9
- import time
10
- import threading
11
- from datetime import datetime, timedelta
12
- from pathlib import Path
13
-
14
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
15
-
16
-
17
- class TestLifecycleScheduler:
18
- """Test lifecycle scheduler background evaluation."""
19
-
20
- def setup_method(self):
21
- self.tmp_dir = tempfile.mkdtemp()
22
- self.db_path = os.path.join(self.tmp_dir, "test.db")
23
- conn = sqlite3.connect(self.db_path)
24
- conn.execute("""
25
- CREATE TABLE memories (
26
- id INTEGER PRIMARY KEY AUTOINCREMENT,
27
- content TEXT NOT NULL,
28
- importance INTEGER DEFAULT 5,
29
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
30
- last_accessed TIMESTAMP,
31
- access_count INTEGER DEFAULT 0,
32
- lifecycle_state TEXT DEFAULT 'active',
33
- lifecycle_updated_at TIMESTAMP,
34
- lifecycle_history TEXT DEFAULT '[]',
35
- access_level TEXT DEFAULT 'public',
36
- profile TEXT DEFAULT 'default'
37
- )
38
- """)
39
- now = datetime.now()
40
- # Insert a stale memory that should be evaluated for transition
41
- conn.execute(
42
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
43
- ("stale memory", 3, "active", (now - timedelta(days=45)).isoformat(), (now - timedelta(days=100)).isoformat()),
44
- )
45
- # Insert a fresh memory that should stay
46
- conn.execute(
47
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) VALUES (?, ?, ?, ?, ?)",
48
- ("fresh memory", 8, "active", now.isoformat(), (now - timedelta(days=10)).isoformat()),
49
- )
50
- conn.commit()
51
- conn.close()
52
-
53
- def teardown_method(self):
54
- import shutil
55
- shutil.rmtree(self.tmp_dir, ignore_errors=True)
56
-
57
- def test_scheduler_creation(self):
58
- """Scheduler can be created with default settings."""
59
- from lifecycle.lifecycle_scheduler import LifecycleScheduler
60
- scheduler = LifecycleScheduler(self.db_path)
61
- assert scheduler is not None
62
- assert scheduler.interval_seconds == 21600 # 6 hours default
63
-
64
- def test_run_now_executes_evaluation(self):
65
- """Manual trigger runs evaluation and transitions eligible memories."""
66
- from lifecycle.lifecycle_scheduler import LifecycleScheduler
67
- scheduler = LifecycleScheduler(self.db_path)
68
- result = scheduler.run_now()
69
- assert result is not None
70
- assert "evaluation" in result
71
- assert "enforcement" in result
72
-
73
- def test_run_now_transitions_stale_memories(self):
74
- """run_now should transition stale memories."""
75
- from lifecycle.lifecycle_scheduler import LifecycleScheduler
76
- scheduler = LifecycleScheduler(self.db_path)
77
- result = scheduler.run_now()
78
- # Memory 1 (stale 45d, importance 3) should be recommended for transition
79
- eval_recs = result["evaluation"]["recommendations"]
80
- if eval_recs:
81
- transitioned = result["evaluation"]["transitioned"]
82
- assert transitioned >= 1
83
-
84
- def test_fresh_memory_stays_active(self):
85
- """Fresh high-importance memory should NOT be transitioned."""
86
- from lifecycle.lifecycle_scheduler import LifecycleScheduler
87
- scheduler = LifecycleScheduler(self.db_path)
88
- scheduler.run_now()
89
- conn = sqlite3.connect(self.db_path)
90
- row = conn.execute("SELECT lifecycle_state FROM memories WHERE id=2").fetchone()
91
- conn.close()
92
- assert row[0] == "active"
93
-
94
- def test_scheduler_thread_is_daemon(self):
95
- """Scheduler thread should be daemonic (doesn't prevent exit)."""
96
- from lifecycle.lifecycle_scheduler import LifecycleScheduler
97
- scheduler = LifecycleScheduler(self.db_path, interval_seconds=3600)
98
- scheduler.start()
99
- assert scheduler._timer is not None
100
- assert scheduler._timer.daemon is True
101
- scheduler.stop()
102
-
103
- def test_start_and_stop(self):
104
- """Scheduler can be started and stopped."""
105
- from lifecycle.lifecycle_scheduler import LifecycleScheduler
106
- scheduler = LifecycleScheduler(self.db_path, interval_seconds=3600)
107
- scheduler.start()
108
- assert scheduler.is_running is True
109
- scheduler.stop()
110
- assert scheduler.is_running is False
111
-
112
- def test_configurable_interval(self):
113
- """Scheduler interval is configurable."""
114
- from lifecycle.lifecycle_scheduler import LifecycleScheduler
115
- scheduler = LifecycleScheduler(self.db_path, interval_seconds=7200)
116
- assert scheduler.interval_seconds == 7200
117
-
118
- def test_result_structure(self):
119
- """run_now returns properly structured result."""
120
- from lifecycle.lifecycle_scheduler import LifecycleScheduler
121
- scheduler = LifecycleScheduler(self.db_path)
122
- result = scheduler.run_now()
123
- assert "evaluation" in result
124
- assert "enforcement" in result
125
- assert "timestamp" in result
126
- assert "recommendations" in result["evaluation"]
127
- assert "transitioned" in result["evaluation"]