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,130 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Background scheduler for periodic lifecycle evaluation and enforcement.
4
-
5
- Runs on a configurable interval (default: 6 hours) to:
6
- 1. Evaluate all memories for lifecycle transitions
7
- 2. Execute recommended transitions
8
- 3. Enforce bounded growth limits
9
-
10
- Uses daemon threading — does not prevent process exit.
11
- """
12
- import threading
13
- from datetime import datetime
14
- from pathlib import Path
15
- from typing import Optional, Dict, Any, List
16
-
17
- from .lifecycle_engine import LifecycleEngine
18
- from .lifecycle_evaluator import LifecycleEvaluator
19
- from .bounded_growth import BoundedGrowthEnforcer
20
-
21
- # Default interval: 6 hours
22
- DEFAULT_INTERVAL_SECONDS = 21600
23
-
24
-
25
- class LifecycleScheduler:
26
- """Background scheduler for periodic lifecycle evaluation.
27
-
28
- Orchestrates the evaluator, engine, and bounded growth enforcer
29
- on a configurable timer interval.
30
- """
31
-
32
- def __init__(
33
- self,
34
- db_path: Optional[str] = None,
35
- config_path: Optional[str] = None,
36
- interval_seconds: int = DEFAULT_INTERVAL_SECONDS,
37
- ):
38
- if db_path is None:
39
- db_path = str(Path.home() / ".claude-memory" / "memory.db")
40
- self._db_path = str(db_path)
41
- self._config_path = config_path
42
- self.interval_seconds = interval_seconds
43
-
44
- self._engine = LifecycleEngine(self._db_path, config_path=config_path)
45
- self._evaluator = LifecycleEvaluator(self._db_path, config_path=config_path)
46
- self._enforcer = BoundedGrowthEnforcer(self._db_path, config_path=config_path)
47
-
48
- self._timer: Optional[threading.Timer] = None
49
- self._running = False
50
- self._lock = threading.Lock()
51
-
52
- @property
53
- def is_running(self) -> bool:
54
- """Whether the scheduler is currently running."""
55
- return self._running
56
-
57
- def start(self) -> None:
58
- """Start the background scheduler."""
59
- with self._lock:
60
- if self._running:
61
- return
62
- self._running = True
63
- self._schedule_next()
64
-
65
- def stop(self) -> None:
66
- """Stop the background scheduler."""
67
- with self._lock:
68
- self._running = False
69
- if self._timer is not None:
70
- self._timer.cancel()
71
- self._timer = None
72
-
73
- def run_now(self) -> Dict[str, Any]:
74
- """Execute a lifecycle evaluation cycle immediately.
75
-
76
- Returns:
77
- Dict with evaluation results, enforcement results, and timestamp
78
- """
79
- return self._execute_cycle()
80
-
81
- def _schedule_next(self) -> None:
82
- """Schedule the next evaluation cycle."""
83
- self._timer = threading.Timer(self.interval_seconds, self._run_cycle)
84
- self._timer.daemon = True
85
- self._timer.start()
86
-
87
- def _run_cycle(self) -> None:
88
- """Run one evaluation cycle, then schedule the next."""
89
- try:
90
- self._execute_cycle()
91
- except Exception:
92
- pass # Scheduler must not crash
93
- finally:
94
- with self._lock:
95
- if self._running:
96
- self._schedule_next()
97
-
98
- def _execute_cycle(self) -> Dict[str, Any]:
99
- """Core evaluation + enforcement logic.
100
-
101
- 1. Evaluate all memories for potential transitions
102
- 2. Execute recommended transitions via the engine
103
- 3. Enforce bounded growth limits
104
- """
105
- # Step 1: Evaluate
106
- recommendations = self._evaluator.evaluate_memories()
107
-
108
- # Step 2: Execute transitions
109
- transitioned = 0
110
- transition_results: List[Dict] = []
111
- for rec in recommendations:
112
- result = self._engine.transition_memory(
113
- rec["memory_id"], rec["to_state"], reason=rec["reason"]
114
- )
115
- if result.get("success"):
116
- transitioned += 1
117
- transition_results.append(result)
118
-
119
- # Step 3: Enforce bounds
120
- enforcement = self._enforcer.enforce_bounds()
121
-
122
- return {
123
- "timestamp": datetime.now().isoformat(),
124
- "evaluation": {
125
- "recommendations": recommendations,
126
- "transitioned": transitioned,
127
- "transition_results": transition_results,
128
- },
129
- "enforcement": enforcement,
130
- }
@@ -1,285 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Retention policy loading, evaluation, and enforcement.
4
-
5
- Manages retention policies that determine how long memories must be kept
6
- in specific states. Supports GDPR (right to erasure), EU AI Act (audit
7
- retention), and HIPAA (medical record retention) compliance frameworks.
8
-
9
- Policies are stored in a `retention_policies` table alongside the memories
10
- database. Each policy specifies criteria (tags, project_name) for matching
11
- memories and an action (retain, archive, tombstone) with a retention period.
12
- """
13
- import json
14
- import logging
15
- import sqlite3
16
- from pathlib import Path
17
- from typing import Any, Dict, List, Optional, Set
18
-
19
- logger = logging.getLogger(__name__)
20
-
21
- _POLICIES_TABLE_SQL = """
22
- CREATE TABLE IF NOT EXISTS retention_policies (
23
- id INTEGER PRIMARY KEY AUTOINCREMENT,
24
- name TEXT NOT NULL,
25
- retention_days INTEGER NOT NULL,
26
- framework TEXT NOT NULL,
27
- action TEXT NOT NULL,
28
- applies_to TEXT NOT NULL,
29
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
30
- )
31
- """
32
-
33
-
34
- class RetentionPolicyManager:
35
- """Manages retention policies for lifecycle enforcement.
36
-
37
- Evaluates which compliance policies apply to each memory based on
38
- tag and project_name matching. When multiple policies match, the
39
- strictest (shortest retention_days) wins.
40
- """
41
-
42
- def __init__(self, db_path: Optional[str] = None):
43
- self._db_path = db_path
44
- if db_path:
45
- self._ensure_table()
46
-
47
- # ------------------------------------------------------------------
48
- # Internal helpers
49
- # ------------------------------------------------------------------
50
-
51
- def _connect(self) -> sqlite3.Connection:
52
- """Open a connection to the database."""
53
- conn = sqlite3.connect(self._db_path)
54
- conn.row_factory = sqlite3.Row
55
- return conn
56
-
57
- def _ensure_table(self) -> None:
58
- """Create the retention_policies table if it doesn't exist."""
59
- conn = self._connect()
60
- try:
61
- conn.execute(_POLICIES_TABLE_SQL)
62
- conn.commit()
63
- finally:
64
- conn.close()
65
-
66
- # ------------------------------------------------------------------
67
- # Public API
68
- # ------------------------------------------------------------------
69
-
70
- def create_policy(
71
- self,
72
- name: str,
73
- retention_days: int,
74
- framework: str,
75
- action: str,
76
- applies_to: Dict[str, Any],
77
- ) -> int:
78
- """Create a new retention policy.
79
-
80
- Args:
81
- name: Human-readable policy name.
82
- retention_days: Minimum days to retain (0 = immediate action).
83
- framework: Compliance framework (gdpr, hipaa, eu_ai_act, internal).
84
- action: What to do (retain, archive, tombstone).
85
- applies_to: Criteria dict with optional keys: tags, project_name.
86
-
87
- Returns:
88
- The auto-generated policy ID.
89
- """
90
- conn = self._connect()
91
- try:
92
- cursor = conn.execute(
93
- "INSERT INTO retention_policies (name, retention_days, framework, action, applies_to) "
94
- "VALUES (?, ?, ?, ?, ?)",
95
- (name, retention_days, framework, action, json.dumps(applies_to)),
96
- )
97
- conn.commit()
98
- return cursor.lastrowid
99
- finally:
100
- conn.close()
101
-
102
- def list_policies(self) -> List[Dict[str, Any]]:
103
- """Return all retention policies as a list of dicts."""
104
- conn = self._connect()
105
- try:
106
- rows = conn.execute("SELECT * FROM retention_policies ORDER BY id").fetchall()
107
- return [self._row_to_dict(r) for r in rows]
108
- finally:
109
- conn.close()
110
-
111
- def load_policies(self, path: str) -> int:
112
- """Load retention policies from a JSON file.
113
-
114
- The file must contain a JSON array of policy objects, each with
115
- keys: name, retention_days, framework, action, applies_to.
116
-
117
- Args:
118
- path: Absolute or relative path to the JSON policy file.
119
-
120
- Returns:
121
- Number of policies loaded. Returns 0 if file is missing or
122
- contains invalid data, without raising an exception.
123
- """
124
- policy_path = Path(path)
125
- if not policy_path.exists():
126
- logger.debug("Policy file not found: %s", path)
127
- return 0
128
-
129
- try:
130
- data = json.loads(policy_path.read_text(encoding="utf-8"))
131
- except (json.JSONDecodeError, OSError) as exc:
132
- logger.warning("Failed to read policy file %s: %s", path, exc)
133
- return 0
134
-
135
- if not isinstance(data, list):
136
- logger.warning("Policy file must contain a JSON array: %s", path)
137
- return 0
138
-
139
- count = 0
140
- for entry in data:
141
- try:
142
- self.create_policy(
143
- name=entry["name"],
144
- retention_days=entry["retention_days"],
145
- framework=entry["framework"],
146
- action=entry["action"],
147
- applies_to=entry.get("applies_to", {}),
148
- )
149
- count += 1
150
- except (KeyError, TypeError) as exc:
151
- logger.warning("Skipping invalid policy entry: %s", exc)
152
-
153
- return count
154
-
155
- def evaluate_memory(self, memory_id: int) -> Optional[Dict[str, Any]]:
156
- """Determine which retention policy applies to a memory.
157
-
158
- Loads the memory's tags and project_name, then checks every
159
- policy's ``applies_to`` criteria. If multiple policies match,
160
- the **strictest** one wins (lowest ``retention_days``).
161
-
162
- Args:
163
- memory_id: The memory row ID.
164
-
165
- Returns:
166
- A dict with ``policy_name``, ``action``, ``retention_days``,
167
- and ``framework``; or ``None`` if no policy matches.
168
- """
169
- conn = self._connect()
170
- try:
171
- mem_row = conn.execute(
172
- "SELECT tags, project_name FROM memories WHERE id = ?",
173
- (memory_id,),
174
- ).fetchone()
175
- if mem_row is None:
176
- return None
177
-
178
- mem_tags = self._parse_json_field(mem_row["tags"])
179
- mem_project = mem_row["project_name"]
180
-
181
- policies = conn.execute(
182
- "SELECT * FROM retention_policies ORDER BY retention_days ASC"
183
- ).fetchall()
184
-
185
- for policy in policies:
186
- criteria = self._parse_json_field(policy["applies_to"])
187
- if self._policy_matches(criteria, mem_tags, mem_project):
188
- return {
189
- "policy_name": policy["name"],
190
- "action": policy["action"],
191
- "retention_days": policy["retention_days"],
192
- "framework": policy["framework"],
193
- }
194
-
195
- return None
196
- finally:
197
- conn.close()
198
-
199
- def get_protected_memory_ids(self) -> Set[int]:
200
- """Return the set of memory IDs protected by any ``retain`` policy.
201
-
202
- A memory is protected if at least one policy with
203
- ``action='retain'`` matches its tags or project_name.
204
- """
205
- conn = self._connect()
206
- try:
207
- retain_policies = conn.execute(
208
- "SELECT * FROM retention_policies WHERE action = 'retain'"
209
- ).fetchall()
210
- if not retain_policies:
211
- return set()
212
-
213
- memories = conn.execute(
214
- "SELECT id, tags, project_name FROM memories"
215
- ).fetchall()
216
-
217
- protected: Set[int] = set()
218
- for mem in memories:
219
- mem_tags = self._parse_json_field(mem["tags"])
220
- mem_project = mem["project_name"]
221
- for policy in retain_policies:
222
- criteria = self._parse_json_field(policy["applies_to"])
223
- if self._policy_matches(criteria, mem_tags, mem_project):
224
- protected.add(mem["id"])
225
- break # One matching retain policy is enough
226
-
227
- return protected
228
- finally:
229
- conn.close()
230
-
231
- # ------------------------------------------------------------------
232
- # Private helpers
233
- # ------------------------------------------------------------------
234
-
235
- @staticmethod
236
- def _row_to_dict(row: sqlite3.Row) -> Dict[str, Any]:
237
- """Convert a sqlite3.Row to a plain dict with parsed applies_to."""
238
- d = dict(row)
239
- if "applies_to" in d and isinstance(d["applies_to"], str):
240
- try:
241
- d["applies_to"] = json.loads(d["applies_to"])
242
- except (json.JSONDecodeError, TypeError):
243
- d["applies_to"] = {}
244
- return d
245
-
246
- @staticmethod
247
- def _parse_json_field(value: Any) -> Any:
248
- """Parse a JSON string field; return as-is if already parsed."""
249
- if isinstance(value, str):
250
- try:
251
- return json.loads(value)
252
- except (json.JSONDecodeError, TypeError):
253
- return value
254
- return value if value is not None else []
255
-
256
- @staticmethod
257
- def _policy_matches(
258
- criteria: Any, mem_tags: Any, mem_project: Optional[str]
259
- ) -> bool:
260
- """Check if a policy's applies_to criteria match a memory.
261
-
262
- Matching rules:
263
- - If criteria has ``tags``: memory must have at least one
264
- overlapping tag.
265
- - If criteria has ``project_name``: memory's project_name
266
- must equal the criteria value.
267
- - If criteria is empty (``{}``): the policy does NOT match
268
- any memory (opt-in only).
269
- """
270
- if not isinstance(criteria, dict) or not criteria:
271
- return False
272
-
273
- matched = True # Assume match; any failing criterion flips to False
274
-
275
- if "tags" in criteria:
276
- policy_tags = set(criteria["tags"]) if criteria["tags"] else set()
277
- memory_tags = set(mem_tags) if isinstance(mem_tags, list) else set()
278
- if not policy_tags & memory_tags:
279
- matched = False
280
-
281
- if "project_name" in criteria:
282
- if mem_project != criteria["project_name"]:
283
- matched = False
284
-
285
- return matched
@@ -1,193 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for bounded growth enforcement — memory count limits.
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 TestBoundedGrowth:
17
- """Test bounded growth enforcement and memory scoring."""
18
-
19
- def setup_method(self):
20
- self.tmp_dir = tempfile.mkdtemp()
21
- self.db_path = os.path.join(self.tmp_dir, "test.db")
22
- conn = sqlite3.connect(self.db_path)
23
- conn.execute("""
24
- CREATE TABLE memories (
25
- id INTEGER PRIMARY KEY AUTOINCREMENT,
26
- content TEXT NOT NULL,
27
- importance INTEGER DEFAULT 5,
28
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
29
- last_accessed TIMESTAMP,
30
- access_count INTEGER DEFAULT 0,
31
- lifecycle_state TEXT DEFAULT 'active',
32
- lifecycle_updated_at TIMESTAMP,
33
- lifecycle_history TEXT DEFAULT '[]',
34
- access_level TEXT DEFAULT 'public',
35
- profile TEXT DEFAULT 'default'
36
- )
37
- """)
38
- now = datetime.now()
39
-
40
- # Memory 1: HIGH value — importance 9, accessed today, frequently used
41
- conn.execute(
42
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at, access_count) VALUES (?, ?, ?, ?, ?, ?)",
43
- ("high value memory", 9, "active", now.isoformat(), (now - timedelta(days=30)).isoformat(), 20),
44
- )
45
- # Memory 2: MEDIUM-HIGH — importance 7, accessed 5d ago
46
- conn.execute(
47
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at, access_count) VALUES (?, ?, ?, ?, ?, ?)",
48
- ("medium high memory", 7, "active", (now - timedelta(days=5)).isoformat(), (now - timedelta(days=60)).isoformat(), 10),
49
- )
50
- # Memory 3: MEDIUM — importance 5, accessed 10d ago
51
- conn.execute(
52
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at, access_count) VALUES (?, ?, ?, ?, ?, ?)",
53
- ("medium memory", 5, "active", (now - timedelta(days=10)).isoformat(), (now - timedelta(days=90)).isoformat(), 5),
54
- )
55
- # Memory 4: LOW — importance 3, accessed 20d ago, rarely used
56
- conn.execute(
57
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at, access_count) VALUES (?, ?, ?, ?, ?, ?)",
58
- ("low value memory", 3, "active", (now - timedelta(days=20)).isoformat(), (now - timedelta(days=120)).isoformat(), 2),
59
- )
60
- # Memory 5: LOWEST — importance 1, accessed 40d ago, never reused
61
- conn.execute(
62
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at, access_count) VALUES (?, ?, ?, ?, ?, ?)",
63
- ("lowest value memory", 1, "active", (now - timedelta(days=40)).isoformat(), (now - timedelta(days=150)).isoformat(), 0),
64
- )
65
- # Memory 6: Warm state (for warm bounds test) — importance 2, stale
66
- conn.execute(
67
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at, access_count) VALUES (?, ?, ?, ?, ?, ?)",
68
- ("warm memory A", 2, "warm", (now - timedelta(days=50)).isoformat(), (now - timedelta(days=200)).isoformat(), 1),
69
- )
70
- # Memory 7: Warm state — importance 4
71
- conn.execute(
72
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at, access_count) VALUES (?, ?, ?, ?, ?, ?)",
73
- ("warm memory B", 4, "warm", (now - timedelta(days=30)).isoformat(), (now - timedelta(days=100)).isoformat(), 3),
74
- )
75
- conn.commit()
76
- conn.close()
77
-
78
- def teardown_method(self):
79
- import shutil
80
- shutil.rmtree(self.tmp_dir, ignore_errors=True)
81
-
82
- def test_no_action_under_limit(self):
83
- """No transitions when counts are within bounds."""
84
- from lifecycle.bounded_growth import BoundedGrowthEnforcer
85
- enforcer = BoundedGrowthEnforcer(self.db_path)
86
- result = enforcer.enforce_bounds()
87
- assert result["enforced"] is False
88
- assert len(result["transitions"]) == 0
89
-
90
- def test_enforce_active_limit(self):
91
- """When active_count > max_active, excess memories transition to warm."""
92
- from lifecycle.bounded_growth import BoundedGrowthEnforcer
93
- config_path = os.path.join(self.tmp_dir, "lifecycle_config.json")
94
- with open(config_path, "w") as f:
95
- json.dump({"bounds": {"max_active": 3, "max_warm": 100}}, f)
96
- enforcer = BoundedGrowthEnforcer(self.db_path, config_path=config_path)
97
- result = enforcer.enforce_bounds()
98
- assert result["enforced"] is True
99
- # 5 active, limit 3 -> 2 should transition
100
- assert len(result["transitions"]) == 2
101
-
102
- def test_lowest_scoring_evicted_first(self):
103
- """The lowest-scoring memories should be the ones transitioned."""
104
- from lifecycle.bounded_growth import BoundedGrowthEnforcer
105
- config_path = os.path.join(self.tmp_dir, "lifecycle_config.json")
106
- with open(config_path, "w") as f:
107
- json.dump({"bounds": {"max_active": 3, "max_warm": 100}}, f)
108
- enforcer = BoundedGrowthEnforcer(self.db_path, config_path=config_path)
109
- result = enforcer.enforce_bounds()
110
- evicted_ids = {t["memory_id"] for t in result["transitions"]}
111
- # Memory 5 (importance 1, stale 40d) and Memory 4 (importance 3, stale 20d)
112
- # should be evicted — lowest scores
113
- assert 5 in evicted_ids
114
- assert 4 in evicted_ids
115
- # Top 3 memories (1, 2, 3) should survive
116
- assert 1 not in evicted_ids
117
- assert 2 not in evicted_ids
118
- assert 3 not in evicted_ids
119
-
120
- def test_evicted_memories_now_warm(self):
121
- """Evicted memories should now be in 'warm' state in the database."""
122
- from lifecycle.bounded_growth import BoundedGrowthEnforcer
123
- config_path = os.path.join(self.tmp_dir, "lifecycle_config.json")
124
- with open(config_path, "w") as f:
125
- json.dump({"bounds": {"max_active": 3, "max_warm": 100}}, f)
126
- enforcer = BoundedGrowthEnforcer(self.db_path, config_path=config_path)
127
- enforcer.enforce_bounds()
128
- conn = sqlite3.connect(self.db_path)
129
- row4 = conn.execute("SELECT lifecycle_state FROM memories WHERE id=4").fetchone()
130
- row5 = conn.execute("SELECT lifecycle_state FROM memories WHERE id=5").fetchone()
131
- conn.close()
132
- assert row4[0] == "warm"
133
- assert row5[0] == "warm"
134
-
135
- def test_enforce_warm_limit(self):
136
- """When warm_count > max_warm, excess warm memories transition to cold."""
137
- from lifecycle.bounded_growth import BoundedGrowthEnforcer
138
- config_path = os.path.join(self.tmp_dir, "lifecycle_config.json")
139
- with open(config_path, "w") as f:
140
- json.dump({"bounds": {"max_active": 100, "max_warm": 1}}, f)
141
- enforcer = BoundedGrowthEnforcer(self.db_path, config_path=config_path)
142
- result = enforcer.enforce_bounds()
143
- assert result["enforced"] is True
144
- # 2 warm (ids 6, 7), limit 1 -> 1 transition
145
- warm_transitions = [t for t in result["transitions"] if t["from_state"] == "warm"]
146
- assert len(warm_transitions) == 1
147
- # Memory 6 (importance 2, stale 50d) should be evicted before Memory 7 (importance 4, stale 30d)
148
- assert warm_transitions[0]["memory_id"] == 6
149
-
150
- def test_score_memory_importance_matters(self):
151
- """Higher importance -> higher score, all else equal."""
152
- from lifecycle.bounded_growth import BoundedGrowthEnforcer
153
- enforcer = BoundedGrowthEnforcer(self.db_path)
154
- scores = enforcer.score_all_memories()
155
- # Memory 1 (importance 9) should score higher than Memory 5 (importance 1)
156
- score_map = {s["memory_id"]: s["score"] for s in scores}
157
- assert score_map[1] > score_map[5]
158
-
159
- def test_score_memory_recency_matters(self):
160
- """More recently accessed -> higher score."""
161
- from lifecycle.bounded_growth import BoundedGrowthEnforcer
162
- enforcer = BoundedGrowthEnforcer(self.db_path)
163
- scores = enforcer.score_all_memories()
164
- score_map = {s["memory_id"]: s["score"] for s in scores}
165
- # Memory 1 (accessed today) should score higher than Memory 3 (accessed 10d ago)
166
- # (both active, Memory 1 also has higher importance, so this should hold)
167
- assert score_map[1] > score_map[3]
168
-
169
- def test_score_all_returns_all_active(self):
170
- """score_all_memories returns scores for all memories in given state."""
171
- from lifecycle.bounded_growth import BoundedGrowthEnforcer
172
- enforcer = BoundedGrowthEnforcer(self.db_path)
173
- scores = enforcer.score_all_memories(state="active")
174
- assert len(scores) == 5 # 5 active memories
175
-
176
- def test_result_structure(self):
177
- """enforce_bounds returns properly structured result dict."""
178
- from lifecycle.bounded_growth import BoundedGrowthEnforcer
179
- enforcer = BoundedGrowthEnforcer(self.db_path)
180
- result = enforcer.enforce_bounds()
181
- assert "enforced" in result
182
- assert "active_count" in result
183
- assert "active_limit" in result
184
- assert "warm_count" in result
185
- assert "warm_limit" in result
186
- assert "transitions" in result
187
- assert isinstance(result["transitions"], list)
188
-
189
- def test_default_bounds(self):
190
- """Default bounds should be max_active=10000, max_warm=5000."""
191
- from lifecycle.bounded_growth import DEFAULT_BOUNDS
192
- assert DEFAULT_BOUNDS["max_active"] == 10000
193
- assert DEFAULT_BOUNDS["max_warm"] == 5000