memorymaster 3.2.2__tar.gz → 3.3.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 (201) hide show
  1. {memorymaster-3.2.2/memorymaster.egg-info → memorymaster-3.3.0}/PKG-INFO +1 -1
  2. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/__init__.py +1 -1
  3. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/_storage_read.py +60 -0
  4. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/_storage_schema.py +51 -15
  5. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/cli.py +16 -0
  6. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/cli_handlers_curation.py +80 -0
  7. memorymaster-3.3.0/memorymaster/entity_registry.py +255 -0
  8. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/models.py +11 -0
  9. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/service.py +27 -0
  10. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/storage.py +7 -0
  11. {memorymaster-3.2.2 → memorymaster-3.3.0/memorymaster.egg-info}/PKG-INFO +1 -1
  12. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster.egg-info/SOURCES.txt +1 -0
  13. {memorymaster-3.2.2 → memorymaster-3.3.0}/pyproject.toml +1 -1
  14. {memorymaster-3.2.2 → memorymaster-3.3.0}/LICENSE +0 -0
  15. {memorymaster-3.2.2 → memorymaster-3.3.0}/README.md +0 -0
  16. {memorymaster-3.2.2 → memorymaster-3.3.0}/benchmarks/longmemeval_runner.py +0 -0
  17. {memorymaster-3.2.2 → memorymaster-3.3.0}/benchmarks/longmemeval_vector_runner.py +0 -0
  18. {memorymaster-3.2.2 → memorymaster-3.3.0}/benchmarks/perf_smoke.py +0 -0
  19. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/__main__.py +0 -0
  20. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/_storage_lifecycle.py +0 -0
  21. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/_storage_shared.py +0 -0
  22. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/_storage_write_claims.py +0 -0
  23. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/access_control.py +0 -0
  24. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/auto_extractor.py +0 -0
  25. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/auto_resolver.py +0 -0
  26. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/claim_verifier.py +0 -0
  27. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/cli_handlers_basic.py +0 -0
  28. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/cli_helpers.py +0 -0
  29. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/config.py +0 -0
  30. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/config_templates/claude-md-append.md +0 -0
  31. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/config_templates/codex-agents-md-append.md +0 -0
  32. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/config_templates/hooks/memorymaster-auto-ingest.py +0 -0
  33. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/config_templates/hooks/memorymaster-classify.py +0 -0
  34. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/config_templates/hooks/memorymaster-precompact.py +0 -0
  35. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/config_templates/hooks/memorymaster-recall.py +0 -0
  36. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/config_templates/hooks/memorymaster-session-start.py +0 -0
  37. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/config_templates/hooks/memorymaster-steward-cycle.py +0 -0
  38. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/config_templates/hooks/memorymaster-validate-wiki.py +0 -0
  39. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/conflict_resolver.py +0 -0
  40. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/context_hook.py +0 -0
  41. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/context_optimizer.py +0 -0
  42. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/daily_notes.py +0 -0
  43. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/dashboard.py +0 -0
  44. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/db_merge.py +0 -0
  45. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/dream_bridge.py +0 -0
  46. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/embeddings.py +0 -0
  47. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/entity_graph.py +0 -0
  48. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/feedback.py +0 -0
  49. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/jobs/__init__.py +0 -0
  50. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/jobs/compact_summaries.py +0 -0
  51. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/jobs/compactor.py +0 -0
  52. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/jobs/decay.py +0 -0
  53. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/jobs/dedup.py +0 -0
  54. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/jobs/deterministic.py +0 -0
  55. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/jobs/extractor.py +0 -0
  56. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/jobs/staleness.py +0 -0
  57. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/jobs/validator.py +0 -0
  58. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/lifecycle.py +0 -0
  59. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/llm_provider.py +0 -0
  60. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/llm_steward.py +0 -0
  61. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/mcp_server.py +0 -0
  62. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/metrics_exporter.py +0 -0
  63. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/operator.py +0 -0
  64. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/operator_queue.py +0 -0
  65. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/plugins.py +0 -0
  66. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/policy.py +0 -0
  67. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/postgres_store.py +0 -0
  68. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/qdrant_backend.py +0 -0
  69. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/qmd_bridge.py +0 -0
  70. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/query_classifier.py +0 -0
  71. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/retrieval.py +0 -0
  72. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/retry.py +0 -0
  73. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/review.py +0 -0
  74. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/rl_trainer.py +0 -0
  75. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/scheduler.py +0 -0
  76. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/schema.py +0 -0
  77. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/schema.sql +0 -0
  78. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/schema_postgres.sql +0 -0
  79. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/security.py +0 -0
  80. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/session_tracker.py +0 -0
  81. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/setup_hooks.py +0 -0
  82. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/skill_evolver.py +0 -0
  83. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/snapshot.py +0 -0
  84. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/steward.py +0 -0
  85. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/store_factory.py +0 -0
  86. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/transcript_miner.py +0 -0
  87. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/turn_schema.py +0 -0
  88. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/vault_bases.py +0 -0
  89. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/vault_curator.py +0 -0
  90. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/vault_exporter.py +0 -0
  91. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/vault_linter.py +0 -0
  92. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/vault_log.py +0 -0
  93. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/vault_query_capture.py +0 -0
  94. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/vault_synthesis.py +0 -0
  95. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/verbatim_store.py +0 -0
  96. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/webhook.py +0 -0
  97. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster/wiki_engine.py +0 -0
  98. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster.egg-info/dependency_links.txt +0 -0
  99. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster.egg-info/entry_points.txt +0 -0
  100. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster.egg-info/requires.txt +0 -0
  101. {memorymaster-3.2.2 → memorymaster-3.3.0}/memorymaster.egg-info/top_level.txt +0 -0
  102. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/alert_operator_metrics.py +0 -0
  103. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/autoresearch_daemon.py +0 -0
  104. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/claude_to_turns.py +0 -0
  105. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/codex_live_to_turns.py +0 -0
  106. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/compaction_edge_cases.py +0 -0
  107. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/compaction_trace_report.py +0 -0
  108. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/compaction_trace_validate.py +0 -0
  109. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/confusion_matrix_eval.py +0 -0
  110. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/conversation_importer.py +0 -0
  111. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/conversation_to_turns.py +0 -0
  112. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/e2e_operator.py +0 -0
  113. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/email_live_to_turns.py +0 -0
  114. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/eval_memorymaster.py +0 -0
  115. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/generate_drill_signoff.py +0 -0
  116. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/git_to_turns.py +0 -0
  117. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/github_live_to_turns.py +0 -0
  118. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/gitnexus_to_claims.py +0 -0
  119. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/ingest_planning_docs.py +0 -0
  120. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/jira_live_to_turns.py +0 -0
  121. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/messages_to_turns.py +0 -0
  122. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/operator_metrics.py +0 -0
  123. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/recurring_incident_drill.py +0 -0
  124. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/release_readiness.py +0 -0
  125. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/run_codex_autologger.py +0 -0
  126. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/run_incident_drill.py +0 -0
  127. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/scheduled_ingest.py +0 -0
  128. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/setup-hooks.py +0 -0
  129. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/slack_live_to_turns.py +0 -0
  130. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/tickets_to_turns.py +0 -0
  131. {memorymaster-3.2.2 → memorymaster-3.3.0}/scripts/webhook_to_turns.py +0 -0
  132. {memorymaster-3.2.2 → memorymaster-3.3.0}/setup.cfg +0 -0
  133. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/conftest.py +0 -0
  134. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_access_control.py +0 -0
  135. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_auto_extractor.py +0 -0
  136. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_auto_resolver.py +0 -0
  137. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_auto_validate.py +0 -0
  138. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_claim_links.py +0 -0
  139. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_claude_to_turns.py +0 -0
  140. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_cli_json_flag.py +0 -0
  141. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_cli_ready.py +0 -0
  142. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_cli_review_queue.py +0 -0
  143. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_cli_subcommands.py +0 -0
  144. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_compact_summaries.py +0 -0
  145. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_compaction_trace.py +0 -0
  146. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_config.py +0 -0
  147. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_conflict_resolver.py +0 -0
  148. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_confusion_matrix_eval.py +0 -0
  149. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_connection_retry.py +0 -0
  150. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_connectors.py +0 -0
  151. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_context_hook.py +0 -0
  152. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_context_optimizer.py +0 -0
  153. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_conversation_to_turns.py +0 -0
  154. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_dashboard.py +0 -0
  155. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_dedup.py +0 -0
  156. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_deterministic_predicates.py +0 -0
  157. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_embeddings_coverage.py +0 -0
  158. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_entity_graph.py +0 -0
  159. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_events_schema.py +0 -0
  160. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_feedback.py +0 -0
  161. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_fts5_search.py +0 -0
  162. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_handler_regressions.py +0 -0
  163. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_human_id.py +0 -0
  164. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_incident_drill_runner.py +0 -0
  165. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_integration_workflows.py +0 -0
  166. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_lifecycle.py +0 -0
  167. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_llm_steward_coverage.py +0 -0
  168. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_llm_steward_key_rotation.py +0 -0
  169. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_mcp_helpers.py +0 -0
  170. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_metrics_exporter.py +0 -0
  171. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_obsidian_mind_patterns.py +0 -0
  172. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_operator.py +0 -0
  173. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_operator_queue.py +0 -0
  174. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_perf_smoke_config.py +0 -0
  175. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_plugins.py +0 -0
  176. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_policy_coverage.py +0 -0
  177. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_postgres_parity.py +0 -0
  178. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_qdrant_backend.py +0 -0
  179. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_qmd_bridge.py +0 -0
  180. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_query_classifier.py +0 -0
  181. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_reliability_hardening.py +0 -0
  182. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_review.py +0 -0
  183. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_rl_trainer.py +0 -0
  184. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_scheduler.py +0 -0
  185. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_schema.py +0 -0
  186. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_security_access.py +0 -0
  187. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_security_patterns.py +0 -0
  188. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_service_coverage.py +0 -0
  189. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_session_tracker.py +0 -0
  190. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_snapshot.py +0 -0
  191. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_sqlite_core.py +0 -0
  192. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_staleness.py +0 -0
  193. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_stealth_mode.py +0 -0
  194. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_steward.py +0 -0
  195. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_steward_resolution_parity.py +0 -0
  196. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_store_factory.py +0 -0
  197. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_tenant_isolation.py +0 -0
  198. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_turn_schema.py +0 -0
  199. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_vault_exporter.py +0 -0
  200. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_vector_search.py +0 -0
  201. {memorymaster-3.2.2 → memorymaster-3.3.0}/tests/test_webhook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memorymaster
3
- Version: 3.2.2
3
+ Version: 3.3.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
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "3.2.2"
5
+ __version__ = "3.3.0"
@@ -590,3 +590,63 @@ class _ReadMixin:
590
590
  ).fetchall()
591
591
  return [self._row_to_citation(row) for row in rows]
592
592
 
593
+ def traverse_relationships(
594
+ self,
595
+ start_claim_id: int,
596
+ *,
597
+ link_types: list[str] | None = None,
598
+ max_depth: int = 3,
599
+ direction: str = "both",
600
+ ) -> list[dict]:
601
+ """Traverse the claim relationship graph from a starting claim.
602
+
603
+ Returns a list of dicts: [{"claim": Claim, "depth": int, "path": [int],
604
+ "link_type": str}]. BFS traversal, stops at max_depth. direction can be
605
+ "outgoing" (source→target), "incoming" (target→source), or "both".
606
+
607
+ Inspired by GBrain's graph traversal queries — "what depends on Qdrant?"
608
+ becomes traverse_relationships(qdrant_claim_id, link_types=["depends_on"]).
609
+ """
610
+ with self.connect() as conn:
611
+ visited: set[int] = {start_claim_id}
612
+ queue: list[tuple[int, int, list[int], str]] = [] # (claim_id, depth, path, via_link_type)
613
+
614
+ # Seed with depth-0 neighbors
615
+ def _get_neighbors(claim_id: int) -> list[tuple[int, str]]:
616
+ neighbors: list[tuple[int, str]] = []
617
+ if direction in ("outgoing", "both"):
618
+ q = "SELECT target_id, link_type FROM claim_links WHERE source_id = ?"
619
+ for row in conn.execute(q, (claim_id,)).fetchall():
620
+ if link_types is None or row[1] in link_types:
621
+ neighbors.append((row[0], row[1]))
622
+ if direction in ("incoming", "both"):
623
+ q = "SELECT source_id, link_type FROM claim_links WHERE target_id = ?"
624
+ for row in conn.execute(q, (claim_id,)).fetchall():
625
+ if link_types is None or row[1] in link_types:
626
+ neighbors.append((row[0], row[1]))
627
+ return neighbors
628
+
629
+ for neighbor_id, link_type in _get_neighbors(start_claim_id):
630
+ if neighbor_id not in visited:
631
+ visited.add(neighbor_id)
632
+ queue.append((neighbor_id, 1, [start_claim_id, neighbor_id], link_type))
633
+
634
+ results: list[dict] = []
635
+ while queue:
636
+ cid, depth, path, via_type = queue.pop(0)
637
+ claim = self.get_claim(cid, include_citations=False)
638
+ if claim:
639
+ results.append({
640
+ "claim": claim,
641
+ "depth": depth,
642
+ "path": path,
643
+ "link_type": via_type,
644
+ })
645
+ if depth < max_depth:
646
+ for neighbor_id, link_type in _get_neighbors(cid):
647
+ if neighbor_id not in visited:
648
+ visited.add(neighbor_id)
649
+ queue.append((neighbor_id, depth + 1, path + [neighbor_id], link_type))
650
+
651
+ return results
652
+
@@ -262,21 +262,57 @@ class _SchemaMixin:
262
262
 
263
263
  @staticmethod
264
264
  def _ensure_claim_links_schema(conn: sqlite3.Connection) -> None:
265
- conn.execute(
266
- """
267
- CREATE TABLE IF NOT EXISTS claim_links (
268
- id INTEGER PRIMARY KEY AUTOINCREMENT,
269
- source_id INTEGER NOT NULL,
270
- target_id INTEGER NOT NULL,
271
- link_type TEXT NOT NULL,
272
- created_at TEXT NOT NULL,
273
- FOREIGN KEY (source_id) REFERENCES claims(id) ON DELETE CASCADE,
274
- FOREIGN KEY (target_id) REFERENCES claims(id) ON DELETE CASCADE,
275
- CHECK (source_id <> target_id),
276
- CHECK (link_type IN ('relates_to', 'supersedes', 'derived_from', 'contradicts', 'supports'))
277
- )
278
- """
279
- )
265
+ from memorymaster.models import CLAIM_LINK_TYPES
266
+
267
+ # Build the CHECK constraint from the canonical CLAIM_LINK_TYPES tuple
268
+ # so new types only need to be added in models.py.
269
+ types_sql = ", ".join(f"'{t}'" for t in CLAIM_LINK_TYPES)
270
+ check_clause = f"CHECK (link_type IN ({types_sql}))"
271
+
272
+ # Check if table exists and whether it needs migration (old CHECK with only 5 types)
273
+ existing = conn.execute(
274
+ "SELECT sql FROM sqlite_master WHERE type='table' AND name='claim_links'"
275
+ ).fetchone()
276
+
277
+ if existing and existing[0]:
278
+ # Table exists — check if it has the old 5-type CHECK
279
+ if "'implements'" not in existing[0]:
280
+ # Migrate: rename old → create new → copy → drop old
281
+ conn.execute("ALTER TABLE claim_links RENAME TO _claim_links_old")
282
+ conn.execute(f"""
283
+ CREATE TABLE claim_links (
284
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
285
+ source_id INTEGER NOT NULL,
286
+ target_id INTEGER NOT NULL,
287
+ link_type TEXT NOT NULL,
288
+ created_at TEXT NOT NULL,
289
+ FOREIGN KEY (source_id) REFERENCES claims(id) ON DELETE CASCADE,
290
+ FOREIGN KEY (target_id) REFERENCES claims(id) ON DELETE CASCADE,
291
+ CHECK (source_id <> target_id),
292
+ {check_clause}
293
+ )
294
+ """)
295
+ conn.execute("""
296
+ INSERT INTO claim_links (id, source_id, target_id, link_type, created_at)
297
+ SELECT id, source_id, target_id, link_type, created_at FROM _claim_links_old
298
+ """)
299
+ conn.execute("DROP TABLE _claim_links_old")
300
+ else:
301
+ # Fresh creation
302
+ conn.execute(f"""
303
+ CREATE TABLE IF NOT EXISTS claim_links (
304
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
305
+ source_id INTEGER NOT NULL,
306
+ target_id INTEGER NOT NULL,
307
+ link_type TEXT NOT NULL,
308
+ created_at TEXT NOT NULL,
309
+ FOREIGN KEY (source_id) REFERENCES claims(id) ON DELETE CASCADE,
310
+ FOREIGN KEY (target_id) REFERENCES claims(id) ON DELETE CASCADE,
311
+ CHECK (source_id <> target_id),
312
+ {check_clause}
313
+ )
314
+ """)
315
+
280
316
  conn.execute(
281
317
  "CREATE UNIQUE INDEX IF NOT EXISTS idx_claim_links_unique ON claim_links(source_id, target_id, link_type)"
282
318
  )
@@ -375,6 +375,22 @@ def build_parser() -> argparse.ArgumentParser:
375
375
  dream_clean_cmd.add_argument("--project", default=None, help="Project path to compute Claude Code memory dir slug")
376
376
  dream_clean_cmd.add_argument("--dry-run", action="store_true", help="Preview what would be removed without deleting files")
377
377
 
378
+ # Entity registry (GBrain-inspired)
379
+ entity_list = sub.add_parser("entity-list", help="List canonical entities with alias and claim counts")
380
+ entity_list.add_argument("--scope", default="", help="Filter by scope prefix")
381
+ entity_list.add_argument("--type", default="", help="Filter by entity type")
382
+ entity_list.add_argument("--limit", type=int, default=50)
383
+
384
+ entity_merge = sub.add_parser("entity-merge", help="Merge two entities (move aliases + claims to target)")
385
+ entity_merge.add_argument("keep_id", type=int, help="Entity ID to keep")
386
+ entity_merge.add_argument("merge_id", type=int, help="Entity ID to merge into keep_id")
387
+
388
+ entity_aliases_cmd = sub.add_parser("entity-aliases", help="List or add aliases for an entity")
389
+ entity_aliases_cmd.add_argument("entity_id", type=int, help="Entity ID")
390
+ entity_aliases_cmd.add_argument("--add", default="", help="Add this alias to the entity")
391
+
392
+ entity_backfill = sub.add_parser("entity-backfill", help="Backfill entity_id on claims with subject but no entity")
393
+
378
394
  return parser
379
395
 
380
396
 
@@ -673,3 +673,83 @@ COMMAND_HANDLERS["dream-seed"] = _handle_dream_seed
673
673
  COMMAND_HANDLERS["dream-ingest"] = _handle_dream_ingest
674
674
  COMMAND_HANDLERS["dream-sync"] = _handle_dream_sync
675
675
  COMMAND_HANDLERS["dream-clean"] = _handle_dream_clean
676
+
677
+
678
+ # ---------------------------------------------------------------------------
679
+ # Entity registry (GBrain-inspired)
680
+ # ---------------------------------------------------------------------------
681
+
682
+ def _handle_entity_list(args, service, parser, effective_db) -> int:
683
+ from memorymaster.entity_registry import list_entities
684
+ t0 = time.perf_counter()
685
+ with service.store.connect() as conn:
686
+ entities = list_entities(
687
+ conn,
688
+ scope=args.scope or None,
689
+ entity_type=getattr(args, "type", "") or None,
690
+ limit=args.limit,
691
+ )
692
+ elapsed_ms = (time.perf_counter() - t0) * 1000
693
+ if args.json_output:
694
+ print(_json_envelope(entities, total=len(entities), query_ms=elapsed_ms))
695
+ else:
696
+ print(f"Entities ({len(entities)}):")
697
+ for e in entities:
698
+ print(f" #{e['id']} [{e['type']}] {e['name']} — {e['alias_count']} aliases, {e['claim_count']} claims (scope={e['scope']})")
699
+ return 0
700
+
701
+
702
+ def _handle_entity_merge(args, service, parser, effective_db) -> int:
703
+ from memorymaster.entity_registry import merge_entities
704
+ t0 = time.perf_counter()
705
+ with service.store.connect() as conn:
706
+ result = merge_entities(conn, args.keep_id, args.merge_id)
707
+ conn.commit()
708
+ elapsed_ms = (time.perf_counter() - t0) * 1000
709
+ if args.json_output:
710
+ print(_json_envelope(result, query_ms=elapsed_ms))
711
+ else:
712
+ print(f"Merged entity #{args.merge_id} → #{args.keep_id}: {result['merged_aliases']} aliases, {result['updated_claims']} claims moved ({elapsed_ms:.0f}ms)")
713
+ return 0
714
+
715
+
716
+ def _handle_entity_aliases(args, service, parser, effective_db) -> int:
717
+ from memorymaster.entity_registry import get_aliases, add_alias
718
+ t0 = time.perf_counter()
719
+ with service.store.connect() as conn:
720
+ if args.add:
721
+ added = add_alias(conn, args.entity_id, args.add)
722
+ conn.commit()
723
+ if args.json_output:
724
+ print(_json_envelope({"added": added, "alias": args.add}, query_ms=(time.perf_counter() - t0) * 1000))
725
+ else:
726
+ print(f"{'Added' if added else 'Already exists'}: '{args.add}' → entity #{args.entity_id}")
727
+ else:
728
+ aliases = get_aliases(conn, args.entity_id)
729
+ elapsed_ms = (time.perf_counter() - t0) * 1000
730
+ if args.json_output:
731
+ print(_json_envelope(aliases, total=len(aliases), query_ms=elapsed_ms))
732
+ else:
733
+ print(f"Aliases for entity #{args.entity_id} ({len(aliases)}):")
734
+ for a in aliases:
735
+ print(f" - {a}")
736
+ return 0
737
+
738
+
739
+ def _handle_entity_backfill(args, service, parser, effective_db) -> int:
740
+ from memorymaster.entity_registry import backfill_entities
741
+ t0 = time.perf_counter()
742
+ with service.store.connect() as conn:
743
+ result = backfill_entities(conn)
744
+ elapsed_ms = (time.perf_counter() - t0) * 1000
745
+ if args.json_output:
746
+ print(_json_envelope(result, query_ms=elapsed_ms))
747
+ else:
748
+ print(f"Backfill: {result['entities_created']} entities created, {result['claims_resolved']} claims resolved ({result['subjects_processed']} subjects processed, {elapsed_ms:.0f}ms)")
749
+ return 0
750
+
751
+
752
+ COMMAND_HANDLERS["entity-list"] = _handle_entity_list
753
+ COMMAND_HANDLERS["entity-merge"] = _handle_entity_merge
754
+ COMMAND_HANDLERS["entity-aliases"] = _handle_entity_aliases
755
+ COMMAND_HANDLERS["entity-backfill"] = _handle_entity_backfill
@@ -0,0 +1,255 @@
1
+ """Entity Registry — canonical entities with alias resolution.
2
+
3
+ Inspired by GBrain's entity registry pattern: every subject string resolves
4
+ to a canonical entity so that "MemoryMaster", "memorymaster", "MM" all point
5
+ to the same node. This turns the flat claims DB into a real knowledge graph.
6
+
7
+ Tables:
8
+ - entities: canonical entity (id, name, type, scope, created/updated)
9
+ - entity_aliases: maps normalized alias strings → entity id
10
+
11
+ Resolution flow (on ingest):
12
+ 1. Normalize subject string (lowercase, strip, collapse separators)
13
+ 2. Look up in entity_aliases
14
+ 3. If found → return canonical entity_id
15
+ 4. If not found → create new entity + register the alias
16
+ 5. Store entity_id on the claim
17
+
18
+ The claim.subject column stays as free-text for display; entity_id is the
19
+ canonical FK used for grouping and traversal.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import logging
24
+ import re
25
+ import sqlite3
26
+ from datetime import datetime, timezone
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ _NORMALIZE_RE = re.compile(r"[\s_\-\.]+")
31
+
32
+
33
+ def normalize_alias(raw: str) -> str:
34
+ """Normalize a subject string for alias lookup.
35
+
36
+ Lowercases, collapses whitespace/dashes/underscores/dots into single
37
+ dashes, strips leading/trailing separators. Truncates to 200 chars.
38
+ """
39
+ if not raw:
40
+ return ""
41
+ return _NORMALIZE_RE.sub("-", raw.strip().lower()).strip("-")[:200]
42
+
43
+
44
+ def ensure_entity_schema(conn: sqlite3.Connection) -> None:
45
+ """Create entity tables if they don't exist. Idempotent."""
46
+ conn.executescript("""
47
+ CREATE TABLE IF NOT EXISTS entities (
48
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
49
+ canonical_name TEXT NOT NULL UNIQUE,
50
+ entity_type TEXT NOT NULL DEFAULT 'unknown',
51
+ scope TEXT NOT NULL DEFAULT 'global',
52
+ created_at TEXT NOT NULL,
53
+ updated_at TEXT NOT NULL
54
+ );
55
+
56
+ CREATE TABLE IF NOT EXISTS entity_aliases (
57
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
58
+ entity_id INTEGER NOT NULL,
59
+ alias TEXT NOT NULL UNIQUE,
60
+ original_form TEXT NOT NULL,
61
+ created_at TEXT NOT NULL,
62
+ FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE
63
+ );
64
+
65
+ CREATE INDEX IF NOT EXISTS idx_entity_aliases_alias
66
+ ON entity_aliases(alias);
67
+ CREATE INDEX IF NOT EXISTS idx_entity_aliases_entity_id
68
+ ON entity_aliases(entity_id);
69
+ """)
70
+
71
+
72
+ def _utc_now() -> str:
73
+ return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
74
+
75
+
76
+ def resolve_or_create(
77
+ conn: sqlite3.Connection,
78
+ subject: str,
79
+ *,
80
+ entity_type: str = "unknown",
81
+ scope: str = "global",
82
+ ) -> int:
83
+ """Resolve a subject string to a canonical entity_id, creating if needed.
84
+
85
+ Returns the entity_id (int). Thread-safe via SQLite serialization.
86
+ """
87
+ alias = normalize_alias(subject)
88
+ if not alias:
89
+ return 0
90
+
91
+ # Fast path: alias already registered
92
+ row = conn.execute(
93
+ "SELECT entity_id FROM entity_aliases WHERE alias = ?", (alias,)
94
+ ).fetchone()
95
+ if row:
96
+ return row[0]
97
+
98
+ # Slow path: create entity + register alias
99
+ now = _utc_now()
100
+ display_name = subject.strip()[:200]
101
+
102
+ cur = conn.execute(
103
+ """INSERT OR IGNORE INTO entities (canonical_name, entity_type, scope, created_at, updated_at)
104
+ VALUES (?, ?, ?, ?, ?)""",
105
+ (display_name, entity_type, scope, now, now),
106
+ )
107
+ if cur.lastrowid and cur.lastrowid > 0:
108
+ entity_id = cur.lastrowid
109
+ else:
110
+ # INSERT OR IGNORE hit a duplicate canonical_name — fetch existing
111
+ existing = conn.execute(
112
+ "SELECT id FROM entities WHERE canonical_name = ?", (display_name,)
113
+ ).fetchone()
114
+ entity_id = existing[0] if existing else 0
115
+
116
+ if entity_id > 0:
117
+ conn.execute(
118
+ """INSERT OR IGNORE INTO entity_aliases (entity_id, alias, original_form, created_at)
119
+ VALUES (?, ?, ?, ?)""",
120
+ (entity_id, alias, subject.strip(), now),
121
+ )
122
+
123
+ return entity_id
124
+
125
+
126
+ def merge_entities(
127
+ conn: sqlite3.Connection,
128
+ keep_id: int,
129
+ merge_id: int,
130
+ ) -> dict:
131
+ """Merge entity merge_id INTO keep_id. All aliases of merge_id move to keep_id.
132
+
133
+ Returns {"merged_aliases": int, "updated_claims": int}.
134
+ """
135
+ # Move aliases
136
+ cur = conn.execute(
137
+ "UPDATE entity_aliases SET entity_id = ? WHERE entity_id = ?",
138
+ (keep_id, merge_id),
139
+ )
140
+ merged_aliases = cur.rowcount
141
+
142
+ # Move claims that reference merge_id
143
+ cur2 = conn.execute(
144
+ "UPDATE claims SET entity_id = ? WHERE entity_id = ?",
145
+ (keep_id, merge_id),
146
+ )
147
+ updated_claims = cur2.rowcount
148
+
149
+ # Delete the merged entity
150
+ conn.execute("DELETE FROM entities WHERE id = ?", (merge_id,))
151
+
152
+ return {"merged_aliases": merged_aliases, "updated_claims": updated_claims}
153
+
154
+
155
+ def add_alias(conn: sqlite3.Connection, entity_id: int, alias_text: str) -> bool:
156
+ """Register an additional alias for an entity. Returns True if added."""
157
+ alias = normalize_alias(alias_text)
158
+ if not alias:
159
+ return False
160
+ now = _utc_now()
161
+ try:
162
+ conn.execute(
163
+ """INSERT INTO entity_aliases (entity_id, alias, original_form, created_at)
164
+ VALUES (?, ?, ?, ?)""",
165
+ (entity_id, alias, alias_text.strip(), now),
166
+ )
167
+ return True
168
+ except sqlite3.IntegrityError:
169
+ return False # alias already registered
170
+
171
+
172
+ def list_entities(
173
+ conn: sqlite3.Connection,
174
+ *,
175
+ scope: str | None = None,
176
+ entity_type: str | None = None,
177
+ limit: int = 100,
178
+ ) -> list[dict]:
179
+ """List entities with their alias counts and claim counts."""
180
+ query = """
181
+ SELECT e.id, e.canonical_name, e.entity_type, e.scope, e.created_at,
182
+ COUNT(DISTINCT a.id) as alias_count,
183
+ COUNT(DISTINCT c.id) as claim_count
184
+ FROM entities e
185
+ LEFT JOIN entity_aliases a ON a.entity_id = e.id
186
+ LEFT JOIN claims c ON c.entity_id = e.id
187
+ """
188
+ params: list = []
189
+ wheres: list[str] = []
190
+ if scope:
191
+ wheres.append("e.scope LIKE ?")
192
+ params.append(f"{scope}%")
193
+ if entity_type:
194
+ wheres.append("e.entity_type = ?")
195
+ params.append(entity_type)
196
+ if wheres:
197
+ query += " WHERE " + " AND ".join(wheres)
198
+ query += " GROUP BY e.id ORDER BY claim_count DESC LIMIT ?"
199
+ params.append(limit)
200
+
201
+ rows = conn.execute(query, params).fetchall()
202
+ return [
203
+ {
204
+ "id": r[0],
205
+ "name": r[1],
206
+ "type": r[2],
207
+ "scope": r[3],
208
+ "created_at": r[4],
209
+ "alias_count": r[5],
210
+ "claim_count": r[6],
211
+ }
212
+ for r in rows
213
+ ]
214
+
215
+
216
+ def get_aliases(conn: sqlite3.Connection, entity_id: int) -> list[str]:
217
+ """Get all aliases for an entity."""
218
+ rows = conn.execute(
219
+ "SELECT original_form FROM entity_aliases WHERE entity_id = ? ORDER BY created_at",
220
+ (entity_id,),
221
+ ).fetchall()
222
+ return [r[0] for r in rows]
223
+
224
+
225
+ def backfill_entities(conn: sqlite3.Connection) -> dict:
226
+ """Backfill entity_id on existing claims that have subject but no entity_id.
227
+
228
+ Creates entities and aliases as needed. Returns stats.
229
+ """
230
+ ensure_entity_schema(conn)
231
+
232
+ # Ensure entity_id column exists on claims
233
+ try:
234
+ conn.execute("ALTER TABLE claims ADD COLUMN entity_id INTEGER")
235
+ except sqlite3.OperationalError:
236
+ pass # already exists
237
+
238
+ rows = conn.execute(
239
+ "SELECT DISTINCT subject FROM claims WHERE subject IS NOT NULL AND (entity_id IS NULL OR entity_id = 0)"
240
+ ).fetchall()
241
+
242
+ created = 0
243
+ resolved = 0
244
+ for (subject,) in rows:
245
+ entity_id = resolve_or_create(conn, subject)
246
+ if entity_id > 0:
247
+ cur = conn.execute(
248
+ "UPDATE claims SET entity_id = ? WHERE subject = ? AND (entity_id IS NULL OR entity_id = 0)",
249
+ (entity_id, subject),
250
+ )
251
+ resolved += cur.rowcount
252
+ created += 1
253
+
254
+ conn.commit()
255
+ return {"entities_created": created, "claims_resolved": resolved, "subjects_processed": len(rows)}
@@ -272,11 +272,22 @@ class Event:
272
272
 
273
273
 
274
274
  CLAIM_LINK_TYPES = (
275
+ # Core lifecycle types (original v2.0)
275
276
  "relates_to",
276
277
  "supersedes",
277
278
  "derived_from",
278
279
  "contradicts",
279
280
  "supports",
281
+ # Domain-specific relationship types (GBrain-inspired, v3.3)
282
+ "implements", # claim A describes an implementation of claim B
283
+ "configures", # claim A configures/parametrizes claim B
284
+ "depends_on", # claim A requires claim B to function
285
+ "deployed_on", # claim A is deployed on infrastructure described by claim B
286
+ "owned_by", # claim A is owned/maintained by entity in claim B
287
+ "tested_by", # claim A is validated by test described in claim B
288
+ "documents", # claim A documents behavior of claim B
289
+ "blocks", # claim A blocks progress on claim B
290
+ "enables", # claim A enables/unlocks claim B
280
291
  )
281
292
 
282
293
 
@@ -146,6 +146,21 @@ class MemoryService:
146
146
  )
147
147
  if not sanitized.citations:
148
148
  raise ValueError("At least one citation is required.")
149
+ # Resolve subject → canonical entity (GBrain-inspired entity registry)
150
+ entity_id = 0
151
+ if subject:
152
+ try:
153
+ from memorymaster.entity_registry import resolve_or_create
154
+ with self.store.connect() as _conn:
155
+ entity_id = resolve_or_create(
156
+ _conn, subject,
157
+ entity_type=claim_type or "unknown",
158
+ scope=scope,
159
+ )
160
+ _conn.commit()
161
+ except Exception:
162
+ pass # entity resolution is best-effort, never block ingest
163
+
149
164
  claim = self.store.create_claim(
150
165
  text=sanitized.text,
151
166
  citations=sanitized.citations,
@@ -164,6 +179,18 @@ class MemoryService:
164
179
  source_agent=source_agent,
165
180
  visibility=visibility,
166
181
  )
182
+
183
+ # Set entity_id on the claim (best-effort, don't fail ingest)
184
+ if entity_id > 0:
185
+ try:
186
+ with self.store.connect() as _conn:
187
+ _conn.execute(
188
+ "UPDATE claims SET entity_id = ? WHERE id = ?",
189
+ (entity_id, claim.id),
190
+ )
191
+ _conn.commit()
192
+ except Exception:
193
+ pass
167
194
  if sanitized.is_sensitive:
168
195
  self.store.record_event(
169
196
  claim_id=claim.id,
@@ -90,4 +90,11 @@ class SQLiteStore(_SchemaMixin, _ReadMixin, _WriteClaimsMixin, _LifecycleMixin):
90
90
  self._ensure_agent_columns(conn)
91
91
  self._ensure_version_column(conn)
92
92
  self._ensure_embeddings_schema(conn)
93
+ # Entity registry (GBrain-inspired canonical entities + alias resolution)
94
+ from memorymaster.entity_registry import ensure_entity_schema
95
+ ensure_entity_schema(conn)
96
+ try:
97
+ conn.execute("ALTER TABLE claims ADD COLUMN entity_id INTEGER")
98
+ except sqlite3.OperationalError:
99
+ pass # already exists
93
100
  conn.commit()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memorymaster
3
- Version: 3.2.2
3
+ Version: 3.3.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
@@ -29,6 +29,7 @@ memorymaster/db_merge.py
29
29
  memorymaster/dream_bridge.py
30
30
  memorymaster/embeddings.py
31
31
  memorymaster/entity_graph.py
32
+ memorymaster/entity_registry.py
32
33
  memorymaster/feedback.py
33
34
  memorymaster/lifecycle.py
34
35
  memorymaster/llm_provider.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memorymaster"
7
- version = "3.2.2"
7
+ version = "3.3.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"}]
File without changes
File without changes