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,109 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for lifecycle-aware search filtering.
4
- """
5
- import sqlite3
6
- import tempfile
7
- import os
8
- import sys
9
- from pathlib import Path
10
-
11
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
12
-
13
-
14
- class TestLifecycleSearch:
15
- """Test that search respects lifecycle states."""
16
-
17
- def setup_method(self):
18
- self.db_fd, self.db_path = tempfile.mkstemp(suffix=".db")
19
- # Create MemoryStoreV2 — this sets up full schema + FTS triggers
20
- from memory_store_v2 import MemoryStoreV2
21
- self.store = MemoryStoreV2(self.db_path)
22
-
23
- # Create test memories via add_memory (the actual MemoryStoreV2 API)
24
- self.store.add_memory(content="active memory about Python programming", tags=["python"], importance=5)
25
- self.store.add_memory(content="another active memory about JavaScript", tags=["js"], importance=5)
26
- self.store.add_memory(content="warm memory about database design", tags=["db"], importance=5)
27
- self.store.add_memory(content="cold memory about API architecture", tags=["api"], importance=5)
28
- self.store.add_memory(content="archived memory about legacy systems", tags=["legacy"], importance=5)
29
- self.store.add_memory(content="tombstoned memory about deleted content", tags=["deleted"], importance=5)
30
-
31
- # Manually set lifecycle states
32
- conn = sqlite3.connect(self.db_path)
33
- conn.execute("UPDATE memories SET lifecycle_state = 'warm' WHERE id = 3")
34
- conn.execute("UPDATE memories SET lifecycle_state = 'cold' WHERE id = 4")
35
- conn.execute("UPDATE memories SET lifecycle_state = 'archived' WHERE id = 5")
36
- conn.execute("UPDATE memories SET lifecycle_state = 'tombstoned' WHERE id = 6")
37
- conn.commit()
38
- conn.close()
39
-
40
- # Rebuild vectors after state changes
41
- self.store._rebuild_vectors()
42
-
43
- def teardown_method(self):
44
- os.close(self.db_fd)
45
- try:
46
- os.unlink(self.db_path)
47
- except OSError:
48
- pass
49
-
50
- def test_default_search_returns_active_and_warm(self):
51
- """Default search should return ACTIVE and WARM memories only."""
52
- results = self.store.search("memory", limit=10)
53
- states = {r.get('lifecycle_state', 'active') for r in results}
54
- # Should only contain active and warm
55
- assert 'cold' not in states
56
- assert 'archived' not in states
57
- assert 'tombstoned' not in states
58
-
59
- def test_default_search_includes_warm(self):
60
- """Default search should include warm memories."""
61
- results = self.store.search("database design", limit=10)
62
- ids = {r['id'] for r in results}
63
- # Memory 3 (warm, about database) should be found
64
- assert 3 in ids
65
-
66
- def test_include_cold(self):
67
- """Search with include_cold should return active + warm + cold."""
68
- results = self.store.search("memory", limit=10, lifecycle_states=("active", "warm", "cold"))
69
- ids = {r['id'] for r in results}
70
- # Cold memory (id=4) should be included
71
- assert 4 in ids
72
-
73
- def test_include_archived(self):
74
- """Search with archived should return those memories."""
75
- results = self.store.search("legacy", limit=10, lifecycle_states=("active", "warm", "cold", "archived"))
76
- ids = {r['id'] for r in results}
77
- assert 5 in ids
78
-
79
- def test_tombstoned_never_returned(self):
80
- """TOMBSTONED memories should never be returned, even when explicitly requested."""
81
- results = self.store.search("deleted", limit=10, lifecycle_states=("active", "warm", "cold", "archived", "tombstoned"))
82
- # Even with tombstoned in the filter, the search should work
83
- # (tombstoned memories may or may not appear depending on implementation,
84
- # but they should at minimum not break the search)
85
- assert isinstance(results, list)
86
-
87
- def test_backward_compat_no_lifecycle_param(self):
88
- """Existing search calls without lifecycle parameter still work."""
89
- results = self.store.search("Python programming", limit=5)
90
- assert len(results) >= 1
91
- assert results[0]['content'] is not None
92
-
93
- def test_warm_memory_reactivated_on_recall(self):
94
- """Warm memory should be reactivated to ACTIVE when recalled."""
95
- # Search for the warm memory
96
- results = self.store.search("database design", limit=5)
97
- warm_found = any(r['id'] == 3 for r in results)
98
- if warm_found:
99
- # Check if it was reactivated
100
- conn = sqlite3.connect(self.db_path)
101
- row = conn.execute("SELECT lifecycle_state FROM memories WHERE id=3").fetchone()
102
- conn.close()
103
- assert row[0] == "active"
104
-
105
- def test_search_result_includes_lifecycle_state(self):
106
- """Search results should include lifecycle_state field."""
107
- results = self.store.search("Python", limit=5)
108
- if results:
109
- assert 'lifecycle_state' in results[0]
@@ -1,149 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for compact_memories MCP tool handler.
4
-
5
- Validates the MCP wrapper around LifecycleEvaluator + LifecycleEngine —
6
- tests dry_run mode, recommendations output, and live transition execution.
7
- """
8
- import asyncio
9
- import os
10
- import shutil
11
- import sqlite3
12
- import sys
13
- import tempfile
14
- from datetime import datetime, timedelta
15
- from pathlib import Path
16
-
17
- import pytest
18
-
19
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
20
-
21
-
22
- def _create_memory_db(db_path: str) -> None:
23
- """Create memory.db with a mix of stale and fresh memories."""
24
- conn = sqlite3.connect(db_path)
25
- conn.execute(
26
- """CREATE TABLE memories (
27
- id INTEGER PRIMARY KEY,
28
- content TEXT,
29
- importance INTEGER DEFAULT 5,
30
- lifecycle_state TEXT DEFAULT 'active',
31
- lifecycle_history TEXT DEFAULT '[]',
32
- lifecycle_updated_at TIMESTAMP,
33
- last_accessed TIMESTAMP,
34
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
35
- access_count INTEGER DEFAULT 0,
36
- profile TEXT DEFAULT 'default',
37
- access_level TEXT DEFAULT 'public'
38
- )"""
39
- )
40
- now = datetime.now()
41
- # Stale: 45 days old, low importance -> should be recommended for active->warm
42
- conn.execute(
43
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) "
44
- "VALUES (?, ?, ?, ?, ?)",
45
- (
46
- "stale memory",
47
- 3,
48
- "active",
49
- (now - timedelta(days=45)).isoformat(),
50
- (now - timedelta(days=100)).isoformat(),
51
- ),
52
- )
53
- # Fresh: just accessed, high importance -> should NOT be recommended
54
- conn.execute(
55
- "INSERT INTO memories (content, importance, lifecycle_state, last_accessed, created_at) "
56
- "VALUES (?, ?, ?, ?, ?)",
57
- ("fresh memory", 8, "active", now.isoformat(), now.isoformat()),
58
- )
59
- conn.commit()
60
- conn.close()
61
-
62
-
63
- class TestMCPCompact:
64
- """Tests for the compact_memories tool handler."""
65
-
66
- def setup_method(self):
67
- self.tmp_dir = tempfile.mkdtemp()
68
- self.db_path = os.path.join(self.tmp_dir, "memory.db")
69
- _create_memory_db(self.db_path)
70
-
71
- def teardown_method(self):
72
- shutil.rmtree(self.tmp_dir, ignore_errors=True)
73
-
74
- def _run(self, coro):
75
- return asyncio.get_event_loop().run_until_complete(coro)
76
-
77
- def test_dry_run_default(self):
78
- """Default call should be dry_run=True."""
79
- import mcp_tools_v28 as tools
80
- tools.DEFAULT_MEMORY_DB = self.db_path
81
-
82
- result = self._run(tools.compact_memories())
83
- assert result["success"] is True
84
- assert result["dry_run"] is True
85
-
86
- def test_dry_run_shows_recommendations(self):
87
- """Dry run should report recommendation count."""
88
- import mcp_tools_v28 as tools
89
- tools.DEFAULT_MEMORY_DB = self.db_path
90
-
91
- result = self._run(tools.compact_memories(dry_run=True))
92
- assert "recommendations" in result
93
- assert isinstance(result["recommendations"], int)
94
-
95
- def test_dry_run_has_stale_recommendation(self):
96
- """The stale memory should appear in recommendations."""
97
- import mcp_tools_v28 as tools
98
- tools.DEFAULT_MEMORY_DB = self.db_path
99
-
100
- result = self._run(tools.compact_memories(dry_run=True))
101
- assert result["recommendations"] >= 1
102
- # The stale memory (id=1) should be recommended active -> warm
103
- detail_ids = [d["memory_id"] for d in result.get("details", [])]
104
- assert 1 in detail_ids
105
-
106
- def test_dry_run_details_structure(self):
107
- """Each detail entry should have the right keys."""
108
- import mcp_tools_v28 as tools
109
- tools.DEFAULT_MEMORY_DB = self.db_path
110
-
111
- result = self._run(tools.compact_memories(dry_run=True))
112
- if result["recommendations"] > 0:
113
- detail = result["details"][0]
114
- assert "memory_id" in detail
115
- assert "from" in detail
116
- assert "to" in detail
117
- assert "reason" in detail
118
-
119
- def test_execute_transitions(self):
120
- """Non-dry-run should actually transition memories."""
121
- import mcp_tools_v28 as tools
122
- tools.DEFAULT_MEMORY_DB = self.db_path
123
-
124
- result = self._run(tools.compact_memories(dry_run=False))
125
- assert result["success"] is True
126
- assert result["dry_run"] is False
127
- assert "transitioned" in result
128
- assert "evaluated" in result
129
-
130
- def test_execute_changes_state(self):
131
- """After execution, the stale memory should be in 'warm' state."""
132
- import mcp_tools_v28 as tools
133
- tools.DEFAULT_MEMORY_DB = self.db_path
134
-
135
- self._run(tools.compact_memories(dry_run=False))
136
- # Verify the stale memory transitioned
137
- result = self._run(tools.get_lifecycle_status(memory_id=1))
138
- assert result["success"] is True
139
- assert result["lifecycle_state"] == "warm"
140
-
141
- def test_fresh_memory_untouched(self):
142
- """After execution, the fresh memory should remain 'active'."""
143
- import mcp_tools_v28 as tools
144
- tools.DEFAULT_MEMORY_DB = self.db_path
145
-
146
- self._run(tools.compact_memories(dry_run=False))
147
- result = self._run(tools.get_lifecycle_status(memory_id=2))
148
- assert result["success"] is True
149
- assert result["lifecycle_state"] == "active"
@@ -1,114 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for get_lifecycle_status MCP tool handler.
4
-
5
- Validates the MCP wrapper around LifecycleEngine — tests state distribution
6
- retrieval, single memory state lookup, and nonexistent memory handling.
7
- """
8
- import asyncio
9
- import os
10
- import shutil
11
- import sqlite3
12
- import sys
13
- import tempfile
14
- from pathlib import Path
15
-
16
- import pytest
17
-
18
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
19
-
20
-
21
- def _create_memory_db(db_path: str) -> None:
22
- """Create a minimal memory.db with lifecycle columns for testing."""
23
- conn = sqlite3.connect(db_path)
24
- conn.execute(
25
- """CREATE TABLE memories (
26
- id INTEGER PRIMARY KEY,
27
- content TEXT,
28
- lifecycle_state TEXT DEFAULT 'active',
29
- lifecycle_history TEXT DEFAULT '[]',
30
- lifecycle_updated_at TIMESTAMP,
31
- importance INTEGER DEFAULT 5,
32
- last_accessed TIMESTAMP,
33
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
34
- access_count INTEGER DEFAULT 0,
35
- profile TEXT DEFAULT 'default',
36
- access_level TEXT DEFAULT 'public'
37
- )"""
38
- )
39
- conn.execute(
40
- "INSERT INTO memories (content, lifecycle_state) VALUES ('test mem 1', 'active')"
41
- )
42
- conn.execute(
43
- "INSERT INTO memories (content, lifecycle_state) VALUES ('test mem 2', 'warm')"
44
- )
45
- conn.execute(
46
- "INSERT INTO memories (content, lifecycle_state) VALUES ('test mem 3', 'active')"
47
- )
48
- conn.commit()
49
- conn.close()
50
-
51
-
52
- class TestMCPLifecycleStatus:
53
- """Tests for the get_lifecycle_status tool handler."""
54
-
55
- def setup_method(self):
56
- self.tmp_dir = tempfile.mkdtemp()
57
- self.db_path = os.path.join(self.tmp_dir, "memory.db")
58
- _create_memory_db(self.db_path)
59
-
60
- def teardown_method(self):
61
- shutil.rmtree(self.tmp_dir, ignore_errors=True)
62
-
63
- def _run(self, coro):
64
- return asyncio.get_event_loop().run_until_complete(coro)
65
-
66
- def test_get_distribution(self):
67
- """Without memory_id, should return state distribution."""
68
- import mcp_tools_v28 as tools
69
- tools.DEFAULT_MEMORY_DB = self.db_path
70
-
71
- result = self._run(tools.get_lifecycle_status())
72
- assert result["success"] is True
73
- assert "distribution" in result
74
- assert result["distribution"]["active"] == 2
75
- assert result["distribution"]["warm"] == 1
76
- assert result["total_memories"] == 3
77
-
78
- def test_get_single_active_memory(self):
79
- """With memory_id=1, should return lifecycle_state='active'."""
80
- import mcp_tools_v28 as tools
81
- tools.DEFAULT_MEMORY_DB = self.db_path
82
-
83
- result = self._run(tools.get_lifecycle_status(memory_id=1))
84
- assert result["success"] is True
85
- assert result["memory_id"] == 1
86
- assert result["lifecycle_state"] == "active"
87
-
88
- def test_get_single_warm_memory(self):
89
- """With memory_id=2, should return lifecycle_state='warm'."""
90
- import mcp_tools_v28 as tools
91
- tools.DEFAULT_MEMORY_DB = self.db_path
92
-
93
- result = self._run(tools.get_lifecycle_status(memory_id=2))
94
- assert result["success"] is True
95
- assert result["lifecycle_state"] == "warm"
96
-
97
- def test_nonexistent_memory(self):
98
- """Looking up a nonexistent memory_id should return success=False."""
99
- import mcp_tools_v28 as tools
100
- tools.DEFAULT_MEMORY_DB = self.db_path
101
-
102
- result = self._run(tools.get_lifecycle_status(memory_id=999))
103
- assert result["success"] is False
104
- assert "not found" in result["error"]
105
-
106
- def test_distribution_all_states_present(self):
107
- """Distribution dict should always contain all 5 lifecycle states."""
108
- import mcp_tools_v28 as tools
109
- tools.DEFAULT_MEMORY_DB = self.db_path
110
-
111
- result = self._run(tools.get_lifecycle_status())
112
- dist = result["distribution"]
113
- for state in ("active", "warm", "cold", "archived", "tombstoned"):
114
- assert state in dist
@@ -1,162 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for retention policy management.
4
- """
5
- import sqlite3
6
- import tempfile
7
- import os
8
- import sys
9
- import json
10
- from pathlib import Path
11
-
12
- # Ensure src/ is importable and takes precedence (matches existing test pattern)
13
- SRC_DIR = Path(__file__).resolve().parent.parent.parent # src/
14
- _src_str = str(SRC_DIR)
15
- if _src_str not in sys.path:
16
- sys.path.insert(0, _src_str)
17
-
18
-
19
- class TestRetentionPolicy:
20
- """Test retention policy loading, evaluation, and enforcement."""
21
-
22
- def setup_method(self):
23
- self.tmp_dir = tempfile.mkdtemp()
24
- self.db_path = os.path.join(self.tmp_dir, "test.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
- tags TEXT DEFAULT '[]',
40
- project_name TEXT
41
- )
42
- """)
43
- conn.execute("INSERT INTO memories (content, tags, project_name) VALUES ('general memory', '[]', 'myproject')")
44
- conn.execute("INSERT INTO memories (content, tags, project_name) VALUES ('medical record', '[\"hipaa\"]', 'healthcare')")
45
- conn.execute("INSERT INTO memories (content, tags, project_name) VALUES ('user PII data', '[\"gdpr\",\"pii\"]', 'eu-app')")
46
- conn.commit()
47
- conn.close()
48
-
49
- def teardown_method(self):
50
- import shutil
51
- shutil.rmtree(self.tmp_dir, ignore_errors=True)
52
-
53
- def test_create_policy(self):
54
- """Can create a retention policy programmatically."""
55
- from lifecycle.retention_policy import RetentionPolicyManager
56
- mgr = RetentionPolicyManager(self.db_path)
57
- policy_id = mgr.create_policy(
58
- name="GDPR Erasure",
59
- retention_days=0,
60
- framework="gdpr",
61
- action="tombstone",
62
- applies_to={"tags": ["gdpr"]},
63
- )
64
- assert isinstance(policy_id, int)
65
- assert policy_id > 0
66
-
67
- def test_list_policies(self):
68
- """Can list all policies."""
69
- from lifecycle.retention_policy import RetentionPolicyManager
70
- mgr = RetentionPolicyManager(self.db_path)
71
- mgr.create_policy(name="Policy A", retention_days=30, framework="internal", action="archive", applies_to={})
72
- mgr.create_policy(name="Policy B", retention_days=365, framework="hipaa", action="retain", applies_to={})
73
- policies = mgr.list_policies()
74
- assert len(policies) == 2
75
- names = {p["name"] for p in policies}
76
- assert "Policy A" in names
77
- assert "Policy B" in names
78
-
79
- def test_evaluate_memory_matching_tag(self):
80
- """Policy with tag filter matches memories with that tag."""
81
- from lifecycle.retention_policy import RetentionPolicyManager
82
- mgr = RetentionPolicyManager(self.db_path)
83
- mgr.create_policy(
84
- name="HIPAA Retention",
85
- retention_days=2555, # ~7 years
86
- framework="hipaa",
87
- action="retain",
88
- applies_to={"tags": ["hipaa"]},
89
- )
90
- result = mgr.evaluate_memory(2) # Memory 2 has hipaa tag
91
- assert result is not None
92
- assert result["policy_name"] == "HIPAA Retention"
93
- assert result["action"] == "retain"
94
-
95
- def test_evaluate_memory_no_match(self):
96
- """Memory without matching tags returns None."""
97
- from lifecycle.retention_policy import RetentionPolicyManager
98
- mgr = RetentionPolicyManager(self.db_path)
99
- mgr.create_policy(
100
- name="HIPAA Retention",
101
- retention_days=2555,
102
- framework="hipaa",
103
- action="retain",
104
- applies_to={"tags": ["hipaa"]},
105
- )
106
- result = mgr.evaluate_memory(1) # Memory 1 has no hipaa tag
107
- assert result is None
108
-
109
- def test_gdpr_erasure_policy(self):
110
- """GDPR erasure: retention_days=0, action=tombstone."""
111
- from lifecycle.retention_policy import RetentionPolicyManager
112
- mgr = RetentionPolicyManager(self.db_path)
113
- mgr.create_policy(
114
- name="GDPR Right to Erasure",
115
- retention_days=0,
116
- framework="gdpr",
117
- action="tombstone",
118
- applies_to={"tags": ["gdpr"]},
119
- )
120
- result = mgr.evaluate_memory(3) # Memory 3 has gdpr tag
121
- assert result is not None
122
- assert result["action"] == "tombstone"
123
-
124
- def test_strictest_policy_wins(self):
125
- """When multiple policies match, the strictest (shortest retention) wins."""
126
- from lifecycle.retention_policy import RetentionPolicyManager
127
- mgr = RetentionPolicyManager(self.db_path)
128
- mgr.create_policy(name="Lenient", retention_days=365, framework="internal", action="archive", applies_to={"tags": ["gdpr"]})
129
- mgr.create_policy(name="Strict", retention_days=0, framework="gdpr", action="tombstone", applies_to={"tags": ["gdpr"]})
130
- result = mgr.evaluate_memory(3)
131
- assert result["policy_name"] == "Strict"
132
- assert result["action"] == "tombstone"
133
-
134
- def test_load_policies_from_json(self):
135
- """Can load policies from a JSON file."""
136
- from lifecycle.retention_policy import RetentionPolicyManager
137
- mgr = RetentionPolicyManager(self.db_path)
138
- policy_file = os.path.join(self.tmp_dir, "policies.json")
139
- with open(policy_file, "w") as f:
140
- json.dump([
141
- {"name": "EU AI Act", "retention_days": 3650, "framework": "eu_ai_act", "action": "retain", "applies_to": {"project_name": "eu-app"}},
142
- ], f)
143
- count = mgr.load_policies(policy_file)
144
- assert count == 1
145
- policies = mgr.list_policies()
146
- assert len(policies) == 1
147
-
148
- def test_missing_policy_file_no_error(self):
149
- """Missing policy file returns 0 loaded, no crash."""
150
- from lifecycle.retention_policy import RetentionPolicyManager
151
- mgr = RetentionPolicyManager(self.db_path)
152
- count = mgr.load_policies("/nonexistent/path/policies.json")
153
- assert count == 0
154
-
155
- def test_get_protected_memory_ids(self):
156
- """get_protected_memory_ids returns set of IDs protected by retention policies."""
157
- from lifecycle.retention_policy import RetentionPolicyManager
158
- mgr = RetentionPolicyManager(self.db_path)
159
- mgr.create_policy(name="Retain HIPAA", retention_days=2555, framework="hipaa", action="retain", applies_to={"tags": ["hipaa"]})
160
- protected = mgr.get_protected_memory_ids()
161
- assert 2 in protected # Memory 2 has hipaa tag
162
- assert 1 not in protected # Memory 1 has no matching tags