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,110 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for v2.8 learning.db schema extensions — outcome and behavioral tables.
4
- """
5
- import pytest
6
- import json
7
- import sys
8
- import os
9
- from pathlib import Path
10
-
11
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
12
-
13
-
14
- @pytest.fixture(autouse=True)
15
- def reset_singleton():
16
- from learning.learning_db import LearningDB
17
- LearningDB.reset_instance()
18
- yield
19
- LearningDB.reset_instance()
20
-
21
-
22
- @pytest.fixture
23
- def learning_db(tmp_path):
24
- from learning.learning_db import LearningDB
25
- db_path = tmp_path / "learning.db"
26
- return LearningDB(db_path=db_path)
27
-
28
-
29
- class TestActionOutcomesTable:
30
- def test_table_exists(self, learning_db):
31
- conn = learning_db._get_connection()
32
- tables = [r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()]
33
- conn.close()
34
- assert "action_outcomes" in tables
35
-
36
- def test_store_outcome(self, learning_db):
37
- oid = learning_db.store_outcome([1, 2], "success", action_type="code_written", project="myproject")
38
- assert isinstance(oid, int)
39
- assert oid > 0
40
-
41
- def test_get_outcomes(self, learning_db):
42
- learning_db.store_outcome([1], "success", project="proj1")
43
- learning_db.store_outcome([2], "failure", project="proj1")
44
- results = learning_db.get_outcomes(project="proj1")
45
- assert len(results) == 2
46
-
47
- def test_get_outcomes_by_memory_id(self, learning_db):
48
- learning_db.store_outcome([1, 2], "success")
49
- learning_db.store_outcome([3], "failure")
50
- results = learning_db.get_outcomes(memory_id=1)
51
- assert len(results) == 1
52
- assert 1 in results[0]["memory_ids"]
53
-
54
- def test_outcome_has_profile(self, learning_db):
55
- learning_db.store_outcome([1], "success")
56
- results = learning_db.get_outcomes()
57
- assert results[0]["profile"] == "default"
58
-
59
-
60
- class TestBehavioralPatternsTable:
61
- def test_table_exists(self, learning_db):
62
- conn = learning_db._get_connection()
63
- tables = [r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()]
64
- conn.close()
65
- assert "behavioral_patterns" in tables
66
-
67
- def test_store_pattern(self, learning_db):
68
- pid = learning_db.store_behavioral_pattern("tag_success", "python", success_rate=0.85, evidence_count=10, confidence=0.8)
69
- assert isinstance(pid, int)
70
-
71
- def test_get_patterns(self, learning_db):
72
- learning_db.store_behavioral_pattern("tag_success", "python", confidence=0.8)
73
- learning_db.store_behavioral_pattern("tag_success", "javascript", confidence=0.6)
74
- results = learning_db.get_behavioral_patterns(pattern_type="tag_success")
75
- assert len(results) == 2
76
-
77
- def test_get_patterns_min_confidence(self, learning_db):
78
- learning_db.store_behavioral_pattern("tag_success", "python", confidence=0.8)
79
- learning_db.store_behavioral_pattern("tag_success", "javascript", confidence=0.3)
80
- results = learning_db.get_behavioral_patterns(min_confidence=0.5)
81
- assert len(results) == 1
82
- assert results[0]["pattern_key"] == "python"
83
-
84
-
85
- class TestCrossProjectTable:
86
- def test_table_exists(self, learning_db):
87
- conn = learning_db._get_connection()
88
- tables = [r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()]
89
- conn.close()
90
- assert "cross_project_behaviors" in tables
91
-
92
- def test_store_transfer(self, learning_db):
93
- pid = learning_db.store_behavioral_pattern("tag_success", "python", confidence=0.8)
94
- tid = learning_db.store_cross_project("project_a", "project_b", pid, confidence=0.7)
95
- assert isinstance(tid, int)
96
-
97
- def test_get_transfers(self, learning_db):
98
- pid = learning_db.store_behavioral_pattern("tag_success", "python", confidence=0.8)
99
- learning_db.store_cross_project("proj_a", "proj_b", pid, confidence=0.7)
100
- results = learning_db.get_cross_project_transfers(source_project="proj_a")
101
- assert len(results) == 1
102
- assert results[0]["target_project"] == "proj_b"
103
-
104
- def test_existing_tables_untouched(self, learning_db):
105
- """Existing 6 tables should still exist."""
106
- conn = learning_db._get_connection()
107
- tables = {r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()}
108
- conn.close()
109
- for expected in ["transferable_patterns", "workflow_patterns", "ranking_feedback", "ranking_models", "source_quality", "engagement_metrics"]:
110
- assert expected in tables
@@ -1,48 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for unified learning status with v2.8 engines.
4
- """
5
- import sys
6
- from pathlib import Path
7
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
8
-
9
-
10
- class TestUnifiedLearningStatus:
11
- def test_get_status_has_v28_engines(self):
12
- """Status should include lifecycle, behavioral, compliance info."""
13
- from learning import get_status
14
- status = get_status()
15
- assert "v28_engines" in status
16
-
17
- def test_v28_engines_structure(self):
18
- from learning import get_status
19
- status = get_status()
20
- engines = status["v28_engines"]
21
- assert "lifecycle" in engines
22
- assert "behavioral" in engines
23
- assert "compliance" in engines
24
-
25
- def test_lifecycle_status_included(self):
26
- from learning import get_status
27
- status = get_status()
28
- lifecycle = status["v28_engines"]["lifecycle"]
29
- assert "available" in lifecycle
30
-
31
- def test_behavioral_status_included(self):
32
- from learning import get_status
33
- status = get_status()
34
- behavioral = status["v28_engines"]["behavioral"]
35
- assert "available" in behavioral
36
-
37
- def test_compliance_status_included(self):
38
- from learning import get_status
39
- status = get_status()
40
- compliance = status["v28_engines"]["compliance"]
41
- assert "available" in compliance
42
-
43
- def test_graceful_when_engines_unavailable(self):
44
- """Status should not crash even if engine imports fail."""
45
- from learning import get_status
46
- status = get_status()
47
- # Should always return a dict with v28_engines
48
- assert isinstance(status["v28_engines"], dict)
@@ -1,48 +0,0 @@
1
- # SPDX-License-Identifier: MIT
2
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
3
- """Tests for v2.8 outcome signal types in feedback collector.
4
- """
5
- import pytest
6
- import sys
7
- from pathlib import Path
8
-
9
- sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
10
-
11
-
12
- class TestOutcomeSignalTypes:
13
- """Verify outcome signal types are registered and have correct values."""
14
-
15
- def test_outcome_success_registered(self):
16
- from learning.feedback_collector import FeedbackCollector
17
- assert "outcome_success" in FeedbackCollector.SIGNAL_VALUES
18
- assert FeedbackCollector.SIGNAL_VALUES["outcome_success"] == 1.0
19
-
20
- def test_outcome_partial_registered(self):
21
- from learning.feedback_collector import FeedbackCollector
22
- assert "outcome_partial" in FeedbackCollector.SIGNAL_VALUES
23
- assert FeedbackCollector.SIGNAL_VALUES["outcome_partial"] == 0.5
24
-
25
- def test_outcome_failure_registered(self):
26
- from learning.feedback_collector import FeedbackCollector
27
- assert "outcome_failure" in FeedbackCollector.SIGNAL_VALUES
28
- assert FeedbackCollector.SIGNAL_VALUES["outcome_failure"] == 0.0
29
-
30
- def test_outcome_retry_registered(self):
31
- from learning.feedback_collector import FeedbackCollector
32
- assert "outcome_retry" in FeedbackCollector.SIGNAL_VALUES
33
- assert FeedbackCollector.SIGNAL_VALUES["outcome_retry"] == 0.2
34
-
35
- def test_existing_signals_unchanged(self):
36
- """All 17 original signal types still present with correct values."""
37
- from learning.feedback_collector import FeedbackCollector
38
- SV = FeedbackCollector.SIGNAL_VALUES
39
- assert SV["mcp_used_high"] == 1.0
40
- assert SV["dashboard_thumbs_up"] == 1.0
41
- assert SV["implicit_positive_timegap"] == 0.6
42
- assert SV["passive_decay"] == 0.0
43
- assert len(SV) == 21 # 17 original + 4 new
44
-
45
- def test_total_signal_count(self):
46
- """Should have exactly 21 signal types (17 + 4)."""
47
- from learning.feedback_collector import FeedbackCollector
48
- assert len(FeedbackCollector.SIGNAL_VALUES) == 21
@@ -1,292 +0,0 @@
1
- #!/usr/bin/env python3
2
- # SPDX-License-Identifier: MIT
3
- # Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
4
- import sqlite3
5
- from pathlib import Path
6
-
7
- import pytest
8
-
9
-
10
- # ---------------------------------------------------------------------------
11
- # Fixtures
12
- # ---------------------------------------------------------------------------
13
-
14
- @pytest.fixture
15
- def memory_db(tmp_path):
16
- """Create a minimal memory.db with the required schema."""
17
- db_path = tmp_path / "memory.db"
18
- conn = sqlite3.connect(str(db_path))
19
- cursor = conn.cursor()
20
- cursor.execute('''
21
- CREATE TABLE IF NOT EXISTS memories (
22
- id INTEGER PRIMARY KEY AUTOINCREMENT,
23
- content TEXT NOT NULL,
24
- summary TEXT,
25
- project_path TEXT,
26
- project_name TEXT,
27
- tags TEXT DEFAULT '[]',
28
- category TEXT,
29
- parent_id INTEGER,
30
- tree_path TEXT DEFAULT '/',
31
- depth INTEGER DEFAULT 0,
32
- memory_type TEXT DEFAULT 'session',
33
- importance INTEGER DEFAULT 5,
34
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
35
- last_accessed TIMESTAMP,
36
- access_count INTEGER DEFAULT 0,
37
- content_hash TEXT,
38
- cluster_id INTEGER,
39
- profile TEXT DEFAULT 'default',
40
- created_by TEXT,
41
- source_protocol TEXT,
42
- trust_score REAL DEFAULT 1.0
43
- )
44
- ''')
45
- conn.commit()
46
- conn.close()
47
- return db_path
48
-
49
-
50
- @pytest.fixture
51
- def pcm(memory_db):
52
- from src.learning.project_context_manager import ProjectContextManager
53
- return ProjectContextManager(memory_db_path=memory_db)
54
-
55
-
56
- def _insert_memories(db_path, memories):
57
- conn = sqlite3.connect(str(db_path))
58
- cursor = conn.cursor()
59
- for m in memories:
60
- cursor.execute('''
61
- INSERT INTO memories (content, project_name, project_path,
62
- profile, cluster_id, created_at)
63
- VALUES (?, ?, ?, ?, ?, ?)
64
- ''', (
65
- m.get('content', 'test'),
66
- m.get('project_name'),
67
- m.get('project_path'),
68
- m.get('profile', 'default'),
69
- m.get('cluster_id'),
70
- m.get('created_at', '2026-02-16 10:00:00'),
71
- ))
72
- conn.commit()
73
- conn.close()
74
-
75
-
76
- # ---------------------------------------------------------------------------
77
- # Path Extraction
78
- # ---------------------------------------------------------------------------
79
-
80
- class TestExtractProjectFromPath:
81
- """Test the static _extract_project_from_path method."""
82
-
83
- def test_projects_parent(self):
84
- from src.learning.project_context_manager import ProjectContextManager
85
- result = ProjectContextManager._extract_project_from_path(
86
- "/Users/varun/projects/MY_PROJECT/src/main.py"
87
- )
88
- assert result == "MY_PROJECT"
89
-
90
- def test_repos_parent(self):
91
- from src.learning.project_context_manager import ProjectContextManager
92
- result = ProjectContextManager._extract_project_from_path(
93
- "/home/dev/repos/my-app/lib/util.js"
94
- )
95
- assert result == "my-app"
96
-
97
- def test_documents_parent(self):
98
- from src.learning.project_context_manager import ProjectContextManager
99
- result = ProjectContextManager._extract_project_from_path(
100
- "/Users/varun/Documents/AGENTIC_Official/SuperLocalMemoryV2-repo/src/learning/foo.py"
101
- )
102
- assert result == "SuperLocalMemoryV2-repo"
103
-
104
- def test_workspace_services(self):
105
- from src.learning.project_context_manager import ProjectContextManager
106
- result = ProjectContextManager._extract_project_from_path(
107
- "/workspace/services/auth-service/index.ts"
108
- )
109
- assert result == "auth-service"
110
-
111
- def test_empty_path(self):
112
- from src.learning.project_context_manager import ProjectContextManager
113
- assert ProjectContextManager._extract_project_from_path("") is None
114
-
115
- def test_none_path(self):
116
- from src.learning.project_context_manager import ProjectContextManager
117
- assert ProjectContextManager._extract_project_from_path(None) is None
118
-
119
- def test_short_path(self):
120
- from src.learning.project_context_manager import ProjectContextManager
121
- assert ProjectContextManager._extract_project_from_path("/") is None
122
-
123
- def test_github_parent(self):
124
- from src.learning.project_context_manager import ProjectContextManager
125
- result = ProjectContextManager._extract_project_from_path(
126
- "/home/user/github/cool-project/README.md"
127
- )
128
- assert result == "cool-project"
129
-
130
- def test_skip_dirs_not_returned(self):
131
- """Directories like src, lib, node_modules should not be project names."""
132
- from src.learning.project_context_manager import ProjectContextManager
133
- result = ProjectContextManager._extract_project_from_path(
134
- "/Users/dev/projects/myapp/src/lib/util.py"
135
- )
136
- assert result == "myapp"
137
-
138
- def test_code_parent(self):
139
- from src.learning.project_context_manager import ProjectContextManager
140
- result = ProjectContextManager._extract_project_from_path(
141
- "/Users/dev/code/awesome-tool/main.py"
142
- )
143
- assert result == "awesome-tool"
144
-
145
-
146
- # ---------------------------------------------------------------------------
147
- # Project Detection
148
- # ---------------------------------------------------------------------------
149
-
150
- class TestDetectCurrentProject:
151
- def test_with_explicit_project_tags(self, pcm, memory_db):
152
- """Signal 1: project_name tag should dominate."""
153
- memories = [
154
- {"content": "test", "project_name": "MyProject"},
155
- {"content": "test", "project_name": "MyProject"},
156
- {"content": "test", "project_name": "MyProject"},
157
- ]
158
- recent = [
159
- {"project_name": "MyProject", "project_path": None, "cluster_id": None,
160
- "profile": "default", "content": "test"}
161
- for _ in range(3)
162
- ]
163
- result = pcm.detect_current_project(recent_memories=recent)
164
- assert result == "MyProject"
165
-
166
- def test_with_project_paths(self, pcm):
167
- """Signal 2: project_path analysis."""
168
- recent = [
169
- {"project_name": None,
170
- "project_path": "/Users/dev/projects/SLM/src/main.py",
171
- "cluster_id": None, "profile": "default", "content": "test"}
172
- for _ in range(5)
173
- ]
174
- result = pcm.detect_current_project(recent_memories=recent)
175
- assert result == "SLM"
176
-
177
- def test_with_profiles(self, pcm):
178
- """Signal 3: active profile as weak signal."""
179
- recent = [
180
- {"project_name": None, "project_path": None,
181
- "cluster_id": None, "profile": "work", "content": "test"}
182
- ]
183
- # Profile is weak (weight=1), may not win alone due to 40% threshold
184
- # with just 1 memory. But adding it to existing signal helps.
185
- recent_with_name = [
186
- {"project_name": "work", "project_path": None,
187
- "cluster_id": None, "profile": "work", "content": "test"}
188
- ]
189
- result = pcm.detect_current_project(recent_memories=recent_with_name)
190
- assert result == "work"
191
-
192
- def test_empty_memories(self, pcm):
193
- result = pcm.detect_current_project(recent_memories=[])
194
- assert result is None
195
-
196
- def test_ambiguous_results_none(self, pcm):
197
- """When no project clears 40% threshold, return None."""
198
- recent = [
199
- {"project_name": "A", "project_path": None, "cluster_id": None,
200
- "profile": "default", "content": "test"},
201
- {"project_name": "B", "project_path": None, "cluster_id": None,
202
- "profile": "default", "content": "test"},
203
- {"project_name": "C", "project_path": None, "cluster_id": None,
204
- "profile": "default", "content": "test"},
205
- ]
206
- # Each gets 3 points (weight 3 for project_tag), total = 9
207
- # 3/9 = 33% < 40% threshold, so None
208
- result = pcm.detect_current_project(recent_memories=recent)
209
- assert result is None
210
-
211
- def test_mixed_signals(self, pcm):
212
- """Both project_name and project_path pointing to same project."""
213
- recent = [
214
- {"project_name": "SLM", "project_path": "/projects/SLM/src/a.py",
215
- "cluster_id": None, "profile": "default", "content": "test"},
216
- {"project_name": "SLM", "project_path": "/projects/SLM/src/b.py",
217
- "cluster_id": None, "profile": "default", "content": "test"},
218
- ]
219
- result = pcm.detect_current_project(recent_memories=recent)
220
- assert result == "SLM"
221
-
222
-
223
- # ---------------------------------------------------------------------------
224
- # Project Boost
225
- # ---------------------------------------------------------------------------
226
-
227
- class TestGetProjectBoost:
228
- def test_match_returns_1_0(self, pcm):
229
- memory = {"project_name": "MyProject"}
230
- assert pcm.get_project_boost(memory, "MyProject") == 1.0
231
-
232
- def test_case_insensitive_match(self, pcm):
233
- memory = {"project_name": "myproject"}
234
- assert pcm.get_project_boost(memory, "MyProject") == 1.0
235
-
236
- def test_mismatch_returns_0_3(self, pcm):
237
- memory = {"project_name": "OtherProject"}
238
- assert pcm.get_project_boost(memory, "MyProject") == 0.3
239
-
240
- def test_no_current_project_returns_0_6(self, pcm):
241
- memory = {"project_name": "Anything"}
242
- assert pcm.get_project_boost(memory, None) == 0.6
243
-
244
- def test_no_project_info_returns_0_6(self, pcm):
245
- memory = {"content": "no project info"}
246
- assert pcm.get_project_boost(memory, "MyProject") == 0.6
247
-
248
- def test_path_match(self, pcm):
249
- memory = {"project_path": "/projects/SLM/src/main.py"}
250
- assert pcm.get_project_boost(memory, "SLM") == 1.0
251
-
252
- def test_path_mismatch(self, pcm):
253
- memory = {"project_path": "/projects/OTHER/src/main.py"}
254
- assert pcm.get_project_boost(memory, "SLM") == 0.3
255
-
256
-
257
- # ---------------------------------------------------------------------------
258
- # safe_get
259
- # ---------------------------------------------------------------------------
260
-
261
- class TestSafeGet:
262
- def test_normal_value(self, pcm):
263
- assert pcm._safe_get({"key": "value"}, "key") == "value"
264
-
265
- def test_missing_key(self, pcm):
266
- assert pcm._safe_get({"key": "value"}, "other") is None
267
-
268
- def test_none_value(self, pcm):
269
- assert pcm._safe_get({"key": None}, "key") is None
270
-
271
- def test_empty_string(self, pcm):
272
- assert pcm._safe_get({"key": ""}, "key") is None
273
-
274
- def test_whitespace_string(self, pcm):
275
- assert pcm._safe_get({"key": " "}, "key") is None
276
-
277
- def test_integer_value(self, pcm):
278
- assert pcm._safe_get({"key": 42}, "key") == 42
279
-
280
-
281
- # ---------------------------------------------------------------------------
282
- # Cache Invalidation
283
- # ---------------------------------------------------------------------------
284
-
285
- class TestCacheInvalidation:
286
- def test_invalidate_cache(self, pcm):
287
- # Force column cache to be populated
288
- pcm._get_available_columns()
289
- assert pcm._available_columns is not None
290
-
291
- pcm.invalidate_cache()
292
- assert pcm._available_columns is None