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
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
# Copyright (c) 2026 Qualixar / SuperLocalMemory (superlocalmemory.com)
|
|
4
|
+
# Part of Qualixar | Author: Varun Pratap Bhardwaj (qualixar.com | varunpratap.com)
|
|
5
|
+
"""
|
|
6
|
+
SourceQualityScorer -- Beta-binomial source quality scoring for V3 learning.
|
|
7
|
+
|
|
8
|
+
Each memory source (agent, URL, manual, etc.) gets a quality score based on
|
|
9
|
+
how often its memories are confirmed vs contradicted or ignored.
|
|
10
|
+
|
|
11
|
+
Scoring (Beta-Binomial with Laplace smoothing):
|
|
12
|
+
quality = (alpha + positives) / (alpha + beta + total)
|
|
13
|
+
|
|
14
|
+
With alpha=1, beta=1 (uniform prior):
|
|
15
|
+
- New source, 0 evidence -> 1/2 = 0.50
|
|
16
|
+
- 8 positive out of 10 -> 9/12 = 0.75
|
|
17
|
+
- 1 positive out of 10 -> 2/12 = 0.17
|
|
18
|
+
|
|
19
|
+
Storage:
|
|
20
|
+
Uses direct sqlite3 with a self-contained ``source_quality`` table.
|
|
21
|
+
NOT coupled to V3 DatabaseManager.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
import sqlite3
|
|
28
|
+
import threading
|
|
29
|
+
from datetime import datetime, timezone
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Any, Dict, Optional
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger("superlocalmemory.learning.source_quality")
|
|
34
|
+
|
|
35
|
+
# Beta-Binomial prior (Laplace / uniform)
|
|
36
|
+
_ALPHA = 1.0
|
|
37
|
+
_BETA = 1.0
|
|
38
|
+
|
|
39
|
+
# Default quality for unknown sources = alpha / (alpha + beta)
|
|
40
|
+
DEFAULT_QUALITY = _ALPHA / (_ALPHA + _BETA) # 0.5
|
|
41
|
+
|
|
42
|
+
_CREATE_TABLE = """
|
|
43
|
+
CREATE TABLE IF NOT EXISTS source_quality (
|
|
44
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
45
|
+
profile_id TEXT NOT NULL,
|
|
46
|
+
source_id TEXT NOT NULL,
|
|
47
|
+
alpha REAL NOT NULL DEFAULT 1.0,
|
|
48
|
+
beta REAL NOT NULL DEFAULT 1.0,
|
|
49
|
+
updated_at TEXT NOT NULL
|
|
50
|
+
)
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
_CREATE_UNIQUE = """
|
|
54
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_sq_profile_source
|
|
55
|
+
ON source_quality (profile_id, source_id)
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _utcnow_iso() -> str:
|
|
60
|
+
"""Return current UTC time as ISO-8601 string."""
|
|
61
|
+
return datetime.now(timezone.utc).isoformat()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class SourceQualityScorer:
|
|
65
|
+
"""
|
|
66
|
+
Beta-binomial source quality scoring.
|
|
67
|
+
|
|
68
|
+
Maintains per-(profile, source) alpha/beta parameters. Positive
|
|
69
|
+
outcomes increment alpha; negative outcomes increment beta.
|
|
70
|
+
Quality = alpha / (alpha + beta).
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
db_path: Path to the sqlite3 database file.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, db_path: Path) -> None:
|
|
77
|
+
self._db_path = Path(db_path)
|
|
78
|
+
self._lock = threading.Lock()
|
|
79
|
+
self._ensure_schema()
|
|
80
|
+
|
|
81
|
+
# ------------------------------------------------------------------
|
|
82
|
+
# Schema
|
|
83
|
+
# ------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
def _ensure_schema(self) -> None:
|
|
86
|
+
conn = self._connect()
|
|
87
|
+
try:
|
|
88
|
+
conn.execute(_CREATE_TABLE)
|
|
89
|
+
conn.execute(_CREATE_UNIQUE)
|
|
90
|
+
conn.commit()
|
|
91
|
+
finally:
|
|
92
|
+
conn.close()
|
|
93
|
+
|
|
94
|
+
def _connect(self) -> sqlite3.Connection:
|
|
95
|
+
conn = sqlite3.connect(str(self._db_path), timeout=10)
|
|
96
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
97
|
+
conn.execute("PRAGMA busy_timeout=5000")
|
|
98
|
+
conn.row_factory = sqlite3.Row
|
|
99
|
+
return conn
|
|
100
|
+
|
|
101
|
+
# ------------------------------------------------------------------
|
|
102
|
+
# Public API: record outcome
|
|
103
|
+
# ------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
def record_outcome(
|
|
106
|
+
self,
|
|
107
|
+
profile_id: str,
|
|
108
|
+
source_id: str,
|
|
109
|
+
outcome: str,
|
|
110
|
+
) -> None:
|
|
111
|
+
"""
|
|
112
|
+
Record an observation for a source.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
profile_id: Profile context.
|
|
116
|
+
source_id: Identifier of the source (agent name, URL, etc.).
|
|
117
|
+
outcome: ``"positive"`` or ``"negative"``.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
ValueError: If outcome is not ``"positive"`` or ``"negative"``.
|
|
121
|
+
"""
|
|
122
|
+
if outcome not in ("positive", "negative"):
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"outcome must be 'positive' or 'negative', got {outcome!r}"
|
|
125
|
+
)
|
|
126
|
+
if not profile_id or not source_id:
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
now = _utcnow_iso()
|
|
130
|
+
|
|
131
|
+
with self._lock:
|
|
132
|
+
conn = self._connect()
|
|
133
|
+
try:
|
|
134
|
+
# Ensure row exists (INSERT OR IGNORE with defaults)
|
|
135
|
+
conn.execute(
|
|
136
|
+
"INSERT OR IGNORE INTO source_quality "
|
|
137
|
+
"(profile_id, source_id, alpha, beta, updated_at) "
|
|
138
|
+
"VALUES (?, ?, ?, ?, ?)",
|
|
139
|
+
(profile_id, source_id, _ALPHA, _BETA, now),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Update the appropriate parameter
|
|
143
|
+
if outcome == "positive":
|
|
144
|
+
conn.execute(
|
|
145
|
+
"UPDATE source_quality "
|
|
146
|
+
"SET alpha = alpha + 1.0, updated_at = ? "
|
|
147
|
+
"WHERE profile_id = ? AND source_id = ?",
|
|
148
|
+
(now, profile_id, source_id),
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
conn.execute(
|
|
152
|
+
"UPDATE source_quality "
|
|
153
|
+
"SET beta = beta + 1.0, updated_at = ? "
|
|
154
|
+
"WHERE profile_id = ? AND source_id = ?",
|
|
155
|
+
(now, profile_id, source_id),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
conn.commit()
|
|
159
|
+
finally:
|
|
160
|
+
conn.close()
|
|
161
|
+
|
|
162
|
+
# ------------------------------------------------------------------
|
|
163
|
+
# Public API: read quality
|
|
164
|
+
# ------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
def get_quality(self, profile_id: str, source_id: str) -> float:
|
|
167
|
+
"""
|
|
168
|
+
Get the quality score for a specific source.
|
|
169
|
+
|
|
170
|
+
Returns the Beta-binomial posterior mean:
|
|
171
|
+
quality = alpha / (alpha + beta)
|
|
172
|
+
|
|
173
|
+
If the source has never been observed, returns the prior
|
|
174
|
+
mean (0.5).
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
profile_id: Profile context.
|
|
178
|
+
source_id: Source identifier.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Quality score in [0.0, 1.0].
|
|
182
|
+
"""
|
|
183
|
+
conn = self._connect()
|
|
184
|
+
try:
|
|
185
|
+
row = conn.execute(
|
|
186
|
+
"SELECT alpha, beta FROM source_quality "
|
|
187
|
+
"WHERE profile_id = ? AND source_id = ?",
|
|
188
|
+
(profile_id, source_id),
|
|
189
|
+
).fetchone()
|
|
190
|
+
|
|
191
|
+
if row is None:
|
|
192
|
+
return DEFAULT_QUALITY
|
|
193
|
+
|
|
194
|
+
alpha = float(row["alpha"])
|
|
195
|
+
beta = float(row["beta"])
|
|
196
|
+
denom = alpha + beta
|
|
197
|
+
if denom <= 0:
|
|
198
|
+
return DEFAULT_QUALITY
|
|
199
|
+
return alpha / denom
|
|
200
|
+
finally:
|
|
201
|
+
conn.close()
|
|
202
|
+
|
|
203
|
+
def get_all_qualities(self, profile_id: str) -> Dict[str, float]:
|
|
204
|
+
"""
|
|
205
|
+
Get quality scores for all sources observed under a profile.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
profile_id: Profile context.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Dict mapping source_id -> quality score (0.0 to 1.0).
|
|
212
|
+
"""
|
|
213
|
+
conn = self._connect()
|
|
214
|
+
try:
|
|
215
|
+
rows = conn.execute(
|
|
216
|
+
"SELECT source_id, alpha, beta FROM source_quality "
|
|
217
|
+
"WHERE profile_id = ?",
|
|
218
|
+
(profile_id,),
|
|
219
|
+
).fetchall()
|
|
220
|
+
|
|
221
|
+
result: Dict[str, float] = {}
|
|
222
|
+
for r in rows:
|
|
223
|
+
alpha = float(r["alpha"])
|
|
224
|
+
beta = float(r["beta"])
|
|
225
|
+
denom = alpha + beta
|
|
226
|
+
score = alpha / denom if denom > 0 else DEFAULT_QUALITY
|
|
227
|
+
result[r["source_id"]] = score
|
|
228
|
+
return result
|
|
229
|
+
finally:
|
|
230
|
+
conn.close()
|
|
231
|
+
|
|
232
|
+
# ------------------------------------------------------------------
|
|
233
|
+
# Public API: diagnostics
|
|
234
|
+
# ------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
def get_detailed(
|
|
237
|
+
self, profile_id: str, source_id: str,
|
|
238
|
+
) -> Dict[str, Any]:
|
|
239
|
+
"""
|
|
240
|
+
Get detailed quality information for a single source.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Dict with alpha, beta, quality, updated_at.
|
|
244
|
+
Returns defaults if the source has not been observed.
|
|
245
|
+
"""
|
|
246
|
+
conn = self._connect()
|
|
247
|
+
try:
|
|
248
|
+
row = conn.execute(
|
|
249
|
+
"SELECT alpha, beta, updated_at FROM source_quality "
|
|
250
|
+
"WHERE profile_id = ? AND source_id = ?",
|
|
251
|
+
(profile_id, source_id),
|
|
252
|
+
).fetchone()
|
|
253
|
+
|
|
254
|
+
if row is None:
|
|
255
|
+
return {
|
|
256
|
+
"alpha": _ALPHA,
|
|
257
|
+
"beta": _BETA,
|
|
258
|
+
"quality": DEFAULT_QUALITY,
|
|
259
|
+
"updated_at": None,
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
alpha = float(row["alpha"])
|
|
263
|
+
beta = float(row["beta"])
|
|
264
|
+
denom = alpha + beta
|
|
265
|
+
return {
|
|
266
|
+
"alpha": alpha,
|
|
267
|
+
"beta": beta,
|
|
268
|
+
"quality": alpha / denom if denom > 0 else DEFAULT_QUALITY,
|
|
269
|
+
"updated_at": row["updated_at"],
|
|
270
|
+
}
|
|
271
|
+
finally:
|
|
272
|
+
conn.close()
|
|
273
|
+
|
|
274
|
+
def get_all_detailed(self, profile_id: str) -> Dict[str, Dict[str, Any]]:
|
|
275
|
+
"""
|
|
276
|
+
Get detailed quality data for all sources under a profile.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Dict mapping source_id -> detail dict.
|
|
280
|
+
"""
|
|
281
|
+
conn = self._connect()
|
|
282
|
+
try:
|
|
283
|
+
rows = conn.execute(
|
|
284
|
+
"SELECT source_id, alpha, beta, updated_at "
|
|
285
|
+
"FROM source_quality WHERE profile_id = ? "
|
|
286
|
+
"ORDER BY (alpha / (alpha + beta)) DESC",
|
|
287
|
+
(profile_id,),
|
|
288
|
+
).fetchall()
|
|
289
|
+
|
|
290
|
+
result: Dict[str, Dict[str, Any]] = {}
|
|
291
|
+
for r in rows:
|
|
292
|
+
alpha = float(r["alpha"])
|
|
293
|
+
beta = float(r["beta"])
|
|
294
|
+
denom = alpha + beta
|
|
295
|
+
result[r["source_id"]] = {
|
|
296
|
+
"alpha": alpha,
|
|
297
|
+
"beta": beta,
|
|
298
|
+
"quality": alpha / denom if denom > 0 else DEFAULT_QUALITY,
|
|
299
|
+
"updated_at": r["updated_at"],
|
|
300
|
+
}
|
|
301
|
+
return result
|
|
302
|
+
finally:
|
|
303
|
+
conn.close()
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Workflow pattern miner -- sliding-window sequence and temporal mining.
|
|
6
|
+
|
|
7
|
+
Detects repeating workflow sequences and time-of-day activity patterns
|
|
8
|
+
from memory creation timestamps and content. Uses n-gram sliding
|
|
9
|
+
windows (length 2-5) over a classified activity stream.
|
|
10
|
+
|
|
11
|
+
Seven activity types: docs, architecture, code, test, debug, deploy, config.
|
|
12
|
+
|
|
13
|
+
Ported from V2 WorkflowPatternMiner with V2 LearningDB deps removed.
|
|
14
|
+
Direct sqlite3 for storage.
|
|
15
|
+
|
|
16
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import re
|
|
24
|
+
import sqlite3
|
|
25
|
+
from collections import Counter
|
|
26
|
+
from datetime import UTC, datetime
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any, Optional
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# Activity taxonomy (7 categories)
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
ACTIVITY_TYPES: dict[str, list[str]] = {
|
|
37
|
+
"docs": [
|
|
38
|
+
"documentation", "readme", "wiki", "spec", "prd",
|
|
39
|
+
"design doc", "changelog", "api doc",
|
|
40
|
+
],
|
|
41
|
+
"architecture": [
|
|
42
|
+
"architecture", "diagram", "system design", "schema",
|
|
43
|
+
"api design", "data model", "erd",
|
|
44
|
+
],
|
|
45
|
+
"code": [
|
|
46
|
+
"implement", "function", "class", "module", "refactor",
|
|
47
|
+
"code", "feature", "component",
|
|
48
|
+
],
|
|
49
|
+
"test": [
|
|
50
|
+
"test", "pytest", "jest", "coverage", "assertion",
|
|
51
|
+
"mock", "spec", "unit test",
|
|
52
|
+
],
|
|
53
|
+
"debug": [
|
|
54
|
+
"bug", "fix", "error", "stack trace", "debug",
|
|
55
|
+
"issue", "exception", "traceback",
|
|
56
|
+
],
|
|
57
|
+
"deploy": [
|
|
58
|
+
"deploy", "docker", "ci/cd", "pipeline", "release",
|
|
59
|
+
"production", "staging", "build",
|
|
60
|
+
],
|
|
61
|
+
"config": [
|
|
62
|
+
"config", "env", "settings", "setup", "install",
|
|
63
|
+
"dependency", "package", "requirements",
|
|
64
|
+
],
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Pre-compiled regex per keyword for word-boundary matching
|
|
68
|
+
_KEYWORD_PATTERNS: list[tuple[str, re.Pattern]] = []
|
|
69
|
+
for _act, _kws in ACTIVITY_TYPES.items():
|
|
70
|
+
for _kw in _kws:
|
|
71
|
+
_KEYWORD_PATTERNS.append(
|
|
72
|
+
(_act, re.compile(r"\b" + re.escape(_kw) + r"\b", re.IGNORECASE))
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# Schema for local action log
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
_SCHEMA = """
|
|
80
|
+
CREATE TABLE IF NOT EXISTS workflow_actions (
|
|
81
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
82
|
+
profile_id TEXT NOT NULL,
|
|
83
|
+
action TEXT NOT NULL,
|
|
84
|
+
metadata TEXT DEFAULT '{}',
|
|
85
|
+
created_at TEXT NOT NULL
|
|
86
|
+
);
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_wf_profile
|
|
88
|
+
ON workflow_actions(profile_id, created_at);
|
|
89
|
+
|
|
90
|
+
CREATE TABLE IF NOT EXISTS workflow_patterns (
|
|
91
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
92
|
+
profile_id TEXT NOT NULL,
|
|
93
|
+
pattern_type TEXT NOT NULL,
|
|
94
|
+
pattern_key TEXT NOT NULL,
|
|
95
|
+
pattern_value TEXT DEFAULT '{}',
|
|
96
|
+
confidence REAL DEFAULT 0.0,
|
|
97
|
+
evidence_count INTEGER DEFAULT 0,
|
|
98
|
+
created_at TEXT NOT NULL
|
|
99
|
+
);
|
|
100
|
+
CREATE INDEX IF NOT EXISTS idx_wp_profile
|
|
101
|
+
ON workflow_patterns(profile_id, pattern_type);
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class WorkflowMiner:
|
|
106
|
+
"""Mine workflow sequences and temporal patterns from memory content.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
db_path: Path to a sqlite database. If the file does not exist
|
|
110
|
+
it is created with the required schema.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, db_path: Path | str) -> None:
|
|
114
|
+
self._db_path = Path(db_path)
|
|
115
|
+
self._ensure_schema()
|
|
116
|
+
|
|
117
|
+
# ------------------------------------------------------------------
|
|
118
|
+
# Public API
|
|
119
|
+
# ------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
def record_action(
|
|
122
|
+
self, profile_id: str, action: str, metadata: dict[str, Any] | None = None
|
|
123
|
+
) -> None:
|
|
124
|
+
"""Record a user action for later mining."""
|
|
125
|
+
now = datetime.now(UTC).isoformat()
|
|
126
|
+
meta_json = json.dumps(metadata or {})
|
|
127
|
+
conn = sqlite3.connect(str(self._db_path))
|
|
128
|
+
try:
|
|
129
|
+
conn.execute(
|
|
130
|
+
"INSERT INTO workflow_actions (profile_id, action, metadata, created_at) "
|
|
131
|
+
"VALUES (?, ?, ?, ?)",
|
|
132
|
+
(profile_id, action, meta_json, now),
|
|
133
|
+
)
|
|
134
|
+
conn.commit()
|
|
135
|
+
finally:
|
|
136
|
+
conn.close()
|
|
137
|
+
|
|
138
|
+
def mine(self, profile_id: str, min_support: float = 0.3) -> list[dict]:
|
|
139
|
+
"""Mine workflow sequence patterns for a profile.
|
|
140
|
+
|
|
141
|
+
Returns a list of pattern dicts sorted by support descending.
|
|
142
|
+
"""
|
|
143
|
+
actions = self._fetch_actions(profile_id)
|
|
144
|
+
if len(actions) < 2:
|
|
145
|
+
return []
|
|
146
|
+
|
|
147
|
+
activity_stream = [a["action"] for a in actions]
|
|
148
|
+
return self._mine_sequences(activity_stream, min_support)
|
|
149
|
+
|
|
150
|
+
def mine_from_memories(
|
|
151
|
+
self,
|
|
152
|
+
memories: list[dict],
|
|
153
|
+
min_support: float = 0.3,
|
|
154
|
+
) -> list[dict]:
|
|
155
|
+
"""Mine sequences from a pre-fetched list of memory dicts.
|
|
156
|
+
|
|
157
|
+
Each dict should have a ``content`` key.
|
|
158
|
+
"""
|
|
159
|
+
stream: list[str] = []
|
|
160
|
+
for mem in memories:
|
|
161
|
+
activity = classify_activity(mem.get("content", ""))
|
|
162
|
+
if activity != "unknown":
|
|
163
|
+
stream.append(activity)
|
|
164
|
+
if len(stream) < 2:
|
|
165
|
+
return []
|
|
166
|
+
return self._mine_sequences(stream, min_support)
|
|
167
|
+
|
|
168
|
+
def mine_temporal(self, memories: list[dict]) -> dict[str, dict]:
|
|
169
|
+
"""Detect time-of-day activity preferences.
|
|
170
|
+
|
|
171
|
+
Returns dict keyed by bucket (morning/afternoon/evening/night).
|
|
172
|
+
Buckets with < 5 evidence memories are omitted.
|
|
173
|
+
"""
|
|
174
|
+
buckets: dict[str, Counter] = {
|
|
175
|
+
"morning": Counter(),
|
|
176
|
+
"afternoon": Counter(),
|
|
177
|
+
"evening": Counter(),
|
|
178
|
+
"night": Counter(),
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for mem in memories:
|
|
182
|
+
activity = classify_activity(mem.get("content", ""))
|
|
183
|
+
if activity == "unknown":
|
|
184
|
+
continue
|
|
185
|
+
hour = _parse_hour(mem.get("created_at"))
|
|
186
|
+
if hour is None:
|
|
187
|
+
continue
|
|
188
|
+
bucket = _hour_to_bucket(hour)
|
|
189
|
+
buckets[bucket][activity] += 1
|
|
190
|
+
|
|
191
|
+
result: dict[str, dict] = {}
|
|
192
|
+
for bucket_name, counter in buckets.items():
|
|
193
|
+
total = sum(counter.values())
|
|
194
|
+
if total < 5:
|
|
195
|
+
continue
|
|
196
|
+
dominant, dom_count = counter.most_common(1)[0]
|
|
197
|
+
result[bucket_name] = {
|
|
198
|
+
"dominant_activity": dominant,
|
|
199
|
+
"confidence": round(dom_count / total, 4),
|
|
200
|
+
"evidence_count": total,
|
|
201
|
+
"distribution": dict(counter),
|
|
202
|
+
}
|
|
203
|
+
return result
|
|
204
|
+
|
|
205
|
+
# ------------------------------------------------------------------
|
|
206
|
+
# Internals
|
|
207
|
+
# ------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
def _ensure_schema(self) -> None:
|
|
210
|
+
conn = sqlite3.connect(str(self._db_path))
|
|
211
|
+
try:
|
|
212
|
+
conn.executescript(_SCHEMA)
|
|
213
|
+
finally:
|
|
214
|
+
conn.close()
|
|
215
|
+
|
|
216
|
+
def _fetch_actions(self, profile_id: str) -> list[dict]:
|
|
217
|
+
conn = sqlite3.connect(str(self._db_path))
|
|
218
|
+
conn.row_factory = sqlite3.Row
|
|
219
|
+
try:
|
|
220
|
+
cur = conn.execute(
|
|
221
|
+
"SELECT action, created_at FROM workflow_actions "
|
|
222
|
+
"WHERE profile_id = ? ORDER BY created_at ASC LIMIT 500",
|
|
223
|
+
(profile_id,),
|
|
224
|
+
)
|
|
225
|
+
return [dict(r) for r in cur.fetchall()]
|
|
226
|
+
except sqlite3.OperationalError:
|
|
227
|
+
return []
|
|
228
|
+
finally:
|
|
229
|
+
conn.close()
|
|
230
|
+
|
|
231
|
+
@staticmethod
|
|
232
|
+
def _mine_sequences(
|
|
233
|
+
activity_stream: list[str], min_support: float
|
|
234
|
+
) -> list[dict]:
|
|
235
|
+
"""Extract n-gram sequences and filter by support."""
|
|
236
|
+
all_patterns: list[dict] = []
|
|
237
|
+
|
|
238
|
+
for n in range(2, 6):
|
|
239
|
+
if len(activity_stream) < n:
|
|
240
|
+
continue
|
|
241
|
+
ngram_counts: Counter = Counter()
|
|
242
|
+
total_windows = len(activity_stream) - n + 1
|
|
243
|
+
|
|
244
|
+
for i in range(total_windows):
|
|
245
|
+
ngram = tuple(activity_stream[i : i + n])
|
|
246
|
+
# Skip consecutive identical activities (noise)
|
|
247
|
+
if any(ngram[j] == ngram[j + 1] for j in range(len(ngram) - 1)):
|
|
248
|
+
continue
|
|
249
|
+
ngram_counts[ngram] += 1
|
|
250
|
+
|
|
251
|
+
for ngram, count in ngram_counts.items():
|
|
252
|
+
support = count / total_windows if total_windows > 0 else 0.0
|
|
253
|
+
if support >= min_support:
|
|
254
|
+
all_patterns.append({
|
|
255
|
+
"sequence": list(ngram),
|
|
256
|
+
"support": round(support, 4),
|
|
257
|
+
"count": count,
|
|
258
|
+
"length": n,
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
all_patterns.sort(key=lambda p: (-p["support"], -p["length"]))
|
|
262
|
+
return all_patterns[:20]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# ----------------------------------------------------------------------
|
|
266
|
+
# Module-level helpers
|
|
267
|
+
# ----------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def classify_activity(content: str) -> str:
|
|
271
|
+
"""Classify content into one of 7 activity types or 'unknown'."""
|
|
272
|
+
if not content:
|
|
273
|
+
return "unknown"
|
|
274
|
+
scores: Counter = Counter()
|
|
275
|
+
for act_type, pattern in _KEYWORD_PATTERNS:
|
|
276
|
+
if pattern.search(content):
|
|
277
|
+
scores[act_type] += 1
|
|
278
|
+
if not scores:
|
|
279
|
+
return "unknown"
|
|
280
|
+
return scores.most_common(1)[0][0]
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _hour_to_bucket(hour: int) -> str:
|
|
284
|
+
if 6 <= hour <= 11:
|
|
285
|
+
return "morning"
|
|
286
|
+
if 12 <= hour <= 17:
|
|
287
|
+
return "afternoon"
|
|
288
|
+
if 18 <= hour <= 23:
|
|
289
|
+
return "evening"
|
|
290
|
+
return "night"
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _parse_hour(timestamp: str | None) -> int | None:
|
|
294
|
+
if not timestamp:
|
|
295
|
+
return None
|
|
296
|
+
for fmt in (
|
|
297
|
+
"%Y-%m-%dT%H:%M:%S",
|
|
298
|
+
"%Y-%m-%d %H:%M:%S",
|
|
299
|
+
"%Y-%m-%dT%H:%M:%S.%f",
|
|
300
|
+
"%Y-%m-%d %H:%M:%S.%f",
|
|
301
|
+
):
|
|
302
|
+
try:
|
|
303
|
+
return datetime.strptime(timestamp, fmt).hour
|
|
304
|
+
except (ValueError, TypeError):
|
|
305
|
+
continue
|
|
306
|
+
try:
|
|
307
|
+
return datetime.fromisoformat(timestamp).hour
|
|
308
|
+
except (ValueError, TypeError):
|
|
309
|
+
return None
|
|
File without changes
|