superlocalmemory 2.8.6 → 3.0.1
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.
- package/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +62 -48
- package/conftest.py +5 -0
- package/docs/api-reference.md +284 -0
- package/docs/architecture.md +149 -0
- package/docs/auto-memory.md +150 -0
- package/docs/cli-reference.md +276 -0
- package/docs/compliance.md +191 -0
- package/docs/configuration.md +182 -0
- package/docs/getting-started.md +102 -0
- package/docs/ide-setup.md +261 -0
- package/docs/mcp-tools.md +220 -0
- package/docs/migration-from-v2.md +170 -0
- package/docs/profiles.md +173 -0
- package/docs/troubleshooting.md +310 -0
- package/{configs → ide/configs}/antigravity-mcp.json +3 -3
- package/ide/configs/chatgpt-desktop-mcp.json +16 -0
- package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
- package/{configs → ide/configs}/codex-mcp.toml +4 -4
- package/{configs → ide/configs}/continue-mcp.yaml +4 -3
- package/{configs → ide/configs}/continue-skills.yaml +6 -6
- package/ide/configs/cursor-mcp.json +15 -0
- package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
- package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
- package/{configs → ide/configs}/opencode-mcp.json +2 -2
- package/{configs → ide/configs}/perplexity-mcp.json +2 -2
- package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
- package/{configs → ide/configs}/windsurf-mcp.json +3 -3
- package/{configs → ide/configs}/zed-mcp.json +2 -2
- package/{hooks → ide/hooks}/context-hook.js +9 -20
- package/ide/hooks/memory-list-skill.js +70 -0
- package/ide/hooks/memory-profile-skill.js +101 -0
- package/ide/hooks/memory-recall-skill.js +62 -0
- package/ide/hooks/memory-remember-skill.js +68 -0
- package/ide/hooks/memory-reset-skill.js +160 -0
- package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
- package/ide/integrations/langchain/README.md +106 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
- package/ide/integrations/langchain/pyproject.toml +38 -0
- package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
- package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
- package/ide/integrations/langchain/tests/test_security.py +117 -0
- package/ide/integrations/llamaindex/README.md +81 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
- package/ide/integrations/llamaindex/pyproject.toml +43 -0
- package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
- package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
- package/ide/integrations/llamaindex/tests/test_security.py +241 -0
- package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
- package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
- package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
- package/package.json +13 -22
- package/pyproject.toml +85 -0
- package/scripts/build-dmg.sh +417 -0
- package/scripts/install-skills.ps1 +334 -0
- package/scripts/postinstall.js +2 -2
- package/scripts/start-dashboard.ps1 +52 -0
- package/scripts/start-dashboard.sh +41 -0
- package/scripts/sync-wiki.ps1 +127 -0
- package/scripts/sync-wiki.sh +82 -0
- package/scripts/test-dmg.sh +161 -0
- package/scripts/test-npm-package.ps1 +252 -0
- package/scripts/test-npm-package.sh +207 -0
- package/scripts/verify-install.ps1 +294 -0
- package/scripts/verify-install.sh +266 -0
- package/src/superlocalmemory/__init__.py +0 -0
- package/src/superlocalmemory/attribution/__init__.py +9 -0
- package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
- package/src/superlocalmemory/attribution/signer.py +153 -0
- package/src/superlocalmemory/attribution/watermark.py +189 -0
- package/src/superlocalmemory/cli/__init__.py +5 -0
- package/src/superlocalmemory/cli/commands.py +245 -0
- package/src/superlocalmemory/cli/main.py +89 -0
- package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
- package/src/superlocalmemory/cli/post_install.py +99 -0
- package/src/superlocalmemory/cli/setup_wizard.py +129 -0
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +204 -0
- package/src/superlocalmemory/compliance/audit.py +314 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
- package/src/superlocalmemory/compliance/gdpr.py +294 -0
- package/src/superlocalmemory/compliance/lifecycle.py +158 -0
- package/src/superlocalmemory/compliance/retention.py +232 -0
- package/src/superlocalmemory/compliance/scheduler.py +148 -0
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/config.py +391 -0
- package/src/superlocalmemory/core/embeddings.py +293 -0
- package/src/superlocalmemory/core/engine.py +701 -0
- package/src/superlocalmemory/core/hooks.py +65 -0
- package/src/superlocalmemory/core/maintenance.py +172 -0
- package/src/superlocalmemory/core/modes.py +140 -0
- package/src/superlocalmemory/core/profiles.py +234 -0
- package/src/superlocalmemory/core/registry.py +117 -0
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/consolidator.py +485 -0
- package/src/superlocalmemory/encoding/emotional.py +125 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
- package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
- package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
- package/src/superlocalmemory/encoding/foresight.py +91 -0
- package/src/superlocalmemory/encoding/graph_builder.py +302 -0
- package/src/superlocalmemory/encoding/observation_builder.py +160 -0
- package/src/superlocalmemory/encoding/scene_builder.py +183 -0
- package/src/superlocalmemory/encoding/signal_inference.py +90 -0
- package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
- package/src/superlocalmemory/encoding/type_router.py +235 -0
- package/src/superlocalmemory/hooks/__init__.py +3 -0
- package/src/superlocalmemory/hooks/auto_capture.py +111 -0
- package/src/superlocalmemory/hooks/auto_recall.py +93 -0
- package/src/superlocalmemory/hooks/ide_connector.py +204 -0
- package/src/superlocalmemory/hooks/rules_engine.py +99 -0
- package/src/superlocalmemory/infra/__init__.py +3 -0
- package/src/superlocalmemory/infra/auth_middleware.py +82 -0
- package/src/superlocalmemory/infra/backup.py +317 -0
- package/src/superlocalmemory/infra/cache_manager.py +267 -0
- package/src/superlocalmemory/infra/event_bus.py +381 -0
- package/src/superlocalmemory/infra/rate_limiter.py +135 -0
- package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +172 -0
- package/src/superlocalmemory/learning/behavioral.py +490 -0
- package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
- package/src/superlocalmemory/learning/bootstrap.py +298 -0
- package/src/superlocalmemory/learning/cross_project.py +399 -0
- package/src/superlocalmemory/learning/database.py +376 -0
- package/src/superlocalmemory/learning/engagement.py +323 -0
- package/src/superlocalmemory/learning/features.py +138 -0
- package/src/superlocalmemory/learning/feedback.py +316 -0
- package/src/superlocalmemory/learning/outcomes.py +255 -0
- package/src/superlocalmemory/learning/project_context.py +366 -0
- package/src/superlocalmemory/learning/ranker.py +155 -0
- package/src/superlocalmemory/learning/source_quality.py +303 -0
- package/src/superlocalmemory/learning/workflows.py +309 -0
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +316 -0
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/fisher.py +356 -0
- package/src/superlocalmemory/math/langevin.py +398 -0
- package/src/superlocalmemory/math/sheaf.py +257 -0
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/resources.py +245 -0
- package/src/superlocalmemory/mcp/server.py +61 -0
- package/src/superlocalmemory/mcp/tools.py +18 -0
- package/src/superlocalmemory/mcp/tools_core.py +305 -0
- package/src/superlocalmemory/mcp/tools_v28.py +223 -0
- package/src/superlocalmemory/mcp/tools_v3.py +286 -0
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +295 -0
- package/src/superlocalmemory/retrieval/ann_index.py +223 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
- package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
- package/src/superlocalmemory/retrieval/engine.py +390 -0
- package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
- package/src/superlocalmemory/retrieval/fusion.py +78 -0
- package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
- package/src/superlocalmemory/retrieval/reranker.py +154 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
- package/src/superlocalmemory/retrieval/strategy.py +96 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
- package/src/superlocalmemory/server/__init__.py +1 -0
- package/src/superlocalmemory/server/api.py +248 -0
- package/src/superlocalmemory/server/routes/__init__.py +4 -0
- package/src/superlocalmemory/server/routes/agents.py +107 -0
- package/src/superlocalmemory/server/routes/backup.py +91 -0
- package/src/superlocalmemory/server/routes/behavioral.py +127 -0
- package/src/superlocalmemory/server/routes/compliance.py +160 -0
- package/src/superlocalmemory/server/routes/data_io.py +188 -0
- package/src/superlocalmemory/server/routes/events.py +183 -0
- package/src/superlocalmemory/server/routes/helpers.py +85 -0
- package/src/superlocalmemory/server/routes/learning.py +273 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
- package/src/superlocalmemory/server/routes/memories.py +399 -0
- package/src/superlocalmemory/server/routes/profiles.py +219 -0
- package/src/superlocalmemory/server/routes/stats.py +346 -0
- package/src/superlocalmemory/server/routes/v3_api.py +365 -0
- package/src/superlocalmemory/server/routes/ws.py +82 -0
- package/src/superlocalmemory/server/security_middleware.py +57 -0
- package/src/superlocalmemory/server/ui.py +245 -0
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/access_control.py +182 -0
- package/src/superlocalmemory/storage/database.py +594 -0
- package/src/superlocalmemory/storage/migrations.py +303 -0
- package/src/superlocalmemory/storage/models.py +406 -0
- package/src/superlocalmemory/storage/schema.py +726 -0
- package/src/superlocalmemory/storage/v2_migrator.py +317 -0
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +130 -0
- package/src/superlocalmemory/trust/provenance.py +124 -0
- package/src/superlocalmemory/trust/scorer.py +347 -0
- package/src/superlocalmemory/trust/signals.py +153 -0
- package/ui/index.html +278 -5
- package/ui/js/auto-settings.js +70 -0
- package/ui/js/dashboard.js +90 -0
- package/ui/js/fact-detail.js +92 -0
- package/ui/js/feedback.js +2 -2
- package/ui/js/ide-status.js +102 -0
- package/ui/js/math-health.js +98 -0
- package/ui/js/recall-lab.js +127 -0
- package/ui/js/settings.js +2 -2
- package/ui/js/trust-dashboard.js +73 -0
- package/api_server.py +0 -724
- package/bin/aider-smart +0 -72
- package/bin/superlocalmemoryv2-learning +0 -4
- package/bin/superlocalmemoryv2-list +0 -3
- package/bin/superlocalmemoryv2-patterns +0 -4
- package/bin/superlocalmemoryv2-profile +0 -3
- package/bin/superlocalmemoryv2-recall +0 -3
- package/bin/superlocalmemoryv2-remember +0 -3
- package/bin/superlocalmemoryv2-reset +0 -3
- package/bin/superlocalmemoryv2-status +0 -3
- package/configs/chatgpt-desktop-mcp.json +0 -16
- package/configs/cursor-mcp.json +0 -15
- package/hooks/memory-list-skill.js +0 -139
- package/hooks/memory-profile-skill.js +0 -273
- package/hooks/memory-recall-skill.js +0 -114
- package/hooks/memory-remember-skill.js +0 -127
- package/hooks/memory-reset-skill.js +0 -274
- package/mcp_server.py +0 -1808
- package/requirements-core.txt +0 -22
- package/requirements-learning.txt +0 -12
- package/requirements.txt +0 -12
- package/src/agent_registry.py +0 -411
- package/src/auth_middleware.py +0 -61
- package/src/auto_backup.py +0 -459
- package/src/behavioral/__init__.py +0 -49
- package/src/behavioral/behavioral_listener.py +0 -203
- package/src/behavioral/behavioral_patterns.py +0 -275
- package/src/behavioral/cross_project_transfer.py +0 -206
- package/src/behavioral/outcome_inference.py +0 -194
- package/src/behavioral/outcome_tracker.py +0 -193
- package/src/behavioral/tests/__init__.py +0 -4
- package/src/behavioral/tests/test_behavioral_integration.py +0 -108
- package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
- package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
- package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
- package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
- package/src/behavioral/tests/test_outcome_inference.py +0 -107
- package/src/behavioral/tests/test_outcome_tracker.py +0 -96
- package/src/cache_manager.py +0 -518
- package/src/compliance/__init__.py +0 -48
- package/src/compliance/abac_engine.py +0 -149
- package/src/compliance/abac_middleware.py +0 -116
- package/src/compliance/audit_db.py +0 -215
- package/src/compliance/audit_logger.py +0 -148
- package/src/compliance/retention_manager.py +0 -289
- package/src/compliance/retention_scheduler.py +0 -186
- package/src/compliance/tests/__init__.py +0 -4
- package/src/compliance/tests/test_abac_enforcement.py +0 -95
- package/src/compliance/tests/test_abac_engine.py +0 -124
- package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
- package/src/compliance/tests/test_audit_db.py +0 -123
- package/src/compliance/tests/test_audit_logger.py +0 -98
- package/src/compliance/tests/test_mcp_audit.py +0 -128
- package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
- package/src/compliance/tests/test_retention_manager.py +0 -131
- package/src/compliance/tests/test_retention_scheduler.py +0 -99
- package/src/compression/__init__.py +0 -25
- package/src/compression/cli.py +0 -150
- package/src/compression/cold_storage.py +0 -217
- package/src/compression/config.py +0 -72
- package/src/compression/orchestrator.py +0 -133
- package/src/compression/tier2_compressor.py +0 -228
- package/src/compression/tier3_compressor.py +0 -153
- package/src/compression/tier_classifier.py +0 -148
- package/src/db_connection_manager.py +0 -536
- package/src/embedding_engine.py +0 -63
- package/src/embeddings/__init__.py +0 -47
- package/src/embeddings/cache.py +0 -70
- package/src/embeddings/cli.py +0 -113
- package/src/embeddings/constants.py +0 -47
- package/src/embeddings/database.py +0 -91
- package/src/embeddings/engine.py +0 -247
- package/src/embeddings/model_loader.py +0 -145
- package/src/event_bus.py +0 -562
- package/src/graph/__init__.py +0 -36
- package/src/graph/build_helpers.py +0 -74
- package/src/graph/cli.py +0 -87
- package/src/graph/cluster_builder.py +0 -188
- package/src/graph/cluster_summary.py +0 -148
- package/src/graph/constants.py +0 -47
- package/src/graph/edge_builder.py +0 -162
- package/src/graph/entity_extractor.py +0 -95
- package/src/graph/graph_core.py +0 -226
- package/src/graph/graph_search.py +0 -231
- package/src/graph/hierarchical.py +0 -207
- package/src/graph/schema.py +0 -99
- package/src/graph_engine.py +0 -52
- package/src/hnsw_index.py +0 -628
- package/src/hybrid_search.py +0 -46
- package/src/learning/__init__.py +0 -217
- package/src/learning/adaptive_ranker.py +0 -682
- package/src/learning/bootstrap/__init__.py +0 -69
- package/src/learning/bootstrap/constants.py +0 -93
- package/src/learning/bootstrap/db_queries.py +0 -316
- package/src/learning/bootstrap/sampling.py +0 -82
- package/src/learning/bootstrap/text_utils.py +0 -71
- package/src/learning/cross_project_aggregator.py +0 -857
- package/src/learning/db/__init__.py +0 -40
- package/src/learning/db/constants.py +0 -44
- package/src/learning/db/schema.py +0 -279
- package/src/learning/engagement_tracker.py +0 -628
- package/src/learning/feature_extractor.py +0 -708
- package/src/learning/feedback_collector.py +0 -806
- package/src/learning/learning_db.py +0 -915
- package/src/learning/project_context_manager.py +0 -572
- package/src/learning/ranking/__init__.py +0 -33
- package/src/learning/ranking/constants.py +0 -84
- package/src/learning/ranking/helpers.py +0 -278
- package/src/learning/source_quality_scorer.py +0 -676
- package/src/learning/synthetic_bootstrap.py +0 -755
- package/src/learning/tests/test_adaptive_ranker.py +0 -325
- package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
- package/src/learning/tests/test_aggregator.py +0 -306
- package/src/learning/tests/test_auto_retrain_v28.py +0 -35
- package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
- package/src/learning/tests/test_feature_extractor_v28.py +0 -93
- package/src/learning/tests/test_feedback_collector.py +0 -294
- package/src/learning/tests/test_learning_db.py +0 -602
- package/src/learning/tests/test_learning_db_v28.py +0 -110
- package/src/learning/tests/test_learning_init_v28.py +0 -48
- package/src/learning/tests/test_outcome_signals.py +0 -48
- package/src/learning/tests/test_project_context.py +0 -292
- package/src/learning/tests/test_schema_migration.py +0 -319
- package/src/learning/tests/test_signal_inference.py +0 -397
- package/src/learning/tests/test_source_quality.py +0 -351
- package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
- package/src/learning/tests/test_workflow_miner.py +0 -318
- package/src/learning/workflow_pattern_miner.py +0 -655
- package/src/lifecycle/__init__.py +0 -54
- package/src/lifecycle/bounded_growth.py +0 -239
- package/src/lifecycle/compaction_engine.py +0 -226
- package/src/lifecycle/lifecycle_engine.py +0 -355
- package/src/lifecycle/lifecycle_evaluator.py +0 -257
- package/src/lifecycle/lifecycle_scheduler.py +0 -130
- package/src/lifecycle/retention_policy.py +0 -285
- package/src/lifecycle/tests/test_bounded_growth.py +0 -193
- package/src/lifecycle/tests/test_compaction.py +0 -179
- package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
- package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
- package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
- package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
- package/src/lifecycle/tests/test_mcp_compact.py +0 -149
- package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
- package/src/lifecycle/tests/test_retention_policy.py +0 -162
- package/src/mcp_tools_v28.py +0 -281
- package/src/memory/__init__.py +0 -36
- package/src/memory/cli.py +0 -205
- package/src/memory/constants.py +0 -39
- package/src/memory/helpers.py +0 -28
- package/src/memory/schema.py +0 -166
- package/src/memory-profiles.py +0 -595
- package/src/memory-reset.py +0 -491
- package/src/memory_compression.py +0 -989
- package/src/memory_store_v2.py +0 -1155
- package/src/migrate_v1_to_v2.py +0 -629
- package/src/pattern_learner.py +0 -34
- package/src/patterns/__init__.py +0 -24
- package/src/patterns/analyzers.py +0 -251
- package/src/patterns/learner.py +0 -271
- package/src/patterns/scoring.py +0 -171
- package/src/patterns/store.py +0 -225
- package/src/patterns/terminology.py +0 -140
- package/src/provenance_tracker.py +0 -312
- package/src/qualixar_attribution.py +0 -139
- package/src/qualixar_watermark.py +0 -78
- package/src/query_optimizer.py +0 -511
- package/src/rate_limiter.py +0 -83
- package/src/search/__init__.py +0 -20
- package/src/search/cli.py +0 -77
- package/src/search/constants.py +0 -26
- package/src/search/engine.py +0 -241
- package/src/search/fusion.py +0 -122
- package/src/search/index_loader.py +0 -114
- package/src/search/methods.py +0 -162
- package/src/search_engine_v2.py +0 -401
- package/src/setup_validator.py +0 -482
- package/src/subscription_manager.py +0 -391
- package/src/tree/__init__.py +0 -59
- package/src/tree/builder.py +0 -185
- package/src/tree/nodes.py +0 -202
- package/src/tree/queries.py +0 -257
- package/src/tree/schema.py +0 -80
- package/src/tree_manager.py +0 -19
- package/src/trust/__init__.py +0 -45
- package/src/trust/constants.py +0 -66
- package/src/trust/queries.py +0 -157
- package/src/trust/schema.py +0 -95
- package/src/trust/scorer.py +0 -299
- package/src/trust/signals.py +0 -95
- package/src/trust_scorer.py +0 -44
- package/ui/app.js +0 -1588
- package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
- package/ui/js/graph-cytoscape.js +0 -1168
- package/ui/js/graph-d3-backup.js +0 -32
- package/ui/js/graph.js +0 -32
- package/ui_server.py +0 -286
- /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
- /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
- /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
- /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
- /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
- /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
- /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
- /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
- /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
- /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
- /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
- /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
- /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
- /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
- /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
- /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
- /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
- /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
- /package/{completions → ide/completions}/slm.bash +0 -0
- /package/{completions → ide/completions}/slm.zsh +0 -0
- /package/{configs → ide/configs}/cody-commands.json +0 -0
- /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
- /package/{install.ps1 → scripts/install.ps1} +0 -0
- /package/{install.sh → scripts/install.sh} +0 -0
package/src/cache_manager.py
DELETED
|
@@ -1,518 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
4
|
-
"""SuperLocalMemory V2 - Cache Manager
|
|
5
|
-
|
|
6
|
-
Solution Architect & Original Creator
|
|
7
|
-
|
|
8
|
-
(see LICENSE file)
|
|
9
|
-
|
|
10
|
-
ATTRIBUTION REQUIRED: This notice must be preserved in all copies.
|
|
11
|
-
"""
|
|
12
|
-
"""
|
|
13
|
-
Cache Manager - LRU Cache for Search Results
|
|
14
|
-
|
|
15
|
-
Implements Least Recently Used (LRU) cache for search query results to reduce
|
|
16
|
-
redundant computation and improve response times.
|
|
17
|
-
|
|
18
|
-
Key Features:
|
|
19
|
-
1. LRU Eviction Policy: Automatically removes least recently used entries
|
|
20
|
-
2. TTL Support: Optional time-to-live for cache entries
|
|
21
|
-
3. Size-Based Eviction: Maximum cache size in number of entries
|
|
22
|
-
4. Memory-Efficient: Uses OrderedDict for O(1) access and updates
|
|
23
|
-
5. Thread-Safe: Optional thread safety for concurrent access
|
|
24
|
-
|
|
25
|
-
Performance Impact:
|
|
26
|
-
- Cache hit: ~0.1ms (negligible overhead)
|
|
27
|
-
- Cache miss: Standard search time
|
|
28
|
-
- Target cache hit rate: 30-50% for typical usage
|
|
29
|
-
|
|
30
|
-
Usage:
|
|
31
|
-
cache = CacheManager(max_size=100, ttl_seconds=300)
|
|
32
|
-
|
|
33
|
-
# Try cache first
|
|
34
|
-
result = cache.get("python web")
|
|
35
|
-
if result is None:
|
|
36
|
-
# Cache miss - perform search
|
|
37
|
-
result = search_engine.search("python web")
|
|
38
|
-
cache.put("python web", result)
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
import time
|
|
42
|
-
import hashlib
|
|
43
|
-
import json
|
|
44
|
-
from collections import OrderedDict
|
|
45
|
-
from typing import Any, Optional, Dict, Tuple
|
|
46
|
-
from threading import RLock
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class CacheEntry:
|
|
50
|
-
"""
|
|
51
|
-
Single cache entry with metadata.
|
|
52
|
-
|
|
53
|
-
Stores:
|
|
54
|
-
- value: Cached result
|
|
55
|
-
- timestamp: Creation time for TTL validation
|
|
56
|
-
- access_count: Number of times accessed (for analytics)
|
|
57
|
-
- size_estimate: Memory size estimate in bytes
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
__slots__ = ['value', 'timestamp', 'access_count', 'size_estimate']
|
|
61
|
-
|
|
62
|
-
def __init__(self, value: Any, size_estimate: int = 0):
|
|
63
|
-
"""
|
|
64
|
-
Create cache entry.
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
value: Value to cache
|
|
68
|
-
size_estimate: Estimated size in bytes
|
|
69
|
-
"""
|
|
70
|
-
self.value = value
|
|
71
|
-
self.timestamp = time.time()
|
|
72
|
-
self.access_count = 0
|
|
73
|
-
self.size_estimate = size_estimate
|
|
74
|
-
|
|
75
|
-
def is_expired(self, ttl_seconds: Optional[float]) -> bool:
|
|
76
|
-
"""
|
|
77
|
-
Check if entry has exceeded TTL.
|
|
78
|
-
|
|
79
|
-
Args:
|
|
80
|
-
ttl_seconds: Time-to-live in seconds (None = no expiry)
|
|
81
|
-
|
|
82
|
-
Returns:
|
|
83
|
-
True if expired, False otherwise
|
|
84
|
-
"""
|
|
85
|
-
if ttl_seconds is None:
|
|
86
|
-
return False
|
|
87
|
-
|
|
88
|
-
age = time.time() - self.timestamp
|
|
89
|
-
return age > ttl_seconds
|
|
90
|
-
|
|
91
|
-
def mark_accessed(self) -> None:
|
|
92
|
-
"""Mark entry as accessed (increment counter)."""
|
|
93
|
-
self.access_count += 1
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
class CacheManager:
|
|
97
|
-
"""
|
|
98
|
-
LRU cache manager for search results with TTL support.
|
|
99
|
-
|
|
100
|
-
Uses OrderedDict to maintain insertion/access order efficiently.
|
|
101
|
-
When cache is full, least recently used entry is evicted.
|
|
102
|
-
|
|
103
|
-
Thread-safe when thread_safe=True.
|
|
104
|
-
"""
|
|
105
|
-
|
|
106
|
-
def __init__(
|
|
107
|
-
self,
|
|
108
|
-
max_size: int = 100,
|
|
109
|
-
ttl_seconds: Optional[float] = 300,
|
|
110
|
-
thread_safe: bool = False
|
|
111
|
-
):
|
|
112
|
-
"""
|
|
113
|
-
Initialize cache manager.
|
|
114
|
-
|
|
115
|
-
Args:
|
|
116
|
-
max_size: Maximum number of cache entries
|
|
117
|
-
ttl_seconds: Time-to-live for entries (None = no expiry)
|
|
118
|
-
thread_safe: Enable thread-safe operations
|
|
119
|
-
"""
|
|
120
|
-
self.max_size = max_size
|
|
121
|
-
self.ttl_seconds = ttl_seconds
|
|
122
|
-
self.thread_safe = thread_safe
|
|
123
|
-
|
|
124
|
-
# LRU cache storage
|
|
125
|
-
self._cache: OrderedDict[str, CacheEntry] = OrderedDict()
|
|
126
|
-
|
|
127
|
-
# Thread safety lock
|
|
128
|
-
self._lock = RLock() if thread_safe else None
|
|
129
|
-
|
|
130
|
-
# Statistics
|
|
131
|
-
self._hits = 0
|
|
132
|
-
self._misses = 0
|
|
133
|
-
self._evictions = 0
|
|
134
|
-
self._total_size_estimate = 0
|
|
135
|
-
|
|
136
|
-
def _hash_key(self, query: str, **kwargs) -> str:
|
|
137
|
-
"""
|
|
138
|
-
Generate cache key from query and parameters.
|
|
139
|
-
|
|
140
|
-
Args:
|
|
141
|
-
query: Search query
|
|
142
|
-
**kwargs: Additional parameters to include in key
|
|
143
|
-
|
|
144
|
-
Returns:
|
|
145
|
-
Hash string for cache key
|
|
146
|
-
"""
|
|
147
|
-
# Create deterministic key from query + parameters
|
|
148
|
-
key_data = {
|
|
149
|
-
'query': query,
|
|
150
|
-
**kwargs
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
# Sort keys for deterministic hashing
|
|
154
|
-
key_str = json.dumps(key_data, sort_keys=True)
|
|
155
|
-
|
|
156
|
-
# Hash for compact key
|
|
157
|
-
return hashlib.sha256(key_str.encode()).hexdigest()[:16]
|
|
158
|
-
|
|
159
|
-
def _estimate_size(self, value: Any) -> int:
|
|
160
|
-
"""
|
|
161
|
-
Estimate memory size of cached value.
|
|
162
|
-
|
|
163
|
-
Rough estimate for monitoring memory usage.
|
|
164
|
-
|
|
165
|
-
Args:
|
|
166
|
-
value: Value to estimate
|
|
167
|
-
|
|
168
|
-
Returns:
|
|
169
|
-
Estimated size in bytes
|
|
170
|
-
"""
|
|
171
|
-
try:
|
|
172
|
-
# For lists of tuples (typical search results)
|
|
173
|
-
if isinstance(value, list):
|
|
174
|
-
# Rough estimate: 100 bytes per result
|
|
175
|
-
return len(value) * 100
|
|
176
|
-
|
|
177
|
-
# For other types, try JSON serialization size
|
|
178
|
-
return len(json.dumps(value, default=str))
|
|
179
|
-
except Exception:
|
|
180
|
-
# Fallback: assume moderate size
|
|
181
|
-
return 1000
|
|
182
|
-
|
|
183
|
-
def get(
|
|
184
|
-
self,
|
|
185
|
-
query: str,
|
|
186
|
-
**kwargs
|
|
187
|
-
) -> Optional[Any]:
|
|
188
|
-
"""
|
|
189
|
-
Get cached result for query.
|
|
190
|
-
|
|
191
|
-
Args:
|
|
192
|
-
query: Search query
|
|
193
|
-
**kwargs: Additional parameters used in cache key
|
|
194
|
-
|
|
195
|
-
Returns:
|
|
196
|
-
Cached result if found and valid, None otherwise
|
|
197
|
-
"""
|
|
198
|
-
key = self._hash_key(query, **kwargs)
|
|
199
|
-
|
|
200
|
-
# Thread-safe access
|
|
201
|
-
if self._lock:
|
|
202
|
-
self._lock.acquire()
|
|
203
|
-
|
|
204
|
-
try:
|
|
205
|
-
# Check if key exists
|
|
206
|
-
if key not in self._cache:
|
|
207
|
-
self._misses += 1
|
|
208
|
-
return None
|
|
209
|
-
|
|
210
|
-
entry = self._cache[key]
|
|
211
|
-
|
|
212
|
-
# Check TTL expiry
|
|
213
|
-
if entry.is_expired(self.ttl_seconds):
|
|
214
|
-
# Remove expired entry
|
|
215
|
-
del self._cache[key]
|
|
216
|
-
self._total_size_estimate -= entry.size_estimate
|
|
217
|
-
self._misses += 1
|
|
218
|
-
return None
|
|
219
|
-
|
|
220
|
-
# Move to end (mark as recently used)
|
|
221
|
-
self._cache.move_to_end(key)
|
|
222
|
-
entry.mark_accessed()
|
|
223
|
-
|
|
224
|
-
self._hits += 1
|
|
225
|
-
return entry.value
|
|
226
|
-
|
|
227
|
-
finally:
|
|
228
|
-
if self._lock:
|
|
229
|
-
self._lock.release()
|
|
230
|
-
|
|
231
|
-
def put(
|
|
232
|
-
self,
|
|
233
|
-
query: str,
|
|
234
|
-
value: Any,
|
|
235
|
-
**kwargs
|
|
236
|
-
) -> None:
|
|
237
|
-
"""
|
|
238
|
-
Store result in cache.
|
|
239
|
-
|
|
240
|
-
Args:
|
|
241
|
-
query: Search query
|
|
242
|
-
value: Result to cache
|
|
243
|
-
**kwargs: Additional parameters used in cache key
|
|
244
|
-
"""
|
|
245
|
-
key = self._hash_key(query, **kwargs)
|
|
246
|
-
size_estimate = self._estimate_size(value)
|
|
247
|
-
|
|
248
|
-
# Thread-safe access
|
|
249
|
-
if self._lock:
|
|
250
|
-
self._lock.acquire()
|
|
251
|
-
|
|
252
|
-
try:
|
|
253
|
-
# Check if key already exists (update)
|
|
254
|
-
if key in self._cache:
|
|
255
|
-
old_entry = self._cache[key]
|
|
256
|
-
self._total_size_estimate -= old_entry.size_estimate
|
|
257
|
-
del self._cache[key]
|
|
258
|
-
|
|
259
|
-
# Check if cache is full
|
|
260
|
-
if len(self._cache) >= self.max_size:
|
|
261
|
-
# Evict least recently used (first item)
|
|
262
|
-
evicted_key, evicted_entry = self._cache.popitem(last=False)
|
|
263
|
-
self._total_size_estimate -= evicted_entry.size_estimate
|
|
264
|
-
self._evictions += 1
|
|
265
|
-
|
|
266
|
-
# Add new entry (at end = most recently used)
|
|
267
|
-
entry = CacheEntry(value, size_estimate)
|
|
268
|
-
self._cache[key] = entry
|
|
269
|
-
self._total_size_estimate += size_estimate
|
|
270
|
-
|
|
271
|
-
finally:
|
|
272
|
-
if self._lock:
|
|
273
|
-
self._lock.release()
|
|
274
|
-
|
|
275
|
-
def invalidate(self, query: str, **kwargs) -> bool:
|
|
276
|
-
"""
|
|
277
|
-
Remove specific entry from cache.
|
|
278
|
-
|
|
279
|
-
Args:
|
|
280
|
-
query: Search query
|
|
281
|
-
**kwargs: Additional parameters
|
|
282
|
-
|
|
283
|
-
Returns:
|
|
284
|
-
True if entry was removed, False if not found
|
|
285
|
-
"""
|
|
286
|
-
key = self._hash_key(query, **kwargs)
|
|
287
|
-
|
|
288
|
-
if self._lock:
|
|
289
|
-
self._lock.acquire()
|
|
290
|
-
|
|
291
|
-
try:
|
|
292
|
-
if key in self._cache:
|
|
293
|
-
entry = self._cache[key]
|
|
294
|
-
del self._cache[key]
|
|
295
|
-
self._total_size_estimate -= entry.size_estimate
|
|
296
|
-
return True
|
|
297
|
-
return False
|
|
298
|
-
|
|
299
|
-
finally:
|
|
300
|
-
if self._lock:
|
|
301
|
-
self._lock.release()
|
|
302
|
-
|
|
303
|
-
def clear(self) -> None:
|
|
304
|
-
"""Clear entire cache."""
|
|
305
|
-
if self._lock:
|
|
306
|
-
self._lock.acquire()
|
|
307
|
-
|
|
308
|
-
try:
|
|
309
|
-
self._cache.clear()
|
|
310
|
-
self._total_size_estimate = 0
|
|
311
|
-
|
|
312
|
-
finally:
|
|
313
|
-
if self._lock:
|
|
314
|
-
self._lock.release()
|
|
315
|
-
|
|
316
|
-
def evict_expired(self) -> int:
|
|
317
|
-
"""
|
|
318
|
-
Manually evict all expired entries.
|
|
319
|
-
|
|
320
|
-
Returns:
|
|
321
|
-
Number of entries evicted
|
|
322
|
-
"""
|
|
323
|
-
if self.ttl_seconds is None:
|
|
324
|
-
return 0
|
|
325
|
-
|
|
326
|
-
if self._lock:
|
|
327
|
-
self._lock.acquire()
|
|
328
|
-
|
|
329
|
-
try:
|
|
330
|
-
expired_keys = [
|
|
331
|
-
key for key, entry in self._cache.items()
|
|
332
|
-
if entry.is_expired(self.ttl_seconds)
|
|
333
|
-
]
|
|
334
|
-
|
|
335
|
-
for key in expired_keys:
|
|
336
|
-
entry = self._cache[key]
|
|
337
|
-
del self._cache[key]
|
|
338
|
-
self._total_size_estimate -= entry.size_estimate
|
|
339
|
-
|
|
340
|
-
return len(expired_keys)
|
|
341
|
-
|
|
342
|
-
finally:
|
|
343
|
-
if self._lock:
|
|
344
|
-
self._lock.release()
|
|
345
|
-
|
|
346
|
-
def get_stats(self) -> Dict[str, Any]:
|
|
347
|
-
"""
|
|
348
|
-
Get cache statistics.
|
|
349
|
-
|
|
350
|
-
Returns:
|
|
351
|
-
Dictionary with cache statistics
|
|
352
|
-
"""
|
|
353
|
-
total_requests = self._hits + self._misses
|
|
354
|
-
hit_rate = self._hits / total_requests if total_requests > 0 else 0.0
|
|
355
|
-
|
|
356
|
-
# Average access count
|
|
357
|
-
avg_access_count = 0.0
|
|
358
|
-
if self._cache:
|
|
359
|
-
avg_access_count = sum(
|
|
360
|
-
entry.access_count for entry in self._cache.values()
|
|
361
|
-
) / len(self._cache)
|
|
362
|
-
|
|
363
|
-
return {
|
|
364
|
-
'max_size': self.max_size,
|
|
365
|
-
'current_size': len(self._cache),
|
|
366
|
-
'ttl_seconds': self.ttl_seconds,
|
|
367
|
-
'hits': self._hits,
|
|
368
|
-
'misses': self._misses,
|
|
369
|
-
'hit_rate': hit_rate,
|
|
370
|
-
'evictions': self._evictions,
|
|
371
|
-
'total_size_estimate_kb': self._total_size_estimate / 1024,
|
|
372
|
-
'avg_access_count': avg_access_count,
|
|
373
|
-
'thread_safe': self.thread_safe
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
def get_top_queries(self, limit: int = 10) -> list:
|
|
377
|
-
"""
|
|
378
|
-
Get most frequently accessed queries.
|
|
379
|
-
|
|
380
|
-
Args:
|
|
381
|
-
limit: Maximum number of queries to return
|
|
382
|
-
|
|
383
|
-
Returns:
|
|
384
|
-
List of (query_hash, access_count) tuples
|
|
385
|
-
"""
|
|
386
|
-
if self._lock:
|
|
387
|
-
self._lock.acquire()
|
|
388
|
-
|
|
389
|
-
try:
|
|
390
|
-
queries = [
|
|
391
|
-
(key, entry.access_count)
|
|
392
|
-
for key, entry in self._cache.items()
|
|
393
|
-
]
|
|
394
|
-
|
|
395
|
-
queries.sort(key=lambda x: x[1], reverse=True)
|
|
396
|
-
return queries[:limit]
|
|
397
|
-
|
|
398
|
-
finally:
|
|
399
|
-
if self._lock:
|
|
400
|
-
self._lock.release()
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
# CLI interface for testing
|
|
404
|
-
if __name__ == "__main__":
|
|
405
|
-
import random
|
|
406
|
-
|
|
407
|
-
print("Cache Manager - Demo")
|
|
408
|
-
print("=" * 60)
|
|
409
|
-
|
|
410
|
-
# Initialize cache
|
|
411
|
-
cache = CacheManager(max_size=5, ttl_seconds=10)
|
|
412
|
-
|
|
413
|
-
print("\nCache Configuration:")
|
|
414
|
-
stats = cache.get_stats()
|
|
415
|
-
print(f" Max Size: {stats['max_size']}")
|
|
416
|
-
print(f" TTL: {stats['ttl_seconds']}s")
|
|
417
|
-
|
|
418
|
-
# Simulate search queries
|
|
419
|
-
queries = [
|
|
420
|
-
"python programming",
|
|
421
|
-
"javascript web",
|
|
422
|
-
"machine learning",
|
|
423
|
-
"database sql",
|
|
424
|
-
"api rest"
|
|
425
|
-
]
|
|
426
|
-
|
|
427
|
-
# Mock search results
|
|
428
|
-
def mock_search(query: str) -> None:
|
|
429
|
-
"""Simulate search result."""
|
|
430
|
-
return [
|
|
431
|
-
(f"doc_{i}", random.random())
|
|
432
|
-
for i in range(3)
|
|
433
|
-
]
|
|
434
|
-
|
|
435
|
-
print("\n" + "=" * 60)
|
|
436
|
-
print("Simulating Search Operations:")
|
|
437
|
-
print("=" * 60)
|
|
438
|
-
|
|
439
|
-
# First pass - all cache misses
|
|
440
|
-
print("\nPass 1 (Cold Cache):")
|
|
441
|
-
for query in queries:
|
|
442
|
-
result = cache.get(query)
|
|
443
|
-
if result is None:
|
|
444
|
-
print(f" MISS: '{query}' - performing search")
|
|
445
|
-
result = mock_search(query)
|
|
446
|
-
cache.put(query, result)
|
|
447
|
-
else:
|
|
448
|
-
print(f" HIT: '{query}'")
|
|
449
|
-
|
|
450
|
-
# Second pass - all cache hits
|
|
451
|
-
print("\nPass 2 (Warm Cache):")
|
|
452
|
-
for query in queries:
|
|
453
|
-
result = cache.get(query)
|
|
454
|
-
if result is None:
|
|
455
|
-
print(f" MISS: '{query}' - performing search")
|
|
456
|
-
result = mock_search(query)
|
|
457
|
-
cache.put(query, result)
|
|
458
|
-
else:
|
|
459
|
-
print(f" HIT: '{query}'")
|
|
460
|
-
|
|
461
|
-
# Third pass - add more queries to trigger eviction
|
|
462
|
-
print("\nPass 3 (Cache Overflow - LRU Eviction):")
|
|
463
|
-
extra_queries = [
|
|
464
|
-
"neural networks",
|
|
465
|
-
"cloud computing",
|
|
466
|
-
"devops kubernetes"
|
|
467
|
-
]
|
|
468
|
-
|
|
469
|
-
for query in extra_queries:
|
|
470
|
-
result = cache.get(query)
|
|
471
|
-
if result is None:
|
|
472
|
-
print(f" MISS: '{query}' - performing search")
|
|
473
|
-
result = mock_search(query)
|
|
474
|
-
cache.put(query, result)
|
|
475
|
-
|
|
476
|
-
# Check if old queries were evicted
|
|
477
|
-
print("\nPass 4 (Check Evictions):")
|
|
478
|
-
for query in queries[:3]:
|
|
479
|
-
result = cache.get(query)
|
|
480
|
-
if result is None:
|
|
481
|
-
print(f" EVICTED: '{query}'")
|
|
482
|
-
else:
|
|
483
|
-
print(f" RETAINED: '{query}'")
|
|
484
|
-
|
|
485
|
-
# Display statistics
|
|
486
|
-
print("\n" + "=" * 60)
|
|
487
|
-
print("Cache Statistics:")
|
|
488
|
-
print("=" * 60)
|
|
489
|
-
|
|
490
|
-
stats = cache.get_stats()
|
|
491
|
-
for key, value in stats.items():
|
|
492
|
-
if isinstance(value, float):
|
|
493
|
-
print(f" {key}: {value:.2f}")
|
|
494
|
-
else:
|
|
495
|
-
print(f" {key}: {value}")
|
|
496
|
-
|
|
497
|
-
# Test TTL expiry
|
|
498
|
-
print("\n" + "=" * 60)
|
|
499
|
-
print("Testing TTL Expiry:")
|
|
500
|
-
print("=" * 60)
|
|
501
|
-
|
|
502
|
-
cache_ttl = CacheManager(max_size=10, ttl_seconds=2)
|
|
503
|
-
cache_ttl.put("test query", mock_search("test"))
|
|
504
|
-
|
|
505
|
-
print("\n Immediately after cache:")
|
|
506
|
-
result = cache_ttl.get("test query")
|
|
507
|
-
print(f" Result: {'HIT' if result else 'MISS'}")
|
|
508
|
-
|
|
509
|
-
print("\n After 3 seconds (exceeds TTL):")
|
|
510
|
-
time.sleep(3)
|
|
511
|
-
result = cache_ttl.get("test query")
|
|
512
|
-
print(f" Result: {'HIT' if result else 'MISS (expired)'}")
|
|
513
|
-
|
|
514
|
-
print("\n" + "=" * 60)
|
|
515
|
-
print("Performance Impact:")
|
|
516
|
-
print(" Cache hit: ~0.1ms overhead")
|
|
517
|
-
print(" Cache miss: Standard search time + 0.1ms")
|
|
518
|
-
print(" Target hit rate: 30-50% for typical usage")
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""SLM v2.8 Compliance Engine — ABAC + Audit Trail + Retention.
|
|
4
|
-
|
|
5
|
-
Enterprise-grade access control, tamper-evident audit trail,
|
|
6
|
-
and retention policy management for GDPR/EU AI Act/HIPAA.
|
|
7
|
-
|
|
8
|
-
Graceful degradation: if this module fails to import,
|
|
9
|
-
all agents have full access (v2.7 behavior).
|
|
10
|
-
"""
|
|
11
|
-
import threading
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from typing import Optional, Dict, Any
|
|
14
|
-
|
|
15
|
-
COMPLIANCE_AVAILABLE = False
|
|
16
|
-
_init_error = None
|
|
17
|
-
|
|
18
|
-
try:
|
|
19
|
-
from .abac_engine import ABACEngine
|
|
20
|
-
from .audit_db import AuditDB
|
|
21
|
-
COMPLIANCE_AVAILABLE = True
|
|
22
|
-
except ImportError as e:
|
|
23
|
-
_init_error = str(e)
|
|
24
|
-
|
|
25
|
-
_abac_engine: Optional["ABACEngine"] = None
|
|
26
|
-
_abac_lock = threading.Lock()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def get_abac_engine(config_path: Optional[Path] = None) -> Optional["ABACEngine"]:
|
|
30
|
-
"""Get or create the ABAC engine singleton."""
|
|
31
|
-
global _abac_engine
|
|
32
|
-
if not COMPLIANCE_AVAILABLE:
|
|
33
|
-
return None
|
|
34
|
-
with _abac_lock:
|
|
35
|
-
if _abac_engine is None:
|
|
36
|
-
try:
|
|
37
|
-
_abac_engine = ABACEngine(config_path)
|
|
38
|
-
except Exception:
|
|
39
|
-
return None
|
|
40
|
-
return _abac_engine
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def get_status() -> Dict[str, Any]:
|
|
44
|
-
return {
|
|
45
|
-
"compliance_available": COMPLIANCE_AVAILABLE,
|
|
46
|
-
"init_error": _init_error,
|
|
47
|
-
"abac_active": _abac_engine is not None,
|
|
48
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Attribute-Based Access Control policy evaluation.
|
|
4
|
-
|
|
5
|
-
Evaluates access requests against JSON-defined policies using
|
|
6
|
-
subject, resource, and action attributes. Deny-first semantics
|
|
7
|
-
ensure any matching deny policy blocks access regardless of
|
|
8
|
-
allow policies. When no policies exist, all access is permitted
|
|
9
|
-
(backward compatible with v2.7 default-allow behavior).
|
|
10
|
-
|
|
11
|
-
Policy format:
|
|
12
|
-
{
|
|
13
|
-
"name": str, # Human-readable policy name
|
|
14
|
-
"effect": str, # "allow" or "deny"
|
|
15
|
-
"subjects": dict, # Attribute constraints on the requester
|
|
16
|
-
"resources": dict, # Attribute constraints on the resource
|
|
17
|
-
"actions": list[str] # Actions this policy applies to
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
Matching rules:
|
|
21
|
-
- "*" matches any value for that attribute
|
|
22
|
-
- Specific values require exact match
|
|
23
|
-
- All attributes in the policy must match for the policy to apply
|
|
24
|
-
"""
|
|
25
|
-
import json
|
|
26
|
-
import logging
|
|
27
|
-
from pathlib import Path
|
|
28
|
-
from typing import Any, Dict, List, Optional
|
|
29
|
-
|
|
30
|
-
logger = logging.getLogger(__name__)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class ABACEngine:
|
|
34
|
-
"""Evaluates ABAC policies for memory access control.
|
|
35
|
-
|
|
36
|
-
Deny-first evaluation: if ANY deny policy matches the request,
|
|
37
|
-
access is denied. If no deny matches, access is allowed
|
|
38
|
-
(default-allow preserves v2.7 backward compatibility).
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
def __init__(self, config_path: Optional[str] = None) -> None:
|
|
42
|
-
self._config_path = config_path
|
|
43
|
-
self.policies: List[Dict[str, Any]] = []
|
|
44
|
-
if config_path:
|
|
45
|
-
self._load_policies(config_path)
|
|
46
|
-
|
|
47
|
-
def _load_policies(self, path: str) -> None:
|
|
48
|
-
"""Load policies from a JSON file. Graceful on missing/invalid."""
|
|
49
|
-
try:
|
|
50
|
-
raw = Path(path).read_text(encoding="utf-8")
|
|
51
|
-
data = json.loads(raw)
|
|
52
|
-
if isinstance(data, list):
|
|
53
|
-
self.policies = data
|
|
54
|
-
logger.info("Loaded %d ABAC policies from %s", len(data), path)
|
|
55
|
-
else:
|
|
56
|
-
logger.warning("ABAC policy file is not a list: %s", path)
|
|
57
|
-
except FileNotFoundError:
|
|
58
|
-
logger.debug("No ABAC policy file at %s — default allow", path)
|
|
59
|
-
except (json.JSONDecodeError, OSError) as exc:
|
|
60
|
-
logger.warning("Failed to parse ABAC policies: %s", exc)
|
|
61
|
-
|
|
62
|
-
def evaluate(
|
|
63
|
-
self,
|
|
64
|
-
subject: Dict[str, Any],
|
|
65
|
-
resource: Dict[str, Any],
|
|
66
|
-
action: str,
|
|
67
|
-
) -> Dict[str, Any]:
|
|
68
|
-
"""Evaluate an access request against loaded policies.
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
subject: Attributes of the requester (e.g. agent_id).
|
|
72
|
-
resource: Attributes of the target resource.
|
|
73
|
-
action: The action being requested (read/write/delete).
|
|
74
|
-
|
|
75
|
-
Returns:
|
|
76
|
-
Dict with keys: allowed (bool), reason (str),
|
|
77
|
-
and policy_name (str) when a specific policy decided.
|
|
78
|
-
"""
|
|
79
|
-
if not self.policies:
|
|
80
|
-
return {"allowed": True, "reason": "no_policies_loaded"}
|
|
81
|
-
|
|
82
|
-
# Phase 1: check all deny policies first
|
|
83
|
-
for policy in self.policies:
|
|
84
|
-
if policy.get("effect") != "deny":
|
|
85
|
-
continue
|
|
86
|
-
if self._matches(policy, subject, resource, action):
|
|
87
|
-
return {
|
|
88
|
-
"allowed": False,
|
|
89
|
-
"reason": "denied_by_policy",
|
|
90
|
-
"policy_name": policy.get("name", "unnamed"),
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
# Phase 2: check allow policies
|
|
94
|
-
for policy in self.policies:
|
|
95
|
-
if policy.get("effect") != "allow":
|
|
96
|
-
continue
|
|
97
|
-
if self._matches(policy, subject, resource, action):
|
|
98
|
-
return {
|
|
99
|
-
"allowed": True,
|
|
100
|
-
"reason": "allowed_by_policy",
|
|
101
|
-
"policy_name": policy.get("name", "unnamed"),
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
# Phase 3: no matching policy — default allow (backward compat)
|
|
105
|
-
return {"allowed": True, "reason": "no_matching_policy"}
|
|
106
|
-
|
|
107
|
-
# ------------------------------------------------------------------
|
|
108
|
-
# Internal matching helpers
|
|
109
|
-
# ------------------------------------------------------------------
|
|
110
|
-
|
|
111
|
-
def _matches(
|
|
112
|
-
self,
|
|
113
|
-
policy: Dict[str, Any],
|
|
114
|
-
subject: Dict[str, Any],
|
|
115
|
-
resource: Dict[str, Any],
|
|
116
|
-
action: str,
|
|
117
|
-
) -> bool:
|
|
118
|
-
"""Return True if policy matches the request."""
|
|
119
|
-
if not self._action_matches(policy.get("actions", []), action):
|
|
120
|
-
return False
|
|
121
|
-
if not self._attrs_match(policy.get("subjects", {}), subject):
|
|
122
|
-
return False
|
|
123
|
-
if not self._attrs_match(policy.get("resources", {}), resource):
|
|
124
|
-
return False
|
|
125
|
-
return True
|
|
126
|
-
|
|
127
|
-
@staticmethod
|
|
128
|
-
def _action_matches(policy_actions: List[str], action: str) -> bool:
|
|
129
|
-
"""Check if the requested action is in the policy's action list."""
|
|
130
|
-
if "*" in policy_actions:
|
|
131
|
-
return True
|
|
132
|
-
return action in policy_actions
|
|
133
|
-
|
|
134
|
-
@staticmethod
|
|
135
|
-
def _attrs_match(
|
|
136
|
-
policy_attrs: Dict[str, Any],
|
|
137
|
-
request_attrs: Dict[str, Any],
|
|
138
|
-
) -> bool:
|
|
139
|
-
"""Check if all policy attribute constraints are satisfied.
|
|
140
|
-
|
|
141
|
-
Every key in policy_attrs must either be "*" (match anything)
|
|
142
|
-
or exactly equal the corresponding value in request_attrs.
|
|
143
|
-
"""
|
|
144
|
-
for key, expected in policy_attrs.items():
|
|
145
|
-
if expected == "*":
|
|
146
|
-
continue
|
|
147
|
-
if request_attrs.get(key) != expected:
|
|
148
|
-
return False
|
|
149
|
-
return True
|