superlocalmemory 2.8.6 → 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 (431) hide show
  1. package/LICENSE +9 -1
  2. package/NOTICE +63 -0
  3. package/README.md +165 -480
  4. package/bin/slm +17 -449
  5. package/bin/slm-npm +1 -1
  6. package/conftest.py +5 -0
  7. package/docs/api-reference.md +284 -0
  8. package/docs/architecture.md +149 -0
  9. package/docs/auto-memory.md +150 -0
  10. package/docs/cli-reference.md +276 -0
  11. package/docs/compliance.md +191 -0
  12. package/docs/configuration.md +182 -0
  13. package/docs/getting-started.md +102 -0
  14. package/docs/ide-setup.md +261 -0
  15. package/docs/mcp-tools.md +220 -0
  16. package/docs/migration-from-v2.md +170 -0
  17. package/docs/profiles.md +173 -0
  18. package/docs/troubleshooting.md +310 -0
  19. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  20. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  21. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  22. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  23. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  24. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  25. package/ide/configs/cursor-mcp.json +15 -0
  26. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  27. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  28. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  29. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  30. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  31. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  32. package/{configs → ide/configs}/zed-mcp.json +2 -2
  33. package/{hooks → ide/hooks}/context-hook.js +9 -20
  34. package/ide/hooks/memory-list-skill.js +70 -0
  35. package/ide/hooks/memory-profile-skill.js +101 -0
  36. package/ide/hooks/memory-recall-skill.js +62 -0
  37. package/ide/hooks/memory-remember-skill.js +68 -0
  38. package/ide/hooks/memory-reset-skill.js +160 -0
  39. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  40. package/ide/integrations/langchain/README.md +106 -0
  41. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  42. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  43. package/ide/integrations/langchain/pyproject.toml +38 -0
  44. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  45. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  46. package/ide/integrations/langchain/tests/test_security.py +117 -0
  47. package/ide/integrations/llamaindex/README.md +81 -0
  48. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  49. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  50. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  51. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  52. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  53. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  54. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  55. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  56. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  57. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  58. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  59. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  60. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  61. package/package.json +13 -22
  62. package/pyproject.toml +85 -0
  63. package/scripts/build-dmg.sh +417 -0
  64. package/scripts/install-skills.ps1 +334 -0
  65. package/scripts/postinstall.js +2 -2
  66. package/scripts/start-dashboard.ps1 +52 -0
  67. package/scripts/start-dashboard.sh +41 -0
  68. package/scripts/sync-wiki.ps1 +127 -0
  69. package/scripts/sync-wiki.sh +82 -0
  70. package/scripts/test-dmg.sh +161 -0
  71. package/scripts/test-npm-package.ps1 +252 -0
  72. package/scripts/test-npm-package.sh +207 -0
  73. package/scripts/verify-install.ps1 +294 -0
  74. package/scripts/verify-install.sh +266 -0
  75. package/src/superlocalmemory/__init__.py +0 -0
  76. package/src/superlocalmemory/attribution/__init__.py +9 -0
  77. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  78. package/src/superlocalmemory/attribution/signer.py +153 -0
  79. package/src/superlocalmemory/attribution/watermark.py +189 -0
  80. package/src/superlocalmemory/cli/__init__.py +5 -0
  81. package/src/superlocalmemory/cli/commands.py +245 -0
  82. package/src/superlocalmemory/cli/main.py +89 -0
  83. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  84. package/src/superlocalmemory/cli/post_install.py +99 -0
  85. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  86. package/src/superlocalmemory/compliance/__init__.py +0 -0
  87. package/src/superlocalmemory/compliance/abac.py +204 -0
  88. package/src/superlocalmemory/compliance/audit.py +314 -0
  89. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  90. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  91. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  92. package/src/superlocalmemory/compliance/retention.py +232 -0
  93. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  94. package/src/superlocalmemory/core/__init__.py +0 -0
  95. package/src/superlocalmemory/core/config.py +391 -0
  96. package/src/superlocalmemory/core/embeddings.py +293 -0
  97. package/src/superlocalmemory/core/engine.py +701 -0
  98. package/src/superlocalmemory/core/hooks.py +65 -0
  99. package/src/superlocalmemory/core/maintenance.py +172 -0
  100. package/src/superlocalmemory/core/modes.py +140 -0
  101. package/src/superlocalmemory/core/profiles.py +234 -0
  102. package/src/superlocalmemory/core/registry.py +117 -0
  103. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  104. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  105. package/src/superlocalmemory/encoding/__init__.py +0 -0
  106. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  107. package/src/superlocalmemory/encoding/emotional.py +125 -0
  108. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  109. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  110. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  111. package/src/superlocalmemory/encoding/foresight.py +91 -0
  112. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  113. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  114. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  115. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  116. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  117. package/src/superlocalmemory/encoding/type_router.py +235 -0
  118. package/src/superlocalmemory/hooks/__init__.py +3 -0
  119. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  120. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  121. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  122. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  123. package/src/superlocalmemory/infra/__init__.py +3 -0
  124. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  125. package/src/superlocalmemory/infra/backup.py +317 -0
  126. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  127. package/src/superlocalmemory/infra/event_bus.py +381 -0
  128. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  129. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  130. package/src/superlocalmemory/learning/__init__.py +0 -0
  131. package/src/superlocalmemory/learning/adaptive.py +172 -0
  132. package/src/superlocalmemory/learning/behavioral.py +490 -0
  133. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  134. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  135. package/src/superlocalmemory/learning/cross_project.py +399 -0
  136. package/src/superlocalmemory/learning/database.py +376 -0
  137. package/src/superlocalmemory/learning/engagement.py +323 -0
  138. package/src/superlocalmemory/learning/features.py +138 -0
  139. package/src/superlocalmemory/learning/feedback.py +316 -0
  140. package/src/superlocalmemory/learning/outcomes.py +255 -0
  141. package/src/superlocalmemory/learning/project_context.py +366 -0
  142. package/src/superlocalmemory/learning/ranker.py +155 -0
  143. package/src/superlocalmemory/learning/source_quality.py +303 -0
  144. package/src/superlocalmemory/learning/workflows.py +309 -0
  145. package/src/superlocalmemory/llm/__init__.py +0 -0
  146. package/src/superlocalmemory/llm/backbone.py +316 -0
  147. package/src/superlocalmemory/math/__init__.py +0 -0
  148. package/src/superlocalmemory/math/fisher.py +356 -0
  149. package/src/superlocalmemory/math/langevin.py +398 -0
  150. package/src/superlocalmemory/math/sheaf.py +257 -0
  151. package/src/superlocalmemory/mcp/__init__.py +0 -0
  152. package/src/superlocalmemory/mcp/resources.py +245 -0
  153. package/src/superlocalmemory/mcp/server.py +61 -0
  154. package/src/superlocalmemory/mcp/tools.py +18 -0
  155. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  156. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  157. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  158. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  159. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  160. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  161. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  162. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  163. package/src/superlocalmemory/retrieval/engine.py +390 -0
  164. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  165. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  166. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  167. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  168. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  169. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  170. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  171. package/src/superlocalmemory/server/__init__.py +1 -0
  172. package/src/superlocalmemory/server/api.py +248 -0
  173. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  174. package/src/superlocalmemory/server/routes/agents.py +107 -0
  175. package/src/superlocalmemory/server/routes/backup.py +91 -0
  176. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  177. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  178. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  179. package/src/superlocalmemory/server/routes/events.py +183 -0
  180. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  181. package/src/superlocalmemory/server/routes/learning.py +273 -0
  182. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  183. package/src/superlocalmemory/server/routes/memories.py +399 -0
  184. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  185. package/src/superlocalmemory/server/routes/stats.py +346 -0
  186. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  187. package/src/superlocalmemory/server/routes/ws.py +82 -0
  188. package/src/superlocalmemory/server/security_middleware.py +57 -0
  189. package/src/superlocalmemory/server/ui.py +245 -0
  190. package/src/superlocalmemory/storage/__init__.py +0 -0
  191. package/src/superlocalmemory/storage/access_control.py +182 -0
  192. package/src/superlocalmemory/storage/database.py +594 -0
  193. package/src/superlocalmemory/storage/migrations.py +303 -0
  194. package/src/superlocalmemory/storage/models.py +406 -0
  195. package/src/superlocalmemory/storage/schema.py +726 -0
  196. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  197. package/src/superlocalmemory/trust/__init__.py +0 -0
  198. package/src/superlocalmemory/trust/gate.py +130 -0
  199. package/src/superlocalmemory/trust/provenance.py +124 -0
  200. package/src/superlocalmemory/trust/scorer.py +347 -0
  201. package/src/superlocalmemory/trust/signals.py +153 -0
  202. package/ui/index.html +278 -5
  203. package/ui/js/auto-settings.js +70 -0
  204. package/ui/js/dashboard.js +90 -0
  205. package/ui/js/fact-detail.js +92 -0
  206. package/ui/js/feedback.js +2 -2
  207. package/ui/js/ide-status.js +102 -0
  208. package/ui/js/math-health.js +98 -0
  209. package/ui/js/recall-lab.js +127 -0
  210. package/ui/js/settings.js +2 -2
  211. package/ui/js/trust-dashboard.js +73 -0
  212. package/api_server.py +0 -724
  213. package/bin/aider-smart +0 -72
  214. package/bin/superlocalmemoryv2-learning +0 -4
  215. package/bin/superlocalmemoryv2-list +0 -3
  216. package/bin/superlocalmemoryv2-patterns +0 -4
  217. package/bin/superlocalmemoryv2-profile +0 -3
  218. package/bin/superlocalmemoryv2-recall +0 -3
  219. package/bin/superlocalmemoryv2-remember +0 -3
  220. package/bin/superlocalmemoryv2-reset +0 -3
  221. package/bin/superlocalmemoryv2-status +0 -3
  222. package/configs/chatgpt-desktop-mcp.json +0 -16
  223. package/configs/cursor-mcp.json +0 -15
  224. package/hooks/memory-list-skill.js +0 -139
  225. package/hooks/memory-profile-skill.js +0 -273
  226. package/hooks/memory-recall-skill.js +0 -114
  227. package/hooks/memory-remember-skill.js +0 -127
  228. package/hooks/memory-reset-skill.js +0 -274
  229. package/mcp_server.py +0 -1808
  230. package/requirements-core.txt +0 -22
  231. package/requirements-learning.txt +0 -12
  232. package/requirements.txt +0 -12
  233. package/src/agent_registry.py +0 -411
  234. package/src/auth_middleware.py +0 -61
  235. package/src/auto_backup.py +0 -459
  236. package/src/behavioral/__init__.py +0 -49
  237. package/src/behavioral/behavioral_listener.py +0 -203
  238. package/src/behavioral/behavioral_patterns.py +0 -275
  239. package/src/behavioral/cross_project_transfer.py +0 -206
  240. package/src/behavioral/outcome_inference.py +0 -194
  241. package/src/behavioral/outcome_tracker.py +0 -193
  242. package/src/behavioral/tests/__init__.py +0 -4
  243. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  244. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  245. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  246. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  247. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  248. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  249. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  250. package/src/cache_manager.py +0 -518
  251. package/src/compliance/__init__.py +0 -48
  252. package/src/compliance/abac_engine.py +0 -149
  253. package/src/compliance/abac_middleware.py +0 -116
  254. package/src/compliance/audit_db.py +0 -215
  255. package/src/compliance/audit_logger.py +0 -148
  256. package/src/compliance/retention_manager.py +0 -289
  257. package/src/compliance/retention_scheduler.py +0 -186
  258. package/src/compliance/tests/__init__.py +0 -4
  259. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  260. package/src/compliance/tests/test_abac_engine.py +0 -124
  261. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  262. package/src/compliance/tests/test_audit_db.py +0 -123
  263. package/src/compliance/tests/test_audit_logger.py +0 -98
  264. package/src/compliance/tests/test_mcp_audit.py +0 -128
  265. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  266. package/src/compliance/tests/test_retention_manager.py +0 -131
  267. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  268. package/src/compression/__init__.py +0 -25
  269. package/src/compression/cli.py +0 -150
  270. package/src/compression/cold_storage.py +0 -217
  271. package/src/compression/config.py +0 -72
  272. package/src/compression/orchestrator.py +0 -133
  273. package/src/compression/tier2_compressor.py +0 -228
  274. package/src/compression/tier3_compressor.py +0 -153
  275. package/src/compression/tier_classifier.py +0 -148
  276. package/src/db_connection_manager.py +0 -536
  277. package/src/embedding_engine.py +0 -63
  278. package/src/embeddings/__init__.py +0 -47
  279. package/src/embeddings/cache.py +0 -70
  280. package/src/embeddings/cli.py +0 -113
  281. package/src/embeddings/constants.py +0 -47
  282. package/src/embeddings/database.py +0 -91
  283. package/src/embeddings/engine.py +0 -247
  284. package/src/embeddings/model_loader.py +0 -145
  285. package/src/event_bus.py +0 -562
  286. package/src/graph/__init__.py +0 -36
  287. package/src/graph/build_helpers.py +0 -74
  288. package/src/graph/cli.py +0 -87
  289. package/src/graph/cluster_builder.py +0 -188
  290. package/src/graph/cluster_summary.py +0 -148
  291. package/src/graph/constants.py +0 -47
  292. package/src/graph/edge_builder.py +0 -162
  293. package/src/graph/entity_extractor.py +0 -95
  294. package/src/graph/graph_core.py +0 -226
  295. package/src/graph/graph_search.py +0 -231
  296. package/src/graph/hierarchical.py +0 -207
  297. package/src/graph/schema.py +0 -99
  298. package/src/graph_engine.py +0 -52
  299. package/src/hnsw_index.py +0 -628
  300. package/src/hybrid_search.py +0 -46
  301. package/src/learning/__init__.py +0 -217
  302. package/src/learning/adaptive_ranker.py +0 -682
  303. package/src/learning/bootstrap/__init__.py +0 -69
  304. package/src/learning/bootstrap/constants.py +0 -93
  305. package/src/learning/bootstrap/db_queries.py +0 -316
  306. package/src/learning/bootstrap/sampling.py +0 -82
  307. package/src/learning/bootstrap/text_utils.py +0 -71
  308. package/src/learning/cross_project_aggregator.py +0 -857
  309. package/src/learning/db/__init__.py +0 -40
  310. package/src/learning/db/constants.py +0 -44
  311. package/src/learning/db/schema.py +0 -279
  312. package/src/learning/engagement_tracker.py +0 -628
  313. package/src/learning/feature_extractor.py +0 -708
  314. package/src/learning/feedback_collector.py +0 -806
  315. package/src/learning/learning_db.py +0 -915
  316. package/src/learning/project_context_manager.py +0 -572
  317. package/src/learning/ranking/__init__.py +0 -33
  318. package/src/learning/ranking/constants.py +0 -84
  319. package/src/learning/ranking/helpers.py +0 -278
  320. package/src/learning/source_quality_scorer.py +0 -676
  321. package/src/learning/synthetic_bootstrap.py +0 -755
  322. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  323. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  324. package/src/learning/tests/test_aggregator.py +0 -306
  325. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  326. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  327. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  328. package/src/learning/tests/test_feedback_collector.py +0 -294
  329. package/src/learning/tests/test_learning_db.py +0 -602
  330. package/src/learning/tests/test_learning_db_v28.py +0 -110
  331. package/src/learning/tests/test_learning_init_v28.py +0 -48
  332. package/src/learning/tests/test_outcome_signals.py +0 -48
  333. package/src/learning/tests/test_project_context.py +0 -292
  334. package/src/learning/tests/test_schema_migration.py +0 -319
  335. package/src/learning/tests/test_signal_inference.py +0 -397
  336. package/src/learning/tests/test_source_quality.py +0 -351
  337. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  338. package/src/learning/tests/test_workflow_miner.py +0 -318
  339. package/src/learning/workflow_pattern_miner.py +0 -655
  340. package/src/lifecycle/__init__.py +0 -54
  341. package/src/lifecycle/bounded_growth.py +0 -239
  342. package/src/lifecycle/compaction_engine.py +0 -226
  343. package/src/lifecycle/lifecycle_engine.py +0 -355
  344. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  345. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  346. package/src/lifecycle/retention_policy.py +0 -285
  347. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  348. package/src/lifecycle/tests/test_compaction.py +0 -179
  349. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  350. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  351. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  352. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  353. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  354. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  355. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  356. package/src/mcp_tools_v28.py +0 -281
  357. package/src/memory/__init__.py +0 -36
  358. package/src/memory/cli.py +0 -205
  359. package/src/memory/constants.py +0 -39
  360. package/src/memory/helpers.py +0 -28
  361. package/src/memory/schema.py +0 -166
  362. package/src/memory-profiles.py +0 -595
  363. package/src/memory-reset.py +0 -491
  364. package/src/memory_compression.py +0 -989
  365. package/src/memory_store_v2.py +0 -1155
  366. package/src/migrate_v1_to_v2.py +0 -629
  367. package/src/pattern_learner.py +0 -34
  368. package/src/patterns/__init__.py +0 -24
  369. package/src/patterns/analyzers.py +0 -251
  370. package/src/patterns/learner.py +0 -271
  371. package/src/patterns/scoring.py +0 -171
  372. package/src/patterns/store.py +0 -225
  373. package/src/patterns/terminology.py +0 -140
  374. package/src/provenance_tracker.py +0 -312
  375. package/src/qualixar_attribution.py +0 -139
  376. package/src/qualixar_watermark.py +0 -78
  377. package/src/query_optimizer.py +0 -511
  378. package/src/rate_limiter.py +0 -83
  379. package/src/search/__init__.py +0 -20
  380. package/src/search/cli.py +0 -77
  381. package/src/search/constants.py +0 -26
  382. package/src/search/engine.py +0 -241
  383. package/src/search/fusion.py +0 -122
  384. package/src/search/index_loader.py +0 -114
  385. package/src/search/methods.py +0 -162
  386. package/src/search_engine_v2.py +0 -401
  387. package/src/setup_validator.py +0 -482
  388. package/src/subscription_manager.py +0 -391
  389. package/src/tree/__init__.py +0 -59
  390. package/src/tree/builder.py +0 -185
  391. package/src/tree/nodes.py +0 -202
  392. package/src/tree/queries.py +0 -257
  393. package/src/tree/schema.py +0 -80
  394. package/src/tree_manager.py +0 -19
  395. package/src/trust/__init__.py +0 -45
  396. package/src/trust/constants.py +0 -66
  397. package/src/trust/queries.py +0 -157
  398. package/src/trust/schema.py +0 -95
  399. package/src/trust/scorer.py +0 -299
  400. package/src/trust/signals.py +0 -95
  401. package/src/trust_scorer.py +0 -44
  402. package/ui/app.js +0 -1588
  403. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  404. package/ui/js/graph-cytoscape.js +0 -1168
  405. package/ui/js/graph-d3-backup.js +0 -32
  406. package/ui/js/graph.js +0 -32
  407. package/ui_server.py +0 -286
  408. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  409. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  410. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  411. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  412. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  413. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  414. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  415. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  416. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  417. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  418. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  419. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  420. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  421. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  422. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  423. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  424. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  425. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  426. /package/{completions → ide/completions}/slm.bash +0 -0
  427. /package/{completions → ide/completions}/slm.zsh +0 -0
  428. /package/{configs → ide/configs}/cody-commands.json +0 -0
  429. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
  430. /package/{install.ps1 → scripts/install.ps1} +0 -0
  431. /package/{install.sh → scripts/install.sh} +0 -0
@@ -1,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