memorymaster 3.10.0__tar.gz → 3.12.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 (294) hide show
  1. {memorymaster-3.10.0 → memorymaster-3.12.0}/PKG-INFO +1 -1
  2. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/claim_edges.py +54 -3
  3. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/closets.py +31 -10
  4. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/context_hook.py +52 -6
  5. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster.egg-info/PKG-INFO +1 -1
  6. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster.egg-info/SOURCES.txt +1 -0
  7. {memorymaster-3.10.0 → memorymaster-3.12.0}/pyproject.toml +1 -1
  8. memorymaster-3.12.0/tests/test_v311_fixes.py +172 -0
  9. {memorymaster-3.10.0 → memorymaster-3.12.0}/LICENSE +0 -0
  10. {memorymaster-3.10.0 → memorymaster-3.12.0}/README.md +0 -0
  11. {memorymaster-3.10.0 → memorymaster-3.12.0}/artifacts/bm25-per-field-eval-harness.py +0 -0
  12. {memorymaster-3.10.0 → memorymaster-3.12.0}/benchmarks/longmemeval_runner.py +0 -0
  13. {memorymaster-3.10.0 → memorymaster-3.12.0}/benchmarks/longmemeval_vector_runner.py +0 -0
  14. {memorymaster-3.10.0 → memorymaster-3.12.0}/benchmarks/perf_smoke.py +0 -0
  15. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/__init__.py +0 -0
  16. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/__main__.py +0 -0
  17. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/_storage_lifecycle.py +0 -0
  18. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/_storage_read.py +0 -0
  19. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/_storage_schema.py +0 -0
  20. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/_storage_shared.py +0 -0
  21. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/_storage_write_claims.py +0 -0
  22. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/access_control.py +0 -0
  23. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/auto_extractor.py +0 -0
  24. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/auto_resolver.py +0 -0
  25. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/claim_verifier.py +0 -0
  26. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/cli.py +0 -0
  27. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/cli_handlers_basic.py +0 -0
  28. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/cli_handlers_curation.py +0 -0
  29. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/cli_helpers.py +0 -0
  30. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config.py +0 -0
  31. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/claude-md-append.md +0 -0
  32. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/codex-agents-md-append.md +0 -0
  33. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-auto-ingest.py +0 -0
  34. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-classify.py +0 -0
  35. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-dream-sync.py +0 -0
  36. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-observe.py +0 -0
  37. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-precompact.py +0 -0
  38. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-recall.py +0 -0
  39. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-session-start.py +0 -0
  40. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-steward-cycle.py +0 -0
  41. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-validate-wiki.py +0 -0
  42. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/conflict_resolver.py +0 -0
  43. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/context_optimizer.py +0 -0
  44. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/daily_notes.py +0 -0
  45. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/dashboard.py +0 -0
  46. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/db_merge.py +0 -0
  47. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/dream_bridge.py +0 -0
  48. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/embeddings.py +0 -0
  49. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/entity_extractor.py +0 -0
  50. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/entity_graph.py +0 -0
  51. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/entity_registry.py +0 -0
  52. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/federated_graphify.py +0 -0
  53. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/feedback.py +0 -0
  54. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/graph_store.py +0 -0
  55. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/hook_log.py +0 -0
  56. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/__init__.py +0 -0
  57. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/compact_summaries.py +0 -0
  58. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/compactor.py +0 -0
  59. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/decay.py +0 -0
  60. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/dedup.py +0 -0
  61. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/deterministic.py +0 -0
  62. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/extractor.py +0 -0
  63. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/staleness.py +0 -0
  64. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/validator.py +0 -0
  65. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/key_rotator.py +0 -0
  66. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/lifecycle.py +0 -0
  67. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/llm_provider.py +0 -0
  68. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/llm_steward.py +0 -0
  69. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/mcp_server.py +0 -0
  70. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/metrics_exporter.py +0 -0
  71. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/models.py +0 -0
  72. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/operator.py +0 -0
  73. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/operator_queue.py +0 -0
  74. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/plugins.py +0 -0
  75. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/policy.py +0 -0
  76. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/postgres_store.py +0 -0
  77. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/qdrant_backend.py +0 -0
  78. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/qdrant_recall_fallback.py +0 -0
  79. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/qmd_bridge.py +0 -0
  80. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/query_classifier.py +0 -0
  81. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/query_expansion.py +0 -0
  82. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/recall_fusion.py +0 -0
  83. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/recall_tokenizer.py +0 -0
  84. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/retrieval.py +0 -0
  85. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/retry.py +0 -0
  86. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/review.py +0 -0
  87. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/rl_trainer.py +0 -0
  88. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/scheduler.py +0 -0
  89. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/schema.py +0 -0
  90. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/schema.sql +0 -0
  91. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/schema_postgres.sql +0 -0
  92. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/scope_utils.py +0 -0
  93. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/security.py +0 -0
  94. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/service.py +0 -0
  95. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/session_tracker.py +0 -0
  96. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/setup_hooks.py +0 -0
  97. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/skill_evolver.py +0 -0
  98. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/snapshot.py +0 -0
  99. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/steward.py +0 -0
  100. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/steward_classifier.py +0 -0
  101. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/steward_features.py +0 -0
  102. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/storage.py +0 -0
  103. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/store_factory.py +0 -0
  104. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/transcript_miner.py +0 -0
  105. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/turn_schema.py +0 -0
  106. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_bases.py +0 -0
  107. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_curator.py +0 -0
  108. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_exporter.py +0 -0
  109. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_linter.py +0 -0
  110. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_log.py +0 -0
  111. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_query_capture.py +0 -0
  112. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_synthesis.py +0 -0
  113. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/verbatim_recall.py +0 -0
  114. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/verbatim_store.py +0 -0
  115. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/webhook.py +0 -0
  116. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/wiki_engine.py +0 -0
  117. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/wiki_freshness.py +0 -0
  118. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/wiki_similarity.py +0 -0
  119. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/wiki_validate.py +0 -0
  120. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster.egg-info/dependency_links.txt +0 -0
  121. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster.egg-info/entry_points.txt +0 -0
  122. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster.egg-info/requires.txt +0 -0
  123. {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster.egg-info/top_level.txt +0 -0
  124. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/agg_recall_latency.py +0 -0
  125. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/alert_operator_metrics.py +0 -0
  126. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/autoresearch_daemon.py +0 -0
  127. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/backfill_entity_extraction.py +0 -0
  128. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/backfill_graph_store.py +0 -0
  129. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/backfill_stop_hook_citations.py +0 -0
  130. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/backtest_steward_classifier.py +0 -0
  131. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/build_steward_training_set.py +0 -0
  132. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/check_hook_template_drift.py +0 -0
  133. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/claude_to_turns.py +0 -0
  134. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/codex_live_to_turns.py +0 -0
  135. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/compaction_edge_cases.py +0 -0
  136. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/compaction_trace_report.py +0 -0
  137. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/compaction_trace_validate.py +0 -0
  138. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/confusion_matrix_eval.py +0 -0
  139. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/conversation_importer.py +0 -0
  140. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/conversation_to_turns.py +0 -0
  141. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/e2e_operator.py +0 -0
  142. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/email_live_to_turns.py +0 -0
  143. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_bm25_sweep.py +0 -0
  144. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_classify_f1.py +0 -0
  145. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_memorymaster.py +0 -0
  146. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_recall_precision_at_5.py +0 -0
  147. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_recall_quality.py +0 -0
  148. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_steward_pareto.py +0 -0
  149. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_verbatim_recall.py +0 -0
  150. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/expand_recall_eval.py +0 -0
  151. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/generate_drill_signoff.py +0 -0
  152. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/git_to_turns.py +0 -0
  153. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/github_live_to_turns.py +0 -0
  154. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/gitnexus_to_claims.py +0 -0
  155. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/grid_recall_weights.py +0 -0
  156. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/index_claims_to_qdrant.py +0 -0
  157. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/ingest_planning_docs.py +0 -0
  158. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/jira_live_to_turns.py +0 -0
  159. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/label_prompts_with_judge.py +0 -0
  160. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/llm_benchmark.py +0 -0
  161. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/merge_scope_variants.py +0 -0
  162. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/messages_to_turns.py +0 -0
  163. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/operator_metrics.py +0 -0
  164. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/precompute_candidates.py +0 -0
  165. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/recurring_incident_drill.py +0 -0
  166. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/release_readiness.py +0 -0
  167. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/run_codex_autologger.py +0 -0
  168. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/run_incident_drill.py +0 -0
  169. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/run_longmemeval.py +0 -0
  170. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/scheduled_ingest.py +0 -0
  171. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/setup-hooks.py +0 -0
  172. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/slack_live_to_turns.py +0 -0
  173. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/sync_hook_templates.py +0 -0
  174. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/tickets_to_turns.py +0 -0
  175. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/train_steward_classifier.py +0 -0
  176. {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/webhook_to_turns.py +0 -0
  177. {memorymaster-3.10.0 → memorymaster-3.12.0}/setup.cfg +0 -0
  178. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/conftest.py +0 -0
  179. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/integration/test_extract_llm_ollama_live.py +0 -0
  180. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_access_control.py +0 -0
  181. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_auto_extractor.py +0 -0
  182. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_auto_ingest_hook_citations.py +0 -0
  183. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_auto_ingest_hook_schema.py +0 -0
  184. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_auto_resolver.py +0 -0
  185. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_auto_validate.py +0 -0
  186. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_bm25_per_field.py +0 -0
  187. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_claim_edges.py +0 -0
  188. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_claim_links.py +0 -0
  189. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_claim_type_ranking.py +0 -0
  190. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_classify_hook_f1.py +0 -0
  191. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_classify_hook_latency.py +0 -0
  192. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_claude_to_turns.py +0 -0
  193. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_cli_json_flag.py +0 -0
  194. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_cli_ready.py +0 -0
  195. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_cli_review_queue.py +0 -0
  196. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_cli_subcommands.py +0 -0
  197. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_closets.py +0 -0
  198. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_closets_recall_integration.py +0 -0
  199. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_compact_summaries.py +0 -0
  200. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_compaction_trace.py +0 -0
  201. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_config.py +0 -0
  202. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_conflict_resolver.py +0 -0
  203. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_confusion_matrix_eval.py +0 -0
  204. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_connection_retry.py +0 -0
  205. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_connectors.py +0 -0
  206. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_context_hook.py +0 -0
  207. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_context_optimizer.py +0 -0
  208. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_conversation_to_turns.py +0 -0
  209. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_dashboard.py +0 -0
  210. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_dedup.py +0 -0
  211. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_deterministic_predicates.py +0 -0
  212. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_embeddings_coverage.py +0 -0
  213. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_entity_extractor.py +0 -0
  214. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_entity_extractor_llm.py +0 -0
  215. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_entity_graph.py +0 -0
  216. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_entity_new_kinds.py +0 -0
  217. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_entity_regex_v3.py +0 -0
  218. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_entity_registry.py +0 -0
  219. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_eval_harness.py +0 -0
  220. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_events_schema.py +0 -0
  221. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_extract_llm_ollama.py +0 -0
  222. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_federated_graphify_mcp.py +0 -0
  223. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_feedback.py +0 -0
  224. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_fts5_search.py +0 -0
  225. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_graph_distance.py +0 -0
  226. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_graph_store.py +0 -0
  227. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_handler_regressions.py +0 -0
  228. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_hook_env_isolation.py +0 -0
  229. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_human_id.py +0 -0
  230. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_incident_drill_runner.py +0 -0
  231. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_integration_workflows.py +0 -0
  232. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_key_rotator.py +0 -0
  233. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_lifecycle.py +0 -0
  234. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_llm_fallback.py +0 -0
  235. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_llm_provider_claude_cli.py +0 -0
  236. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_llm_steward_coverage.py +0 -0
  237. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_llm_steward_key_rotation.py +0 -0
  238. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_mcp_helpers.py +0 -0
  239. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_metrics_exporter.py +0 -0
  240. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_obsidian_mind_patterns.py +0 -0
  241. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_operator.py +0 -0
  242. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_operator_queue.py +0 -0
  243. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_perf_smoke_config.py +0 -0
  244. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_plugins.py +0 -0
  245. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_policy_coverage.py +0 -0
  246. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_policy_mode_env.py +0 -0
  247. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_postgres_parity.py +0 -0
  248. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_qdrant_backend.py +0 -0
  249. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_qmd_bridge.py +0 -0
  250. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_query_classifier.py +0 -0
  251. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_query_expansion.py +0 -0
  252. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_recall_entity_fanout.py +0 -0
  253. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_recall_fusion.py +0 -0
  254. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_recall_latency.py +0 -0
  255. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_recall_precision_at_5.py +0 -0
  256. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_recall_tokenizer.py +0 -0
  257. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_recall_vector_fallback.py +0 -0
  258. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_reliability_hardening.py +0 -0
  259. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_review.py +0 -0
  260. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_rl_trainer.py +0 -0
  261. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_rrf_auto_gate.py +0 -0
  262. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_scheduler.py +0 -0
  263. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_schema.py +0 -0
  264. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_scope_boost.py +0 -0
  265. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_scope_utils.py +0 -0
  266. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_security_access.py +0 -0
  267. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_security_patterns.py +0 -0
  268. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_sensitivity_filter_adversarial.py +0 -0
  269. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_sensitivity_filter_adversarial_v2.py +0 -0
  270. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_service_coverage.py +0 -0
  271. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_session_tracker.py +0 -0
  272. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_snapshot.py +0 -0
  273. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_sqlite_core.py +0 -0
  274. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_staleness.py +0 -0
  275. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_stealth_mode.py +0 -0
  276. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_steward.py +0 -0
  277. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_steward_classifier.py +0 -0
  278. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_steward_features.py +0 -0
  279. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_steward_features_v3.py +0 -0
  280. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_steward_resolution_parity.py +0 -0
  281. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_store_factory.py +0 -0
  282. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_tenant_isolation.py +0 -0
  283. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_turn_schema.py +0 -0
  284. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_two_pass_recall.py +0 -0
  285. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_v390_e2e.py +0 -0
  286. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_v391_strict_warnings.py +0 -0
  287. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_vault_exporter.py +0 -0
  288. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_vector_search.py +0 -0
  289. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_verbatim_recall.py +0 -0
  290. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_webhook.py +0 -0
  291. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_wiki_binding.py +0 -0
  292. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_wiki_freshness.py +0 -0
  293. {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_wiki_similarity_multiscope.py +0 -0
  294. {memorymaster-3.10.0 → memorymaster-3.12.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.10.0
3
+ Version: 3.12.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
@@ -39,6 +39,7 @@ __all__ = [
39
39
 
40
40
  MENTION_KIND = "mentions"
41
41
  SUPERSEDES_KIND = "supersedes"
42
+ SHARES_ENTITY_KIND = "shares_entity" # v3.11 P3 — claim_a and claim_b have the same primary entity_id
42
43
 
43
44
 
44
45
  _CLAIM_NUM_RE = re.compile(r"\bclaims?\s+(\d{1,6})\b", re.IGNORECASE)
@@ -112,14 +113,31 @@ def extract_edges_for_claim(
112
113
  return edges
113
114
 
114
115
 
115
- def rebuild_edges(db_path: str | Path, *, batch_size: int = 500) -> dict[str, int]:
116
+ def rebuild_edges(
117
+ db_path: str | Path,
118
+ *,
119
+ batch_size: int = 500,
120
+ include_shares_entity: bool = True,
121
+ shares_entity_max_per_pivot: int = 50,
122
+ ) -> dict[str, int]:
116
123
  """Walk the entire claims table and rebuild the claim_edges index.
117
124
 
118
- Returns counters: ``{"claims_scanned": N, "edges_written": M, "supersession_edges": K}``.
125
+ Returns counters: ``{"claims_scanned": N, "edges_written": M, "supersession_edges": K, "shares_entity_edges": L}``.
119
126
 
120
127
  Idempotent: ``INSERT OR IGNORE`` against the composite primary key.
128
+
129
+ v3.11 P3 — when ``include_shares_entity`` (default True), also writes
130
+ ``shares_entity`` edges between any two claims that share their primary
131
+ ``entity_id``. Capped at ``shares_entity_max_per_pivot`` neighbors per
132
+ entity to bound the explosion: a popular entity (say, "claim") with 200
133
+ referencing claims would otherwise produce 200×199/2 = 19,900 edges.
121
134
  """
122
- counters = {"claims_scanned": 0, "edges_written": 0, "supersession_edges": 0}
135
+ counters = {
136
+ "claims_scanned": 0,
137
+ "edges_written": 0,
138
+ "supersession_edges": 0,
139
+ "shares_entity_edges": 0,
140
+ }
123
141
  conn = sqlite3.connect(str(db_path))
124
142
  try:
125
143
  ensure_claim_edges_schema(conn)
@@ -151,6 +169,39 @@ def rebuild_edges(db_path: str | Path, *, batch_size: int = 500) -> dict[str, in
151
169
  if counters["claims_scanned"] % batch_size == 0:
152
170
  conn.commit()
153
171
  conn.commit()
172
+
173
+ # v3.11 P3 — shares_entity edges. One pass over claims-grouped-by-
174
+ # entity_id; for each non-trivial group emit pairwise edges (capped).
175
+ if include_shares_entity:
176
+ try:
177
+ groups = conn.execute(
178
+ """
179
+ SELECT entity_id, GROUP_CONCAT(id) AS ids
180
+ FROM claims
181
+ WHERE entity_id IS NOT NULL
182
+ GROUP BY entity_id
183
+ HAVING COUNT(*) BETWEEN 2 AND ?
184
+ """,
185
+ (shares_entity_max_per_pivot,),
186
+ ).fetchall()
187
+ except sqlite3.OperationalError:
188
+ groups = []
189
+ for _eid, ids_str in groups:
190
+ ids = [int(x) for x in (ids_str or "").split(",") if x.strip().isdigit()]
191
+ # Pairwise edges (a, b) with a < b only — keeps it bidirectional
192
+ # via a single canonical row.
193
+ for i, a in enumerate(ids):
194
+ for b in ids[i + 1:]:
195
+ cur = conn.execute(
196
+ "INSERT OR IGNORE INTO claim_edges "
197
+ "(src_claim_id, dst_claim_id, edge_kind, created_at) "
198
+ "VALUES (?, ?, ?, ?)",
199
+ (a, b, SHARES_ENTITY_KIND, now),
200
+ )
201
+ if cur.rowcount:
202
+ counters["edges_written"] += 1
203
+ counters["shares_entity_edges"] += 1
204
+ conn.commit()
154
205
  finally:
155
206
  conn.close()
156
207
  return counters
@@ -186,10 +186,21 @@ def rebuild_closets(
186
186
 
187
187
 
188
188
  def search_closets(
189
- db_path: str | Path, query: str, *, limit: int = 5
190
- ) -> list[tuple[str, list[int]]]:
189
+ db_path: str | Path, query: str, *, limit: int = 5, with_scores: bool = False
190
+ ) -> list[tuple[str, list[int]]] | list[tuple[str, list[int], float]]:
191
191
  """Return ``[(slug, claim_ids), ...]`` ranked by FTS5 BM25.
192
192
 
193
+ When ``with_scores=True`` (v3.11 P1), returns ``[(slug, claim_ids, score)]``
194
+ where ``score`` is the FTS5 BM25 score normalised to [0, 1] across this
195
+ result set. The recall hook uses this to weight closet hits proportional
196
+ to how well the query matched, instead of the v3.10 constant 1.0 that
197
+ flooded the top-5 with article-membership noise.
198
+
199
+ BM25 from SQLite's FTS5 returns NEGATIVE values where 0 = no match and
200
+ more-negative = better match (`ORDER BY bm25(...) ASC` puts best first).
201
+ We normalise: ``score = best_bm25 / row_bm25`` so the best match scores
202
+ 1.0 and weaker matches scale below.
203
+
193
204
  Defensive: if the table doesn't exist or the query is malformed, returns
194
205
  an empty list rather than raising.
195
206
  """
@@ -197,9 +208,6 @@ def search_closets(
197
208
  return []
198
209
  conn = sqlite3.connect(str(db_path))
199
210
  try:
200
- # Sanitize query: FTS5 MATCH treats most punctuation as syntax. The
201
- # safest path for LLM-shaped queries is to extract bare alphanumeric
202
- # tokens and OR them together.
203
211
  tokens = [t for t in re.findall(r"[A-Za-z0-9_]{3,}", query) if t]
204
212
  if not tokens:
205
213
  return []
@@ -207,26 +215,39 @@ def search_closets(
207
215
  try:
208
216
  rows = conn.execute(
209
217
  """
210
- SELECT c.article_slug, c.claim_ids_json
218
+ SELECT c.article_slug, c.claim_ids_json, bm25(closets_fts) AS bm25_score
211
219
  FROM closets_fts ft
212
220
  JOIN closets c ON c.article_slug = ft.article_slug
213
221
  WHERE closets_fts MATCH ?
214
- ORDER BY bm25(closets_fts)
222
+ ORDER BY bm25_score
215
223
  LIMIT ?
216
224
  """,
217
225
  (fts_query, limit),
218
226
  ).fetchall()
219
227
  except sqlite3.OperationalError:
220
228
  return []
221
- out: list[tuple[str, list[int]]] = []
222
- for slug, ids_json in rows:
229
+ if not rows:
230
+ return []
231
+ # FTS5 BM25 returns negative numbers; lower (more negative) = better.
232
+ # The first row is the best. Normalise so best=1.0, others scale below.
233
+ best = abs(rows[0][2]) if rows[0][2] else 1.0
234
+ if best == 0.0:
235
+ best = 1.0
236
+ out: list[tuple] = []
237
+ for slug, ids_json, bm25_score in rows:
223
238
  try:
224
239
  ids = json.loads(ids_json or "[]")
225
240
  if not isinstance(ids, list):
226
241
  ids = []
227
242
  except (json.JSONDecodeError, ValueError):
228
243
  ids = []
229
- out.append((str(slug), [int(i) for i in ids if isinstance(i, int)]))
244
+ normalised = abs(bm25_score) / best if bm25_score else 0.0
245
+ normalised = max(0.0, min(1.0, normalised))
246
+ cleaned_ids = [int(i) for i in ids if isinstance(i, int)]
247
+ if with_scores:
248
+ out.append((str(slug), cleaned_ids, normalised))
249
+ else:
250
+ out.append((str(slug), cleaned_ids))
230
251
  return out
231
252
  finally:
232
253
  conn.close()
@@ -426,6 +426,18 @@ def _closets_enabled() -> bool:
426
426
  return raw not in ("0", "false", "False", "no", "off", "")
427
427
 
428
428
 
429
+ def _closets_boost_only() -> bool:
430
+ """v3.11 P1b — when set, closets only BOOST already-recalled rows.
431
+
432
+ The default is OFF (legacy v3.10 behaviour: hydrate new candidates AND
433
+ boost). Boost-only mode is MemPalace's stricter "boost signal, never
434
+ gate" interpretation — it can't displace real lexical matches because
435
+ it never adds new candidates, only re-ranks the existing set.
436
+ """
437
+ raw = os.environ.get("MEMORYMASTER_RECALL_CLOSETS_BOOST_ONLY", "0").strip()
438
+ return raw not in ("0", "false", "False", "no", "off", "")
439
+
440
+
429
441
  def _two_pass_max_neighbors() -> int:
430
442
  """Cap neighbors discovered per recall call (default 20)."""
431
443
  raw = os.environ.get("MEMORYMASTER_RECALL_TWO_PASS_MAX", "20").strip()
@@ -1305,7 +1317,10 @@ def _recall_impl(
1305
1317
  _search_closets = None
1306
1318
  if _search_closets is not None:
1307
1319
  try:
1308
- closet_hits = _search_closets(db, query, limit=5)
1320
+ # v3.11 P1 fix: cap to 3 articles (was 5) to bound the
1321
+ # candidate flood, and request BM25-normalised scores so
1322
+ # weaker matches don't dominate ranking with constant 1.0.
1323
+ closet_hits = _search_closets(db, query, limit=3, with_scores=True)
1309
1324
  except Exception as exc: # noqa: BLE001
1310
1325
  logger.debug("closets stream skipped (search): %s", exc)
1311
1326
  closet_hits = []
@@ -1315,11 +1330,22 @@ def _recall_impl(
1315
1330
  c = row.get("claim")
1316
1331
  if c is not None and getattr(c, "id", None) is not None:
1317
1332
  existing_by_id[int(c.id)] = row
1318
- for _slug, claim_ids in closet_hits:
1333
+ boost_only = _closets_boost_only()
1334
+ for hit in closet_hits:
1335
+ if len(hit) == 3:
1336
+ _slug, claim_ids, score = hit
1337
+ else:
1338
+ _slug, claim_ids = hit
1339
+ score = 1.0
1319
1340
  for cid in claim_ids:
1320
1341
  if cid in existing_by_id:
1321
- # Boost: closet_score=1.0 is the constant signal
1322
- existing_by_id[cid]["closet_score"] = 1.0
1342
+ prev = float(existing_by_id[cid].get("closet_score") or 0.0)
1343
+ existing_by_id[cid]["closet_score"] = max(prev, float(score))
1344
+ continue
1345
+ if boost_only:
1346
+ # v3.11 P1b — never hydrate new candidates;
1347
+ # closets are pure re-ranking. Skip cids not
1348
+ # already in the candidate set.
1323
1349
  continue
1324
1350
  try:
1325
1351
  claim = svc.store.get_claim(cid)
@@ -1335,7 +1361,7 @@ def _recall_impl(
1335
1361
  "confidence_score": float(getattr(claim, "confidence", 0.0) or 0.0),
1336
1362
  "vector_score": 0.0,
1337
1363
  "entity_score": 0.0,
1338
- "closet_score": 1.0,
1364
+ "closet_score": float(score),
1339
1365
  "source": "closets",
1340
1366
  }
1341
1367
  )
@@ -1414,7 +1440,27 @@ def _recall_impl(
1414
1440
  # at the end of _relevance. Default 0.0 → query_type stays None and the
1415
1441
  # boost is a no-op, preserving bit-identical ranking.
1416
1442
  w_claim_type = _recall_weight("W_CLAIM_TYPE")
1417
- query_claim_type = classify_observation(query) if w_claim_type > 0.0 else None
1443
+ # v3.11 P2 — derive query intent from query_classifier (sharper than the
1444
+ # 6-pattern classify_observation which matched "preference" too greedily).
1445
+ # Map query_type → claim_type so the boost compares apples to apples.
1446
+ if w_claim_type > 0.0:
1447
+ try:
1448
+ from memorymaster.query_classifier import classify_query
1449
+ qt = classify_query(query)
1450
+ _query_to_claim_type = {
1451
+ "constraint_check": "constraint",
1452
+ "preference": "preference",
1453
+ "fact_lookup": "fact",
1454
+ "verification": "fact",
1455
+ "temporal": "event",
1456
+ "relational": "fact",
1457
+ "open_ended": None, # too vague to bias
1458
+ }
1459
+ query_claim_type = _query_to_claim_type.get(qt)
1460
+ except Exception: # noqa: BLE001
1461
+ query_claim_type = classify_observation(query)
1462
+ else:
1463
+ query_claim_type = None
1418
1464
  # Scope-aware retrieval boost (roadmap 1.2). Multiplier applied to the
1419
1465
  # final _relevance score for claims whose scope matches the current
1420
1466
  # project scope. 0.0 (default) → no boost, ranking bit-identical to legacy.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memorymaster
3
- Version: 3.10.0
3
+ Version: 3.12.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
@@ -278,6 +278,7 @@ tests/test_store_factory.py
278
278
  tests/test_tenant_isolation.py
279
279
  tests/test_turn_schema.py
280
280
  tests/test_two_pass_recall.py
281
+ tests/test_v311_fixes.py
281
282
  tests/test_v390_e2e.py
282
283
  tests/test_v391_strict_warnings.py
283
284
  tests/test_vault_exporter.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memorymaster"
7
- version = "3.10.0"
7
+ version = "3.12.0"
8
8
  description = "Production-grade memory reliability system for AI coding agents. Lifecycle-managed claims with citations, conflict detection, steward governance, and MCP integration."
9
9
  license = {text = "MIT"}
10
10
  authors = [{name = "wolverin0"}]
@@ -0,0 +1,172 @@
1
+ """Tests for v3.11.0 P1 + P2 + P3 fixes."""
2
+ from __future__ import annotations
3
+
4
+ import sqlite3
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ from memorymaster import context_hook
10
+ from memorymaster.claim_edges import (
11
+ SHARES_ENTITY_KIND,
12
+ rebuild_edges,
13
+ )
14
+ from memorymaster.closets import (
15
+ ensure_closets_schema,
16
+ rebuild_closets,
17
+ search_closets,
18
+ )
19
+
20
+
21
+ # --- P1 — F6 BM25-scaled scoring + boost-only mode -------------------------
22
+
23
+
24
+ def test_p1_search_closets_with_scores_returns_normalised(tmp_path):
25
+ """search_closets(with_scores=True) returns 3-tuples with score in [0, 1]."""
26
+ vault = tmp_path / "vault" / "wiki"
27
+ vault.mkdir(parents=True)
28
+ (vault / "alpha.md").write_text(
29
+ "---\ntitle: a\ndescription: a\ntype: x\nscope: y\ntags: []\ndate: 2026-04-27\nclaims: [1]\n---\n\nMemPalace closets MemPalace pattern.\n",
30
+ encoding="utf-8",
31
+ )
32
+ (vault / "beta.md").write_text(
33
+ "---\ntitle: b\ndescription: b\ntype: x\nscope: y\ntags: []\ndate: 2026-04-27\nclaims: [2]\n---\n\nMemPalace mentioned briefly.\n",
34
+ encoding="utf-8",
35
+ )
36
+ db = tmp_path / "test.db"
37
+ rebuild_closets(db, vault)
38
+ hits = search_closets(db, "MemPalace closets", limit=5, with_scores=True)
39
+ assert len(hits) >= 1
40
+ for hit in hits:
41
+ assert len(hit) == 3
42
+ slug, claim_ids, score = hit
43
+ assert 0.0 <= score <= 1.0
44
+ # First hit should have score=1.0 (best match normalised to 1.0)
45
+ assert hits[0][2] == pytest.approx(1.0)
46
+
47
+
48
+ def test_p1_legacy_search_closets_still_returns_2_tuples(tmp_path):
49
+ """Backwards-compat: search_closets() without with_scores still returns 2-tuples."""
50
+ vault = tmp_path / "vault" / "wiki"
51
+ vault.mkdir(parents=True)
52
+ (vault / "x.md").write_text(
53
+ "---\ntitle: x\ndescription: x\ntype: t\nscope: s\ntags: []\ndate: 2026-04-27\nclaims: [1]\n---\n\nMemPalace.\n",
54
+ encoding="utf-8",
55
+ )
56
+ db = tmp_path / "test.db"
57
+ rebuild_closets(db, vault)
58
+ hits = search_closets(db, "MemPalace")
59
+ assert len(hits) >= 1
60
+ for hit in hits:
61
+ assert len(hit) == 2 # not 3
62
+
63
+
64
+ def test_p1_closets_boost_only_default_off():
65
+ assert context_hook._closets_boost_only() is False
66
+
67
+
68
+ def test_p1_closets_boost_only_via_env(monkeypatch):
69
+ monkeypatch.setenv("MEMORYMASTER_RECALL_CLOSETS_BOOST_ONLY", "1")
70
+ assert context_hook._closets_boost_only() is True
71
+
72
+
73
+ # --- P2 — F1 swap to query_classifier --------------------------------------
74
+
75
+
76
+ def test_p2_query_classifier_recognised_in_recall():
77
+ """The query_classifier module's classify_query is callable."""
78
+ from memorymaster.query_classifier import classify_query
79
+
80
+ assert classify_query("what database does this use?") == "fact_lookup"
81
+ assert classify_query("we must use postgres") == "constraint_check"
82
+ assert classify_query("when was that changed?") == "temporal"
83
+
84
+
85
+ # --- P3 — F8 shares_entity edges -------------------------------------------
86
+
87
+
88
+ def _make_db_with_entities(tmp_path: Path) -> Path:
89
+ db = tmp_path / "test.db"
90
+ conn = sqlite3.connect(str(db))
91
+ try:
92
+ conn.executescript(
93
+ """
94
+ CREATE TABLE claims (
95
+ id INTEGER PRIMARY KEY,
96
+ text TEXT,
97
+ human_id TEXT,
98
+ replaced_by_claim_id INTEGER,
99
+ entity_id INTEGER
100
+ );
101
+ INSERT INTO claims (id, text, human_id, replaced_by_claim_id, entity_id) VALUES
102
+ (1, 'Mentions FastAPI', 'mm-1111', NULL, 100),
103
+ (2, 'Also mentions FastAPI', 'mm-2222', NULL, 100),
104
+ (3, 'Third claim about FastAPI', 'mm-3333', NULL, 100),
105
+ (4, 'About a different entity', 'mm-4444', NULL, 200),
106
+ (5, 'No entity at all', 'mm-5555', NULL, NULL);
107
+ """
108
+ )
109
+ conn.commit()
110
+ finally:
111
+ conn.close()
112
+ return db
113
+
114
+
115
+ def test_p3_shares_entity_edges_written(tmp_path):
116
+ """Three claims share entity_id=100 → 3 pairwise edges (1-2, 1-3, 2-3)."""
117
+ db = _make_db_with_entities(tmp_path)
118
+ counters = rebuild_edges(db, include_shares_entity=True)
119
+ assert counters["shares_entity_edges"] == 3
120
+ # Verify in table
121
+ conn = sqlite3.connect(str(db))
122
+ try:
123
+ rows = conn.execute(
124
+ "SELECT src_claim_id, dst_claim_id FROM claim_edges WHERE edge_kind = ?",
125
+ (SHARES_ENTITY_KIND,),
126
+ ).fetchall()
127
+ finally:
128
+ conn.close()
129
+ pairs = {(min(s, d), max(s, d)) for s, d in rows}
130
+ assert pairs == {(1, 2), (1, 3), (2, 3)}
131
+
132
+
133
+ def test_p3_singleton_entity_produces_no_edges(tmp_path):
134
+ """Claim 4 is the only one with entity_id=200; no shares_entity edges."""
135
+ db = _make_db_with_entities(tmp_path)
136
+ rebuild_edges(db, include_shares_entity=True)
137
+ conn = sqlite3.connect(str(db))
138
+ try:
139
+ rows = conn.execute(
140
+ "SELECT * FROM claim_edges WHERE edge_kind = ? AND (src_claim_id = 4 OR dst_claim_id = 4)",
141
+ (SHARES_ENTITY_KIND,),
142
+ ).fetchall()
143
+ finally:
144
+ conn.close()
145
+ assert rows == []
146
+
147
+
148
+ def test_p3_max_per_pivot_caps_explosion(tmp_path):
149
+ """When >max claims share an entity, the entire group is skipped (overflow guard)."""
150
+ db = tmp_path / "many.db"
151
+ conn = sqlite3.connect(str(db))
152
+ try:
153
+ conn.executescript(
154
+ "CREATE TABLE claims (id INTEGER PRIMARY KEY, text TEXT, human_id TEXT, replaced_by_claim_id INTEGER, entity_id INTEGER);"
155
+ )
156
+ for i in range(1, 11):
157
+ conn.execute(
158
+ "INSERT INTO claims VALUES (?, ?, ?, NULL, 999)",
159
+ (i, f"claim {i}", f"mm-{i:04d}"),
160
+ )
161
+ conn.commit()
162
+ finally:
163
+ conn.close()
164
+ # Cap at 5 — group of 10 won't qualify (HAVING COUNT(*) BETWEEN 2 AND 5)
165
+ counters = rebuild_edges(db, shares_entity_max_per_pivot=5)
166
+ assert counters["shares_entity_edges"] == 0
167
+
168
+
169
+ def test_p3_shares_entity_disabled_when_flag_off(tmp_path):
170
+ db = _make_db_with_entities(tmp_path)
171
+ counters = rebuild_edges(db, include_shares_entity=False)
172
+ assert counters["shares_entity_edges"] == 0
File without changes
File without changes