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.
Files changed (431) hide show
  1. package/LICENSE +9 -1
  2. package/NOTICE +63 -0
  3. package/README.md +165 -480
  4. package/bin/slm +17 -449
  5. package/bin/slm-npm +62 -48
  6. package/conftest.py +5 -0
  7. package/docs/api-reference.md +284 -0
  8. package/docs/architecture.md +149 -0
  9. package/docs/auto-memory.md +150 -0
  10. package/docs/cli-reference.md +276 -0
  11. package/docs/compliance.md +191 -0
  12. package/docs/configuration.md +182 -0
  13. package/docs/getting-started.md +102 -0
  14. package/docs/ide-setup.md +261 -0
  15. package/docs/mcp-tools.md +220 -0
  16. package/docs/migration-from-v2.md +170 -0
  17. package/docs/profiles.md +173 -0
  18. package/docs/troubleshooting.md +310 -0
  19. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  20. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  21. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  22. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  23. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  24. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  25. package/ide/configs/cursor-mcp.json +15 -0
  26. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  27. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  28. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  29. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  30. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  31. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  32. package/{configs → ide/configs}/zed-mcp.json +2 -2
  33. package/{hooks → ide/hooks}/context-hook.js +9 -20
  34. package/ide/hooks/memory-list-skill.js +70 -0
  35. package/ide/hooks/memory-profile-skill.js +101 -0
  36. package/ide/hooks/memory-recall-skill.js +62 -0
  37. package/ide/hooks/memory-remember-skill.js +68 -0
  38. package/ide/hooks/memory-reset-skill.js +160 -0
  39. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  40. package/ide/integrations/langchain/README.md +106 -0
  41. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  42. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  43. package/ide/integrations/langchain/pyproject.toml +38 -0
  44. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  45. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  46. package/ide/integrations/langchain/tests/test_security.py +117 -0
  47. package/ide/integrations/llamaindex/README.md +81 -0
  48. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  49. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  50. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  51. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  52. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  53. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  54. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  55. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  56. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  57. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  58. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  59. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  60. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  61. package/package.json +13 -22
  62. package/pyproject.toml +85 -0
  63. package/scripts/build-dmg.sh +417 -0
  64. package/scripts/install-skills.ps1 +334 -0
  65. package/scripts/postinstall.js +2 -2
  66. package/scripts/start-dashboard.ps1 +52 -0
  67. package/scripts/start-dashboard.sh +41 -0
  68. package/scripts/sync-wiki.ps1 +127 -0
  69. package/scripts/sync-wiki.sh +82 -0
  70. package/scripts/test-dmg.sh +161 -0
  71. package/scripts/test-npm-package.ps1 +252 -0
  72. package/scripts/test-npm-package.sh +207 -0
  73. package/scripts/verify-install.ps1 +294 -0
  74. package/scripts/verify-install.sh +266 -0
  75. package/src/superlocalmemory/__init__.py +0 -0
  76. package/src/superlocalmemory/attribution/__init__.py +9 -0
  77. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  78. package/src/superlocalmemory/attribution/signer.py +153 -0
  79. package/src/superlocalmemory/attribution/watermark.py +189 -0
  80. package/src/superlocalmemory/cli/__init__.py +5 -0
  81. package/src/superlocalmemory/cli/commands.py +245 -0
  82. package/src/superlocalmemory/cli/main.py +89 -0
  83. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  84. package/src/superlocalmemory/cli/post_install.py +99 -0
  85. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  86. package/src/superlocalmemory/compliance/__init__.py +0 -0
  87. package/src/superlocalmemory/compliance/abac.py +204 -0
  88. package/src/superlocalmemory/compliance/audit.py +314 -0
  89. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  90. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  91. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  92. package/src/superlocalmemory/compliance/retention.py +232 -0
  93. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  94. package/src/superlocalmemory/core/__init__.py +0 -0
  95. package/src/superlocalmemory/core/config.py +391 -0
  96. package/src/superlocalmemory/core/embeddings.py +293 -0
  97. package/src/superlocalmemory/core/engine.py +701 -0
  98. package/src/superlocalmemory/core/hooks.py +65 -0
  99. package/src/superlocalmemory/core/maintenance.py +172 -0
  100. package/src/superlocalmemory/core/modes.py +140 -0
  101. package/src/superlocalmemory/core/profiles.py +234 -0
  102. package/src/superlocalmemory/core/registry.py +117 -0
  103. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  104. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  105. package/src/superlocalmemory/encoding/__init__.py +0 -0
  106. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  107. package/src/superlocalmemory/encoding/emotional.py +125 -0
  108. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  109. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  110. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  111. package/src/superlocalmemory/encoding/foresight.py +91 -0
  112. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  113. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  114. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  115. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  116. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  117. package/src/superlocalmemory/encoding/type_router.py +235 -0
  118. package/src/superlocalmemory/hooks/__init__.py +3 -0
  119. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  120. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  121. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  122. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  123. package/src/superlocalmemory/infra/__init__.py +3 -0
  124. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  125. package/src/superlocalmemory/infra/backup.py +317 -0
  126. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  127. package/src/superlocalmemory/infra/event_bus.py +381 -0
  128. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  129. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  130. package/src/superlocalmemory/learning/__init__.py +0 -0
  131. package/src/superlocalmemory/learning/adaptive.py +172 -0
  132. package/src/superlocalmemory/learning/behavioral.py +490 -0
  133. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  134. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  135. package/src/superlocalmemory/learning/cross_project.py +399 -0
  136. package/src/superlocalmemory/learning/database.py +376 -0
  137. package/src/superlocalmemory/learning/engagement.py +323 -0
  138. package/src/superlocalmemory/learning/features.py +138 -0
  139. package/src/superlocalmemory/learning/feedback.py +316 -0
  140. package/src/superlocalmemory/learning/outcomes.py +255 -0
  141. package/src/superlocalmemory/learning/project_context.py +366 -0
  142. package/src/superlocalmemory/learning/ranker.py +155 -0
  143. package/src/superlocalmemory/learning/source_quality.py +303 -0
  144. package/src/superlocalmemory/learning/workflows.py +309 -0
  145. package/src/superlocalmemory/llm/__init__.py +0 -0
  146. package/src/superlocalmemory/llm/backbone.py +316 -0
  147. package/src/superlocalmemory/math/__init__.py +0 -0
  148. package/src/superlocalmemory/math/fisher.py +356 -0
  149. package/src/superlocalmemory/math/langevin.py +398 -0
  150. package/src/superlocalmemory/math/sheaf.py +257 -0
  151. package/src/superlocalmemory/mcp/__init__.py +0 -0
  152. package/src/superlocalmemory/mcp/resources.py +245 -0
  153. package/src/superlocalmemory/mcp/server.py +61 -0
  154. package/src/superlocalmemory/mcp/tools.py +18 -0
  155. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  156. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  157. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  158. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  159. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  160. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  161. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  162. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  163. package/src/superlocalmemory/retrieval/engine.py +390 -0
  164. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  165. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  166. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  167. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  168. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  169. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  170. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  171. package/src/superlocalmemory/server/__init__.py +1 -0
  172. package/src/superlocalmemory/server/api.py +248 -0
  173. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  174. package/src/superlocalmemory/server/routes/agents.py +107 -0
  175. package/src/superlocalmemory/server/routes/backup.py +91 -0
  176. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  177. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  178. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  179. package/src/superlocalmemory/server/routes/events.py +183 -0
  180. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  181. package/src/superlocalmemory/server/routes/learning.py +273 -0
  182. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  183. package/src/superlocalmemory/server/routes/memories.py +399 -0
  184. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  185. package/src/superlocalmemory/server/routes/stats.py +346 -0
  186. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  187. package/src/superlocalmemory/server/routes/ws.py +82 -0
  188. package/src/superlocalmemory/server/security_middleware.py +57 -0
  189. package/src/superlocalmemory/server/ui.py +245 -0
  190. package/src/superlocalmemory/storage/__init__.py +0 -0
  191. package/src/superlocalmemory/storage/access_control.py +182 -0
  192. package/src/superlocalmemory/storage/database.py +594 -0
  193. package/src/superlocalmemory/storage/migrations.py +303 -0
  194. package/src/superlocalmemory/storage/models.py +406 -0
  195. package/src/superlocalmemory/storage/schema.py +726 -0
  196. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  197. package/src/superlocalmemory/trust/__init__.py +0 -0
  198. package/src/superlocalmemory/trust/gate.py +130 -0
  199. package/src/superlocalmemory/trust/provenance.py +124 -0
  200. package/src/superlocalmemory/trust/scorer.py +347 -0
  201. package/src/superlocalmemory/trust/signals.py +153 -0
  202. package/ui/index.html +278 -5
  203. package/ui/js/auto-settings.js +70 -0
  204. package/ui/js/dashboard.js +90 -0
  205. package/ui/js/fact-detail.js +92 -0
  206. package/ui/js/feedback.js +2 -2
  207. package/ui/js/ide-status.js +102 -0
  208. package/ui/js/math-health.js +98 -0
  209. package/ui/js/recall-lab.js +127 -0
  210. package/ui/js/settings.js +2 -2
  211. package/ui/js/trust-dashboard.js +73 -0
  212. package/api_server.py +0 -724
  213. package/bin/aider-smart +0 -72
  214. package/bin/superlocalmemoryv2-learning +0 -4
  215. package/bin/superlocalmemoryv2-list +0 -3
  216. package/bin/superlocalmemoryv2-patterns +0 -4
  217. package/bin/superlocalmemoryv2-profile +0 -3
  218. package/bin/superlocalmemoryv2-recall +0 -3
  219. package/bin/superlocalmemoryv2-remember +0 -3
  220. package/bin/superlocalmemoryv2-reset +0 -3
  221. package/bin/superlocalmemoryv2-status +0 -3
  222. package/configs/chatgpt-desktop-mcp.json +0 -16
  223. package/configs/cursor-mcp.json +0 -15
  224. package/hooks/memory-list-skill.js +0 -139
  225. package/hooks/memory-profile-skill.js +0 -273
  226. package/hooks/memory-recall-skill.js +0 -114
  227. package/hooks/memory-remember-skill.js +0 -127
  228. package/hooks/memory-reset-skill.js +0 -274
  229. package/mcp_server.py +0 -1808
  230. package/requirements-core.txt +0 -22
  231. package/requirements-learning.txt +0 -12
  232. package/requirements.txt +0 -12
  233. package/src/agent_registry.py +0 -411
  234. package/src/auth_middleware.py +0 -61
  235. package/src/auto_backup.py +0 -459
  236. package/src/behavioral/__init__.py +0 -49
  237. package/src/behavioral/behavioral_listener.py +0 -203
  238. package/src/behavioral/behavioral_patterns.py +0 -275
  239. package/src/behavioral/cross_project_transfer.py +0 -206
  240. package/src/behavioral/outcome_inference.py +0 -194
  241. package/src/behavioral/outcome_tracker.py +0 -193
  242. package/src/behavioral/tests/__init__.py +0 -4
  243. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  244. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  245. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  246. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  247. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  248. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  249. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  250. package/src/cache_manager.py +0 -518
  251. package/src/compliance/__init__.py +0 -48
  252. package/src/compliance/abac_engine.py +0 -149
  253. package/src/compliance/abac_middleware.py +0 -116
  254. package/src/compliance/audit_db.py +0 -215
  255. package/src/compliance/audit_logger.py +0 -148
  256. package/src/compliance/retention_manager.py +0 -289
  257. package/src/compliance/retention_scheduler.py +0 -186
  258. package/src/compliance/tests/__init__.py +0 -4
  259. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  260. package/src/compliance/tests/test_abac_engine.py +0 -124
  261. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  262. package/src/compliance/tests/test_audit_db.py +0 -123
  263. package/src/compliance/tests/test_audit_logger.py +0 -98
  264. package/src/compliance/tests/test_mcp_audit.py +0 -128
  265. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  266. package/src/compliance/tests/test_retention_manager.py +0 -131
  267. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  268. package/src/compression/__init__.py +0 -25
  269. package/src/compression/cli.py +0 -150
  270. package/src/compression/cold_storage.py +0 -217
  271. package/src/compression/config.py +0 -72
  272. package/src/compression/orchestrator.py +0 -133
  273. package/src/compression/tier2_compressor.py +0 -228
  274. package/src/compression/tier3_compressor.py +0 -153
  275. package/src/compression/tier_classifier.py +0 -148
  276. package/src/db_connection_manager.py +0 -536
  277. package/src/embedding_engine.py +0 -63
  278. package/src/embeddings/__init__.py +0 -47
  279. package/src/embeddings/cache.py +0 -70
  280. package/src/embeddings/cli.py +0 -113
  281. package/src/embeddings/constants.py +0 -47
  282. package/src/embeddings/database.py +0 -91
  283. package/src/embeddings/engine.py +0 -247
  284. package/src/embeddings/model_loader.py +0 -145
  285. package/src/event_bus.py +0 -562
  286. package/src/graph/__init__.py +0 -36
  287. package/src/graph/build_helpers.py +0 -74
  288. package/src/graph/cli.py +0 -87
  289. package/src/graph/cluster_builder.py +0 -188
  290. package/src/graph/cluster_summary.py +0 -148
  291. package/src/graph/constants.py +0 -47
  292. package/src/graph/edge_builder.py +0 -162
  293. package/src/graph/entity_extractor.py +0 -95
  294. package/src/graph/graph_core.py +0 -226
  295. package/src/graph/graph_search.py +0 -231
  296. package/src/graph/hierarchical.py +0 -207
  297. package/src/graph/schema.py +0 -99
  298. package/src/graph_engine.py +0 -52
  299. package/src/hnsw_index.py +0 -628
  300. package/src/hybrid_search.py +0 -46
  301. package/src/learning/__init__.py +0 -217
  302. package/src/learning/adaptive_ranker.py +0 -682
  303. package/src/learning/bootstrap/__init__.py +0 -69
  304. package/src/learning/bootstrap/constants.py +0 -93
  305. package/src/learning/bootstrap/db_queries.py +0 -316
  306. package/src/learning/bootstrap/sampling.py +0 -82
  307. package/src/learning/bootstrap/text_utils.py +0 -71
  308. package/src/learning/cross_project_aggregator.py +0 -857
  309. package/src/learning/db/__init__.py +0 -40
  310. package/src/learning/db/constants.py +0 -44
  311. package/src/learning/db/schema.py +0 -279
  312. package/src/learning/engagement_tracker.py +0 -628
  313. package/src/learning/feature_extractor.py +0 -708
  314. package/src/learning/feedback_collector.py +0 -806
  315. package/src/learning/learning_db.py +0 -915
  316. package/src/learning/project_context_manager.py +0 -572
  317. package/src/learning/ranking/__init__.py +0 -33
  318. package/src/learning/ranking/constants.py +0 -84
  319. package/src/learning/ranking/helpers.py +0 -278
  320. package/src/learning/source_quality_scorer.py +0 -676
  321. package/src/learning/synthetic_bootstrap.py +0 -755
  322. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  323. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  324. package/src/learning/tests/test_aggregator.py +0 -306
  325. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  326. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  327. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  328. package/src/learning/tests/test_feedback_collector.py +0 -294
  329. package/src/learning/tests/test_learning_db.py +0 -602
  330. package/src/learning/tests/test_learning_db_v28.py +0 -110
  331. package/src/learning/tests/test_learning_init_v28.py +0 -48
  332. package/src/learning/tests/test_outcome_signals.py +0 -48
  333. package/src/learning/tests/test_project_context.py +0 -292
  334. package/src/learning/tests/test_schema_migration.py +0 -319
  335. package/src/learning/tests/test_signal_inference.py +0 -397
  336. package/src/learning/tests/test_source_quality.py +0 -351
  337. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  338. package/src/learning/tests/test_workflow_miner.py +0 -318
  339. package/src/learning/workflow_pattern_miner.py +0 -655
  340. package/src/lifecycle/__init__.py +0 -54
  341. package/src/lifecycle/bounded_growth.py +0 -239
  342. package/src/lifecycle/compaction_engine.py +0 -226
  343. package/src/lifecycle/lifecycle_engine.py +0 -355
  344. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  345. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  346. package/src/lifecycle/retention_policy.py +0 -285
  347. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  348. package/src/lifecycle/tests/test_compaction.py +0 -179
  349. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  350. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  351. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  352. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  353. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  354. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  355. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  356. package/src/mcp_tools_v28.py +0 -281
  357. package/src/memory/__init__.py +0 -36
  358. package/src/memory/cli.py +0 -205
  359. package/src/memory/constants.py +0 -39
  360. package/src/memory/helpers.py +0 -28
  361. package/src/memory/schema.py +0 -166
  362. package/src/memory-profiles.py +0 -595
  363. package/src/memory-reset.py +0 -491
  364. package/src/memory_compression.py +0 -989
  365. package/src/memory_store_v2.py +0 -1155
  366. package/src/migrate_v1_to_v2.py +0 -629
  367. package/src/pattern_learner.py +0 -34
  368. package/src/patterns/__init__.py +0 -24
  369. package/src/patterns/analyzers.py +0 -251
  370. package/src/patterns/learner.py +0 -271
  371. package/src/patterns/scoring.py +0 -171
  372. package/src/patterns/store.py +0 -225
  373. package/src/patterns/terminology.py +0 -140
  374. package/src/provenance_tracker.py +0 -312
  375. package/src/qualixar_attribution.py +0 -139
  376. package/src/qualixar_watermark.py +0 -78
  377. package/src/query_optimizer.py +0 -511
  378. package/src/rate_limiter.py +0 -83
  379. package/src/search/__init__.py +0 -20
  380. package/src/search/cli.py +0 -77
  381. package/src/search/constants.py +0 -26
  382. package/src/search/engine.py +0 -241
  383. package/src/search/fusion.py +0 -122
  384. package/src/search/index_loader.py +0 -114
  385. package/src/search/methods.py +0 -162
  386. package/src/search_engine_v2.py +0 -401
  387. package/src/setup_validator.py +0 -482
  388. package/src/subscription_manager.py +0 -391
  389. package/src/tree/__init__.py +0 -59
  390. package/src/tree/builder.py +0 -185
  391. package/src/tree/nodes.py +0 -202
  392. package/src/tree/queries.py +0 -257
  393. package/src/tree/schema.py +0 -80
  394. package/src/tree_manager.py +0 -19
  395. package/src/trust/__init__.py +0 -45
  396. package/src/trust/constants.py +0 -66
  397. package/src/trust/queries.py +0 -157
  398. package/src/trust/schema.py +0 -95
  399. package/src/trust/scorer.py +0 -299
  400. package/src/trust/signals.py +0 -95
  401. package/src/trust_scorer.py +0 -44
  402. package/ui/app.js +0 -1588
  403. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  404. package/ui/js/graph-cytoscape.js +0 -1168
  405. package/ui/js/graph-d3-backup.js +0 -32
  406. package/ui/js/graph.js +0 -32
  407. package/ui_server.py +0 -286
  408. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  409. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  410. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  411. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  412. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  413. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  414. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  415. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  416. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  417. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  418. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  419. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  420. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  421. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  422. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  423. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  424. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  425. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  426. /package/{completions → ide/completions}/slm.bash +0 -0
  427. /package/{completions → ide/completions}/slm.zsh +0 -0
  428. /package/{configs → ide/configs}/cody-commands.json +0 -0
  429. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
  430. /package/{install.ps1 → scripts/install.ps1} +0 -0
  431. /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