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/patterns/store.py
DELETED
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
4
|
-
"""
|
|
5
|
-
Pattern Store - SQLite-backed pattern storage and retrieval.
|
|
6
|
-
|
|
7
|
-
Handles identity_patterns and pattern_examples tables,
|
|
8
|
-
including schema migration, CRUD operations, and profile-scoped queries.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import sqlite3
|
|
12
|
-
import json
|
|
13
|
-
import logging
|
|
14
|
-
from typing import Dict, List, Optional, Any
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger(__name__)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class PatternStore:
|
|
21
|
-
"""Handles pattern storage and retrieval."""
|
|
22
|
-
|
|
23
|
-
def __init__(self, db_path: Path):
|
|
24
|
-
self.db_path = db_path
|
|
25
|
-
self._init_tables()
|
|
26
|
-
|
|
27
|
-
def _init_tables(self):
|
|
28
|
-
"""Initialize pattern tables if they don't exist, or recreate if schema is incomplete."""
|
|
29
|
-
conn = sqlite3.connect(self.db_path)
|
|
30
|
-
cursor = conn.cursor()
|
|
31
|
-
|
|
32
|
-
# Check if existing tables have correct schema
|
|
33
|
-
for table_name, required_cols in [
|
|
34
|
-
('identity_patterns', {'pattern_type', 'key', 'value', 'confidence'}),
|
|
35
|
-
('pattern_examples', {'pattern_id', 'memory_id'}),
|
|
36
|
-
]:
|
|
37
|
-
cursor.execute(f"PRAGMA table_info({table_name})")
|
|
38
|
-
existing_cols = {row[1] for row in cursor.fetchall()}
|
|
39
|
-
if existing_cols and not required_cols.issubset(existing_cols):
|
|
40
|
-
logger.warning(f"Dropping incomplete {table_name} table (missing: {required_cols - existing_cols})")
|
|
41
|
-
cursor.execute(f'DROP TABLE IF EXISTS {table_name}')
|
|
42
|
-
|
|
43
|
-
# Identity patterns table
|
|
44
|
-
cursor.execute('''
|
|
45
|
-
CREATE TABLE IF NOT EXISTS identity_patterns (
|
|
46
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47
|
-
pattern_type TEXT NOT NULL,
|
|
48
|
-
key TEXT NOT NULL,
|
|
49
|
-
value TEXT NOT NULL,
|
|
50
|
-
confidence REAL DEFAULT 0.5,
|
|
51
|
-
evidence_count INTEGER DEFAULT 1,
|
|
52
|
-
memory_ids TEXT,
|
|
53
|
-
category TEXT,
|
|
54
|
-
profile TEXT DEFAULT 'default',
|
|
55
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
56
|
-
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
57
|
-
UNIQUE(pattern_type, key, category, profile)
|
|
58
|
-
)
|
|
59
|
-
''')
|
|
60
|
-
|
|
61
|
-
# Add profile column if upgrading from older schema
|
|
62
|
-
try:
|
|
63
|
-
cursor.execute('ALTER TABLE identity_patterns ADD COLUMN profile TEXT DEFAULT "default"')
|
|
64
|
-
except sqlite3.OperationalError:
|
|
65
|
-
pass # Column already exists
|
|
66
|
-
|
|
67
|
-
# Pattern examples table
|
|
68
|
-
cursor.execute('''
|
|
69
|
-
CREATE TABLE IF NOT EXISTS pattern_examples (
|
|
70
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
71
|
-
pattern_id INTEGER NOT NULL,
|
|
72
|
-
memory_id INTEGER NOT NULL,
|
|
73
|
-
example_text TEXT,
|
|
74
|
-
FOREIGN KEY (pattern_id) REFERENCES identity_patterns(id) ON DELETE CASCADE,
|
|
75
|
-
FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
|
|
76
|
-
)
|
|
77
|
-
''')
|
|
78
|
-
|
|
79
|
-
# Indexes
|
|
80
|
-
cursor.execute('CREATE INDEX IF NOT EXISTS idx_pattern_type ON identity_patterns(pattern_type)')
|
|
81
|
-
cursor.execute('CREATE INDEX IF NOT EXISTS idx_pattern_confidence ON identity_patterns(confidence)')
|
|
82
|
-
cursor.execute('CREATE INDEX IF NOT EXISTS idx_pattern_profile ON identity_patterns(profile)')
|
|
83
|
-
|
|
84
|
-
conn.commit()
|
|
85
|
-
conn.close()
|
|
86
|
-
|
|
87
|
-
def save_pattern(self, pattern: Dict[str, Any]) -> int:
|
|
88
|
-
"""Save or update a pattern (scoped by profile)."""
|
|
89
|
-
conn = sqlite3.connect(self.db_path)
|
|
90
|
-
cursor = conn.cursor()
|
|
91
|
-
profile = pattern.get('profile', 'default')
|
|
92
|
-
|
|
93
|
-
try:
|
|
94
|
-
# Check if pattern exists for this profile
|
|
95
|
-
cursor.execute('''
|
|
96
|
-
SELECT id FROM identity_patterns
|
|
97
|
-
WHERE pattern_type = ? AND key = ? AND category = ? AND profile = ?
|
|
98
|
-
''', (pattern['pattern_type'], pattern['key'], pattern['category'], profile))
|
|
99
|
-
|
|
100
|
-
existing = cursor.fetchone()
|
|
101
|
-
|
|
102
|
-
memory_ids_json = json.dumps(pattern['memory_ids'])
|
|
103
|
-
|
|
104
|
-
if existing:
|
|
105
|
-
# Update existing pattern
|
|
106
|
-
pattern_id = existing[0]
|
|
107
|
-
cursor.execute('''
|
|
108
|
-
UPDATE identity_patterns
|
|
109
|
-
SET value = ?, confidence = ?, evidence_count = ?,
|
|
110
|
-
memory_ids = ?, updated_at = CURRENT_TIMESTAMP
|
|
111
|
-
WHERE id = ?
|
|
112
|
-
''', (
|
|
113
|
-
pattern['value'],
|
|
114
|
-
pattern['confidence'],
|
|
115
|
-
pattern['evidence_count'],
|
|
116
|
-
memory_ids_json,
|
|
117
|
-
pattern_id
|
|
118
|
-
))
|
|
119
|
-
else:
|
|
120
|
-
# Insert new pattern
|
|
121
|
-
cursor.execute('''
|
|
122
|
-
INSERT INTO identity_patterns
|
|
123
|
-
(pattern_type, key, value, confidence, evidence_count, memory_ids, category, profile)
|
|
124
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
125
|
-
''', (
|
|
126
|
-
pattern['pattern_type'],
|
|
127
|
-
pattern['key'],
|
|
128
|
-
pattern['value'],
|
|
129
|
-
pattern['confidence'],
|
|
130
|
-
pattern['evidence_count'],
|
|
131
|
-
memory_ids_json,
|
|
132
|
-
pattern['category'],
|
|
133
|
-
profile
|
|
134
|
-
))
|
|
135
|
-
pattern_id = cursor.lastrowid
|
|
136
|
-
|
|
137
|
-
# Save examples
|
|
138
|
-
self._save_pattern_examples(cursor, pattern_id, pattern['memory_ids'], pattern['key'])
|
|
139
|
-
|
|
140
|
-
conn.commit()
|
|
141
|
-
return pattern_id
|
|
142
|
-
|
|
143
|
-
finally:
|
|
144
|
-
conn.close()
|
|
145
|
-
|
|
146
|
-
def _save_pattern_examples(self, cursor, pattern_id: int, memory_ids: List[int], key: str):
|
|
147
|
-
"""Save representative examples for pattern."""
|
|
148
|
-
# Clear old examples
|
|
149
|
-
cursor.execute('DELETE FROM pattern_examples WHERE pattern_id = ?', (pattern_id,))
|
|
150
|
-
|
|
151
|
-
# Save top 3 examples
|
|
152
|
-
for memory_id in memory_ids[:3]:
|
|
153
|
-
cursor.execute('SELECT content FROM memories WHERE id = ?', (memory_id,))
|
|
154
|
-
row = cursor.fetchone()
|
|
155
|
-
|
|
156
|
-
if row:
|
|
157
|
-
content = row[0]
|
|
158
|
-
excerpt = self._extract_relevant_excerpt(content, key)
|
|
159
|
-
|
|
160
|
-
cursor.execute('''
|
|
161
|
-
INSERT INTO pattern_examples (pattern_id, memory_id, example_text)
|
|
162
|
-
VALUES (?, ?, ?)
|
|
163
|
-
''', (pattern_id, memory_id, excerpt))
|
|
164
|
-
|
|
165
|
-
def _extract_relevant_excerpt(self, content: str, key: str) -> str:
|
|
166
|
-
"""Extract 150-char excerpt showing pattern."""
|
|
167
|
-
# Find first mention of key term
|
|
168
|
-
key_lower = key.lower().replace('_', ' ')
|
|
169
|
-
idx = content.lower().find(key_lower)
|
|
170
|
-
|
|
171
|
-
if idx >= 0:
|
|
172
|
-
start = max(0, idx - 50)
|
|
173
|
-
end = min(len(content), idx + 100)
|
|
174
|
-
excerpt = content[start:end]
|
|
175
|
-
return excerpt if len(excerpt) <= 150 else excerpt[:150] + '...'
|
|
176
|
-
|
|
177
|
-
# Fallback: first 150 chars
|
|
178
|
-
return content[:150] + ('...' if len(content) > 150 else '')
|
|
179
|
-
|
|
180
|
-
def get_patterns(self, min_confidence: float = 0.7, pattern_type: Optional[str] = None,
|
|
181
|
-
profile: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
182
|
-
"""Get patterns above confidence threshold, optionally filtered by profile."""
|
|
183
|
-
conn = sqlite3.connect(self.db_path)
|
|
184
|
-
try:
|
|
185
|
-
cursor = conn.cursor()
|
|
186
|
-
|
|
187
|
-
# Build query with optional filters
|
|
188
|
-
conditions = ['confidence >= ?']
|
|
189
|
-
params = [min_confidence]
|
|
190
|
-
|
|
191
|
-
if pattern_type:
|
|
192
|
-
conditions.append('pattern_type = ?')
|
|
193
|
-
params.append(pattern_type)
|
|
194
|
-
|
|
195
|
-
if profile:
|
|
196
|
-
conditions.append('profile = ?')
|
|
197
|
-
params.append(profile)
|
|
198
|
-
|
|
199
|
-
where_clause = ' AND '.join(conditions)
|
|
200
|
-
cursor.execute(f'''
|
|
201
|
-
SELECT id, pattern_type, key, value, confidence, evidence_count,
|
|
202
|
-
updated_at, created_at, category
|
|
203
|
-
FROM identity_patterns
|
|
204
|
-
WHERE {where_clause}
|
|
205
|
-
ORDER BY confidence DESC, evidence_count DESC
|
|
206
|
-
''', params)
|
|
207
|
-
|
|
208
|
-
patterns = []
|
|
209
|
-
for row in cursor.fetchall():
|
|
210
|
-
patterns.append({
|
|
211
|
-
'id': row[0],
|
|
212
|
-
'pattern_type': row[1],
|
|
213
|
-
'key': row[2],
|
|
214
|
-
'value': row[3],
|
|
215
|
-
'confidence': row[4],
|
|
216
|
-
'evidence_count': row[5],
|
|
217
|
-
'frequency': row[5],
|
|
218
|
-
'last_seen': row[6],
|
|
219
|
-
'created_at': row[7],
|
|
220
|
-
'category': row[8]
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
finally:
|
|
224
|
-
conn.close()
|
|
225
|
-
return patterns
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
4
|
-
"""
|
|
5
|
-
Terminology Learner - User-specific term definition extraction.
|
|
6
|
-
|
|
7
|
-
Learns how the user defines ambiguous terms like 'optimize', 'refactor', etc.
|
|
8
|
-
by analyzing contextual co-occurrence patterns across memories.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import sqlite3
|
|
12
|
-
import re
|
|
13
|
-
import logging
|
|
14
|
-
from typing import Dict, List, Optional, Any
|
|
15
|
-
from collections import Counter
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
|
|
18
|
-
logger = logging.getLogger(__name__)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class TerminologyLearner:
|
|
22
|
-
"""Learns user-specific definitions of common terms."""
|
|
23
|
-
|
|
24
|
-
def __init__(self, db_path: Path):
|
|
25
|
-
self.db_path = db_path
|
|
26
|
-
|
|
27
|
-
# Common ambiguous terms to learn
|
|
28
|
-
self.ambiguous_terms = [
|
|
29
|
-
'optimize', 'refactor', 'clean', 'simple',
|
|
30
|
-
'mvp', 'prototype', 'scale', 'production-ready',
|
|
31
|
-
'fix', 'improve', 'update', 'enhance'
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
def learn_terminology(self, memory_ids: List[int]) -> Dict[str, Dict[str, Any]]:
|
|
35
|
-
"""Learn user-specific term definitions."""
|
|
36
|
-
patterns = {}
|
|
37
|
-
|
|
38
|
-
conn = sqlite3.connect(self.db_path)
|
|
39
|
-
try:
|
|
40
|
-
cursor = conn.cursor()
|
|
41
|
-
|
|
42
|
-
for term in self.ambiguous_terms:
|
|
43
|
-
contexts = []
|
|
44
|
-
|
|
45
|
-
# Find all contexts where term appears
|
|
46
|
-
for memory_id in memory_ids:
|
|
47
|
-
cursor.execute('SELECT content FROM memories WHERE id = ?', (memory_id,))
|
|
48
|
-
row = cursor.fetchone()
|
|
49
|
-
|
|
50
|
-
if not row:
|
|
51
|
-
continue
|
|
52
|
-
|
|
53
|
-
content = row[0]
|
|
54
|
-
|
|
55
|
-
# Find term in content (case-insensitive)
|
|
56
|
-
pattern = r'\b' + re.escape(term) + r'\b'
|
|
57
|
-
for match in re.finditer(pattern, content, re.IGNORECASE):
|
|
58
|
-
term_idx = match.start()
|
|
59
|
-
|
|
60
|
-
# Extract 100-char window around term
|
|
61
|
-
start = max(0, term_idx - 100)
|
|
62
|
-
end = min(len(content), term_idx + len(term) + 100)
|
|
63
|
-
context_window = content[start:end]
|
|
64
|
-
|
|
65
|
-
contexts.append({
|
|
66
|
-
'memory_id': memory_id,
|
|
67
|
-
'context': context_window
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
# Analyze contexts to extract meaning (need at least 3 examples)
|
|
71
|
-
if len(contexts) >= 3:
|
|
72
|
-
definition = self._extract_definition(term, contexts)
|
|
73
|
-
|
|
74
|
-
if definition:
|
|
75
|
-
evidence_list = list(set([ctx['memory_id'] for ctx in contexts]))
|
|
76
|
-
|
|
77
|
-
# Confidence increases with more examples, capped at 0.95
|
|
78
|
-
confidence = min(0.95, 0.6 + (len(contexts) * 0.05))
|
|
79
|
-
|
|
80
|
-
patterns[term] = {
|
|
81
|
-
'pattern_type': 'terminology',
|
|
82
|
-
'key': term,
|
|
83
|
-
'value': definition,
|
|
84
|
-
'confidence': round(confidence, 2),
|
|
85
|
-
'evidence_count': len(evidence_list),
|
|
86
|
-
'memory_ids': evidence_list,
|
|
87
|
-
'category': 'general'
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
finally:
|
|
91
|
-
conn.close()
|
|
92
|
-
return patterns
|
|
93
|
-
|
|
94
|
-
def _extract_definition(self, term: str, contexts: List[Dict]) -> Optional[str]:
|
|
95
|
-
"""Extract definition from contexts using pattern matching."""
|
|
96
|
-
# Collect words near the term across all contexts
|
|
97
|
-
nearby_words = []
|
|
98
|
-
|
|
99
|
-
for ctx in contexts:
|
|
100
|
-
words = re.findall(r'\b\w+\b', ctx['context'].lower())
|
|
101
|
-
nearby_words.extend(words)
|
|
102
|
-
|
|
103
|
-
# Count word frequencies
|
|
104
|
-
word_counts = Counter(nearby_words)
|
|
105
|
-
|
|
106
|
-
# Remove the term itself and common stopwords
|
|
107
|
-
stopwords = {'the', 'a', 'an', 'is', 'to', 'for', 'of', 'in', 'on', 'at',
|
|
108
|
-
'and', 'or', 'but', 'with', 'from', 'by', 'this', 'that'}
|
|
109
|
-
word_counts = Counter({w: c for w, c in word_counts.items()
|
|
110
|
-
if w not in stopwords and w != term.lower()})
|
|
111
|
-
|
|
112
|
-
# Get top co-occurring words
|
|
113
|
-
top_words = [w for w, _ in word_counts.most_common(8)]
|
|
114
|
-
|
|
115
|
-
# Apply heuristic rules based on term and context
|
|
116
|
-
if term == 'optimize':
|
|
117
|
-
if any(w in top_words for w in ['performance', 'speed', 'faster', 'latency']):
|
|
118
|
-
return "Performance optimization (speed/latency)"
|
|
119
|
-
elif any(w in top_words for w in ['code', 'clean', 'refactor']):
|
|
120
|
-
return "Code quality optimization"
|
|
121
|
-
|
|
122
|
-
elif term == 'refactor':
|
|
123
|
-
if any(w in top_words for w in ['architecture', 'structure', 'design']):
|
|
124
|
-
return "Architecture change, not just renaming"
|
|
125
|
-
elif any(w in top_words for w in ['clean', 'organize', 'simplify']):
|
|
126
|
-
return "Code organization improvement"
|
|
127
|
-
|
|
128
|
-
elif term == 'mvp':
|
|
129
|
-
if any(w in top_words for w in ['core', 'basic', 'essential', 'minimal']):
|
|
130
|
-
return "Core features only, no polish"
|
|
131
|
-
|
|
132
|
-
elif term == 'production-ready':
|
|
133
|
-
if any(w in top_words for w in ['test', 'error', 'monitoring', 'deploy']):
|
|
134
|
-
return "Fully tested and monitored for deployment"
|
|
135
|
-
|
|
136
|
-
# Generic definition if specific pattern not matched
|
|
137
|
-
if len(top_words) >= 3:
|
|
138
|
-
return f"Commonly used with: {', '.join(top_words[:3])}"
|
|
139
|
-
|
|
140
|
-
return None
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
# Copyright (c) 2026 SuperLocalMemory (superlocalmemory.com)
|
|
4
|
-
"""
|
|
5
|
-
ProvenanceTracker — Tracks the origin and lineage of every memory.
|
|
6
|
-
|
|
7
|
-
Adds provenance columns to the memories table:
|
|
8
|
-
created_by — Agent ID that created this memory (e.g., "mcp:claude-desktop")
|
|
9
|
-
source_protocol — Protocol used (mcp, cli, rest, python)
|
|
10
|
-
trust_score — Trust score at time of creation (default 1.0)
|
|
11
|
-
provenance_chain — JSON array of derivation history
|
|
12
|
-
|
|
13
|
-
This enables:
|
|
14
|
-
- "Who wrote this?" queries for the dashboard
|
|
15
|
-
- Trust-weighted recall (v2.6 — higher trust = higher ranking)
|
|
16
|
-
- Audit trail for enterprise compliance (v3.0)
|
|
17
|
-
- Memory lineage tracking (if agent B derives from agent A's memory)
|
|
18
|
-
|
|
19
|
-
Column migration is safe: uses ALTER TABLE ADD COLUMN with try/except.
|
|
20
|
-
Old databases without provenance columns work fine — values default.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
import json
|
|
24
|
-
import logging
|
|
25
|
-
import sqlite3
|
|
26
|
-
import threading
|
|
27
|
-
from datetime import datetime
|
|
28
|
-
from pathlib import Path
|
|
29
|
-
from typing import Optional, Dict, Any
|
|
30
|
-
|
|
31
|
-
logger = logging.getLogger("superlocalmemory.provenance")
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class ProvenanceTracker:
|
|
35
|
-
"""
|
|
36
|
-
Tracks provenance (origin) metadata for memories.
|
|
37
|
-
|
|
38
|
-
Singleton per database path. Thread-safe.
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
_instances: Dict[str, "ProvenanceTracker"] = {}
|
|
42
|
-
_instances_lock = threading.Lock()
|
|
43
|
-
|
|
44
|
-
@classmethod
|
|
45
|
-
def get_instance(cls, db_path: Optional[Path] = None) -> "ProvenanceTracker":
|
|
46
|
-
"""Get or create the singleton ProvenanceTracker."""
|
|
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
|
-
self._init_schema()
|
|
69
|
-
logger.info("ProvenanceTracker initialized: db=%s", self.db_path)
|
|
70
|
-
|
|
71
|
-
def _init_schema(self):
|
|
72
|
-
"""
|
|
73
|
-
Add provenance columns to memories table (safe migration).
|
|
74
|
-
|
|
75
|
-
Uses ALTER TABLE ADD COLUMN wrapped in try/except — safe for:
|
|
76
|
-
- Fresh databases (columns don't exist yet)
|
|
77
|
-
- Existing databases (columns might already exist)
|
|
78
|
-
- Concurrent migrations (OperationalError caught)
|
|
79
|
-
"""
|
|
80
|
-
provenance_columns = {
|
|
81
|
-
'created_by': "TEXT DEFAULT 'user'",
|
|
82
|
-
'source_protocol': "TEXT DEFAULT 'cli'",
|
|
83
|
-
'trust_score': "REAL DEFAULT 1.0",
|
|
84
|
-
'provenance_chain': "TEXT DEFAULT '[]'",
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
try:
|
|
88
|
-
from db_connection_manager import DbConnectionManager
|
|
89
|
-
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
90
|
-
|
|
91
|
-
def _migrate(conn):
|
|
92
|
-
cursor = conn.cursor()
|
|
93
|
-
|
|
94
|
-
# Check existing columns
|
|
95
|
-
cursor.execute("PRAGMA table_info(memories)")
|
|
96
|
-
existing = {row[1] for row in cursor.fetchall()}
|
|
97
|
-
|
|
98
|
-
for col_name, col_type in provenance_columns.items():
|
|
99
|
-
if col_name not in existing:
|
|
100
|
-
try:
|
|
101
|
-
cursor.execute(f"ALTER TABLE memories ADD COLUMN {col_name} {col_type}")
|
|
102
|
-
except sqlite3.OperationalError:
|
|
103
|
-
pass # Column already exists (concurrent migration)
|
|
104
|
-
|
|
105
|
-
# Index for provenance queries
|
|
106
|
-
try:
|
|
107
|
-
cursor.execute("CREATE INDEX IF NOT EXISTS idx_created_by ON memories(created_by)")
|
|
108
|
-
except sqlite3.OperationalError:
|
|
109
|
-
pass
|
|
110
|
-
|
|
111
|
-
try:
|
|
112
|
-
cursor.execute("CREATE INDEX IF NOT EXISTS idx_source_protocol ON memories(source_protocol)")
|
|
113
|
-
except sqlite3.OperationalError:
|
|
114
|
-
pass
|
|
115
|
-
|
|
116
|
-
conn.commit()
|
|
117
|
-
|
|
118
|
-
mgr.execute_write(_migrate)
|
|
119
|
-
|
|
120
|
-
except ImportError:
|
|
121
|
-
conn = sqlite3.connect(str(self.db_path))
|
|
122
|
-
cursor = conn.cursor()
|
|
123
|
-
|
|
124
|
-
cursor.execute("PRAGMA table_info(memories)")
|
|
125
|
-
existing = {row[1] for row in cursor.fetchall()}
|
|
126
|
-
|
|
127
|
-
for col_name, col_type in provenance_columns.items():
|
|
128
|
-
if col_name not in existing:
|
|
129
|
-
try:
|
|
130
|
-
cursor.execute(f"ALTER TABLE memories ADD COLUMN {col_name} {col_type}")
|
|
131
|
-
except sqlite3.OperationalError:
|
|
132
|
-
pass
|
|
133
|
-
|
|
134
|
-
try:
|
|
135
|
-
cursor.execute("CREATE INDEX IF NOT EXISTS idx_created_by ON memories(created_by)")
|
|
136
|
-
except sqlite3.OperationalError:
|
|
137
|
-
pass
|
|
138
|
-
try:
|
|
139
|
-
cursor.execute("CREATE INDEX IF NOT EXISTS idx_source_protocol ON memories(source_protocol)")
|
|
140
|
-
except sqlite3.OperationalError:
|
|
141
|
-
pass
|
|
142
|
-
|
|
143
|
-
conn.commit()
|
|
144
|
-
conn.close()
|
|
145
|
-
|
|
146
|
-
# =========================================================================
|
|
147
|
-
# Record Provenance
|
|
148
|
-
# =========================================================================
|
|
149
|
-
|
|
150
|
-
def record_provenance(
|
|
151
|
-
self,
|
|
152
|
-
memory_id: int,
|
|
153
|
-
created_by: str = "user",
|
|
154
|
-
source_protocol: str = "cli",
|
|
155
|
-
trust_score: float = 1.0,
|
|
156
|
-
derived_from: Optional[int] = None,
|
|
157
|
-
) -> None:
|
|
158
|
-
"""
|
|
159
|
-
Record provenance metadata for a memory.
|
|
160
|
-
|
|
161
|
-
Called after a memory is created. Updates the provenance columns
|
|
162
|
-
on the memories table row.
|
|
163
|
-
|
|
164
|
-
Args:
|
|
165
|
-
memory_id: ID of the memory to annotate
|
|
166
|
-
created_by: Agent ID that created this memory
|
|
167
|
-
source_protocol: Protocol used (mcp, cli, rest, python)
|
|
168
|
-
trust_score: Trust score at time of creation
|
|
169
|
-
derived_from: If this memory was derived from another, its ID
|
|
170
|
-
"""
|
|
171
|
-
trust_score = max(0.0, min(1.0, trust_score))
|
|
172
|
-
chain = json.dumps([derived_from] if derived_from else [])
|
|
173
|
-
|
|
174
|
-
try:
|
|
175
|
-
from db_connection_manager import DbConnectionManager
|
|
176
|
-
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
177
|
-
|
|
178
|
-
def _update(conn):
|
|
179
|
-
conn.execute('''
|
|
180
|
-
UPDATE memories
|
|
181
|
-
SET created_by = ?, source_protocol = ?,
|
|
182
|
-
trust_score = ?, provenance_chain = ?
|
|
183
|
-
WHERE id = ?
|
|
184
|
-
''', (created_by, source_protocol, trust_score, chain, memory_id))
|
|
185
|
-
conn.commit()
|
|
186
|
-
|
|
187
|
-
mgr.execute_write(_update)
|
|
188
|
-
|
|
189
|
-
except Exception as e:
|
|
190
|
-
# Provenance failure must never break core operations
|
|
191
|
-
logger.error("Failed to record provenance for memory %d: %s", memory_id, e)
|
|
192
|
-
|
|
193
|
-
# =========================================================================
|
|
194
|
-
# Query Provenance
|
|
195
|
-
# =========================================================================
|
|
196
|
-
|
|
197
|
-
def get_provenance(self, memory_id: int) -> Optional[dict]:
|
|
198
|
-
"""
|
|
199
|
-
Get provenance metadata for a specific memory.
|
|
200
|
-
|
|
201
|
-
Args:
|
|
202
|
-
memory_id: Memory ID to query
|
|
203
|
-
|
|
204
|
-
Returns:
|
|
205
|
-
Dict with created_by, source_protocol, trust_score, provenance_chain
|
|
206
|
-
or None if memory not found
|
|
207
|
-
"""
|
|
208
|
-
try:
|
|
209
|
-
from db_connection_manager import DbConnectionManager
|
|
210
|
-
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
211
|
-
|
|
212
|
-
with mgr.read_connection() as conn:
|
|
213
|
-
cursor = conn.cursor()
|
|
214
|
-
cursor.execute("""
|
|
215
|
-
SELECT id, created_by, source_protocol, trust_score, provenance_chain
|
|
216
|
-
FROM memories WHERE id = ?
|
|
217
|
-
""", (memory_id,))
|
|
218
|
-
row = cursor.fetchone()
|
|
219
|
-
|
|
220
|
-
if not row:
|
|
221
|
-
return None
|
|
222
|
-
|
|
223
|
-
chain = []
|
|
224
|
-
try:
|
|
225
|
-
chain = json.loads(row[4]) if row[4] else []
|
|
226
|
-
except (json.JSONDecodeError, TypeError):
|
|
227
|
-
pass
|
|
228
|
-
|
|
229
|
-
return {
|
|
230
|
-
"memory_id": row[0],
|
|
231
|
-
"created_by": row[1] or "user",
|
|
232
|
-
"source_protocol": row[2] or "cli",
|
|
233
|
-
"trust_score": row[3] if row[3] is not None else 1.0,
|
|
234
|
-
"provenance_chain": chain,
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
except Exception as e:
|
|
238
|
-
logger.error("Failed to get provenance for memory %d: %s", memory_id, e)
|
|
239
|
-
return None
|
|
240
|
-
|
|
241
|
-
def get_memories_by_agent(self, agent_id: str, limit: int = 50) -> list:
|
|
242
|
-
"""
|
|
243
|
-
Get all memories created by a specific agent.
|
|
244
|
-
|
|
245
|
-
Args:
|
|
246
|
-
agent_id: Agent ID to query
|
|
247
|
-
limit: Max results
|
|
248
|
-
|
|
249
|
-
Returns:
|
|
250
|
-
List of (memory_id, created_at, trust_score) tuples
|
|
251
|
-
"""
|
|
252
|
-
try:
|
|
253
|
-
from db_connection_manager import DbConnectionManager
|
|
254
|
-
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
255
|
-
|
|
256
|
-
with mgr.read_connection() as conn:
|
|
257
|
-
cursor = conn.cursor()
|
|
258
|
-
cursor.execute("""
|
|
259
|
-
SELECT id, created_at, trust_score
|
|
260
|
-
FROM memories
|
|
261
|
-
WHERE created_by = ?
|
|
262
|
-
ORDER BY created_at DESC
|
|
263
|
-
LIMIT ?
|
|
264
|
-
""", (agent_id, limit))
|
|
265
|
-
return [
|
|
266
|
-
{"memory_id": r[0], "created_at": r[1], "trust_score": r[2]}
|
|
267
|
-
for r in cursor.fetchall()
|
|
268
|
-
]
|
|
269
|
-
|
|
270
|
-
except Exception as e:
|
|
271
|
-
logger.error("Failed to get memories by agent %s: %s", agent_id, e)
|
|
272
|
-
return []
|
|
273
|
-
|
|
274
|
-
def get_provenance_stats(self) -> dict:
|
|
275
|
-
"""Get provenance statistics across all memories."""
|
|
276
|
-
try:
|
|
277
|
-
from db_connection_manager import DbConnectionManager
|
|
278
|
-
mgr = DbConnectionManager.get_instance(self.db_path)
|
|
279
|
-
|
|
280
|
-
with mgr.read_connection() as conn:
|
|
281
|
-
cursor = conn.cursor()
|
|
282
|
-
|
|
283
|
-
cursor.execute("""
|
|
284
|
-
SELECT created_by, COUNT(*) as count
|
|
285
|
-
FROM memories
|
|
286
|
-
WHERE created_by IS NOT NULL
|
|
287
|
-
GROUP BY created_by
|
|
288
|
-
ORDER BY count DESC
|
|
289
|
-
""")
|
|
290
|
-
by_agent = dict(cursor.fetchall())
|
|
291
|
-
|
|
292
|
-
cursor.execute("""
|
|
293
|
-
SELECT source_protocol, COUNT(*) as count
|
|
294
|
-
FROM memories
|
|
295
|
-
WHERE source_protocol IS NOT NULL
|
|
296
|
-
GROUP BY source_protocol
|
|
297
|
-
ORDER BY count DESC
|
|
298
|
-
""")
|
|
299
|
-
by_protocol = dict(cursor.fetchall())
|
|
300
|
-
|
|
301
|
-
cursor.execute("SELECT AVG(trust_score) FROM memories WHERE trust_score IS NOT NULL")
|
|
302
|
-
avg_trust = cursor.fetchone()[0]
|
|
303
|
-
|
|
304
|
-
return {
|
|
305
|
-
"by_agent": by_agent,
|
|
306
|
-
"by_protocol": by_protocol,
|
|
307
|
-
"avg_trust_score": round(avg_trust, 3) if avg_trust else 1.0,
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
except Exception as e:
|
|
311
|
-
logger.error("Failed to get provenance stats: %s", e)
|
|
312
|
-
return {"by_agent": {}, "by_protocol": {}, "error": str(e)}
|