superlocalmemory 2.8.6 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +1 -1
- 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
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
4
|
-
"""
|
|
5
|
-
SubscriptionManager — Manages durable and ephemeral event subscriptions.
|
|
6
|
-
|
|
7
|
-
Subscribers register interest in specific event types and receive matching
|
|
8
|
-
events via their chosen channel (SSE, WebSocket, Webhook).
|
|
9
|
-
|
|
10
|
-
Subscription Types:
|
|
11
|
-
Durable (default) — Persisted to DB, survives disconnect, auto-replay on reconnect
|
|
12
|
-
Ephemeral (opt-in) — In-memory only, dies on disconnect
|
|
13
|
-
|
|
14
|
-
Filter Syntax:
|
|
15
|
-
{
|
|
16
|
-
"event_types": ["memory.created", "memory.deleted"], // null = all types
|
|
17
|
-
"min_importance": 5, // null = no filter
|
|
18
|
-
"source_protocols": ["mcp", "cli"], // null = all protocols
|
|
19
|
-
"projects": ["myapp"] // null = all projects
|
|
20
|
-
}
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
import json
|
|
24
|
-
import logging
|
|
25
|
-
import threading
|
|
26
|
-
from datetime import datetime
|
|
27
|
-
from pathlib import Path
|
|
28
|
-
from typing import Optional, List, Dict, Any
|
|
29
|
-
|
|
30
|
-
logger = logging.getLogger("superlocalmemory.subscriptions")
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class SubscriptionManager:
|
|
34
|
-
"""
|
|
35
|
-
Manages event subscriptions for the Event Bus.
|
|
36
|
-
|
|
37
|
-
Thread-safe. Durable subscriptions persist to SQLite. Ephemeral
|
|
38
|
-
subscriptions are in-memory only.
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
_instances: Dict[str, "SubscriptionManager"] = {}
|
|
42
|
-
_instances_lock = threading.Lock()
|
|
43
|
-
|
|
44
|
-
@classmethod
|
|
45
|
-
def get_instance(cls, db_path: Optional[Path] = None) -> "SubscriptionManager":
|
|
46
|
-
"""Get or create the singleton SubscriptionManager."""
|
|
47
|
-
if db_path is None:
|
|
48
|
-
db_path = Path.home() / ".claude-memory" / "memory.db"
|
|
49
|
-
key = str(db_path)
|
|
50
|
-
with cls._instances_lock:
|
|
51
|
-
if key not in cls._instances:
|
|
52
|
-
cls._instances[key] = cls(db_path)
|
|
53
|
-
return cls._instances[key]
|
|
54
|
-
|
|
55
|
-
@classmethod
|
|
56
|
-
def reset_instance(cls, db_path: Optional[Path] = None) -> None:
|
|
57
|
-
"""Remove singleton. Used for testing."""
|
|
58
|
-
with cls._instances_lock:
|
|
59
|
-
if db_path is None:
|
|
60
|
-
cls._instances.clear()
|
|
61
|
-
else:
|
|
62
|
-
key = str(db_path)
|
|
63
|
-
if key in cls._instances:
|
|
64
|
-
del cls._instances[key]
|
|
65
|
-
|
|
66
|
-
def __init__(self, db_path: Path):
|
|
67
|
-
self.db_path = Path(db_path)
|
|
68
|
-
|
|
69
|
-
# Ephemeral subscriptions (in-memory only)
|
|
70
|
-
self._ephemeral: Dict[str, dict] = {}
|
|
71
|
-
self._ephemeral_lock = threading.Lock()
|
|
72
|
-
|
|
73
|
-
self._init_schema()
|
|
74
|
-
logger.info("SubscriptionManager initialized: db=%s", self.db_path)
|
|
75
|
-
|
|
76
|
-
def _init_schema(self):
|
|
77
|
-
"""Create subscriptions table if it doesn't exist."""
|
|
78
|
-
try:
|
|
79
|
-
from db_connection_manager import DbConnectionManager
|
|
80
|
-
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
81
|
-
|
|
82
|
-
def _create(conn):
|
|
83
|
-
conn.execute('''
|
|
84
|
-
CREATE TABLE IF NOT EXISTS subscriptions (
|
|
85
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
86
|
-
subscriber_id TEXT NOT NULL UNIQUE,
|
|
87
|
-
channel TEXT NOT NULL,
|
|
88
|
-
filter TEXT NOT NULL DEFAULT '{}',
|
|
89
|
-
webhook_url TEXT,
|
|
90
|
-
durable INTEGER DEFAULT 1,
|
|
91
|
-
last_event_id INTEGER DEFAULT 0,
|
|
92
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
93
|
-
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
94
|
-
)
|
|
95
|
-
''')
|
|
96
|
-
conn.execute('''
|
|
97
|
-
CREATE INDEX IF NOT EXISTS idx_subs_channel
|
|
98
|
-
ON subscriptions(channel)
|
|
99
|
-
''')
|
|
100
|
-
conn.commit()
|
|
101
|
-
|
|
102
|
-
mgr.execute_write(_create)
|
|
103
|
-
except ImportError:
|
|
104
|
-
import sqlite3
|
|
105
|
-
conn = sqlite3.connect(str(self.db_path))
|
|
106
|
-
try:
|
|
107
|
-
conn.execute('''
|
|
108
|
-
CREATE TABLE IF NOT EXISTS subscriptions (
|
|
109
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
110
|
-
subscriber_id TEXT NOT NULL UNIQUE,
|
|
111
|
-
channel TEXT NOT NULL,
|
|
112
|
-
filter TEXT NOT NULL DEFAULT '{}',
|
|
113
|
-
webhook_url TEXT,
|
|
114
|
-
durable INTEGER DEFAULT 1,
|
|
115
|
-
last_event_id INTEGER DEFAULT 0,
|
|
116
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
117
|
-
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
118
|
-
)
|
|
119
|
-
''')
|
|
120
|
-
conn.execute('CREATE INDEX IF NOT EXISTS idx_subs_channel ON subscriptions(channel)')
|
|
121
|
-
conn.commit()
|
|
122
|
-
finally:
|
|
123
|
-
conn.close()
|
|
124
|
-
|
|
125
|
-
# =========================================================================
|
|
126
|
-
# Subscribe / Unsubscribe
|
|
127
|
-
# =========================================================================
|
|
128
|
-
|
|
129
|
-
def subscribe(
|
|
130
|
-
self,
|
|
131
|
-
subscriber_id: str,
|
|
132
|
-
channel: str = "sse",
|
|
133
|
-
filter_obj: Optional[dict] = None,
|
|
134
|
-
webhook_url: Optional[str] = None,
|
|
135
|
-
durable: bool = True,
|
|
136
|
-
) -> dict:
|
|
137
|
-
"""
|
|
138
|
-
Register a subscription.
|
|
139
|
-
|
|
140
|
-
Args:
|
|
141
|
-
subscriber_id: Unique identifier for the subscriber
|
|
142
|
-
channel: Delivery channel — 'sse', 'websocket', 'webhook'
|
|
143
|
-
filter_obj: Event filter (see module docstring for syntax)
|
|
144
|
-
webhook_url: URL for webhook channel (required if channel='webhook')
|
|
145
|
-
durable: If True, persists to DB; if False, in-memory only
|
|
146
|
-
|
|
147
|
-
Returns:
|
|
148
|
-
Subscription dict with id and details
|
|
149
|
-
|
|
150
|
-
Raises:
|
|
151
|
-
ValueError: If channel is invalid or webhook_url missing for webhook channel
|
|
152
|
-
"""
|
|
153
|
-
if channel not in ("sse", "websocket", "webhook"):
|
|
154
|
-
raise ValueError(f"Invalid channel: {channel}. Must be sse, websocket, or webhook")
|
|
155
|
-
|
|
156
|
-
if channel == "webhook" and not webhook_url:
|
|
157
|
-
raise ValueError("webhook_url is required for webhook channel")
|
|
158
|
-
|
|
159
|
-
# Validate webhook URL format
|
|
160
|
-
if webhook_url and not (webhook_url.startswith("http://") or webhook_url.startswith("https://")):
|
|
161
|
-
raise ValueError("webhook_url must start with http:// or https://")
|
|
162
|
-
|
|
163
|
-
filter_json = json.dumps(filter_obj or {})
|
|
164
|
-
now = datetime.now().isoformat()
|
|
165
|
-
|
|
166
|
-
sub = {
|
|
167
|
-
"subscriber_id": subscriber_id,
|
|
168
|
-
"channel": channel,
|
|
169
|
-
"filter": filter_obj or {},
|
|
170
|
-
"webhook_url": webhook_url,
|
|
171
|
-
"durable": durable,
|
|
172
|
-
"last_event_id": 0,
|
|
173
|
-
"created_at": now,
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if durable:
|
|
177
|
-
self._persist_subscription(sub, filter_json)
|
|
178
|
-
else:
|
|
179
|
-
with self._ephemeral_lock:
|
|
180
|
-
self._ephemeral[subscriber_id] = sub
|
|
181
|
-
|
|
182
|
-
logger.info("Subscription created: id=%s, channel=%s, durable=%s", subscriber_id, channel, durable)
|
|
183
|
-
return sub
|
|
184
|
-
|
|
185
|
-
def _persist_subscription(self, sub: dict, filter_json: str):
|
|
186
|
-
"""Save durable subscription to database."""
|
|
187
|
-
try:
|
|
188
|
-
from db_connection_manager import DbConnectionManager
|
|
189
|
-
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
190
|
-
|
|
191
|
-
def _upsert(conn):
|
|
192
|
-
conn.execute('''
|
|
193
|
-
INSERT INTO subscriptions (subscriber_id, channel, filter, webhook_url, durable, created_at, updated_at)
|
|
194
|
-
VALUES (?, ?, ?, ?, 1, ?, ?)
|
|
195
|
-
ON CONFLICT(subscriber_id) DO UPDATE SET
|
|
196
|
-
channel = excluded.channel,
|
|
197
|
-
filter = excluded.filter,
|
|
198
|
-
webhook_url = excluded.webhook_url,
|
|
199
|
-
updated_at = excluded.updated_at
|
|
200
|
-
''', (
|
|
201
|
-
sub["subscriber_id"],
|
|
202
|
-
sub["channel"],
|
|
203
|
-
filter_json,
|
|
204
|
-
sub.get("webhook_url"),
|
|
205
|
-
sub["created_at"],
|
|
206
|
-
sub["created_at"],
|
|
207
|
-
))
|
|
208
|
-
conn.commit()
|
|
209
|
-
|
|
210
|
-
mgr.execute_write(_upsert)
|
|
211
|
-
except Exception as e:
|
|
212
|
-
logger.error("Failed to persist subscription: %s", e)
|
|
213
|
-
|
|
214
|
-
def unsubscribe(self, subscriber_id: str) -> bool:
|
|
215
|
-
"""
|
|
216
|
-
Remove a subscription (durable or ephemeral).
|
|
217
|
-
|
|
218
|
-
Args:
|
|
219
|
-
subscriber_id: ID of the subscription to remove
|
|
220
|
-
|
|
221
|
-
Returns:
|
|
222
|
-
True if subscription was found and removed
|
|
223
|
-
"""
|
|
224
|
-
removed = False
|
|
225
|
-
|
|
226
|
-
# Remove ephemeral
|
|
227
|
-
with self._ephemeral_lock:
|
|
228
|
-
if subscriber_id in self._ephemeral:
|
|
229
|
-
del self._ephemeral[subscriber_id]
|
|
230
|
-
removed = True
|
|
231
|
-
|
|
232
|
-
# Remove durable
|
|
233
|
-
try:
|
|
234
|
-
from db_connection_manager import DbConnectionManager
|
|
235
|
-
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
236
|
-
|
|
237
|
-
def _delete(conn):
|
|
238
|
-
conn.execute("DELETE FROM subscriptions WHERE subscriber_id = ?", (subscriber_id,))
|
|
239
|
-
conn.commit()
|
|
240
|
-
return conn.total_changes > 0
|
|
241
|
-
|
|
242
|
-
if mgr.execute_write(_delete):
|
|
243
|
-
removed = True
|
|
244
|
-
except Exception as e:
|
|
245
|
-
logger.error("Failed to delete subscription: %s", e)
|
|
246
|
-
|
|
247
|
-
return removed
|
|
248
|
-
|
|
249
|
-
def update_last_event_id(self, subscriber_id: str, event_id: int) -> None:
|
|
250
|
-
"""Update the last event ID received by a durable subscriber (for replay)."""
|
|
251
|
-
try:
|
|
252
|
-
from db_connection_manager import DbConnectionManager
|
|
253
|
-
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
254
|
-
|
|
255
|
-
def _update(conn):
|
|
256
|
-
conn.execute(
|
|
257
|
-
"UPDATE subscriptions SET last_event_id = ?, updated_at = ? WHERE subscriber_id = ?",
|
|
258
|
-
(event_id, datetime.now().isoformat(), subscriber_id)
|
|
259
|
-
)
|
|
260
|
-
conn.commit()
|
|
261
|
-
|
|
262
|
-
mgr.execute_write(_update)
|
|
263
|
-
except Exception as e:
|
|
264
|
-
logger.error("Failed to update last_event_id: %s", e)
|
|
265
|
-
|
|
266
|
-
# =========================================================================
|
|
267
|
-
# Query Subscriptions
|
|
268
|
-
# =========================================================================
|
|
269
|
-
|
|
270
|
-
def get_matching_subscribers(self, event: dict) -> List[dict]:
|
|
271
|
-
"""
|
|
272
|
-
Get all subscriptions that match a given event.
|
|
273
|
-
|
|
274
|
-
Applies filter logic: event_types, min_importance, source_protocols.
|
|
275
|
-
|
|
276
|
-
Args:
|
|
277
|
-
event: Event dict with event_type, importance, source_protocol, etc.
|
|
278
|
-
|
|
279
|
-
Returns:
|
|
280
|
-
List of matching subscription dicts
|
|
281
|
-
"""
|
|
282
|
-
all_subs = self.list_subscriptions()
|
|
283
|
-
matching = []
|
|
284
|
-
|
|
285
|
-
for sub in all_subs:
|
|
286
|
-
if self._matches_filter(sub.get("filter", {}), event):
|
|
287
|
-
matching.append(sub)
|
|
288
|
-
|
|
289
|
-
return matching
|
|
290
|
-
|
|
291
|
-
def _matches_filter(self, filter_obj: dict, event: dict) -> bool:
|
|
292
|
-
"""Check if an event matches a subscription filter."""
|
|
293
|
-
if not filter_obj:
|
|
294
|
-
return True # No filter = match all
|
|
295
|
-
|
|
296
|
-
# Event type filter
|
|
297
|
-
allowed_types = filter_obj.get("event_types")
|
|
298
|
-
if allowed_types and event.get("event_type") not in allowed_types:
|
|
299
|
-
return False
|
|
300
|
-
|
|
301
|
-
# Importance filter
|
|
302
|
-
min_importance = filter_obj.get("min_importance")
|
|
303
|
-
if min_importance and (event.get("importance", 0) < min_importance):
|
|
304
|
-
return False
|
|
305
|
-
|
|
306
|
-
# Protocol filter
|
|
307
|
-
allowed_protocols = filter_obj.get("source_protocols")
|
|
308
|
-
if allowed_protocols and event.get("source_protocol") not in allowed_protocols:
|
|
309
|
-
return False
|
|
310
|
-
|
|
311
|
-
return True
|
|
312
|
-
|
|
313
|
-
def list_subscriptions(self) -> List[dict]:
|
|
314
|
-
"""Get all active subscriptions (durable + ephemeral)."""
|
|
315
|
-
subs = []
|
|
316
|
-
|
|
317
|
-
# Ephemeral
|
|
318
|
-
with self._ephemeral_lock:
|
|
319
|
-
subs.extend(list(self._ephemeral.values()))
|
|
320
|
-
|
|
321
|
-
# Durable (from DB)
|
|
322
|
-
try:
|
|
323
|
-
from db_connection_manager import DbConnectionManager
|
|
324
|
-
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
325
|
-
|
|
326
|
-
with mgr.read_connection() as conn:
|
|
327
|
-
cursor = conn.cursor()
|
|
328
|
-
cursor.execute("""
|
|
329
|
-
SELECT subscriber_id, channel, filter, webhook_url, durable,
|
|
330
|
-
last_event_id, created_at, updated_at
|
|
331
|
-
FROM subscriptions
|
|
332
|
-
""")
|
|
333
|
-
for row in cursor.fetchall():
|
|
334
|
-
filter_obj = {}
|
|
335
|
-
try:
|
|
336
|
-
filter_obj = json.loads(row[2]) if row[2] else {}
|
|
337
|
-
except (json.JSONDecodeError, TypeError):
|
|
338
|
-
pass
|
|
339
|
-
|
|
340
|
-
subs.append({
|
|
341
|
-
"subscriber_id": row[0],
|
|
342
|
-
"channel": row[1],
|
|
343
|
-
"filter": filter_obj,
|
|
344
|
-
"webhook_url": row[3],
|
|
345
|
-
"durable": bool(row[4]),
|
|
346
|
-
"last_event_id": row[5],
|
|
347
|
-
"created_at": row[6],
|
|
348
|
-
"updated_at": row[7],
|
|
349
|
-
})
|
|
350
|
-
except Exception as e:
|
|
351
|
-
logger.error("Failed to list durable subscriptions: %s", e)
|
|
352
|
-
|
|
353
|
-
return subs
|
|
354
|
-
|
|
355
|
-
def get_subscription(self, subscriber_id: str) -> Optional[dict]:
|
|
356
|
-
"""Get a specific subscription by ID."""
|
|
357
|
-
# Check ephemeral first
|
|
358
|
-
with self._ephemeral_lock:
|
|
359
|
-
if subscriber_id in self._ephemeral:
|
|
360
|
-
return self._ephemeral[subscriber_id]
|
|
361
|
-
|
|
362
|
-
# Check durable
|
|
363
|
-
try:
|
|
364
|
-
from db_connection_manager import DbConnectionManager
|
|
365
|
-
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
366
|
-
|
|
367
|
-
with mgr.read_connection() as conn:
|
|
368
|
-
cursor = conn.cursor()
|
|
369
|
-
cursor.execute(
|
|
370
|
-
"SELECT subscriber_id, channel, filter, webhook_url, durable, last_event_id FROM subscriptions WHERE subscriber_id = ?",
|
|
371
|
-
(subscriber_id,)
|
|
372
|
-
)
|
|
373
|
-
row = cursor.fetchone()
|
|
374
|
-
if row:
|
|
375
|
-
filter_obj = {}
|
|
376
|
-
try:
|
|
377
|
-
filter_obj = json.loads(row[2]) if row[2] else {}
|
|
378
|
-
except (json.JSONDecodeError, TypeError):
|
|
379
|
-
pass
|
|
380
|
-
return {
|
|
381
|
-
"subscriber_id": row[0],
|
|
382
|
-
"channel": row[1],
|
|
383
|
-
"filter": filter_obj,
|
|
384
|
-
"webhook_url": row[3],
|
|
385
|
-
"durable": bool(row[4]),
|
|
386
|
-
"last_event_id": row[5],
|
|
387
|
-
}
|
|
388
|
-
except Exception as e:
|
|
389
|
-
logger.error("Failed to get subscription: %s", e)
|
|
390
|
-
|
|
391
|
-
return None
|
package/src/tree/__init__.py
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tree — Hierarchical Memory Tree Management.
|
|
4
|
-
|
|
5
|
-
Composes the TreeManager class from focused mixin modules:
|
|
6
|
-
- schema.py : DB initialization and root-node bootstrap
|
|
7
|
-
- nodes.py : Node CRUD and count aggregation
|
|
8
|
-
- queries.py : Read-only tree traversal and statistics
|
|
9
|
-
- builder.py : Full tree construction from memories table
|
|
10
|
-
"""
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Optional
|
|
13
|
-
|
|
14
|
-
from .schema import TreeSchemaMixin, MEMORY_DIR, DB_PATH
|
|
15
|
-
from .nodes import TreeNodesMixin
|
|
16
|
-
from .queries import TreeQueriesMixin
|
|
17
|
-
from .builder import TreeBuilderMixin
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class TreeManager(TreeSchemaMixin, TreeNodesMixin, TreeQueriesMixin, TreeBuilderMixin):
|
|
21
|
-
"""
|
|
22
|
-
Manages hierarchical tree structure for memory navigation.
|
|
23
|
-
|
|
24
|
-
Tree Structure:
|
|
25
|
-
Root
|
|
26
|
-
+-- Project: NextJS-App
|
|
27
|
-
| +-- Category: Frontend
|
|
28
|
-
| | +-- Memory: React Components
|
|
29
|
-
| | +-- Memory: State Management
|
|
30
|
-
| +-- Category: Backend
|
|
31
|
-
| +-- Memory: API Routes
|
|
32
|
-
+-- Project: Python-ML
|
|
33
|
-
|
|
34
|
-
Materialized Path Format:
|
|
35
|
-
- Root: "1"
|
|
36
|
-
- Project: "1.2"
|
|
37
|
-
- Category: "1.2.3"
|
|
38
|
-
- Memory: "1.2.3.4"
|
|
39
|
-
|
|
40
|
-
Benefits:
|
|
41
|
-
- Fast subtree queries: WHERE tree_path LIKE '1.2.%'
|
|
42
|
-
- O(1) depth calculation: count dots in path
|
|
43
|
-
- O(1) parent lookup: parse path
|
|
44
|
-
- No recursive CTEs needed
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
def __init__(self, db_path: Optional[Path] = None):
|
|
48
|
-
"""
|
|
49
|
-
Initialize TreeManager.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
db_path: Optional custom database path
|
|
53
|
-
"""
|
|
54
|
-
self.db_path = db_path or DB_PATH
|
|
55
|
-
self._init_db()
|
|
56
|
-
self.root_id = self._ensure_root()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
__all__ = ['TreeManager', 'MEMORY_DIR', 'DB_PATH']
|
package/src/tree/builder.py
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
3
|
-
"""Tree Builder — Constructs the full tree from the memories table.
|
|
4
|
-
|
|
5
|
-
Provides TreeBuilderMixin with build_tree, plus the CLI entry point.
|
|
6
|
-
"""
|
|
7
|
-
import sqlite3
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class TreeBuilderMixin:
|
|
11
|
-
"""Builds the hierarchical tree from flat memory records."""
|
|
12
|
-
|
|
13
|
-
def build_tree(self):
|
|
14
|
-
"""
|
|
15
|
-
Build complete tree structure from memories table.
|
|
16
|
-
|
|
17
|
-
Process:
|
|
18
|
-
1. Clear existing tree (except root)
|
|
19
|
-
2. Group memories by project
|
|
20
|
-
3. Group by category within projects
|
|
21
|
-
4. Link individual memories as leaf nodes
|
|
22
|
-
5. Update aggregated counts
|
|
23
|
-
"""
|
|
24
|
-
conn = sqlite3.connect(self.db_path)
|
|
25
|
-
try:
|
|
26
|
-
cursor = conn.cursor()
|
|
27
|
-
|
|
28
|
-
# Clear existing tree (keep root)
|
|
29
|
-
cursor.execute('DELETE FROM memory_tree WHERE node_type != ?', ('root',))
|
|
30
|
-
|
|
31
|
-
# Step 1: Create project nodes
|
|
32
|
-
cursor.execute('''
|
|
33
|
-
SELECT DISTINCT project_path, project_name
|
|
34
|
-
FROM memories
|
|
35
|
-
WHERE project_path IS NOT NULL
|
|
36
|
-
ORDER BY project_path
|
|
37
|
-
''')
|
|
38
|
-
projects = cursor.fetchall()
|
|
39
|
-
|
|
40
|
-
project_map = {} # project_path -> node_id
|
|
41
|
-
|
|
42
|
-
for project_path, project_name in projects:
|
|
43
|
-
name = project_name or project_path.split('/')[-1]
|
|
44
|
-
node_id = self.add_node('project', name, self.root_id, description=project_path)
|
|
45
|
-
project_map[project_path] = node_id
|
|
46
|
-
|
|
47
|
-
# Step 2: Create category nodes within projects
|
|
48
|
-
cursor.execute('''
|
|
49
|
-
SELECT DISTINCT project_path, category
|
|
50
|
-
FROM memories
|
|
51
|
-
WHERE project_path IS NOT NULL AND category IS NOT NULL
|
|
52
|
-
ORDER BY project_path, category
|
|
53
|
-
''')
|
|
54
|
-
categories = cursor.fetchall()
|
|
55
|
-
|
|
56
|
-
category_map = {} # (project_path, category) -> node_id
|
|
57
|
-
|
|
58
|
-
for project_path, category in categories:
|
|
59
|
-
parent_id = project_map.get(project_path)
|
|
60
|
-
if parent_id:
|
|
61
|
-
node_id = self.add_node('category', category, parent_id)
|
|
62
|
-
category_map[(project_path, category)] = node_id
|
|
63
|
-
|
|
64
|
-
# Step 3: Link memories as leaf nodes
|
|
65
|
-
cursor.execute('''
|
|
66
|
-
SELECT id, content, summary, project_path, category, importance, created_at
|
|
67
|
-
FROM memories
|
|
68
|
-
ORDER BY created_at DESC
|
|
69
|
-
''')
|
|
70
|
-
memories = cursor.fetchall()
|
|
71
|
-
|
|
72
|
-
for mem_id, content, summary, project_path, category, importance, created_at in memories:
|
|
73
|
-
# Determine parent node
|
|
74
|
-
if project_path and category and (project_path, category) in category_map:
|
|
75
|
-
parent_id = category_map[(project_path, category)]
|
|
76
|
-
elif project_path and project_path in project_map:
|
|
77
|
-
parent_id = project_map[project_path]
|
|
78
|
-
else:
|
|
79
|
-
parent_id = self.root_id
|
|
80
|
-
|
|
81
|
-
# Create memory node
|
|
82
|
-
name = summary or content[:60].replace('\n', ' ')
|
|
83
|
-
self.add_node('memory', name, parent_id, memory_id=mem_id, description=content[:200])
|
|
84
|
-
|
|
85
|
-
# Step 4: Update aggregated counts
|
|
86
|
-
self._update_all_counts()
|
|
87
|
-
|
|
88
|
-
conn.commit()
|
|
89
|
-
finally:
|
|
90
|
-
conn.close()
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def run_cli():
|
|
94
|
-
"""CLI entry point for tree_manager."""
|
|
95
|
-
import sys
|
|
96
|
-
import json
|
|
97
|
-
from src.tree import TreeManager
|
|
98
|
-
|
|
99
|
-
tree_mgr = TreeManager()
|
|
100
|
-
|
|
101
|
-
if len(sys.argv) < 2:
|
|
102
|
-
print("TreeManager CLI")
|
|
103
|
-
print("\nCommands:")
|
|
104
|
-
print(" python tree_manager.py build # Build tree from memories")
|
|
105
|
-
print(" python tree_manager.py show [project] [depth] # Show tree structure")
|
|
106
|
-
print(" python tree_manager.py subtree <node_id> # Get subtree")
|
|
107
|
-
print(" python tree_manager.py path <node_id> # Get path to root")
|
|
108
|
-
print(" python tree_manager.py stats # Show statistics")
|
|
109
|
-
print(" python tree_manager.py add <type> <name> <parent_id> # Add node")
|
|
110
|
-
print(" python tree_manager.py delete <node_id> # Delete node")
|
|
111
|
-
sys.exit(0)
|
|
112
|
-
|
|
113
|
-
command = sys.argv[1]
|
|
114
|
-
|
|
115
|
-
if command == "build":
|
|
116
|
-
print("Building tree from memories...")
|
|
117
|
-
tree_mgr.build_tree()
|
|
118
|
-
stats = tree_mgr.get_stats()
|
|
119
|
-
print(f"Tree built: {stats['total_nodes']} nodes, {stats['total_memories']} memories")
|
|
120
|
-
|
|
121
|
-
elif command == "show":
|
|
122
|
-
project = sys.argv[2] if len(sys.argv) > 2 else None
|
|
123
|
-
max_depth = int(sys.argv[3]) if len(sys.argv) > 3 else None
|
|
124
|
-
|
|
125
|
-
tree = tree_mgr.get_tree(project, max_depth)
|
|
126
|
-
|
|
127
|
-
def print_tree(node, indent=0):
|
|
128
|
-
if 'error' in node:
|
|
129
|
-
print(node['error'])
|
|
130
|
-
return
|
|
131
|
-
|
|
132
|
-
prefix = " " * indent
|
|
133
|
-
icon = {"root": "\U0001f333", "project": "\U0001f4c1", "category": "\U0001f4c2", "memory": "\U0001f4c4"}.get(node['type'], "\u2022")
|
|
134
|
-
|
|
135
|
-
print(f"{prefix}{icon} {node['name']} (id={node['id']}, memories={node['memory_count']})")
|
|
136
|
-
|
|
137
|
-
for child in node.get('children', []):
|
|
138
|
-
print_tree(child, indent + 1)
|
|
139
|
-
|
|
140
|
-
print_tree(tree)
|
|
141
|
-
|
|
142
|
-
elif command == "subtree" and len(sys.argv) >= 3:
|
|
143
|
-
node_id = int(sys.argv[2])
|
|
144
|
-
nodes = tree_mgr.get_subtree(node_id)
|
|
145
|
-
|
|
146
|
-
if not nodes:
|
|
147
|
-
print(f"No subtree found for node {node_id}")
|
|
148
|
-
else:
|
|
149
|
-
print(f"Subtree of node {node_id}:")
|
|
150
|
-
for node in nodes:
|
|
151
|
-
indent = " " * (node['depth'] - nodes[0]['depth'] + 1)
|
|
152
|
-
print(f"{indent}- {node['name']} (id={node['id']})")
|
|
153
|
-
|
|
154
|
-
elif command == "path" and len(sys.argv) >= 3:
|
|
155
|
-
node_id = int(sys.argv[2])
|
|
156
|
-
path = tree_mgr.get_path_to_root(node_id)
|
|
157
|
-
|
|
158
|
-
if not path:
|
|
159
|
-
print(f"Node {node_id} not found")
|
|
160
|
-
else:
|
|
161
|
-
print("Path to root:")
|
|
162
|
-
print(" > ".join([f"{n['name']} (id={n['id']})" for n in path]))
|
|
163
|
-
|
|
164
|
-
elif command == "stats":
|
|
165
|
-
stats = tree_mgr.get_stats()
|
|
166
|
-
print(json.dumps(stats, indent=2))
|
|
167
|
-
|
|
168
|
-
elif command == "add" and len(sys.argv) >= 5:
|
|
169
|
-
node_type = sys.argv[2]
|
|
170
|
-
name = sys.argv[3]
|
|
171
|
-
parent_id = int(sys.argv[4])
|
|
172
|
-
|
|
173
|
-
node_id = tree_mgr.add_node(node_type, name, parent_id)
|
|
174
|
-
print(f"Node created with ID: {node_id}")
|
|
175
|
-
|
|
176
|
-
elif command == "delete" and len(sys.argv) >= 3:
|
|
177
|
-
node_id = int(sys.argv[2])
|
|
178
|
-
if tree_mgr.delete_node(node_id):
|
|
179
|
-
print(f"Node {node_id} deleted")
|
|
180
|
-
else:
|
|
181
|
-
print(f"Node {node_id} not found")
|
|
182
|
-
|
|
183
|
-
else:
|
|
184
|
-
print(f"Unknown command: {command}")
|
|
185
|
-
sys.exit(1)
|