memorymaster 3.19.0__tar.gz → 3.21.0__tar.gz

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 (388) hide show
  1. {memorymaster-3.19.0/memorymaster.egg-info → memorymaster-3.21.0}/PKG-INFO +6 -3
  2. {memorymaster-3.19.0 → memorymaster-3.21.0}/README.md +5 -2
  3. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/__init__.py +1 -1
  4. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/cli.py +24 -1
  5. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/cli_handlers_basic.py +92 -0
  6. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/cli_handlers_curation.py +26 -0
  7. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/config_templates/hooks/memorymaster-auto-ingest.py +24 -0
  8. memorymaster-3.21.0/memorymaster/delta_sync.py +172 -0
  9. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/mcp_server.py +51 -0
  10. memorymaster-3.21.0/memorymaster/migrations/0001_initial.py +25 -0
  11. memorymaster-3.21.0/memorymaster/migrations/0002_miner_state.py +38 -0
  12. memorymaster-3.21.0/memorymaster/migrations/__init__.py +41 -0
  13. memorymaster-3.21.0/memorymaster/migrations/runner.py +268 -0
  14. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/postgres_store.py +9 -0
  15. memorymaster-3.21.0/memorymaster/rule_miner.py +443 -0
  16. memorymaster-3.21.0/memorymaster/rules.py +104 -0
  17. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/service.py +39 -0
  18. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/storage.py +10 -0
  19. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/verbatim_store.py +41 -9
  20. {memorymaster-3.19.0 → memorymaster-3.21.0/memorymaster.egg-info}/PKG-INFO +6 -3
  21. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster.egg-info/SOURCES.txt +12 -0
  22. {memorymaster-3.19.0 → memorymaster-3.21.0}/pyproject.toml +1 -1
  23. memorymaster-3.21.0/tests/conftest.py +103 -0
  24. memorymaster-3.21.0/tests/test_backend_parity.py +167 -0
  25. memorymaster-3.21.0/tests/test_delta_sync.py +261 -0
  26. memorymaster-3.21.0/tests/test_migrations.py +292 -0
  27. memorymaster-3.21.0/tests/test_rule_claims.py +181 -0
  28. memorymaster-3.21.0/tests/test_rule_miner.py +297 -0
  29. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_verbatim_dedup.py +36 -0
  30. memorymaster-3.19.0/tests/conftest.py +0 -37
  31. {memorymaster-3.19.0 → memorymaster-3.21.0}/LICENSE +0 -0
  32. {memorymaster-3.19.0 → memorymaster-3.21.0}/artifacts/bm25-per-field-eval-harness.py +0 -0
  33. {memorymaster-3.19.0 → memorymaster-3.21.0}/benchmarks/longmemeval_runner.py +0 -0
  34. {memorymaster-3.19.0 → memorymaster-3.21.0}/benchmarks/longmemeval_vector_runner.py +0 -0
  35. {memorymaster-3.19.0 → memorymaster-3.21.0}/benchmarks/perf_smoke.py +0 -0
  36. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/__main__.py +0 -0
  37. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/_storage_lifecycle.py +0 -0
  38. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/_storage_read.py +0 -0
  39. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/_storage_schema.py +0 -0
  40. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/_storage_shared.py +0 -0
  41. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/_storage_sources.py +0 -0
  42. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/_storage_write_claims.py +0 -0
  43. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/access_control.py +0 -0
  44. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/action_exporters.py +0 -0
  45. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/action_extractor.py +0 -0
  46. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/atlas_claim_extractor.py +0 -0
  47. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/atlas_contract.py +0 -0
  48. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/auto_extractor.py +0 -0
  49. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/auto_resolver.py +0 -0
  50. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/candidate_dedupe.py +0 -0
  51. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/claim_edges.py +0 -0
  52. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/claim_verifier.py +0 -0
  53. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/cli_helpers.py +0 -0
  54. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/closets.py +0 -0
  55. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/config.py +0 -0
  56. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/config_templates/claude-md-append.md +0 -0
  57. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/config_templates/codex-agents-md-append.md +0 -0
  58. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/config_templates/hooks/memorymaster-classify.py +0 -0
  59. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/config_templates/hooks/memorymaster-dream-sync.py +0 -0
  60. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/config_templates/hooks/memorymaster-precompact.py +0 -0
  61. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/config_templates/hooks/memorymaster-recall.py +0 -0
  62. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/config_templates/hooks/memorymaster-session-start.py +0 -0
  63. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/config_templates/hooks/memorymaster-steward-cycle.py +0 -0
  64. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/config_templates/hooks/memorymaster-validate-wiki.py +0 -0
  65. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/conflict_resolver.py +0 -0
  66. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/connectors/__init__.py +0 -0
  67. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/connectors/whatsapp.py +0 -0
  68. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/context_hook.py +0 -0
  69. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/context_optimizer.py +0 -0
  70. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/daily_notes.py +0 -0
  71. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/dashboard.py +0 -0
  72. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/dashboard_auth.py +0 -0
  73. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/db_merge.py +0 -0
  74. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/dream_bridge.py +0 -0
  75. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/embeddings.py +0 -0
  76. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/entity_extractor.py +0 -0
  77. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/entity_graph.py +0 -0
  78. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/entity_registry.py +0 -0
  79. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/federated_graphify.py +0 -0
  80. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/feedback.py +0 -0
  81. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/graph_store.py +0 -0
  82. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/hook_log.py +0 -0
  83. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/jobs/__init__.py +0 -0
  84. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/jobs/calibration.py +0 -0
  85. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/jobs/compact_summaries.py +0 -0
  86. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/jobs/compactor.py +0 -0
  87. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/jobs/daydream_ingest.py +0 -0
  88. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/jobs/decay.py +0 -0
  89. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/jobs/dedup.py +0 -0
  90. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/jobs/deterministic.py +0 -0
  91. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/jobs/entity_graph_export.py +0 -0
  92. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/jobs/extractor.py +0 -0
  93. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/jobs/staleness.py +0 -0
  94. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/jobs/validator.py +0 -0
  95. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/key_rotator.py +0 -0
  96. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/lifecycle.py +0 -0
  97. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/llm_budget.py +0 -0
  98. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/llm_provider.py +0 -0
  99. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/llm_rerank.py +0 -0
  100. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/llm_steward.py +0 -0
  101. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/mcp_path_policy.py +0 -0
  102. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/mcp_usage.py +0 -0
  103. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/media_processing.py +0 -0
  104. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/media_providers.py +0 -0
  105. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/metrics_exporter.py +0 -0
  106. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/models.py +0 -0
  107. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/observability.py +0 -0
  108. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/operator.py +0 -0
  109. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/operator_queue.py +0 -0
  110. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/plugins.py +0 -0
  111. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/policy.py +0 -0
  112. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/qdrant_backend.py +0 -0
  113. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/qdrant_recall_fallback.py +0 -0
  114. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/qmd_bridge.py +0 -0
  115. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/query_classifier.py +0 -0
  116. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/query_expansion.py +0 -0
  117. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/recall_fusion.py +0 -0
  118. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/recall_tokenizer.py +0 -0
  119. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/retrieval.py +0 -0
  120. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/retry.py +0 -0
  121. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/review.py +0 -0
  122. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/rl_trainer.py +0 -0
  123. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/scheduler.py +0 -0
  124. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/schema.py +0 -0
  125. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/schema.sql +0 -0
  126. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/schema_postgres.sql +0 -0
  127. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/scope_utils.py +0 -0
  128. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/security.py +0 -0
  129. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/session_tracker.py +0 -0
  130. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/setup_hooks.py +0 -0
  131. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/skill_evolver.py +0 -0
  132. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/snapshot.py +0 -0
  133. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/steward.py +0 -0
  134. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/steward_classifier.py +0 -0
  135. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/steward_features.py +0 -0
  136. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/store_factory.py +0 -0
  137. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/transcript_miner.py +0 -0
  138. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/turn_schema.py +0 -0
  139. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/vault_bases.py +0 -0
  140. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/vault_curator.py +0 -0
  141. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/vault_exporter.py +0 -0
  142. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/vault_linter.py +0 -0
  143. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/vault_log.py +0 -0
  144. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/vault_query_capture.py +0 -0
  145. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/vault_synthesis.py +0 -0
  146. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/verbatim_recall.py +0 -0
  147. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/webhook.py +0 -0
  148. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/wiki_engine.py +0 -0
  149. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/wiki_freshness.py +0 -0
  150. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/wiki_similarity.py +0 -0
  151. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/wiki_suggest.py +0 -0
  152. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster/wiki_validate.py +0 -0
  153. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster.egg-info/dependency_links.txt +0 -0
  154. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster.egg-info/entry_points.txt +0 -0
  155. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster.egg-info/requires.txt +0 -0
  156. {memorymaster-3.19.0 → memorymaster-3.21.0}/memorymaster.egg-info/top_level.txt +0 -0
  157. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/agg_recall_latency.py +0 -0
  158. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/alert_operator_metrics.py +0 -0
  159. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/audit_dedupe_precision.py +0 -0
  160. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/autoresearch_daemon.py +0 -0
  161. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/backfill_entity_extraction.py +0 -0
  162. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/backfill_graph_store.py +0 -0
  163. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/backfill_stop_hook_citations.py +0 -0
  164. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/backtest_steward_classifier.py +0 -0
  165. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/build_steward_training_set.py +0 -0
  166. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/check_hook_template_drift.py +0 -0
  167. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/claude_to_turns.py +0 -0
  168. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/codex_live_to_turns.py +0 -0
  169. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/compaction_edge_cases.py +0 -0
  170. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/compaction_trace_report.py +0 -0
  171. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/compaction_trace_validate.py +0 -0
  172. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/confusion_matrix_eval.py +0 -0
  173. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/conversation_importer.py +0 -0
  174. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/conversation_to_turns.py +0 -0
  175. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/e2e_operator.py +0 -0
  176. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/email_live_to_turns.py +0 -0
  177. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/eval_bm25_sweep.py +0 -0
  178. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/eval_classify_f1.py +0 -0
  179. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/eval_memorymaster.py +0 -0
  180. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/eval_recall_precision_at_5.py +0 -0
  181. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/eval_recall_quality.py +0 -0
  182. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/eval_steward_pareto.py +0 -0
  183. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/eval_verbatim_recall.py +0 -0
  184. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/expand_recall_eval.py +0 -0
  185. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/generate_drill_signoff.py +0 -0
  186. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/git_to_turns.py +0 -0
  187. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/github_live_to_turns.py +0 -0
  188. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/gitnexus_to_claims.py +0 -0
  189. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/grid_recall_weights.py +0 -0
  190. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/index_claims_to_qdrant.py +0 -0
  191. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/ingest_planning_docs.py +0 -0
  192. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/jira_live_to_turns.py +0 -0
  193. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/label_prompts_with_judge.py +0 -0
  194. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/llm_benchmark.py +0 -0
  195. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/measure_dedupe_thresholds.py +0 -0
  196. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/merge_scope_variants.py +0 -0
  197. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/messages_to_turns.py +0 -0
  198. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/operator_metrics.py +0 -0
  199. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/precompute_candidates.py +0 -0
  200. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/recurring_incident_drill.py +0 -0
  201. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/release_readiness.py +0 -0
  202. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/run_codex_autologger.py +0 -0
  203. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/run_incident_drill.py +0 -0
  204. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/scheduled_ingest.py +0 -0
  205. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/setup-hooks.py +0 -0
  206. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/slack_live_to_turns.py +0 -0
  207. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/sync_hook_templates.py +0 -0
  208. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/tickets_to_turns.py +0 -0
  209. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/train_steward_classifier.py +0 -0
  210. {memorymaster-3.19.0 → memorymaster-3.21.0}/scripts/webhook_to_turns.py +0 -0
  211. {memorymaster-3.19.0 → memorymaster-3.21.0}/setup.cfg +0 -0
  212. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/bench_longmemeval.py +0 -0
  213. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/integration/test_extract_llm_ollama_live.py +0 -0
  214. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_access_control.py +0 -0
  215. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_action_exporters.py +0 -0
  216. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_action_extractor.py +0 -0
  217. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_atlas_claim_extractor.py +0 -0
  218. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_atlas_contract.py +0 -0
  219. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_atlas_source_schema.py +0 -0
  220. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_auto_extractor.py +0 -0
  221. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_auto_ingest_hook_citations.py +0 -0
  222. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_auto_ingest_hook_schema.py +0 -0
  223. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_auto_resolver.py +0 -0
  224. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_auto_validate.py +0 -0
  225. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_bm25_per_field.py +0 -0
  226. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_calibration.py +0 -0
  227. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_calibration_priors_applied.py +0 -0
  228. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_candidate_dedupe.py +0 -0
  229. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_claim_edges.py +0 -0
  230. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_claim_links.py +0 -0
  231. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_claim_type_ranking.py +0 -0
  232. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_classify_hook_f1.py +0 -0
  233. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_classify_hook_latency.py +0 -0
  234. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_claude_to_turns.py +0 -0
  235. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_cli_dry_run.py +0 -0
  236. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_cli_json_flag.py +0 -0
  237. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_cli_ready.py +0 -0
  238. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_cli_review_queue.py +0 -0
  239. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_cli_subcommands.py +0 -0
  240. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_closets.py +0 -0
  241. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_closets_recall_integration.py +0 -0
  242. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_compact_summaries.py +0 -0
  243. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_compact_summaries_sensitivity.py +0 -0
  244. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_compaction_trace.py +0 -0
  245. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_compactor_artifact_order.py +0 -0
  246. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_config.py +0 -0
  247. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_conflict_resolver.py +0 -0
  248. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_confusion_matrix_eval.py +0 -0
  249. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_connection_retry.py +0 -0
  250. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_connectors.py +0 -0
  251. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_context_hook.py +0 -0
  252. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_context_optimizer.py +0 -0
  253. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_context_optimizer_provider.py +0 -0
  254. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_conversation_to_turns.py +0 -0
  255. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_dashboard.py +0 -0
  256. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_dashboard_auth.py +0 -0
  257. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_dashboard_coverage.py +0 -0
  258. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_dashboard_latency.py +0 -0
  259. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_dashboard_lineage.py +0 -0
  260. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_dashboard_review_queue.py +0 -0
  261. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_daydream_ingest.py +0 -0
  262. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_db_merge_confidence_conflict.py +0 -0
  263. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_db_merge_coverage_v2.py +0 -0
  264. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_decay_coverage.py +0 -0
  265. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_decay_respects_pinned.py +0 -0
  266. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_dedup.py +0 -0
  267. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_dedup_cli.py +0 -0
  268. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_dedup_conflict_disambiguation.py +0 -0
  269. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_deterministic_predicates.py +0 -0
  270. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_dream_bridge_coverage_v2.py +0 -0
  271. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_dream_bridge_sensitivity.py +0 -0
  272. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_embeddings_coverage.py +0 -0
  273. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_entity_extractor.py +0 -0
  274. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_entity_extractor_llm.py +0 -0
  275. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_entity_graph.py +0 -0
  276. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_entity_graph_export.py +0 -0
  277. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_entity_new_kinds.py +0 -0
  278. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_entity_regex_v3.py +0 -0
  279. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_entity_registry.py +0 -0
  280. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_eval_harness.py +0 -0
  281. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_events_schema.py +0 -0
  282. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_extract_llm_ollama.py +0 -0
  283. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_federated_graphify_mcp.py +0 -0
  284. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_federated_query_safety.py +0 -0
  285. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_feedback.py +0 -0
  286. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_fts5_search.py +0 -0
  287. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_graph_distance.py +0 -0
  288. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_graph_store.py +0 -0
  289. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_handler_regressions.py +0 -0
  290. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_hook_env_isolation.py +0 -0
  291. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_human_id.py +0 -0
  292. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_incident_drill_runner.py +0 -0
  293. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_integration_workflows.py +0 -0
  294. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_key_rotator.py +0 -0
  295. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_lifecycle.py +0 -0
  296. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_lifecycle_supersede_invariant.py +0 -0
  297. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_llm_budget.py +0 -0
  298. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_llm_fallback.py +0 -0
  299. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_llm_provider_claude_cli.py +0 -0
  300. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_llm_provider_key_rotation.py +0 -0
  301. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_llm_steward_coverage.py +0 -0
  302. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_llm_steward_key_rotation.py +0 -0
  303. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_mcp_filter_bypass.py +0 -0
  304. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_mcp_helpers.py +0 -0
  305. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_mcp_path_policy.py +0 -0
  306. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_mcp_rate_limit.py +0 -0
  307. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_mcp_server_validation.py +0 -0
  308. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_mcp_usage.py +0 -0
  309. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_media_processing.py +0 -0
  310. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_meta_decisions.py +0 -0
  311. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_metrics_exporter.py +0 -0
  312. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_observability.py +0 -0
  313. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_obsidian_mind_patterns.py +0 -0
  314. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_operator.py +0 -0
  315. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_operator_queue.py +0 -0
  316. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_perf_smoke_config.py +0 -0
  317. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_plugins.py +0 -0
  318. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_policy_coverage.py +0 -0
  319. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_policy_mode_env.py +0 -0
  320. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_postgres_parity.py +0 -0
  321. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_qdrant_backend.py +0 -0
  322. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_qmd_bridge.py +0 -0
  323. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_query_classifier.py +0 -0
  324. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_query_expansion.py +0 -0
  325. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_recall_entity_fanout.py +0 -0
  326. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_recall_fusion.py +0 -0
  327. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_recall_latency.py +0 -0
  328. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_recall_precision_at_5.py +0 -0
  329. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_recall_tokenizer.py +0 -0
  330. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_recall_vector_fallback.py +0 -0
  331. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_reliability_hardening.py +0 -0
  332. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_resolvers_concurrent_supersede.py +0 -0
  333. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_retrieval_profile.py +0 -0
  334. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_retrieval_profiles.py +0 -0
  335. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_retrieval_rrf_tiebreaker.py +0 -0
  336. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_retrieval_weights.py +0 -0
  337. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_review.py +0 -0
  338. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_rl_trainer.py +0 -0
  339. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_rrf_auto_gate.py +0 -0
  340. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_scheduler.py +0 -0
  341. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_schema.py +0 -0
  342. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_scope_boost.py +0 -0
  343. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_scope_utils.py +0 -0
  344. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_security_access.py +0 -0
  345. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_security_patterns.py +0 -0
  346. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_sensitivity_filter_adversarial.py +0 -0
  347. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_sensitivity_filter_adversarial_v2.py +0 -0
  348. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_sensitivity_filter_t07.py +0 -0
  349. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_service_coverage.py +0 -0
  350. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_session_tracker.py +0 -0
  351. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_snapshot.py +0 -0
  352. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_snapshot_roundtrip.py +0 -0
  353. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_sqlite_core.py +0 -0
  354. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_staleness.py +0 -0
  355. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_stealth_mode.py +0 -0
  356. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_steward.py +0 -0
  357. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_steward_classifier.py +0 -0
  358. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_steward_daydream_hook.py +0 -0
  359. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_steward_features.py +0 -0
  360. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_steward_features_v3.py +0 -0
  361. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_steward_resolution_parity.py +0 -0
  362. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_storage_parity.py +0 -0
  363. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_store_factory.py +0 -0
  364. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_tenant_isolation.py +0 -0
  365. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_turn_schema.py +0 -0
  366. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_two_pass_recall.py +0 -0
  367. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_v311_fixes.py +0 -0
  368. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_v313_e2e.py +0 -0
  369. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_v313_run_cycle_dedupe.py +0 -0
  370. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_v390_e2e.py +0 -0
  371. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_v391_strict_warnings.py +0 -0
  372. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_vault_exporter.py +0 -0
  373. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_vault_linter_orphan.py +0 -0
  374. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_vector_search.py +0 -0
  375. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_verbatim_recall.py +0 -0
  376. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_verbatim_store.py +0 -0
  377. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_verbatim_store_qdrant.py +0 -0
  378. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_webhook.py +0 -0
  379. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_webhook_hmac.py +0 -0
  380. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_whatsapp_importer.py +0 -0
  381. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_wiki_autopromote.py +0 -0
  382. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_wiki_binding.py +0 -0
  383. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_wiki_engine_idempotency.py +0 -0
  384. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_wiki_explored_and_contradictions.py +0 -0
  385. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_wiki_freshness.py +0 -0
  386. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_wiki_similarity_multiscope.py +0 -0
  387. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_wiki_suggest.py +0 -0
  388. {memorymaster-3.19.0 → memorymaster-3.21.0}/tests/test_wiki_validate_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memorymaster
3
- Version: 3.19.0
3
+ Version: 3.21.0
4
4
  Summary: Production-grade memory reliability system for AI coding agents. Lifecycle-managed claims with citations, conflict detection, steward governance, and MCP integration.
5
5
  Author: wolverin0
6
6
  License: MIT
@@ -52,7 +52,7 @@ Lifecycle-managed claims with citations, conflict detection, steward governance,
52
52
 
53
53
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
54
54
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
55
- [![Tests](https://img.shields.io/badge/tests-1953-green.svg)]()
55
+ [![Tests](https://img.shields.io/badge/tests-2194-green.svg)]()
56
56
  [![MCP Tools](https://img.shields.io/badge/MCP%20tools-24-purple.svg)]()
57
57
  [![CLI Commands](https://img.shields.io/badge/CLI%20commands-86-orange.svg)]()
58
58
  [![PyPI](https://img.shields.io/pypi/v/memorymaster.svg)](https://pypi.org/project/memorymaster/)
@@ -87,6 +87,9 @@ recent PR status, and sensitivity-filter invariants.
87
87
  - **Hybrid retrieval**: vector (sentence-transformers / Gemini) + FTS5 + freshness + confidence
88
88
  - **Context optimizer**: `query_for_context(budget=4000)` returns auto-curated memory that fits your token budget
89
89
  - **Entity graph** with typed relationships and alias resolution
90
+ - **Rule-shaped claims** (new in v3.21.0): prescriptive `when <trigger>, do <action> because <rationale>` claims (`ingest_rule` / `query_rules`) — the shape an agent needs to actually change behaviour next time, not just recall a fact
91
+ - **Correction mining** (new in v3.21.0): `mine-rules` scans the verbatim transcript archive for user corrections and distills them into rule claims; the Stop hook also mines each session's latest correction automatically
92
+ - **Versioned schema migrations** (new in v3.20.0): `migrate` applies SQLite/Postgres migrations with sha256 drift detection; incremental `export-delta` ships small claim deltas for cheap cross-machine sync
90
93
  - **Steward governance**: multi-probe validators (filesystem, format, citation, semantic, tool) with proposal review
91
94
  - **Conflict resolution**: 5-tier auto (confidence > freshness > citations > LLM > manual)
92
95
  - **Auto-redaction** at ingest: JWT, GitHub tokens, Bearer, AWS keys, SSH keys, custom patterns
@@ -187,7 +190,7 @@ For zero-cost offline use, install [Ollama](https://ollama.com), `ollama pull ll
187
190
  }
188
191
  ```
189
192
 
190
- 22 MCP tools: `init_db`, `ingest_claim`, `run_cycle`, `run_steward`, `classify_query`, `query_memory`, `query_for_context`, `list_claims`, `redact_claim_payload`, `pin_claim`, `compact_memory`, `list_events`, `search_verbatim`, `open_dashboard`, `list_steward_proposals`, `resolve_steward_proposal`, `extract_entities`, `entity_stats`, `find_related_claims`, `quality_scores`, `recompute_tiers`, `federated_query`.
193
+ 24 MCP tools: `init_db`, `ingest_claim`, `ingest_rule`, `query_rules`, `run_cycle`, `run_steward`, `classify_query`, `query_memory`, `query_for_context`, `list_claims`, `redact_claim_payload`, `pin_claim`, `compact_memory`, `list_events`, `search_verbatim`, `open_dashboard`, `list_steward_proposals`, `resolve_steward_proposal`, `extract_entities`, `entity_stats`, `find_related_claims`, `quality_scores`, `recompute_tiers`, `federated_query`.
191
194
 
192
195
  See [`.mcp.json.example`](.mcp.json.example) for the full template.
193
196
 
@@ -6,7 +6,7 @@ Lifecycle-managed claims with citations, conflict detection, steward governance,
6
6
 
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
8
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
9
- [![Tests](https://img.shields.io/badge/tests-1953-green.svg)]()
9
+ [![Tests](https://img.shields.io/badge/tests-2194-green.svg)]()
10
10
  [![MCP Tools](https://img.shields.io/badge/MCP%20tools-24-purple.svg)]()
11
11
  [![CLI Commands](https://img.shields.io/badge/CLI%20commands-86-orange.svg)]()
12
12
  [![PyPI](https://img.shields.io/pypi/v/memorymaster.svg)](https://pypi.org/project/memorymaster/)
@@ -41,6 +41,9 @@ recent PR status, and sensitivity-filter invariants.
41
41
  - **Hybrid retrieval**: vector (sentence-transformers / Gemini) + FTS5 + freshness + confidence
42
42
  - **Context optimizer**: `query_for_context(budget=4000)` returns auto-curated memory that fits your token budget
43
43
  - **Entity graph** with typed relationships and alias resolution
44
+ - **Rule-shaped claims** (new in v3.21.0): prescriptive `when <trigger>, do <action> because <rationale>` claims (`ingest_rule` / `query_rules`) — the shape an agent needs to actually change behaviour next time, not just recall a fact
45
+ - **Correction mining** (new in v3.21.0): `mine-rules` scans the verbatim transcript archive for user corrections and distills them into rule claims; the Stop hook also mines each session's latest correction automatically
46
+ - **Versioned schema migrations** (new in v3.20.0): `migrate` applies SQLite/Postgres migrations with sha256 drift detection; incremental `export-delta` ships small claim deltas for cheap cross-machine sync
44
47
  - **Steward governance**: multi-probe validators (filesystem, format, citation, semantic, tool) with proposal review
45
48
  - **Conflict resolution**: 5-tier auto (confidence > freshness > citations > LLM > manual)
46
49
  - **Auto-redaction** at ingest: JWT, GitHub tokens, Bearer, AWS keys, SSH keys, custom patterns
@@ -141,7 +144,7 @@ For zero-cost offline use, install [Ollama](https://ollama.com), `ollama pull ll
141
144
  }
142
145
  ```
143
146
 
144
- 22 MCP tools: `init_db`, `ingest_claim`, `run_cycle`, `run_steward`, `classify_query`, `query_memory`, `query_for_context`, `list_claims`, `redact_claim_payload`, `pin_claim`, `compact_memory`, `list_events`, `search_verbatim`, `open_dashboard`, `list_steward_proposals`, `resolve_steward_proposal`, `extract_entities`, `entity_stats`, `find_related_claims`, `quality_scores`, `recompute_tiers`, `federated_query`.
147
+ 24 MCP tools: `init_db`, `ingest_claim`, `ingest_rule`, `query_rules`, `run_cycle`, `run_steward`, `classify_query`, `query_memory`, `query_for_context`, `list_claims`, `redact_claim_payload`, `pin_claim`, `compact_memory`, `list_events`, `search_verbatim`, `open_dashboard`, `list_steward_proposals`, `resolve_steward_proposal`, `extract_entities`, `entity_stats`, `find_related_claims`, `quality_scores`, `recompute_tiers`, `federated_query`.
145
148
 
146
149
  See [`.mcp.json.example`](.mcp.json.example) for the full template.
147
150
 
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "3.4.1"
5
+ __version__ = "3.21.0"
@@ -23,7 +23,9 @@ from memorymaster.cli_handlers_curation import COMMAND_HANDLERS
23
23
  from memorymaster.cli_handlers_basic import (
24
24
  _handle_decay,
25
25
  _handle_entity_graph_export,
26
+ _handle_export_delta,
26
27
  _handle_ingest_daydream,
28
+ _handle_migrate,
27
29
  _handle_recompute_confidence_priors,
28
30
  _handle_wiki_suggest_links,
29
31
  handle_mcp_usage_report,
@@ -38,6 +40,8 @@ COMMAND_HANDLERS["ingest-daydream"] = _handle_ingest_daydream
38
40
  COMMAND_HANDLERS["mcp-usage-report"] = (
39
41
  lambda args, service, parser, effective_db: handle_mcp_usage_report(args, effective_db)
40
42
  )
43
+ COMMAND_HANDLERS["migrate"] = _handle_migrate
44
+ COMMAND_HANDLERS["export-delta"] = _handle_export_delta
41
45
 
42
46
 
43
47
  def build_parser() -> argparse.ArgumentParser:
@@ -51,6 +55,11 @@ def build_parser() -> argparse.ArgumentParser:
51
55
 
52
56
  sub.add_parser("init-db", help="Create schema in SQLite database")
53
57
 
58
+ migrate = sub.add_parser("migrate", help="Apply pending versioned schema migrations (v3.20.0+)")
59
+ migrate_mode = migrate.add_mutually_exclusive_group()
60
+ migrate_mode.add_argument("--list", action="store_true", help="List known migrations without touching the DB")
61
+ migrate_mode.add_argument("--status", action="store_true", help="Report applied vs pending per migration")
62
+
54
63
  sub.add_parser("stealth-status", help="Show whether stealth mode is active and which DB is in use")
55
64
 
56
65
  ingest = sub.add_parser("ingest", help="Ingest a raw claim with citations")
@@ -447,6 +456,13 @@ def build_parser() -> argparse.ArgumentParser:
447
456
  mine_cmd.add_argument("--scope", default="project", help="Scope for ingested claims")
448
457
  mine_cmd.add_argument("--max", type=int, default=100, help="Max claims to ingest")
449
458
 
459
+ mine_rules_cmd = sub.add_parser("mine-rules", help="Mine verbatim corrections into rule-shaped claims (v3.21.0-R1b)")
460
+ mine_rules_cmd.add_argument("--since-id", dest="since_id", type=int, default=None, help="Override the stored watermark; start scanning after this verbatim id")
461
+ mine_rules_cmd.add_argument("--limit", type=int, default=None, help="Max candidate windows to examine this run (caps LLM calls)")
462
+ mine_rules_cmd.add_argument("--batch-size", dest="batch_size", type=int, default=200, help="Rows fetched per SQL pre-filter page (default: 200)")
463
+ mine_rules_cmd.add_argument("--provider", default="claude_cli", help="LLM provider for this run (default: claude_cli)")
464
+ mine_rules_cmd.add_argument("--reset", action="store_true", help="Clear the stored watermark before running (re-scan from the start)")
465
+
450
466
  verify_cmd = sub.add_parser("verify-claims", help="Cross-check claims against current codebase")
451
467
  verify_cmd.add_argument("--scope", default="", help="Scope filter")
452
468
  verify_cmd.add_argument("--limit", type=int, default=200, help="Max claims to check")
@@ -504,6 +520,13 @@ def build_parser() -> argparse.ArgumentParser:
504
520
  merge_cmd = sub.add_parser("merge-db", help="Merge claims from a remote memorymaster DB (bidirectional sync)")
505
521
  merge_cmd.add_argument("--source", required=True, help="Path to source DB file to merge from")
506
522
 
523
+ delta_cmd = sub.add_parser(
524
+ "export-delta",
525
+ help="Export claims changed since a watermark into a small SQLite delta file (incremental sync)",
526
+ )
527
+ delta_cmd.add_argument("--since", default="", help="ISO-8601 watermark; export claims with updated_at after this (empty = full export)")
528
+ delta_cmd.add_argument("--output", required=True, help="Path to write the delta SQLite file (overwritten if it exists)")
529
+
507
530
  daily = sub.add_parser("daily-note", help="Generate a daily note summarizing today's activity")
508
531
  daily.add_argument("--date", default="", help="Date to generate for (YYYY-MM-DD, default: today)")
509
532
  daily.add_argument("--output", default="", help="Directory to save .md file (default: print to stdout)")
@@ -556,7 +579,7 @@ def main(argv: list[str] | None = None) -> int:
556
579
  effective_db = _resolve_db_path(args)
557
580
 
558
581
  # Commands that don't need MemoryService run first; service is lazy-created once for all others.
559
- _NO_SERVICE_COMMANDS = {"stealth-status", "export-metrics", "wiki-freshness", "mcp-usage-report"}
582
+ _NO_SERVICE_COMMANDS = {"stealth-status", "export-metrics", "wiki-freshness", "mcp-usage-report", "export-delta"}
560
583
 
561
584
  try:
562
585
  handler = COMMAND_HANDLERS.get(args.command)
@@ -1357,3 +1357,95 @@ def _handle_check_staleness(args: argparse.Namespace, service, parser: argparse.
1357
1357
  return 0
1358
1358
 
1359
1359
 
1360
+ def _handle_migrate(args: argparse.Namespace, service, parser: argparse.ArgumentParser, effective_db: str) -> int:
1361
+ """v3.20.0-S1: apply pending schema migrations, or report status.
1362
+
1363
+ Default (no flags): apply every pending migration in version order.
1364
+ --list: dump known migrations (version + description) without touching the DB.
1365
+ --status: query the DB and show applied vs pending per migration.
1366
+ """
1367
+ from memorymaster.migrations import (
1368
+ MigrationRunner,
1369
+ discover_migrations,
1370
+ )
1371
+ from memorymaster.store_factory import is_postgres_dsn
1372
+
1373
+ # --list works without a DB connection at all.
1374
+ if getattr(args, "list", False):
1375
+ migrations = discover_migrations()
1376
+ if args.json_output:
1377
+ payload = [{"version": m.version, "description": m.description} for m in migrations]
1378
+ print(_json_envelope(payload))
1379
+ else:
1380
+ print(f"known migrations ({len(migrations)}):")
1381
+ for m in migrations:
1382
+ print(f" v{m.version:04d} {m.description}")
1383
+ return 0
1384
+
1385
+ backend = "postgres" if is_postgres_dsn(effective_db) else "sqlite"
1386
+ store = service.store
1387
+ with store.connect() as conn:
1388
+ runner = MigrationRunner(conn, backend=backend)
1389
+
1390
+ if getattr(args, "status", False):
1391
+ entries = runner.status()
1392
+ if args.json_output:
1393
+ payload = [
1394
+ {
1395
+ "version": e.version,
1396
+ "description": e.description,
1397
+ "applied": e.applied,
1398
+ "applied_at": e.applied_at,
1399
+ }
1400
+ for e in entries
1401
+ ]
1402
+ print(_json_envelope(payload))
1403
+ else:
1404
+ print(f"backend={backend} db={effective_db}")
1405
+ for e in entries:
1406
+ marker = "[applied]" if e.applied else "[pending]"
1407
+ when = f" applied_at={e.applied_at}" if e.applied_at else ""
1408
+ print(f" v{e.version:04d} {marker} {e.description}{when}")
1409
+ return 0
1410
+
1411
+ # Default: apply pending
1412
+ newly = runner.apply_pending()
1413
+ if args.json_output:
1414
+ print(_json_envelope({"applied": newly, "backend": backend}))
1415
+ else:
1416
+ if not newly:
1417
+ print(f"migrate: nothing to apply (backend={backend}, db={effective_db})")
1418
+ else:
1419
+ print(f"migrate: applied {len(newly)} migration(s) on backend={backend}:")
1420
+ for v in newly:
1421
+ print(f" v{v:04d}")
1422
+ return 0
1423
+
1424
+
1425
+ def _handle_export_delta(args: argparse.Namespace, service, parser: argparse.ArgumentParser, effective_db: str) -> int:
1426
+ """Export claims changed since a watermark into a small SQLite delta file.
1427
+
1428
+ The delta file is a valid `merge-db --source` input. Prints (or JSON-emits)
1429
+ the export counts and the new watermark — callers should record
1430
+ `max_updated_at` and pass it as `--since` on the next run.
1431
+ """
1432
+ from memorymaster.delta_sync import export_delta
1433
+
1434
+ t0 = time.perf_counter()
1435
+ result = export_delta(effective_db, args.since, args.output)
1436
+ elapsed_ms = (time.perf_counter() - t0) * 1000
1437
+ if args.json_output:
1438
+ print(_json_envelope(result, query_ms=elapsed_ms))
1439
+ else:
1440
+ since_label = result["since"] or "(full export)"
1441
+ print(
1442
+ f"export-delta: {result['exported']} claims + {result['citations']} citations "
1443
+ f"since {since_label} -> {args.output}"
1444
+ )
1445
+ if result["max_updated_at"]:
1446
+ print(f" next watermark (--since): {result['max_updated_at']}")
1447
+ else:
1448
+ print(" delta is empty — nothing changed since the watermark")
1449
+ return 0
1450
+
1451
+
@@ -343,6 +343,31 @@ def _handle_mine_transcript(args: argparse.Namespace, service, parser: argparse.
343
343
  return 0
344
344
 
345
345
 
346
+ def _handle_mine_rules(args: argparse.Namespace, service, parser: argparse.ArgumentParser, effective_db: str) -> int:
347
+ from memorymaster.rule_miner import mine_rules
348
+ t0 = time.perf_counter()
349
+ result = mine_rules(
350
+ effective_db,
351
+ service,
352
+ since_id=getattr(args, "since_id", None),
353
+ limit=getattr(args, "limit", None),
354
+ batch_size=getattr(args, "batch_size", 200),
355
+ provider=getattr(args, "provider", "claude_cli"),
356
+ reset=getattr(args, "reset", False),
357
+ )
358
+ elapsed_ms = (time.perf_counter() - t0) * 1000
359
+ if args.json_output:
360
+ print(_json_envelope(result, query_ms=elapsed_ms))
361
+ else:
362
+ abort = f", ABORTED ({result['aborted_reason']})" if result.get("aborted_reason") else ""
363
+ print(
364
+ f"Mined rules: {result['candidates']} candidates, {result['llm_calls']} llm calls, "
365
+ f"{result['ingested']} ingested, {result['duplicates']} dupes, {result['skipped']} skipped "
366
+ f"(watermark={result['last_id']}{abort}, {elapsed_ms:.0f}ms)"
367
+ )
368
+ return 0
369
+
370
+
346
371
  def _handle_wiki_breakdown(args: argparse.Namespace, service, parser: argparse.ArgumentParser, effective_db: str) -> int:
347
372
  from memorymaster.wiki_engine import breakdown
348
373
  t0 = time.perf_counter()
@@ -703,6 +728,7 @@ COMMAND_HANDLERS: dict[str, object] = {
703
728
  "wiki-freshness": _handle_wiki_freshness,
704
729
  "bases-generate": _handle_bases_generate,
705
730
  "mine-transcript": _handle_mine_transcript,
731
+ "mine-rules": _handle_mine_rules,
706
732
  "verify-claims": _handle_verify_claims,
707
733
  "extract-entities": _handle_extract_entities,
708
734
  "entity-stats": _handle_entity_stats,
@@ -180,6 +180,27 @@ Only: bug root causes, decisions, gotchas, constraints. Never: credentials, IPs,
180
180
  pass
181
181
 
182
182
 
183
+ def _run_rule_extraction(transcript_path, cwd):
184
+ """R1b ongoing: mine the latest correction in this session into a rule claim.
185
+
186
+ Reuses memorymaster.rule_miner.mine_transcript_rules (single source of truth
187
+ for the correction->rule prompt + ingest path). Bounded to one window per
188
+ stop to keep the hook fast; rules land as low-confidence candidates."""
189
+ try:
190
+ if not transcript_path or not os.path.exists(transcript_path) or not os.path.exists(DB_PATH):
191
+ return
192
+ from memorymaster.rule_miner import mine_transcript_rules
193
+ from memorymaster.service import MemoryService
194
+
195
+ scope = "project:" + os.path.basename(cwd).lower().replace(" ", "-") if cwd else "global"
196
+ svc = MemoryService(DB_PATH, workspace_root=Path(cwd or PROJECT_ROOT))
197
+ stats = mine_transcript_rules(transcript_path, svc, scope=scope, max_windows=1)
198
+ if stats.get("ingested"):
199
+ sys.stderr.write(f"[MemoryMaster] mined {stats['ingested']} rule(s) from corrections\n")
200
+ except Exception:
201
+ pass
202
+
203
+
183
204
  def main():
184
205
  try:
185
206
  data = json.loads(sys.stdin.read() or "{}")
@@ -232,6 +253,9 @@ def main():
232
253
  # Not time to block — run passive Gemini extraction
233
254
  _run_gemini_extraction(transcript_path, cwd)
234
255
 
256
+ # R1b: mine the latest correction in this session into a rule claim
257
+ _run_rule_extraction(transcript_path, cwd)
258
+
235
259
  sys.stdout.write(json.dumps({"decision": "approve"}))
236
260
 
237
261
 
@@ -0,0 +1,172 @@
1
+ """Delta export for incremental memory sync.
2
+
3
+ Pre-existing sync (``scripts/openclaw-sync.sh``) copied the WHOLE database
4
+ file across the network twice per cycle — fine when the DB was small,
5
+ wasteful at 2.5 GB. The ``merge-db`` step was always append-only (it dedups
6
+ on ``idempotency_key`` + text-hash), but the *transport* moved everything.
7
+
8
+ ``export_delta`` closes that gap: it writes a small SQLite file containing
9
+ ONLY the claims (and their citations) changed since a watermark timestamp.
10
+ That small file is a valid merge source — ``merge-db --source delta.db``
11
+ consumes it unchanged, because the merge engine only reads ``claims`` and
12
+ ``citations`` and ignores everything else.
13
+
14
+ Sync loop with deltas:
15
+
16
+ 1. each side: ``export-delta --since <last_sync> --output delta.db``
17
+ 2. ship the small delta.db over the network (KB, not GB)
18
+ 3. each side: ``merge-db --source <other-side-delta.db>``
19
+ 4. record the new watermark (``max_updated_at`` from the export result)
20
+
21
+ The whole-DB file never crosses the network — which also removes the
22
+ SQLite-over-network-mount corruption risk entirely.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ import sqlite3
28
+ from pathlib import Path
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ # Tables a merge source must carry. The merge engine (db_merge.py) reads
34
+ # `claims` (SELECT *) and `citations` (source/locator/excerpt/created_at).
35
+ # Nothing else is needed — FTS5, events, entity tables are all rebuilt or
36
+ # ignored on the merge side.
37
+ _DELTA_TABLES = ("claims", "citations")
38
+
39
+
40
+ def _copy_table_ddl(src: sqlite3.Connection, out: sqlite3.Connection, table: str) -> None:
41
+ """Copy a table's CREATE statement verbatim from src into out."""
42
+ row = src.execute(
43
+ "SELECT sql FROM sqlite_master WHERE type='table' AND name=?",
44
+ (table,),
45
+ ).fetchone()
46
+ if row is None or not row[0]:
47
+ raise ValueError(f"source DB has no '{table}' table — not a memorymaster DB?")
48
+ out.execute(row[0])
49
+
50
+
51
+ def export_delta(
52
+ source_db: str | Path,
53
+ since: str,
54
+ output_path: str | Path,
55
+ ) -> dict[str, object]:
56
+ """Write claims changed since ``since`` into a small SQLite delta file.
57
+
58
+ Args:
59
+ source_db: path to the full memorymaster DB to export from.
60
+ since: ISO-8601 timestamp watermark. Claims with ``updated_at`` strictly
61
+ greater than this are exported. Pass an empty string or a very
62
+ early date to export everything (full bootstrap).
63
+ output_path: where to write the delta SQLite file. Overwritten if it
64
+ exists.
65
+
66
+ Returns:
67
+ dict with ``exported`` (claim count), ``citations`` (citation count),
68
+ ``since`` (echoed watermark), and ``max_updated_at`` (the newest
69
+ ``updated_at`` seen — use this as the next watermark; None when the
70
+ delta is empty).
71
+
72
+ Raises:
73
+ FileNotFoundError: source DB missing.
74
+ ValueError: source DB lacks the expected tables.
75
+ """
76
+ source_db = str(source_db)
77
+ output_path = Path(output_path)
78
+ if not Path(source_db).exists():
79
+ raise FileNotFoundError(f"Source DB not found: {source_db}")
80
+
81
+ # Fresh output file every time — a stale delta would merge old rows again
82
+ # (harmless thanks to idempotent merge, but wasteful).
83
+ if output_path.exists():
84
+ output_path.unlink()
85
+ output_path.parent.mkdir(parents=True, exist_ok=True)
86
+
87
+ src = sqlite3.connect(source_db)
88
+ src.row_factory = sqlite3.Row
89
+ out = sqlite3.connect(str(output_path))
90
+ try:
91
+ for table in _DELTA_TABLES:
92
+ _copy_table_ddl(src, out, table)
93
+
94
+ # `since` empty => full export. SQLite string comparison on ISO-8601
95
+ # timestamps is chronological, so it works as a watermark.
96
+ #
97
+ # We use `>=`, not `>`. Strictly-after would SKIP any claim whose
98
+ # updated_at exactly equals the watermark — and multiple claims can
99
+ # share a timestamp (same-second ingest). Skipping a claim is silent
100
+ # data loss. `>=` instead re-exports the boundary claim(s); the merge
101
+ # engine is idempotent (dedups on idempotency_key + text-hash), so a
102
+ # re-export costs nothing but a few rows. Safe beats clean.
103
+ watermark = since.strip()
104
+ if watermark:
105
+ claim_rows = src.execute(
106
+ "SELECT * FROM claims WHERE updated_at >= ? ORDER BY updated_at",
107
+ (watermark,),
108
+ ).fetchall()
109
+ else:
110
+ claim_rows = src.execute(
111
+ "SELECT * FROM claims ORDER BY updated_at"
112
+ ).fetchall()
113
+
114
+ if not claim_rows:
115
+ out.commit()
116
+ return {
117
+ "exported": 0,
118
+ "citations": 0,
119
+ "since": watermark,
120
+ "max_updated_at": None,
121
+ }
122
+
123
+ claim_cols = [c[1] for c in src.execute("PRAGMA table_info(claims)").fetchall()]
124
+ placeholders = ",".join("?" for _ in claim_cols)
125
+ col_list = ",".join(claim_cols)
126
+ insert_claim = f"INSERT INTO claims ({col_list}) VALUES ({placeholders})"
127
+
128
+ exported_ids: list[int] = []
129
+ max_updated = ""
130
+ for row in claim_rows:
131
+ out.execute(insert_claim, tuple(row[c] for c in claim_cols))
132
+ exported_ids.append(int(row["id"]))
133
+ updated = str(row["updated_at"] or "")
134
+ if updated > max_updated:
135
+ max_updated = updated
136
+
137
+ # Citations for exactly the exported claims. claim_id linkage is
138
+ # preserved because we keep original claim ids in the delta file.
139
+ cit_cols = [c[1] for c in src.execute("PRAGMA table_info(citations)").fetchall()]
140
+ cit_placeholders = ",".join("?" for _ in cit_cols)
141
+ cit_col_list = ",".join(cit_cols)
142
+ insert_cit = f"INSERT INTO citations ({cit_col_list}) VALUES ({cit_placeholders})"
143
+
144
+ citation_count = 0
145
+ # Chunk the IN clause — SQLite caps host parameters at 999.
146
+ for start in range(0, len(exported_ids), 900):
147
+ batch = exported_ids[start : start + 900]
148
+ qmarks = ",".join("?" for _ in batch)
149
+ cit_rows = src.execute(
150
+ f"SELECT * FROM citations WHERE claim_id IN ({qmarks})",
151
+ batch,
152
+ ).fetchall()
153
+ for cit in cit_rows:
154
+ out.execute(insert_cit, tuple(cit[c] for c in cit_cols))
155
+ citation_count += 1
156
+
157
+ out.commit()
158
+ logger.info(
159
+ "export_delta: %d claims, %d citations since %r",
160
+ len(exported_ids),
161
+ citation_count,
162
+ watermark or "(full)",
163
+ )
164
+ return {
165
+ "exported": len(exported_ids),
166
+ "citations": citation_count,
167
+ "since": watermark,
168
+ "max_updated_at": max_updated or None,
169
+ }
170
+ finally:
171
+ src.close()
172
+ out.close()
@@ -1113,6 +1113,57 @@ if FastMCP is not None:
1113
1113
  )
1114
1114
  return {"ok": True, "rows": len(claims), "claims": [_claim_to_dict(c) for c in claims]}
1115
1115
 
1116
+ @mcp.tool()
1117
+ def ingest_rule(
1118
+ trigger: str,
1119
+ action: str,
1120
+ rationale: str = "",
1121
+ db: str = "memorymaster.db",
1122
+ workspace: str = ".",
1123
+ scope: str = "project",
1124
+ source_agent: str = "mcp",
1125
+ ) -> dict[str, Any]:
1126
+ """Ingest a prescriptive rule-shaped claim (v3.21.0-R1).
1127
+
1128
+ A rule captures "when <trigger>, do <action> because <rationale>" —
1129
+ the behavioural shape, distinct from descriptive fact claims. Stored
1130
+ as a claim_type='rule' claim; retrieve via query_rules.
1131
+ """
1132
+ from memorymaster.rules import build_rule_fields
1133
+
1134
+ svc = _service(db, workspace)
1135
+ fields = build_rule_fields(trigger, action, rationale)
1136
+ # Auto-citation: a rule's provenance is the session that taught it.
1137
+ claim = svc.ingest(
1138
+ **fields,
1139
+ citations=[CitationInput(source=f"agent://{source_agent}", locator="rule", excerpt=action[:200])],
1140
+ scope=scope,
1141
+ source_agent=source_agent,
1142
+ )
1143
+ return {"ok": True, "claim_id": claim.id, "human_id": claim.human_id, "rule": fields["text"]}
1144
+
1145
+ @mcp.tool()
1146
+ def query_rules(
1147
+ query: str,
1148
+ db: str = "memorymaster.db",
1149
+ workspace: str = ".",
1150
+ limit: int = 10,
1151
+ allow_sensitive: bool = False,
1152
+ ) -> dict[str, Any]:
1153
+ """Retrieve rule-shaped claims matching a query, in prescriptive form.
1154
+
1155
+ Returns each rule's trigger / action / rationale / text, ranked by the
1156
+ hybrid retriever. Use this when you want only behavioural rules, not
1157
+ descriptive fact claims.
1158
+ """
1159
+ resolve_allow_sensitive_access(
1160
+ allow_sensitive=allow_sensitive,
1161
+ context="mcp.query_rules",
1162
+ )
1163
+ svc = _service(db, workspace)
1164
+ rules = svc.query_rules(query, limit=limit, allow_sensitive=allow_sensitive)
1165
+ return {"ok": True, "rows": len(rules), "rules": rules}
1166
+
1116
1167
  @mcp.tool()
1117
1168
  def redact_claim_payload(
1118
1169
  claim_id: int,
@@ -0,0 +1,25 @@
1
+ """0001_initial — baseline marker for the v3.20.0 migration framework.
2
+
3
+ This migration is intentionally a no-op. As of v3.20.0-S1 the pre-existing
4
+ schema (from ``schema.sql`` / ``schema_postgres.sql`` and the inline
5
+ ``_ensure_*_schema`` helpers in storage layers) is treated as the baseline
6
+ v0001. The runner stamps this version into ``schema_versions`` on first
7
+ run so subsequent NEW migrations (v0002+) apply cleanly on top.
8
+
9
+ Future migrations starting at v0002 will contain real ``ALTER TABLE`` /
10
+ ``CREATE INDEX`` / data-backfill DDL.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ VERSION = 1
15
+ DESCRIPTION = "baseline (existing schema as of v3.20.0)"
16
+
17
+
18
+ def apply_sqlite(conn) -> None: # noqa: ARG001 — intentional no-op baseline
19
+ """No-op: existing schema is the baseline."""
20
+ return None
21
+
22
+
23
+ def apply_postgres(conn) -> None: # noqa: ARG001 — intentional no-op baseline
24
+ """No-op: existing schema is the baseline."""
25
+ return None
@@ -0,0 +1,38 @@
1
+ """0002_miner_state — KV table for resumable miner watermarks (v3.21.0-R1b).
2
+
3
+ The verbatim rule-miner (:mod:`memorymaster.rule_miner`) needs to remember
4
+ how far through ``verbatim_memories`` it has scanned so re-runs are
5
+ incremental. The watermark is a single integer, not a per-row fact, so it
6
+ lives in a small key-value table rather than a column on the 744k-row
7
+ verbatim table (a column would force a full backfill and contend with the
8
+ Stop hook's inserts under WAL).
9
+
10
+ A KV table also generalizes: future miners reuse it with different keys.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ VERSION = 2
15
+ DESCRIPTION = "miner_state KV table for resumable verbatim mining watermarks"
16
+
17
+ _DDL = """
18
+ CREATE TABLE IF NOT EXISTS miner_state (
19
+ key TEXT PRIMARY KEY,
20
+ value TEXT,
21
+ updated_at TEXT
22
+ )
23
+ """.strip()
24
+
25
+
26
+ def apply_sqlite(conn) -> None:
27
+ conn.execute(_DDL)
28
+ commit = getattr(conn, "commit", None)
29
+ if callable(commit):
30
+ commit()
31
+
32
+
33
+ def apply_postgres(conn) -> None:
34
+ cur = conn.cursor()
35
+ cur.execute(_DDL)
36
+ commit = getattr(conn, "commit", None)
37
+ if callable(commit):
38
+ commit()
@@ -0,0 +1,41 @@
1
+ """Versioned schema migrations for MemoryMaster (v3.20.0-S1).
2
+
3
+ Pre-v3.20 schema evolved via opportunistic ``ALTER TABLE`` with try/except
4
+ sprinkled across storage modules — no version tracking, no rollback, silent
5
+ drift risk between SQLite and Postgres. This package replaces that pattern
6
+ with explicit versioned migrations.
7
+
8
+ Each migration is a Python module under this package named
9
+ ``NNNN_short_description.py`` with four module-level attributes:
10
+
11
+ VERSION: int # 1, 2, 3, ...
12
+ DESCRIPTION: str # one-line human summary
13
+ def apply_sqlite(conn) -> None: ... # SQLite-backend DDL
14
+ def apply_postgres(conn) -> None: ... # Postgres-backend DDL
15
+
16
+ The ``MigrationRunner`` discovers all migration modules, sorts by VERSION,
17
+ and applies only the ones not yet recorded in the ``schema_versions``
18
+ table. Each applied migration's file source is sha256-checksummed and
19
+ stored alongside its version; on subsequent runs, a mismatch raises
20
+ ``MigrationDriftError`` — migrations are immutable once applied.
21
+
22
+ Public API:
23
+
24
+ from memorymaster.migrations import MigrationRunner, MigrationDriftError
25
+ runner = MigrationRunner(conn, backend="sqlite")
26
+ runner.apply_pending()
27
+ runner.status() # -> list of (version, description, applied_at, status)
28
+ """
29
+ from __future__ import annotations
30
+
31
+ from memorymaster.migrations.runner import (
32
+ MigrationDriftError,
33
+ MigrationRunner,
34
+ discover_migrations,
35
+ )
36
+
37
+ __all__ = [
38
+ "MigrationDriftError",
39
+ "MigrationRunner",
40
+ "discover_migrations",
41
+ ]