superlocalmemory 2.8.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +9 -1
  3. package/NOTICE +63 -0
  4. package/README.md +165 -480
  5. package/bin/slm +17 -449
  6. package/bin/slm-npm +2 -2
  7. package/bin/slm.bat +4 -2
  8. package/conftest.py +5 -0
  9. package/docs/api-reference.md +284 -0
  10. package/docs/architecture.md +149 -0
  11. package/docs/auto-memory.md +150 -0
  12. package/docs/cli-reference.md +276 -0
  13. package/docs/compliance.md +191 -0
  14. package/docs/configuration.md +182 -0
  15. package/docs/getting-started.md +102 -0
  16. package/docs/ide-setup.md +261 -0
  17. package/docs/mcp-tools.md +220 -0
  18. package/docs/migration-from-v2.md +170 -0
  19. package/docs/profiles.md +173 -0
  20. package/docs/troubleshooting.md +310 -0
  21. package/{configs → ide/configs}/antigravity-mcp.json +3 -3
  22. package/ide/configs/chatgpt-desktop-mcp.json +16 -0
  23. package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
  24. package/{configs → ide/configs}/codex-mcp.toml +4 -4
  25. package/{configs → ide/configs}/continue-mcp.yaml +4 -3
  26. package/{configs → ide/configs}/continue-skills.yaml +6 -6
  27. package/ide/configs/cursor-mcp.json +15 -0
  28. package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
  29. package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
  30. package/{configs → ide/configs}/opencode-mcp.json +2 -2
  31. package/{configs → ide/configs}/perplexity-mcp.json +2 -2
  32. package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
  33. package/{configs → ide/configs}/windsurf-mcp.json +3 -3
  34. package/{configs → ide/configs}/zed-mcp.json +2 -2
  35. package/{hooks → ide/hooks}/context-hook.js +9 -20
  36. package/ide/hooks/memory-list-skill.js +70 -0
  37. package/ide/hooks/memory-profile-skill.js +101 -0
  38. package/ide/hooks/memory-recall-skill.js +62 -0
  39. package/ide/hooks/memory-remember-skill.js +68 -0
  40. package/ide/hooks/memory-reset-skill.js +160 -0
  41. package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
  42. package/ide/integrations/langchain/README.md +106 -0
  43. package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
  44. package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
  45. package/ide/integrations/langchain/pyproject.toml +38 -0
  46. package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
  47. package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
  48. package/ide/integrations/langchain/tests/test_security.py +117 -0
  49. package/ide/integrations/llamaindex/README.md +81 -0
  50. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
  51. package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
  52. package/ide/integrations/llamaindex/pyproject.toml +43 -0
  53. package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
  54. package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
  55. package/ide/integrations/llamaindex/tests/test_security.py +241 -0
  56. package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
  57. package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
  58. package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
  59. package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
  60. package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
  61. package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
  62. package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
  63. package/package.json +13 -22
  64. package/pyproject.toml +85 -0
  65. package/scripts/build-dmg.sh +417 -0
  66. package/scripts/install-skills.ps1 +334 -0
  67. package/{install.ps1 → scripts/install.ps1} +36 -4
  68. package/{install.sh → scripts/install.sh} +14 -13
  69. package/scripts/postinstall.js +2 -2
  70. package/scripts/start-dashboard.ps1 +52 -0
  71. package/scripts/start-dashboard.sh +41 -0
  72. package/scripts/sync-wiki.ps1 +127 -0
  73. package/scripts/sync-wiki.sh +82 -0
  74. package/scripts/test-dmg.sh +161 -0
  75. package/scripts/test-npm-package.ps1 +252 -0
  76. package/scripts/test-npm-package.sh +207 -0
  77. package/scripts/verify-install.ps1 +294 -0
  78. package/scripts/verify-install.sh +266 -0
  79. package/src/superlocalmemory/__init__.py +0 -0
  80. package/src/superlocalmemory/attribution/__init__.py +9 -0
  81. package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
  82. package/src/superlocalmemory/attribution/signer.py +153 -0
  83. package/src/superlocalmemory/attribution/watermark.py +189 -0
  84. package/src/superlocalmemory/cli/__init__.py +5 -0
  85. package/src/superlocalmemory/cli/commands.py +245 -0
  86. package/src/superlocalmemory/cli/main.py +89 -0
  87. package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
  88. package/src/superlocalmemory/cli/post_install.py +99 -0
  89. package/src/superlocalmemory/cli/setup_wizard.py +129 -0
  90. package/src/superlocalmemory/compliance/__init__.py +0 -0
  91. package/src/superlocalmemory/compliance/abac.py +204 -0
  92. package/src/superlocalmemory/compliance/audit.py +314 -0
  93. package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
  94. package/src/superlocalmemory/compliance/gdpr.py +294 -0
  95. package/src/superlocalmemory/compliance/lifecycle.py +158 -0
  96. package/src/superlocalmemory/compliance/retention.py +232 -0
  97. package/src/superlocalmemory/compliance/scheduler.py +148 -0
  98. package/src/superlocalmemory/core/__init__.py +0 -0
  99. package/src/superlocalmemory/core/config.py +391 -0
  100. package/src/superlocalmemory/core/embeddings.py +293 -0
  101. package/src/superlocalmemory/core/engine.py +701 -0
  102. package/src/superlocalmemory/core/hooks.py +65 -0
  103. package/src/superlocalmemory/core/maintenance.py +172 -0
  104. package/src/superlocalmemory/core/modes.py +140 -0
  105. package/src/superlocalmemory/core/profiles.py +234 -0
  106. package/src/superlocalmemory/core/registry.py +117 -0
  107. package/src/superlocalmemory/dynamics/__init__.py +0 -0
  108. package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
  109. package/src/superlocalmemory/encoding/__init__.py +0 -0
  110. package/src/superlocalmemory/encoding/consolidator.py +485 -0
  111. package/src/superlocalmemory/encoding/emotional.py +125 -0
  112. package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
  113. package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
  114. package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
  115. package/src/superlocalmemory/encoding/foresight.py +91 -0
  116. package/src/superlocalmemory/encoding/graph_builder.py +302 -0
  117. package/src/superlocalmemory/encoding/observation_builder.py +160 -0
  118. package/src/superlocalmemory/encoding/scene_builder.py +183 -0
  119. package/src/superlocalmemory/encoding/signal_inference.py +90 -0
  120. package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
  121. package/src/superlocalmemory/encoding/type_router.py +235 -0
  122. package/src/superlocalmemory/hooks/__init__.py +3 -0
  123. package/src/superlocalmemory/hooks/auto_capture.py +111 -0
  124. package/src/superlocalmemory/hooks/auto_recall.py +93 -0
  125. package/src/superlocalmemory/hooks/ide_connector.py +204 -0
  126. package/src/superlocalmemory/hooks/rules_engine.py +99 -0
  127. package/src/superlocalmemory/infra/__init__.py +3 -0
  128. package/src/superlocalmemory/infra/auth_middleware.py +82 -0
  129. package/src/superlocalmemory/infra/backup.py +317 -0
  130. package/src/superlocalmemory/infra/cache_manager.py +267 -0
  131. package/src/superlocalmemory/infra/event_bus.py +381 -0
  132. package/src/superlocalmemory/infra/rate_limiter.py +135 -0
  133. package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
  134. package/src/superlocalmemory/learning/__init__.py +0 -0
  135. package/src/superlocalmemory/learning/adaptive.py +172 -0
  136. package/src/superlocalmemory/learning/behavioral.py +490 -0
  137. package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
  138. package/src/superlocalmemory/learning/bootstrap.py +298 -0
  139. package/src/superlocalmemory/learning/cross_project.py +399 -0
  140. package/src/superlocalmemory/learning/database.py +376 -0
  141. package/src/superlocalmemory/learning/engagement.py +323 -0
  142. package/src/superlocalmemory/learning/features.py +138 -0
  143. package/src/superlocalmemory/learning/feedback.py +316 -0
  144. package/src/superlocalmemory/learning/outcomes.py +255 -0
  145. package/src/superlocalmemory/learning/project_context.py +366 -0
  146. package/src/superlocalmemory/learning/ranker.py +155 -0
  147. package/src/superlocalmemory/learning/source_quality.py +303 -0
  148. package/src/superlocalmemory/learning/workflows.py +309 -0
  149. package/src/superlocalmemory/llm/__init__.py +0 -0
  150. package/src/superlocalmemory/llm/backbone.py +316 -0
  151. package/src/superlocalmemory/math/__init__.py +0 -0
  152. package/src/superlocalmemory/math/fisher.py +356 -0
  153. package/src/superlocalmemory/math/langevin.py +398 -0
  154. package/src/superlocalmemory/math/sheaf.py +257 -0
  155. package/src/superlocalmemory/mcp/__init__.py +0 -0
  156. package/src/superlocalmemory/mcp/resources.py +245 -0
  157. package/src/superlocalmemory/mcp/server.py +61 -0
  158. package/src/superlocalmemory/mcp/tools.py +18 -0
  159. package/src/superlocalmemory/mcp/tools_core.py +305 -0
  160. package/src/superlocalmemory/mcp/tools_v28.py +223 -0
  161. package/src/superlocalmemory/mcp/tools_v3.py +286 -0
  162. package/src/superlocalmemory/retrieval/__init__.py +0 -0
  163. package/src/superlocalmemory/retrieval/agentic.py +295 -0
  164. package/src/superlocalmemory/retrieval/ann_index.py +223 -0
  165. package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
  166. package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
  167. package/src/superlocalmemory/retrieval/engine.py +390 -0
  168. package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
  169. package/src/superlocalmemory/retrieval/fusion.py +78 -0
  170. package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
  171. package/src/superlocalmemory/retrieval/reranker.py +154 -0
  172. package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
  173. package/src/superlocalmemory/retrieval/strategy.py +96 -0
  174. package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
  175. package/src/superlocalmemory/server/__init__.py +1 -0
  176. package/src/superlocalmemory/server/api.py +248 -0
  177. package/src/superlocalmemory/server/routes/__init__.py +4 -0
  178. package/src/superlocalmemory/server/routes/agents.py +107 -0
  179. package/src/superlocalmemory/server/routes/backup.py +91 -0
  180. package/src/superlocalmemory/server/routes/behavioral.py +127 -0
  181. package/src/superlocalmemory/server/routes/compliance.py +160 -0
  182. package/src/superlocalmemory/server/routes/data_io.py +188 -0
  183. package/src/superlocalmemory/server/routes/events.py +183 -0
  184. package/src/superlocalmemory/server/routes/helpers.py +85 -0
  185. package/src/superlocalmemory/server/routes/learning.py +273 -0
  186. package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
  187. package/src/superlocalmemory/server/routes/memories.py +399 -0
  188. package/src/superlocalmemory/server/routes/profiles.py +219 -0
  189. package/src/superlocalmemory/server/routes/stats.py +346 -0
  190. package/src/superlocalmemory/server/routes/v3_api.py +365 -0
  191. package/src/superlocalmemory/server/routes/ws.py +82 -0
  192. package/src/superlocalmemory/server/security_middleware.py +57 -0
  193. package/src/superlocalmemory/server/ui.py +245 -0
  194. package/src/superlocalmemory/storage/__init__.py +0 -0
  195. package/src/superlocalmemory/storage/access_control.py +182 -0
  196. package/src/superlocalmemory/storage/database.py +594 -0
  197. package/src/superlocalmemory/storage/migrations.py +303 -0
  198. package/src/superlocalmemory/storage/models.py +406 -0
  199. package/src/superlocalmemory/storage/schema.py +726 -0
  200. package/src/superlocalmemory/storage/v2_migrator.py +317 -0
  201. package/src/superlocalmemory/trust/__init__.py +0 -0
  202. package/src/superlocalmemory/trust/gate.py +130 -0
  203. package/src/superlocalmemory/trust/provenance.py +124 -0
  204. package/src/superlocalmemory/trust/scorer.py +347 -0
  205. package/src/superlocalmemory/trust/signals.py +153 -0
  206. package/ui/index.html +278 -5
  207. package/ui/js/auto-settings.js +70 -0
  208. package/ui/js/dashboard.js +90 -0
  209. package/ui/js/fact-detail.js +92 -0
  210. package/ui/js/feedback.js +2 -2
  211. package/ui/js/ide-status.js +102 -0
  212. package/ui/js/math-health.js +98 -0
  213. package/ui/js/recall-lab.js +127 -0
  214. package/ui/js/settings.js +2 -2
  215. package/ui/js/trust-dashboard.js +73 -0
  216. package/api_server.py +0 -724
  217. package/bin/aider-smart +0 -72
  218. package/bin/superlocalmemoryv2-learning +0 -4
  219. package/bin/superlocalmemoryv2-list +0 -3
  220. package/bin/superlocalmemoryv2-patterns +0 -4
  221. package/bin/superlocalmemoryv2-profile +0 -3
  222. package/bin/superlocalmemoryv2-recall +0 -3
  223. package/bin/superlocalmemoryv2-remember +0 -3
  224. package/bin/superlocalmemoryv2-reset +0 -3
  225. package/bin/superlocalmemoryv2-status +0 -3
  226. package/configs/chatgpt-desktop-mcp.json +0 -16
  227. package/configs/cursor-mcp.json +0 -15
  228. package/docs/SECURITY-QUICK-REFERENCE.md +0 -214
  229. package/hooks/memory-list-skill.js +0 -139
  230. package/hooks/memory-profile-skill.js +0 -273
  231. package/hooks/memory-recall-skill.js +0 -114
  232. package/hooks/memory-remember-skill.js +0 -127
  233. package/hooks/memory-reset-skill.js +0 -274
  234. package/mcp_server.py +0 -1800
  235. package/requirements-core.txt +0 -22
  236. package/requirements-learning.txt +0 -12
  237. package/requirements.txt +0 -12
  238. package/src/agent_registry.py +0 -411
  239. package/src/auth_middleware.py +0 -61
  240. package/src/auto_backup.py +0 -459
  241. package/src/behavioral/__init__.py +0 -49
  242. package/src/behavioral/behavioral_listener.py +0 -203
  243. package/src/behavioral/behavioral_patterns.py +0 -275
  244. package/src/behavioral/cross_project_transfer.py +0 -206
  245. package/src/behavioral/outcome_inference.py +0 -194
  246. package/src/behavioral/outcome_tracker.py +0 -193
  247. package/src/behavioral/tests/__init__.py +0 -4
  248. package/src/behavioral/tests/test_behavioral_integration.py +0 -108
  249. package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
  250. package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
  251. package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
  252. package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
  253. package/src/behavioral/tests/test_outcome_inference.py +0 -107
  254. package/src/behavioral/tests/test_outcome_tracker.py +0 -96
  255. package/src/cache_manager.py +0 -518
  256. package/src/compliance/__init__.py +0 -48
  257. package/src/compliance/abac_engine.py +0 -149
  258. package/src/compliance/abac_middleware.py +0 -116
  259. package/src/compliance/audit_db.py +0 -215
  260. package/src/compliance/audit_logger.py +0 -148
  261. package/src/compliance/retention_manager.py +0 -289
  262. package/src/compliance/retention_scheduler.py +0 -186
  263. package/src/compliance/tests/__init__.py +0 -4
  264. package/src/compliance/tests/test_abac_enforcement.py +0 -95
  265. package/src/compliance/tests/test_abac_engine.py +0 -124
  266. package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
  267. package/src/compliance/tests/test_audit_db.py +0 -123
  268. package/src/compliance/tests/test_audit_logger.py +0 -98
  269. package/src/compliance/tests/test_mcp_audit.py +0 -128
  270. package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
  271. package/src/compliance/tests/test_retention_manager.py +0 -131
  272. package/src/compliance/tests/test_retention_scheduler.py +0 -99
  273. package/src/compression/__init__.py +0 -25
  274. package/src/compression/cli.py +0 -150
  275. package/src/compression/cold_storage.py +0 -217
  276. package/src/compression/config.py +0 -72
  277. package/src/compression/orchestrator.py +0 -133
  278. package/src/compression/tier2_compressor.py +0 -228
  279. package/src/compression/tier3_compressor.py +0 -153
  280. package/src/compression/tier_classifier.py +0 -148
  281. package/src/db_connection_manager.py +0 -536
  282. package/src/embedding_engine.py +0 -63
  283. package/src/embeddings/__init__.py +0 -47
  284. package/src/embeddings/cache.py +0 -70
  285. package/src/embeddings/cli.py +0 -113
  286. package/src/embeddings/constants.py +0 -47
  287. package/src/embeddings/database.py +0 -91
  288. package/src/embeddings/engine.py +0 -247
  289. package/src/embeddings/model_loader.py +0 -145
  290. package/src/event_bus.py +0 -562
  291. package/src/graph/__init__.py +0 -36
  292. package/src/graph/build_helpers.py +0 -74
  293. package/src/graph/cli.py +0 -87
  294. package/src/graph/cluster_builder.py +0 -188
  295. package/src/graph/cluster_summary.py +0 -148
  296. package/src/graph/constants.py +0 -47
  297. package/src/graph/edge_builder.py +0 -162
  298. package/src/graph/entity_extractor.py +0 -95
  299. package/src/graph/graph_core.py +0 -226
  300. package/src/graph/graph_search.py +0 -231
  301. package/src/graph/hierarchical.py +0 -207
  302. package/src/graph/schema.py +0 -99
  303. package/src/graph_engine.py +0 -52
  304. package/src/hnsw_index.py +0 -628
  305. package/src/hybrid_search.py +0 -46
  306. package/src/learning/__init__.py +0 -217
  307. package/src/learning/adaptive_ranker.py +0 -682
  308. package/src/learning/bootstrap/__init__.py +0 -69
  309. package/src/learning/bootstrap/constants.py +0 -93
  310. package/src/learning/bootstrap/db_queries.py +0 -316
  311. package/src/learning/bootstrap/sampling.py +0 -82
  312. package/src/learning/bootstrap/text_utils.py +0 -71
  313. package/src/learning/cross_project_aggregator.py +0 -857
  314. package/src/learning/db/__init__.py +0 -40
  315. package/src/learning/db/constants.py +0 -44
  316. package/src/learning/db/schema.py +0 -279
  317. package/src/learning/engagement_tracker.py +0 -628
  318. package/src/learning/feature_extractor.py +0 -708
  319. package/src/learning/feedback_collector.py +0 -806
  320. package/src/learning/learning_db.py +0 -915
  321. package/src/learning/project_context_manager.py +0 -572
  322. package/src/learning/ranking/__init__.py +0 -33
  323. package/src/learning/ranking/constants.py +0 -84
  324. package/src/learning/ranking/helpers.py +0 -278
  325. package/src/learning/source_quality_scorer.py +0 -676
  326. package/src/learning/synthetic_bootstrap.py +0 -755
  327. package/src/learning/tests/test_adaptive_ranker.py +0 -325
  328. package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
  329. package/src/learning/tests/test_aggregator.py +0 -306
  330. package/src/learning/tests/test_auto_retrain_v28.py +0 -35
  331. package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
  332. package/src/learning/tests/test_feature_extractor_v28.py +0 -93
  333. package/src/learning/tests/test_feedback_collector.py +0 -294
  334. package/src/learning/tests/test_learning_db.py +0 -602
  335. package/src/learning/tests/test_learning_db_v28.py +0 -110
  336. package/src/learning/tests/test_learning_init_v28.py +0 -48
  337. package/src/learning/tests/test_outcome_signals.py +0 -48
  338. package/src/learning/tests/test_project_context.py +0 -292
  339. package/src/learning/tests/test_schema_migration.py +0 -319
  340. package/src/learning/tests/test_signal_inference.py +0 -397
  341. package/src/learning/tests/test_source_quality.py +0 -351
  342. package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
  343. package/src/learning/tests/test_workflow_miner.py +0 -318
  344. package/src/learning/workflow_pattern_miner.py +0 -655
  345. package/src/lifecycle/__init__.py +0 -54
  346. package/src/lifecycle/bounded_growth.py +0 -239
  347. package/src/lifecycle/compaction_engine.py +0 -226
  348. package/src/lifecycle/lifecycle_engine.py +0 -355
  349. package/src/lifecycle/lifecycle_evaluator.py +0 -257
  350. package/src/lifecycle/lifecycle_scheduler.py +0 -130
  351. package/src/lifecycle/retention_policy.py +0 -285
  352. package/src/lifecycle/tests/test_bounded_growth.py +0 -193
  353. package/src/lifecycle/tests/test_compaction.py +0 -179
  354. package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
  355. package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
  356. package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
  357. package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
  358. package/src/lifecycle/tests/test_mcp_compact.py +0 -149
  359. package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
  360. package/src/lifecycle/tests/test_retention_policy.py +0 -162
  361. package/src/mcp_tools_v28.py +0 -281
  362. package/src/memory/__init__.py +0 -36
  363. package/src/memory/cli.py +0 -205
  364. package/src/memory/constants.py +0 -39
  365. package/src/memory/helpers.py +0 -28
  366. package/src/memory/schema.py +0 -166
  367. package/src/memory-profiles.py +0 -595
  368. package/src/memory-reset.py +0 -491
  369. package/src/memory_compression.py +0 -989
  370. package/src/memory_store_v2.py +0 -1155
  371. package/src/migrate_v1_to_v2.py +0 -629
  372. package/src/pattern_learner.py +0 -34
  373. package/src/patterns/__init__.py +0 -24
  374. package/src/patterns/analyzers.py +0 -251
  375. package/src/patterns/learner.py +0 -271
  376. package/src/patterns/scoring.py +0 -171
  377. package/src/patterns/store.py +0 -225
  378. package/src/patterns/terminology.py +0 -140
  379. package/src/provenance_tracker.py +0 -312
  380. package/src/qualixar_attribution.py +0 -139
  381. package/src/qualixar_watermark.py +0 -78
  382. package/src/query_optimizer.py +0 -511
  383. package/src/rate_limiter.py +0 -83
  384. package/src/search/__init__.py +0 -20
  385. package/src/search/cli.py +0 -77
  386. package/src/search/constants.py +0 -26
  387. package/src/search/engine.py +0 -241
  388. package/src/search/fusion.py +0 -122
  389. package/src/search/index_loader.py +0 -114
  390. package/src/search/methods.py +0 -162
  391. package/src/search_engine_v2.py +0 -401
  392. package/src/setup_validator.py +0 -482
  393. package/src/subscription_manager.py +0 -391
  394. package/src/tree/__init__.py +0 -59
  395. package/src/tree/builder.py +0 -185
  396. package/src/tree/nodes.py +0 -202
  397. package/src/tree/queries.py +0 -257
  398. package/src/tree/schema.py +0 -80
  399. package/src/tree_manager.py +0 -19
  400. package/src/trust/__init__.py +0 -45
  401. package/src/trust/constants.py +0 -66
  402. package/src/trust/queries.py +0 -157
  403. package/src/trust/schema.py +0 -95
  404. package/src/trust/scorer.py +0 -299
  405. package/src/trust/signals.py +0 -95
  406. package/src/trust_scorer.py +0 -44
  407. package/ui/app.js +0 -1588
  408. package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
  409. package/ui/js/graph-cytoscape.js +0 -1168
  410. package/ui/js/graph-d3-backup.js +0 -32
  411. package/ui/js/graph.js +0 -32
  412. package/ui_server.py +0 -266
  413. /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
  414. /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
  415. /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
  416. /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
  417. /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
  418. /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
  419. /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
  420. /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
  421. /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
  422. /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
  423. /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
  424. /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
  425. /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
  426. /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
  427. /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
  428. /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
  429. /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
  430. /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
  431. /package/{completions → ide/completions}/slm.bash +0 -0
  432. /package/{completions → ide/completions}/slm.zsh +0 -0
  433. /package/{configs → ide/configs}/cody-commands.json +0 -0
  434. /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
@@ -0,0 +1,129 @@
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
+ """Interactive setup wizard for first-time configuration.
6
+
7
+ Guides new users through mode selection and provider setup.
8
+
9
+ Part of Qualixar | Author: Varun Pratap Bhardwaj
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import shutil
16
+
17
+
18
+ def run_wizard() -> None:
19
+ """Run the interactive setup wizard."""
20
+ print()
21
+ print("SuperLocalMemory V3 — First Time Setup")
22
+ print("=" * 40)
23
+ print()
24
+ print("Choose your operating mode:")
25
+ print()
26
+ print(" [A] Local Guardian (default)")
27
+ print(" Zero cloud. Zero LLM. Your data never leaves your machine.")
28
+ print(" EU AI Act compliant. Works immediately.")
29
+ print()
30
+ print(" [B] Smart Local")
31
+ print(" Local LLM via Ollama for answer synthesis.")
32
+ print(" Still private — nothing leaves your machine.")
33
+ print()
34
+ print(" [C] Full Power")
35
+ print(" Cloud LLM for best accuracy (~78% on LoCoMo).")
36
+ print(" Requires: API key from a supported provider.")
37
+ print()
38
+
39
+ choice = input("Select mode [A/B/C] (default: A): ").strip().lower() or "a"
40
+
41
+ if choice not in ("a", "b", "c"):
42
+ print(f"Invalid choice: {choice}. Using Mode A.")
43
+ choice = "a"
44
+
45
+ from superlocalmemory.core.config import SLMConfig
46
+ from superlocalmemory.storage.models import Mode
47
+
48
+ if choice == "a":
49
+ config = SLMConfig.for_mode(Mode.A)
50
+ config.save()
51
+ print()
52
+ print("Mode A configured. Zero cloud, zero LLM.")
53
+ print(f"Config saved to: {config.base_dir / 'config.json'}")
54
+
55
+ elif choice == "b":
56
+ config = SLMConfig.for_mode(Mode.B)
57
+ print()
58
+ print("Checking for Ollama...")
59
+ if shutil.which("ollama"):
60
+ print(" Ollama found!")
61
+ else:
62
+ print(" Ollama not found. Install it from https://ollama.ai")
63
+ print(" After installing, run: ollama pull llama3.2")
64
+ config.save()
65
+ print(f"Config saved to: {config.base_dir / 'config.json'}")
66
+
67
+ elif choice == "c":
68
+ config = SLMConfig.for_mode(Mode.C)
69
+ configure_provider(config)
70
+
71
+ print()
72
+ print("Ready! Your AI now remembers you.")
73
+ print()
74
+
75
+
76
+ def configure_provider(config: object) -> None:
77
+ """Configure LLM provider for Mode C.
78
+
79
+ Args:
80
+ config: An SLMConfig instance (typed as object to avoid circular import
81
+ at module level; actual type checked at runtime).
82
+ """
83
+ from superlocalmemory.core.config import SLMConfig
84
+ from superlocalmemory.storage.models import Mode
85
+
86
+ presets = SLMConfig.provider_presets()
87
+
88
+ print()
89
+ print("Choose your LLM provider:")
90
+ print()
91
+ providers = list(presets.keys())
92
+ for i, name in enumerate(providers, 1):
93
+ preset = presets[name]
94
+ print(f" [{i}] {name.capitalize()} — {preset['model']}")
95
+ print()
96
+
97
+ idx = input(f"Select provider [1-{len(providers)}]: ").strip()
98
+ try:
99
+ provider_name = providers[int(idx) - 1]
100
+ except (ValueError, IndexError):
101
+ print("Invalid choice. Using OpenAI.")
102
+ provider_name = "openai"
103
+
104
+ preset = presets[provider_name]
105
+
106
+ # Resolve API key from environment or prompt
107
+ env_key = preset.get("env_key", "")
108
+ api_key = ""
109
+ if env_key:
110
+ existing = os.environ.get(env_key, "")
111
+ if existing:
112
+ print(f" Found {env_key} in environment.")
113
+ api_key = existing
114
+ else:
115
+ api_key = input(
116
+ f" Enter your {provider_name.capitalize()} API key: ",
117
+ ).strip()
118
+
119
+ updated = SLMConfig.for_mode(
120
+ Mode.C,
121
+ llm_provider=provider_name,
122
+ llm_model=preset["model"],
123
+ llm_api_key=api_key,
124
+ llm_api_base=preset["base_url"],
125
+ )
126
+ updated.save()
127
+ print(f" Provider: {provider_name}")
128
+ print(f" Model: {preset['model']}")
129
+ print(f"Config saved to: {updated.base_dir / 'config.json'}")
File without changes
@@ -0,0 +1,204 @@
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
+ """Attribute-Based Access Control for memory operations.
6
+
7
+ Evaluates policies: (agent_id, profile_id, action) -> allow/deny.
8
+ Default: allow all (open access). Policies restrict specific agents.
9
+
10
+ Actions: "read", "write", "delete", "admin".
11
+ Policies are stored in-memory (loaded from DB on init).
12
+ Simple deny-list approach: if a deny policy matches, access is blocked.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ import sqlite3
19
+ from typing import Any, Optional
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # Valid ABAC actions
24
+ VALID_ACTIONS = frozenset({"read", "write", "delete", "admin"})
25
+
26
+ _POLICY_TABLE_SQL = """
27
+ CREATE TABLE IF NOT EXISTS abac_policies (
28
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
29
+ profile_id TEXT NOT NULL,
30
+ agent_id TEXT NOT NULL,
31
+ action TEXT NOT NULL,
32
+ deny INTEGER NOT NULL DEFAULT 1,
33
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
34
+ )
35
+ """
36
+
37
+
38
+ class AccessDenied(PermissionError):
39
+ """Raised when ABAC denies an operation."""
40
+
41
+
42
+ class ABACEngine:
43
+ """Attribute-Based Access Control for memory operations.
44
+
45
+ Evaluates policies: (agent_id, profile_id, action) -> allow/deny.
46
+ Default: allow all (open access). Deny policies restrict specific
47
+ agent+profile+action combinations.
48
+ """
49
+
50
+ def __init__(self, db: Optional[sqlite3.Connection] = None) -> None:
51
+ self._policies: list[dict[str, Any]] = []
52
+ self._db = db
53
+ if db is not None:
54
+ self._load_policies_from_db(db)
55
+
56
+ # ------------------------------------------------------------------
57
+ # Policy loading
58
+ # ------------------------------------------------------------------
59
+
60
+ def _load_policies_from_db(self, db: sqlite3.Connection) -> None:
61
+ """Load policies from the abac_policies table."""
62
+ try:
63
+ db.execute(_POLICY_TABLE_SQL)
64
+ db.commit()
65
+ rows = db.execute(
66
+ "SELECT profile_id, agent_id, action, deny "
67
+ "FROM abac_policies"
68
+ ).fetchall()
69
+ for row in rows:
70
+ self._policies.append({
71
+ "profile_id": row[0],
72
+ "agent_id": row[1],
73
+ "action": row[2],
74
+ "deny": bool(row[3]),
75
+ })
76
+ logger.info("Loaded %d ABAC policies from DB", len(self._policies))
77
+ except sqlite3.OperationalError as exc:
78
+ logger.warning("Failed to load ABAC policies: %s", exc)
79
+
80
+ # ------------------------------------------------------------------
81
+ # Policy management
82
+ # ------------------------------------------------------------------
83
+
84
+ def add_policy(
85
+ self,
86
+ profile_id: str,
87
+ agent_id: str,
88
+ action: str,
89
+ deny: bool = True,
90
+ ) -> None:
91
+ """Add an access control policy.
92
+
93
+ Args:
94
+ profile_id: Profile this policy applies to.
95
+ agent_id: Agent this policy applies to.
96
+ action: The action to control (read/write/delete/admin).
97
+ deny: If True, this is a deny policy. Default True.
98
+ """
99
+ if action not in VALID_ACTIONS:
100
+ raise ValueError(f"Invalid action '{action}'. Must be one of {VALID_ACTIONS}")
101
+ policy = {
102
+ "profile_id": profile_id,
103
+ "agent_id": agent_id,
104
+ "action": action,
105
+ "deny": deny,
106
+ }
107
+ self._policies.append(policy)
108
+ self._persist_policy(policy)
109
+
110
+ def remove_policy(
111
+ self,
112
+ profile_id: str,
113
+ agent_id: str,
114
+ action: str,
115
+ ) -> None:
116
+ """Remove a policy matching profile_id + agent_id + action."""
117
+ self._policies = [
118
+ p for p in self._policies
119
+ if not (
120
+ p["profile_id"] == profile_id
121
+ and p["agent_id"] == agent_id
122
+ and p["action"] == action
123
+ )
124
+ ]
125
+ if self._db is not None:
126
+ try:
127
+ self._db.execute(
128
+ "DELETE FROM abac_policies WHERE profile_id = ? AND agent_id = ? AND action = ?",
129
+ (profile_id, agent_id, action),
130
+ )
131
+ self._db.commit()
132
+ except sqlite3.OperationalError:
133
+ pass
134
+
135
+ def _persist_policy(self, policy: dict[str, Any]) -> None:
136
+ """Write a policy to DB so it survives restart."""
137
+ if self._db is None:
138
+ return
139
+ try:
140
+ self._db.execute(
141
+ "INSERT INTO abac_policies (profile_id, agent_id, action, deny) "
142
+ "VALUES (?, ?, ?, ?)",
143
+ (policy["profile_id"], policy["agent_id"], policy["action"], int(policy["deny"])),
144
+ )
145
+ self._db.commit()
146
+ except sqlite3.OperationalError as exc:
147
+ logger.warning("Failed to persist ABAC policy: %s", exc)
148
+
149
+ # ------------------------------------------------------------------
150
+ # Access evaluation
151
+ # ------------------------------------------------------------------
152
+
153
+ def check(self, agent_id: str, profile_id: str, action: str) -> bool:
154
+ """Evaluate access. Returns True if allowed, False if denied.
155
+
156
+ Default: allow if no deny policy matches the request.
157
+ Deny-first semantics: any matching deny policy blocks access.
158
+ """
159
+ for policy in self._policies:
160
+ if not policy.get("deny", True):
161
+ continue
162
+ if (
163
+ policy["agent_id"] == agent_id
164
+ and policy["profile_id"] == profile_id
165
+ and policy["action"] == action
166
+ ):
167
+ return False
168
+ return True
169
+
170
+ def check_or_raise(
171
+ self,
172
+ agent_id: str,
173
+ profile_id: str,
174
+ action: str,
175
+ ) -> None:
176
+ """Like check() but raises AccessDenied if denied."""
177
+ if not self.check(agent_id, profile_id, action):
178
+ raise AccessDenied(
179
+ f"Agent '{agent_id}' denied '{action}' "
180
+ f"on profile '{profile_id}'"
181
+ )
182
+
183
+ # ------------------------------------------------------------------
184
+ # Policy listing
185
+ # ------------------------------------------------------------------
186
+
187
+ def list_policies(
188
+ self,
189
+ profile_id: Optional[str] = None,
190
+ ) -> list[dict[str, Any]]:
191
+ """List all policies, optionally filtered by profile.
192
+
193
+ Args:
194
+ profile_id: If provided, only return policies for this profile.
195
+
196
+ Returns:
197
+ List of policy dicts with keys: profile_id, agent_id, action, deny.
198
+ """
199
+ if profile_id is None:
200
+ return [dict(p) for p in self._policies]
201
+ return [
202
+ dict(p) for p in self._policies
203
+ if p["profile_id"] == profile_id
204
+ ]
@@ -0,0 +1,314 @@
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
+ """Tamper-proof hash-chain audit log for compliance.
6
+
7
+ Every operation is logged with a SHA-256 hash that includes the previous
8
+ entry's hash, creating a chain. Tampering with any entry breaks the chain
9
+ and is detectable via verify_integrity().
10
+
11
+ The audit chain uses its OWN sqlite3 connection (not shared DB manager)
12
+ for independence — audit must survive even if the main DB is corrupted.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import hashlib
18
+ import json
19
+ import logging
20
+ import sqlite3
21
+ import threading
22
+ from datetime import datetime, timezone
23
+ from pathlib import Path
24
+ from typing import Any, Optional, Union
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ _GENESIS_HASH = "genesis"
29
+
30
+ _SCHEMA = """
31
+ CREATE TABLE IF NOT EXISTS audit_chain (
32
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
33
+ timestamp TEXT NOT NULL,
34
+ operation TEXT NOT NULL,
35
+ agent_id TEXT DEFAULT '',
36
+ profile_id TEXT DEFAULT '',
37
+ content_hash TEXT DEFAULT '',
38
+ prev_hash TEXT DEFAULT '',
39
+ event_hash TEXT NOT NULL,
40
+ metadata TEXT DEFAULT '{}'
41
+ );
42
+ """
43
+
44
+
45
+ def _compute_hash(
46
+ prev_hash: str,
47
+ operation: str,
48
+ agent_id: str,
49
+ profile_id: str,
50
+ content_hash: str,
51
+ timestamp: str,
52
+ ) -> str:
53
+ """Compute SHA-256 hash for an audit entry.
54
+
55
+ The hash incorporates: prev_hash + operation + agent_id +
56
+ profile_id + content_hash + timestamp.
57
+ """
58
+ payload = (
59
+ f"{prev_hash}{operation}{agent_id}"
60
+ f"{profile_id}{content_hash}{timestamp}"
61
+ )
62
+ return hashlib.sha256(payload.encode("utf-8")).hexdigest()
63
+
64
+
65
+ class AuditChain:
66
+ """Tamper-proof hash-chain audit log for compliance.
67
+
68
+ Every operation is logged with a hash that includes the previous hash,
69
+ creating a chain. Tampering with any entry breaks the chain.
70
+
71
+ Uses its own SQLite connection for independence from main DB.
72
+ """
73
+
74
+ def __init__(self, db_path: Optional[Union[str, Path]] = None) -> None:
75
+ self._db_path = str(db_path) if db_path else ":memory:"
76
+ self._is_memory = self._db_path == ":memory:"
77
+ self._lock = threading.Lock()
78
+ # For in-memory DBs, keep a persistent connection (each connect()
79
+ # to ":memory:" creates a separate empty database).
80
+ self._persistent_conn: Optional[sqlite3.Connection] = None
81
+ if self._is_memory:
82
+ self._persistent_conn = self._make_conn()
83
+ self._init_db()
84
+
85
+ # ------------------------------------------------------------------
86
+ # Internal helpers
87
+ # ------------------------------------------------------------------
88
+
89
+ @staticmethod
90
+ def _make_conn_from_path(path: str) -> sqlite3.Connection:
91
+ """Create a configured SQLite connection."""
92
+ conn = sqlite3.connect(path)
93
+ conn.execute("PRAGMA journal_mode=WAL")
94
+ conn.row_factory = sqlite3.Row
95
+ return conn
96
+
97
+ def _make_conn(self) -> sqlite3.Connection:
98
+ """Create a new configured connection to the audit database."""
99
+ return self._make_conn_from_path(self._db_path)
100
+
101
+ def _get_conn(self) -> sqlite3.Connection:
102
+ """Get a connection to the audit database.
103
+
104
+ For in-memory databases, returns the persistent connection.
105
+ For file databases, creates a new connection each time.
106
+ """
107
+ if self._is_memory and self._persistent_conn is not None:
108
+ return self._persistent_conn
109
+ return self._make_conn()
110
+
111
+ def _release_conn(self, conn: sqlite3.Connection) -> None:
112
+ """Release a connection. Only closes file-based connections."""
113
+ if not self._is_memory:
114
+ conn.close()
115
+
116
+ def _init_db(self) -> None:
117
+ """Initialize the audit_chain table."""
118
+ conn = self._get_conn()
119
+ try:
120
+ conn.executescript(_SCHEMA)
121
+ conn.commit()
122
+ finally:
123
+ self._release_conn(conn)
124
+
125
+ def _get_last_hash(self, conn: sqlite3.Connection) -> str:
126
+ """Get the hash of the most recent entry, or genesis."""
127
+ row = conn.execute(
128
+ "SELECT event_hash FROM audit_chain "
129
+ "ORDER BY id DESC LIMIT 1"
130
+ ).fetchone()
131
+ return row["event_hash"] if row else _GENESIS_HASH
132
+
133
+ # ------------------------------------------------------------------
134
+ # Public API
135
+ # ------------------------------------------------------------------
136
+
137
+ def log(
138
+ self,
139
+ operation: str,
140
+ agent_id: str = "",
141
+ profile_id: str = "",
142
+ content_hash: str = "",
143
+ metadata: Optional[dict[str, Any]] = None,
144
+ ) -> str:
145
+ """Log an audit event. Returns the event hash.
146
+
147
+ Args:
148
+ operation: Type of operation (store, recall, delete, etc.).
149
+ agent_id: ID of the agent performing the operation.
150
+ profile_id: ID of the profile being accessed.
151
+ content_hash: Hash of the content involved (optional).
152
+ metadata: Additional metadata dict (optional).
153
+
154
+ Returns:
155
+ The SHA-256 event hash for this entry.
156
+ """
157
+ ts = datetime.now(timezone.utc).isoformat()
158
+ metadata_str = json.dumps(metadata or {}, sort_keys=True)
159
+
160
+ with self._lock:
161
+ conn = self._get_conn()
162
+ try:
163
+ prev_hash = self._get_last_hash(conn)
164
+ event_hash = _compute_hash(
165
+ prev_hash, operation, agent_id,
166
+ profile_id, content_hash, ts,
167
+ )
168
+ conn.execute(
169
+ "INSERT INTO audit_chain "
170
+ "(timestamp, operation, agent_id, profile_id, "
171
+ " content_hash, prev_hash, event_hash, metadata) "
172
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
173
+ (
174
+ ts, operation, agent_id, profile_id,
175
+ content_hash, prev_hash, event_hash, metadata_str,
176
+ ),
177
+ )
178
+ conn.commit()
179
+ return event_hash
180
+ finally:
181
+ self._release_conn(conn)
182
+
183
+ def query(
184
+ self,
185
+ profile_id: Optional[str] = None,
186
+ agent_id: Optional[str] = None,
187
+ operation: Optional[str] = None,
188
+ start_date: Optional[str] = None,
189
+ end_date: Optional[str] = None,
190
+ limit: int = 100,
191
+ ) -> list[dict[str, Any]]:
192
+ """Query audit events with filters.
193
+
194
+ Args:
195
+ profile_id: Filter by profile ID.
196
+ agent_id: Filter by agent ID.
197
+ operation: Filter by operation type.
198
+ start_date: ISO date string for range start.
199
+ end_date: ISO date string for range end.
200
+ limit: Maximum number of events to return.
201
+
202
+ Returns:
203
+ List of event dicts ordered by id descending.
204
+ """
205
+ clauses: list[str] = []
206
+ params: list[Any] = []
207
+
208
+ if profile_id is not None:
209
+ clauses.append("profile_id = ?")
210
+ params.append(profile_id)
211
+ if agent_id is not None:
212
+ clauses.append("agent_id = ?")
213
+ params.append(agent_id)
214
+ if operation is not None:
215
+ clauses.append("operation = ?")
216
+ params.append(operation)
217
+ if start_date is not None:
218
+ clauses.append("timestamp >= ?")
219
+ params.append(start_date)
220
+ if end_date is not None:
221
+ clauses.append("timestamp <= ?")
222
+ params.append(end_date)
223
+
224
+ where = f"WHERE {' AND '.join(clauses)}" if clauses else ""
225
+ sql = (
226
+ f"SELECT id, timestamp, operation, agent_id, profile_id, "
227
+ f"content_hash, prev_hash, event_hash, metadata "
228
+ f"FROM audit_chain {where} "
229
+ f"ORDER BY id DESC LIMIT ?"
230
+ )
231
+ params.append(limit)
232
+
233
+ with self._lock:
234
+ conn = self._get_conn()
235
+ try:
236
+ rows = conn.execute(sql, params).fetchall()
237
+ return [dict(r) for r in rows]
238
+ finally:
239
+ self._release_conn(conn)
240
+
241
+ def verify_integrity(self) -> bool:
242
+ """Verify the entire hash chain. Returns True if chain is intact.
243
+
244
+ Walks every entry in order, recomputes each hash, and verifies
245
+ it matches the stored hash. Any mismatch means tampering.
246
+ """
247
+ conn = self._get_conn()
248
+ try:
249
+ rows = conn.execute(
250
+ "SELECT id, timestamp, operation, agent_id, profile_id, "
251
+ "content_hash, prev_hash, event_hash "
252
+ "FROM audit_chain ORDER BY id"
253
+ ).fetchall()
254
+ finally:
255
+ self._release_conn(conn)
256
+
257
+ if not rows:
258
+ return True
259
+
260
+ expected_prev = _GENESIS_HASH
261
+ for row in rows:
262
+ row_dict = dict(row)
263
+
264
+ # Check the prev_hash link
265
+ if row_dict["prev_hash"] != expected_prev:
266
+ logger.warning(
267
+ "Audit chain prev_hash mismatch at entry %d",
268
+ row_dict["id"],
269
+ )
270
+ return False
271
+
272
+ # Recompute the entry hash
273
+ computed = _compute_hash(
274
+ row_dict["prev_hash"],
275
+ row_dict["operation"],
276
+ row_dict["agent_id"],
277
+ row_dict["profile_id"],
278
+ row_dict["content_hash"],
279
+ row_dict["timestamp"],
280
+ )
281
+ if computed != row_dict["event_hash"]:
282
+ logger.warning(
283
+ "Audit chain event_hash mismatch at entry %d",
284
+ row_dict["id"],
285
+ )
286
+ return False
287
+
288
+ expected_prev = row_dict["event_hash"]
289
+
290
+ return True
291
+
292
+ def get_stats(self) -> dict[str, int]:
293
+ """Get audit statistics (event counts by operation type).
294
+
295
+ Returns:
296
+ Dict mapping operation names to their counts, plus
297
+ a 'total' key with the chain length.
298
+ """
299
+ conn = self._get_conn()
300
+ try:
301
+ rows = conn.execute(
302
+ "SELECT operation, COUNT(*) AS cnt "
303
+ "FROM audit_chain GROUP BY operation"
304
+ ).fetchall()
305
+ stats: dict[str, int] = {}
306
+ total = 0
307
+ for row in rows:
308
+ count = row["cnt"]
309
+ stats[row["operation"]] = count
310
+ total += count
311
+ stats["total"] = total
312
+ return stats
313
+ finally:
314
+ self._release_conn(conn)