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.
- {memorymaster-3.10.0 → memorymaster-3.12.0}/PKG-INFO +1 -1
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/claim_edges.py +54 -3
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/closets.py +31 -10
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/context_hook.py +52 -6
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster.egg-info/PKG-INFO +1 -1
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster.egg-info/SOURCES.txt +1 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/pyproject.toml +1 -1
- memorymaster-3.12.0/tests/test_v311_fixes.py +172 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/LICENSE +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/README.md +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/artifacts/bm25-per-field-eval-harness.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/benchmarks/longmemeval_runner.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/benchmarks/longmemeval_vector_runner.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/benchmarks/perf_smoke.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/__init__.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/__main__.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/_storage_lifecycle.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/_storage_read.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/_storage_schema.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/_storage_shared.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/_storage_write_claims.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/access_control.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/auto_extractor.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/auto_resolver.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/claim_verifier.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/cli.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/cli_handlers_basic.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/cli_handlers_curation.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/cli_helpers.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/claude-md-append.md +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/codex-agents-md-append.md +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-auto-ingest.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-classify.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-dream-sync.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-observe.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-precompact.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-recall.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-session-start.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-steward-cycle.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/hooks/memorymaster-validate-wiki.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/conflict_resolver.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/context_optimizer.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/daily_notes.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/dashboard.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/db_merge.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/dream_bridge.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/embeddings.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/entity_extractor.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/entity_graph.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/entity_registry.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/federated_graphify.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/feedback.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/graph_store.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/hook_log.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/__init__.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/compact_summaries.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/compactor.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/decay.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/dedup.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/deterministic.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/extractor.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/staleness.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/jobs/validator.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/key_rotator.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/lifecycle.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/llm_provider.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/llm_steward.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/mcp_server.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/metrics_exporter.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/models.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/operator.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/operator_queue.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/plugins.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/policy.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/postgres_store.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/qdrant_backend.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/qdrant_recall_fallback.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/qmd_bridge.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/query_classifier.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/query_expansion.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/recall_fusion.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/recall_tokenizer.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/retrieval.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/retry.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/review.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/rl_trainer.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/scheduler.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/schema.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/schema.sql +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/schema_postgres.sql +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/scope_utils.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/security.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/service.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/session_tracker.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/setup_hooks.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/skill_evolver.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/snapshot.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/steward.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/steward_classifier.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/steward_features.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/storage.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/store_factory.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/transcript_miner.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/turn_schema.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_bases.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_curator.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_exporter.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_linter.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_log.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_query_capture.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/vault_synthesis.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/verbatim_recall.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/verbatim_store.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/webhook.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/wiki_engine.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/wiki_freshness.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/wiki_similarity.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/wiki_validate.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster.egg-info/dependency_links.txt +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster.egg-info/entry_points.txt +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster.egg-info/requires.txt +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster.egg-info/top_level.txt +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/agg_recall_latency.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/alert_operator_metrics.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/autoresearch_daemon.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/backfill_entity_extraction.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/backfill_graph_store.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/backfill_stop_hook_citations.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/backtest_steward_classifier.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/build_steward_training_set.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/check_hook_template_drift.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/claude_to_turns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/codex_live_to_turns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/compaction_edge_cases.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/compaction_trace_report.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/compaction_trace_validate.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/confusion_matrix_eval.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/conversation_importer.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/conversation_to_turns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/e2e_operator.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/email_live_to_turns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_bm25_sweep.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_classify_f1.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_memorymaster.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_recall_precision_at_5.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_recall_quality.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_steward_pareto.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/eval_verbatim_recall.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/expand_recall_eval.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/generate_drill_signoff.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/git_to_turns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/github_live_to_turns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/gitnexus_to_claims.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/grid_recall_weights.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/index_claims_to_qdrant.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/ingest_planning_docs.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/jira_live_to_turns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/label_prompts_with_judge.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/llm_benchmark.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/merge_scope_variants.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/messages_to_turns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/operator_metrics.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/precompute_candidates.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/recurring_incident_drill.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/release_readiness.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/run_codex_autologger.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/run_incident_drill.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/run_longmemeval.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/scheduled_ingest.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/setup-hooks.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/slack_live_to_turns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/sync_hook_templates.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/tickets_to_turns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/train_steward_classifier.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/scripts/webhook_to_turns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/setup.cfg +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/conftest.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/integration/test_extract_llm_ollama_live.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_access_control.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_auto_extractor.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_auto_ingest_hook_citations.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_auto_ingest_hook_schema.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_auto_resolver.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_auto_validate.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_bm25_per_field.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_claim_edges.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_claim_links.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_claim_type_ranking.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_classify_hook_f1.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_classify_hook_latency.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_claude_to_turns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_cli_json_flag.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_cli_ready.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_cli_review_queue.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_cli_subcommands.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_closets.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_closets_recall_integration.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_compact_summaries.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_compaction_trace.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_config.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_conflict_resolver.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_confusion_matrix_eval.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_connection_retry.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_connectors.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_context_hook.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_context_optimizer.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_conversation_to_turns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_dashboard.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_dedup.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_deterministic_predicates.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_embeddings_coverage.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_entity_extractor.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_entity_extractor_llm.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_entity_graph.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_entity_new_kinds.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_entity_regex_v3.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_entity_registry.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_eval_harness.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_events_schema.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_extract_llm_ollama.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_federated_graphify_mcp.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_feedback.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_fts5_search.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_graph_distance.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_graph_store.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_handler_regressions.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_hook_env_isolation.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_human_id.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_incident_drill_runner.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_integration_workflows.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_key_rotator.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_lifecycle.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_llm_fallback.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_llm_provider_claude_cli.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_llm_steward_coverage.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_llm_steward_key_rotation.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_mcp_helpers.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_metrics_exporter.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_obsidian_mind_patterns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_operator.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_operator_queue.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_perf_smoke_config.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_plugins.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_policy_coverage.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_policy_mode_env.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_postgres_parity.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_qdrant_backend.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_qmd_bridge.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_query_classifier.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_query_expansion.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_recall_entity_fanout.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_recall_fusion.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_recall_latency.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_recall_precision_at_5.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_recall_tokenizer.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_recall_vector_fallback.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_reliability_hardening.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_review.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_rl_trainer.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_rrf_auto_gate.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_scheduler.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_schema.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_scope_boost.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_scope_utils.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_security_access.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_security_patterns.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_sensitivity_filter_adversarial.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_sensitivity_filter_adversarial_v2.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_service_coverage.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_session_tracker.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_snapshot.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_sqlite_core.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_staleness.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_stealth_mode.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_steward.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_steward_classifier.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_steward_features.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_steward_features_v3.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_steward_resolution_parity.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_store_factory.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_tenant_isolation.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_turn_schema.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_two_pass_recall.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_v390_e2e.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_v391_strict_warnings.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_vault_exporter.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_vector_search.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_verbatim_recall.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_webhook.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_wiki_binding.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_wiki_freshness.py +0 -0
- {memorymaster-3.10.0 → memorymaster-3.12.0}/tests/test_wiki_similarity_multiscope.py +0 -0
- {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.
|
|
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(
|
|
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 = {
|
|
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
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1322
|
-
existing_by_id[cid]["closet_score"] =
|
|
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":
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/claude-md-append.md
RENAMED
|
File without changes
|
{memorymaster-3.10.0 → memorymaster-3.12.0}/memorymaster/config_templates/codex-agents-md-append.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|