memorymaster 3.5.1__tar.gz → 3.5.2__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 (319) hide show
  1. {memorymaster-3.5.1/memorymaster.egg-info → memorymaster-3.5.2}/PKG-INFO +1 -1
  2. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/config_templates/hooks/memorymaster-steward-cycle.py +15 -4
  3. {memorymaster-3.5.1 → memorymaster-3.5.2/memorymaster.egg-info}/PKG-INFO +1 -1
  4. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster.egg-info/SOURCES.txt +3 -0
  5. {memorymaster-3.5.1 → memorymaster-3.5.2}/pyproject.toml +1 -1
  6. memorymaster-3.5.2/scripts/grid_recall_weights.py +191 -0
  7. memorymaster-3.5.2/tests/test_hook_env_isolation.py +79 -0
  8. memorymaster-3.5.2/tests/test_llm_provider_claude_cli.py +186 -0
  9. {memorymaster-3.5.1 → memorymaster-3.5.2}/LICENSE +0 -0
  10. {memorymaster-3.5.1 → memorymaster-3.5.2}/README.md +0 -0
  11. {memorymaster-3.5.1 → memorymaster-3.5.2}/artifacts/bm25-per-field-eval-harness.py +0 -0
  12. {memorymaster-3.5.1 → memorymaster-3.5.2}/artifacts/l2-haiku-batches/extract.py +0 -0
  13. {memorymaster-3.5.1 → memorymaster-3.5.2}/benchmarks/longmemeval_runner.py +0 -0
  14. {memorymaster-3.5.1 → memorymaster-3.5.2}/benchmarks/longmemeval_vector_runner.py +0 -0
  15. {memorymaster-3.5.1 → memorymaster-3.5.2}/benchmarks/perf_smoke.py +0 -0
  16. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/examples/run_conversation_opd.py +0 -0
  17. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/examples/run_conversation_replay.py +0 -0
  18. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/examples/run_conversation_rl.py +0 -0
  19. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/__init__.py +0 -0
  20. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/__main__.py +0 -0
  21. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/api_server.py +0 -0
  22. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/bedrock_client.py +0 -0
  23. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/calendar_client.py +0 -0
  24. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/claw_adapter.py +0 -0
  25. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/cli.py +0 -0
  26. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/config.py +0 -0
  27. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/config_store.py +0 -0
  28. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/data_formatter.py +0 -0
  29. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/idle_detector.py +0 -0
  30. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/launcher.py +0 -0
  31. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/log_color.py +0 -0
  32. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/openclaw_env_rollout.py +0 -0
  33. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/prm_scorer.py +0 -0
  34. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/rollout.py +0 -0
  35. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/runtime_state.py +0 -0
  36. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/scheduler.py +0 -0
  37. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/sdk_backend.py +0 -0
  38. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/setup_wizard.py +0 -0
  39. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/skill_evolver.py +0 -0
  40. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/skill_manager.py +0 -0
  41. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/trainer.py +0 -0
  42. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/metaclaw/utils.py +0 -0
  43. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/scripts/run_v03_benchmark.py +0 -0
  44. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/tests/conftest.py +0 -0
  45. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/tests/test_cli.py +0 -0
  46. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/tests/test_launcher.py +0 -0
  47. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/tests/test_openclaw_env_rollout.py +0 -0
  48. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/tests/test_runtime_state.py +0 -0
  49. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/tests/test_sdk_backend.py +0 -0
  50. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/tests/test_setup_wizard.py +0 -0
  51. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/tests/test_utils.py +0 -0
  52. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/MetaClaw/tests/test_v03_live_tinker.py +0 -0
  53. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/agent-skill-creator/references/examples/stock-analyzer/scripts/main.py +0 -0
  54. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/agent-skill-creator/scripts/export_utils.py +0 -0
  55. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/agent-skill-creator/scripts/security_scan.py +0 -0
  56. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/agent-skill-creator/scripts/skill_registry.py +0 -0
  57. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/agent-skill-creator/scripts/staleness_check.py +0 -0
  58. {memorymaster-3.5.1 → memorymaster-3.5.2}/cloned/agent-skill-creator/scripts/validate.py +0 -0
  59. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/__init__.py +0 -0
  60. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/__main__.py +0 -0
  61. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/_storage_lifecycle.py +0 -0
  62. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/_storage_read.py +0 -0
  63. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/_storage_schema.py +0 -0
  64. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/_storage_shared.py +0 -0
  65. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/_storage_write_claims.py +0 -0
  66. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/access_control.py +0 -0
  67. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/auto_extractor.py +0 -0
  68. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/auto_resolver.py +0 -0
  69. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/claim_verifier.py +0 -0
  70. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/cli.py +0 -0
  71. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/cli_handlers_basic.py +0 -0
  72. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/cli_handlers_curation.py +0 -0
  73. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/cli_helpers.py +0 -0
  74. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/config.py +0 -0
  75. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/config_templates/claude-md-append.md +0 -0
  76. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/config_templates/codex-agents-md-append.md +0 -0
  77. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/config_templates/hooks/memorymaster-auto-ingest.py +0 -0
  78. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/config_templates/hooks/memorymaster-classify.py +0 -0
  79. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/config_templates/hooks/memorymaster-dream-sync.py +0 -0
  80. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/config_templates/hooks/memorymaster-observe.py +0 -0
  81. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/config_templates/hooks/memorymaster-precompact.py +0 -0
  82. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/config_templates/hooks/memorymaster-recall.py +0 -0
  83. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/config_templates/hooks/memorymaster-session-start.py +0 -0
  84. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/config_templates/hooks/memorymaster-validate-wiki.py +0 -0
  85. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/conflict_resolver.py +0 -0
  86. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/context_hook.py +0 -0
  87. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/context_optimizer.py +0 -0
  88. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/daily_notes.py +0 -0
  89. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/dashboard.py +0 -0
  90. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/db_merge.py +0 -0
  91. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/dream_bridge.py +0 -0
  92. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/embeddings.py +0 -0
  93. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/entity_extractor.py +0 -0
  94. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/entity_graph.py +0 -0
  95. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/entity_registry.py +0 -0
  96. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/feedback.py +0 -0
  97. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/graph_store.py +0 -0
  98. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/hook_log.py +0 -0
  99. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/jobs/__init__.py +0 -0
  100. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/jobs/compact_summaries.py +0 -0
  101. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/jobs/compactor.py +0 -0
  102. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/jobs/decay.py +0 -0
  103. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/jobs/dedup.py +0 -0
  104. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/jobs/deterministic.py +0 -0
  105. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/jobs/extractor.py +0 -0
  106. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/jobs/staleness.py +0 -0
  107. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/jobs/validator.py +0 -0
  108. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/key_rotator.py +0 -0
  109. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/lifecycle.py +0 -0
  110. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/llm_provider.py +0 -0
  111. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/llm_steward.py +0 -0
  112. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/mcp_server.py +0 -0
  113. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/metrics_exporter.py +0 -0
  114. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/models.py +0 -0
  115. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/operator.py +0 -0
  116. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/operator_queue.py +0 -0
  117. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/plugins.py +0 -0
  118. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/policy.py +0 -0
  119. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/postgres_store.py +0 -0
  120. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/qdrant_backend.py +0 -0
  121. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/qdrant_recall_fallback.py +0 -0
  122. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/qmd_bridge.py +0 -0
  123. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/query_classifier.py +0 -0
  124. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/query_expansion.py +0 -0
  125. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/recall_fusion.py +0 -0
  126. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/recall_tokenizer.py +0 -0
  127. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/retrieval.py +0 -0
  128. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/retry.py +0 -0
  129. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/review.py +0 -0
  130. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/rl_trainer.py +0 -0
  131. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/scheduler.py +0 -0
  132. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/schema.py +0 -0
  133. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/schema.sql +0 -0
  134. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/schema_postgres.sql +0 -0
  135. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/security.py +0 -0
  136. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/service.py +0 -0
  137. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/session_tracker.py +0 -0
  138. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/setup_hooks.py +0 -0
  139. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/skill_evolver.py +0 -0
  140. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/snapshot.py +0 -0
  141. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/steward.py +0 -0
  142. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/steward_classifier.py +0 -0
  143. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/steward_features.py +0 -0
  144. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/storage.py +0 -0
  145. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/store_factory.py +0 -0
  146. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/transcript_miner.py +0 -0
  147. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/turn_schema.py +0 -0
  148. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/vault_bases.py +0 -0
  149. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/vault_curator.py +0 -0
  150. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/vault_exporter.py +0 -0
  151. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/vault_linter.py +0 -0
  152. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/vault_log.py +0 -0
  153. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/vault_query_capture.py +0 -0
  154. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/vault_synthesis.py +0 -0
  155. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/verbatim_recall.py +0 -0
  156. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/verbatim_store.py +0 -0
  157. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/webhook.py +0 -0
  158. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/wiki_engine.py +0 -0
  159. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/wiki_freshness.py +0 -0
  160. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster/wiki_similarity.py +0 -0
  161. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster.egg-info/dependency_links.txt +0 -0
  162. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster.egg-info/entry_points.txt +0 -0
  163. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster.egg-info/requires.txt +0 -0
  164. {memorymaster-3.5.1 → memorymaster-3.5.2}/memorymaster.egg-info/top_level.txt +0 -0
  165. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/agg_recall_latency.py +0 -0
  166. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/alert_operator_metrics.py +0 -0
  167. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/autoresearch_daemon.py +0 -0
  168. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/backfill_entity_extraction.py +0 -0
  169. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/backfill_graph_store.py +0 -0
  170. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/backfill_stop_hook_citations.py +0 -0
  171. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/backtest_steward_classifier.py +0 -0
  172. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/build_steward_training_set.py +0 -0
  173. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/check_hook_template_drift.py +0 -0
  174. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/claude_to_turns.py +0 -0
  175. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/codex_live_to_turns.py +0 -0
  176. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/compaction_edge_cases.py +0 -0
  177. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/compaction_trace_report.py +0 -0
  178. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/compaction_trace_validate.py +0 -0
  179. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/confusion_matrix_eval.py +0 -0
  180. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/conversation_importer.py +0 -0
  181. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/conversation_to_turns.py +0 -0
  182. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/e2e_operator.py +0 -0
  183. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/email_live_to_turns.py +0 -0
  184. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/eval_bm25_sweep.py +0 -0
  185. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/eval_classify_f1.py +0 -0
  186. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/eval_memorymaster.py +0 -0
  187. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/eval_recall_precision_at_5.py +0 -0
  188. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/eval_recall_quality.py +0 -0
  189. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/eval_steward_pareto.py +0 -0
  190. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/eval_verbatim_recall.py +0 -0
  191. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/expand_recall_eval.py +0 -0
  192. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/generate_drill_signoff.py +0 -0
  193. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/git_to_turns.py +0 -0
  194. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/github_live_to_turns.py +0 -0
  195. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/gitnexus_to_claims.py +0 -0
  196. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/index_claims_to_qdrant.py +0 -0
  197. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/ingest_planning_docs.py +0 -0
  198. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/jira_live_to_turns.py +0 -0
  199. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/llm_benchmark.py +0 -0
  200. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/merge_scope_variants.py +0 -0
  201. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/messages_to_turns.py +0 -0
  202. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/operator_metrics.py +0 -0
  203. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/recurring_incident_drill.py +0 -0
  204. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/release_readiness.py +0 -0
  205. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/run_codex_autologger.py +0 -0
  206. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/run_incident_drill.py +0 -0
  207. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/run_longmemeval.py +0 -0
  208. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/scheduled_ingest.py +0 -0
  209. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/setup-hooks.py +0 -0
  210. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/slack_live_to_turns.py +0 -0
  211. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/sync_hook_templates.py +0 -0
  212. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/tickets_to_turns.py +0 -0
  213. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/train_steward_classifier.py +0 -0
  214. {memorymaster-3.5.1 → memorymaster-3.5.2}/scripts/webhook_to_turns.py +0 -0
  215. {memorymaster-3.5.1 → memorymaster-3.5.2}/setup.cfg +0 -0
  216. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/conftest.py +0 -0
  217. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/integration/test_extract_llm_ollama_live.py +0 -0
  218. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_access_control.py +0 -0
  219. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_auto_extractor.py +0 -0
  220. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_auto_ingest_hook_citations.py +0 -0
  221. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_auto_ingest_hook_schema.py +0 -0
  222. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_auto_resolver.py +0 -0
  223. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_auto_validate.py +0 -0
  224. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_bm25_per_field.py +0 -0
  225. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_claim_links.py +0 -0
  226. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_classify_hook_f1.py +0 -0
  227. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_classify_hook_latency.py +0 -0
  228. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_claude_to_turns.py +0 -0
  229. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_cli_json_flag.py +0 -0
  230. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_cli_ready.py +0 -0
  231. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_cli_review_queue.py +0 -0
  232. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_cli_subcommands.py +0 -0
  233. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_compact_summaries.py +0 -0
  234. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_compaction_trace.py +0 -0
  235. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_config.py +0 -0
  236. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_conflict_resolver.py +0 -0
  237. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_confusion_matrix_eval.py +0 -0
  238. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_connection_retry.py +0 -0
  239. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_connectors.py +0 -0
  240. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_context_hook.py +0 -0
  241. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_context_optimizer.py +0 -0
  242. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_conversation_to_turns.py +0 -0
  243. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_dashboard.py +0 -0
  244. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_dedup.py +0 -0
  245. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_deterministic_predicates.py +0 -0
  246. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_embeddings_coverage.py +0 -0
  247. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_entity_extractor.py +0 -0
  248. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_entity_extractor_llm.py +0 -0
  249. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_entity_graph.py +0 -0
  250. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_entity_new_kinds.py +0 -0
  251. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_entity_registry.py +0 -0
  252. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_eval_harness.py +0 -0
  253. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_events_schema.py +0 -0
  254. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_extract_llm_ollama.py +0 -0
  255. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_feedback.py +0 -0
  256. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_fts5_search.py +0 -0
  257. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_graph_distance.py +0 -0
  258. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_graph_store.py +0 -0
  259. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_handler_regressions.py +0 -0
  260. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_human_id.py +0 -0
  261. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_incident_drill_runner.py +0 -0
  262. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_integration_workflows.py +0 -0
  263. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_key_rotator.py +0 -0
  264. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_lifecycle.py +0 -0
  265. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_llm_fallback.py +0 -0
  266. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_llm_steward_coverage.py +0 -0
  267. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_llm_steward_key_rotation.py +0 -0
  268. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_mcp_helpers.py +0 -0
  269. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_metrics_exporter.py +0 -0
  270. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_obsidian_mind_patterns.py +0 -0
  271. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_operator.py +0 -0
  272. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_operator_queue.py +0 -0
  273. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_perf_smoke_config.py +0 -0
  274. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_plugins.py +0 -0
  275. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_policy_coverage.py +0 -0
  276. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_policy_mode_env.py +0 -0
  277. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_postgres_parity.py +0 -0
  278. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_qdrant_backend.py +0 -0
  279. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_qmd_bridge.py +0 -0
  280. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_query_classifier.py +0 -0
  281. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_query_expansion.py +0 -0
  282. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_recall_entity_fanout.py +0 -0
  283. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_recall_fusion.py +0 -0
  284. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_recall_latency.py +0 -0
  285. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_recall_precision_at_5.py +0 -0
  286. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_recall_tokenizer.py +0 -0
  287. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_recall_vector_fallback.py +0 -0
  288. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_reliability_hardening.py +0 -0
  289. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_review.py +0 -0
  290. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_rl_trainer.py +0 -0
  291. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_rrf_auto_gate.py +0 -0
  292. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_scheduler.py +0 -0
  293. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_schema.py +0 -0
  294. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_scope_boost.py +0 -0
  295. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_security_access.py +0 -0
  296. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_security_patterns.py +0 -0
  297. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_sensitivity_filter_adversarial.py +0 -0
  298. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_sensitivity_filter_adversarial_v2.py +0 -0
  299. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_service_coverage.py +0 -0
  300. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_session_tracker.py +0 -0
  301. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_snapshot.py +0 -0
  302. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_sqlite_core.py +0 -0
  303. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_staleness.py +0 -0
  304. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_stealth_mode.py +0 -0
  305. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_steward.py +0 -0
  306. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_steward_classifier.py +0 -0
  307. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_steward_features.py +0 -0
  308. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_steward_features_v3.py +0 -0
  309. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_steward_resolution_parity.py +0 -0
  310. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_store_factory.py +0 -0
  311. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_tenant_isolation.py +0 -0
  312. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_turn_schema.py +0 -0
  313. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_vault_exporter.py +0 -0
  314. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_vector_search.py +0 -0
  315. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_verbatim_recall.py +0 -0
  316. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_webhook.py +0 -0
  317. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_wiki_binding.py +0 -0
  318. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_wiki_freshness.py +0 -0
  319. {memorymaster-3.5.1 → memorymaster-3.5.2}/tests/test_wiki_similarity_multiscope.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memorymaster
3
- Version: 3.5.1
3
+ Version: 3.5.2
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
@@ -6,6 +6,19 @@ DB_PATH = os.path.join(PROJECT_ROOT, "memorymaster.db")
6
6
 
7
7
  sys.path.insert(0, PROJECT_ROOT)
8
8
  os.environ["MEMORYMASTER_DEFAULT_DB"] = DB_PATH
9
+
10
+ # LLM stack: claude_cli (Claude Code OAuth via local `claude --print`) is the
11
+ # primary, with Ollama gemma4:e4b as a defensive fallback. Direct assignment
12
+ # (NOT setdefault) — the hook MUST own these vars so an inherited shell env
13
+ # can't silently route LLM calls to a stale provider. Bug observed 2026-04-25:
14
+ # setdefault was a no-op when the inherited env already had MEMORYMASTER_LLM_PROVIDER
15
+ # set, so the new model name routed to the OLD provider → 50× HTTP 404 per cycle
16
+ # before the fallback chain saved it. Captured as v3.5.0 release notes.
17
+ os.environ["MEMORYMASTER_LLM_PROVIDER"] = "claude_cli"
18
+ os.environ["MEMORYMASTER_LLM_MODEL"] = "claude-haiku-4-5-20251001"
19
+ os.environ["MEMORYMASTER_LLM_FALLBACK_PROVIDER"] = "ollama"
20
+ os.environ["MEMORYMASTER_LLM_FALLBACK_MODEL"] = "gemma4:e4b"
21
+
9
22
  os.chdir(PROJECT_ROOT)
10
23
 
11
24
  try:
@@ -36,11 +49,9 @@ try:
36
49
  except Exception as e:
37
50
  print(f"[MemoryMaster] auto-archive error: {e}", file=sys.stderr)
38
51
 
39
- # Wiki absorb (compiled truth + timeline articles)
52
+ # Wiki absorb (compiled truth + timeline articles). Inherits the LLM provider
53
+ # block above — uses the same OAuth-backed haiku stack as the steward.
40
54
  try:
41
- # Keys come from the rotator file (~/.memorymaster/gemini-keys.env) or a
42
- # singular GEMINI_API_KEY env var. Hook must never hardcode credentials.
43
- os.environ.setdefault("MEMORYMASTER_LLM_PROVIDER", "google")
44
55
  from memorymaster.wiki_engine import absorb
45
56
  wiki_path = os.path.join(PROJECT_ROOT, "obsidian-vault", "wiki")
46
57
  stats = absorb(DB_PATH, wiki_path)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memorymaster
3
- Version: 3.5.1
3
+ Version: 3.5.2
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
@@ -189,6 +189,7 @@ scripts/generate_drill_signoff.py
189
189
  scripts/git_to_turns.py
190
190
  scripts/github_live_to_turns.py
191
191
  scripts/gitnexus_to_claims.py
192
+ scripts/grid_recall_weights.py
192
193
  scripts/index_claims_to_qdrant.py
193
194
  scripts/ingest_planning_docs.py
194
195
  scripts/jira_live_to_turns.py
@@ -251,12 +252,14 @@ tests/test_fts5_search.py
251
252
  tests/test_graph_distance.py
252
253
  tests/test_graph_store.py
253
254
  tests/test_handler_regressions.py
255
+ tests/test_hook_env_isolation.py
254
256
  tests/test_human_id.py
255
257
  tests/test_incident_drill_runner.py
256
258
  tests/test_integration_workflows.py
257
259
  tests/test_key_rotator.py
258
260
  tests/test_lifecycle.py
259
261
  tests/test_llm_fallback.py
262
+ tests/test_llm_provider_claude_cli.py
260
263
  tests/test_llm_steward_coverage.py
261
264
  tests/test_llm_steward_key_rotation.py
262
265
  tests/test_mcp_helpers.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "memorymaster"
7
- version = "3.5.1"
7
+ version = "3.5.2"
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,191 @@
1
+ """Grid-search the recall weight knobs against precision@5.
2
+
3
+ Sweeps W_LEXICAL × W_FRESHNESS × W_GRAPH against the existing 100-prompt
4
+ evaluation harness (`scripts/eval_recall_precision_at_5.py`) and writes a
5
+ sorted markdown table + raw JSONL log so a future tweak is reproducible.
6
+
7
+ W_VECTOR is skipped because the local DB has no Qdrant; the stream is a
8
+ no-op without `MEMORYMASTER_USE_QDRANT=1` and a populated index.
9
+
10
+ Usage:
11
+ python scripts/grid_recall_weights.py \
12
+ --prompts artifacts/real-prompts-100.jsonl \
13
+ --db memorymaster.db \
14
+ --output artifacts/recall-weight-tuning-2026-04-26.md
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import itertools
20
+ import json
21
+ import os
22
+ import re
23
+ import subprocess
24
+ import sys
25
+ import time
26
+ from pathlib import Path
27
+
28
+ # Modest 3 × 3 × 4 = 36 grid. Bounded by ~10s/combo wall via subprocess startup.
29
+ W_LEXICAL_GRID = (0.2, 0.3, 0.4)
30
+ W_FRESHNESS_GRID = (0.0, 0.05, 0.1)
31
+ W_GRAPH_GRID = (0.0, 0.05, 0.1, 0.2)
32
+
33
+ METRIC_RE = {
34
+ "precision@5": re.compile(r"precision@5\s*=\s*([\d.]+)"),
35
+ "MAP@5": re.compile(r"MAP@5\s*=\s*([\d.]+)"),
36
+ "hit@5": re.compile(r"hit@5\s*=\s*([\d.]+)"),
37
+ "p95_ms": re.compile(r"p95\s*=\s*([\d.]+)\s*ms"),
38
+ }
39
+
40
+
41
+ def _run_eval(
42
+ eval_script: Path,
43
+ prompts: Path,
44
+ db: Path,
45
+ weights: dict,
46
+ json_out: Path,
47
+ label: str,
48
+ ) -> dict | None:
49
+ env = os.environ.copy()
50
+ for k, v in weights.items():
51
+ env[f"MEMORYMASTER_RECALL_{k}"] = str(v)
52
+ # The GRAPH stream is opt-in: W_GRAPH alone is a no-op unless the stream
53
+ # itself is enabled. Turn it on only when the weight is non-zero — keeps
54
+ # the latency-cost cells out of the grid when they can't possibly help.
55
+ if weights.get("W_GRAPH", 0) > 0:
56
+ env["MEMORYMASTER_RECALL_GRAPH"] = "1"
57
+ # Same for the freshness stream.
58
+ if weights.get("W_FRESHNESS", 0) > 0:
59
+ env["MEMORYMASTER_RECALL_FRESHNESS"] = "1"
60
+
61
+ proc = subprocess.run(
62
+ [
63
+ sys.executable,
64
+ str(eval_script),
65
+ "--prompts",
66
+ str(prompts),
67
+ "--db",
68
+ str(db),
69
+ "--json-out",
70
+ str(json_out),
71
+ "--label",
72
+ label,
73
+ ],
74
+ capture_output=True,
75
+ text=True,
76
+ env=env,
77
+ timeout=600,
78
+ )
79
+ if proc.returncode != 0:
80
+ return {"error": proc.stderr.strip()[:200] or "non-zero exit"}
81
+
82
+ out = proc.stdout
83
+ parsed = {"label": label, **{k: v for k, v in weights.items()}}
84
+ for metric, rgx in METRIC_RE.items():
85
+ m = rgx.search(out)
86
+ parsed[metric] = float(m.group(1)) if m else None
87
+ return parsed
88
+
89
+
90
+ def main() -> int:
91
+ p = argparse.ArgumentParser(description=__doc__)
92
+ p.add_argument("--prompts", type=Path, required=True)
93
+ p.add_argument("--db", type=Path, required=True)
94
+ p.add_argument("--output", type=Path, required=True)
95
+ p.add_argument(
96
+ "--eval-script",
97
+ type=Path,
98
+ default=Path("scripts/eval_recall_precision_at_5.py"),
99
+ )
100
+ p.add_argument(
101
+ "--per-run-json-dir",
102
+ type=Path,
103
+ default=Path("artifacts/grid-runs"),
104
+ help="Per-cell raw eval JSONL dump directory.",
105
+ )
106
+ args = p.parse_args()
107
+
108
+ args.per_run_json_dir.mkdir(parents=True, exist_ok=True)
109
+
110
+ combos = list(
111
+ itertools.product(W_LEXICAL_GRID, W_FRESHNESS_GRID, W_GRAPH_GRID)
112
+ )
113
+ print(f"[grid] running {len(combos)} cells over W_LEXICAL × W_FRESHNESS × W_GRAPH")
114
+
115
+ rows: list[dict] = []
116
+ t_total = time.monotonic()
117
+ for i, (w_lex, w_fresh, w_graph) in enumerate(combos, 1):
118
+ weights = {"W_LEXICAL": w_lex, "W_FRESHNESS": w_fresh, "W_GRAPH": w_graph}
119
+ label = f"L{w_lex}_F{w_fresh}_G{w_graph}"
120
+ json_out = args.per_run_json_dir / f"{label}.jsonl"
121
+ t0 = time.monotonic()
122
+ row = _run_eval(args.eval_script, args.prompts, args.db, weights, json_out, label)
123
+ wall = time.monotonic() - t0
124
+ if row is None:
125
+ row = {"error": "no output"}
126
+ row["wall_s"] = round(wall, 1)
127
+ rows.append(row)
128
+ prec = row.get("precision@5")
129
+ prec_str = f"{prec:.3f}" if isinstance(prec, float) else "ERR"
130
+ print(f"[grid] {i}/{len(combos)} {label} wall={wall:.1f}s p@5={prec_str}")
131
+
132
+ # Pick best by precision@5 (tie-break MAP@5 desc, then p95 asc)
133
+ valid = [r for r in rows if isinstance(r.get("precision@5"), float)]
134
+ valid.sort(
135
+ key=lambda r: (
136
+ -r["precision@5"],
137
+ -(r.get("MAP@5") or 0.0),
138
+ r.get("p95_ms") or 1e9,
139
+ )
140
+ )
141
+
142
+ # Write markdown report
143
+ lines = [
144
+ "# Recall weight grid — precision@5 tuning",
145
+ "",
146
+ f"- Eval prompts: `{args.prompts}` (100, 70 labeled)",
147
+ f"- DB: `{args.db}` (post-L2-backfill snapshot)",
148
+ f"- Grid: W_LEXICAL × W_FRESHNESS × W_GRAPH = "
149
+ f"{len(W_LEXICAL_GRID)} × {len(W_FRESHNESS_GRID)} × {len(W_GRAPH_GRID)} = {len(combos)} cells",
150
+ f"- Total wall: {round(time.monotonic()-t_total, 1)}s",
151
+ "",
152
+ "## Top 10 by precision@5",
153
+ "",
154
+ "| W_LEXICAL | W_FRESHNESS | W_GRAPH | precision@5 | MAP@5 | hit@5 | p95 ms | wall s |",
155
+ "|---|---|---|---|---|---|---|---|",
156
+ ]
157
+ for r in valid[:10]:
158
+ lines.append(
159
+ f"| {r['W_LEXICAL']} | {r['W_FRESHNESS']} | {r['W_GRAPH']} "
160
+ f"| {r['precision@5']:.3f} | {r.get('MAP@5'):.3f} | {r.get('hit@5'):.3f} "
161
+ f"| {r.get('p95_ms')} | {r['wall_s']} |"
162
+ )
163
+ if not valid:
164
+ lines.append("| — | — | — | NO VALID RUNS | | | | |")
165
+ else:
166
+ winner = valid[0]
167
+ lines += [
168
+ "",
169
+ "## Winner",
170
+ "",
171
+ f"`MEMORYMASTER_RECALL_W_LEXICAL={winner['W_LEXICAL']}` "
172
+ f"`MEMORYMASTER_RECALL_W_FRESHNESS={winner['W_FRESHNESS']}` "
173
+ f"`MEMORYMASTER_RECALL_W_GRAPH={winner['W_GRAPH']}`",
174
+ "",
175
+ f"precision@5 = **{winner['precision@5']:.3f}** "
176
+ f"(baseline 0.152, delta = {(winner['precision@5'] - 0.152):+.3f})",
177
+ ]
178
+
179
+ args.output.write_text("\n".join(lines) + "\n", encoding="utf-8")
180
+
181
+ # Also dump rows as JSON for downstream automation
182
+ json_path = args.output.with_suffix(".json")
183
+ json_path.write_text(json.dumps(rows, indent=2), encoding="utf-8")
184
+
185
+ print(f"[grid] wrote {args.output}")
186
+ print(f"[grid] wrote {json_path}")
187
+ return 0
188
+
189
+
190
+ if __name__ == "__main__":
191
+ sys.exit(main())
@@ -0,0 +1,79 @@
1
+ """Regression tests for the steward-cycle hook env wiring (v3.5.0 fix).
2
+
3
+ Bug: the deployed hook used `os.environ.setdefault("MEMORYMASTER_LLM_PROVIDER", ...)`,
4
+ which is a no-op when the inherited shell env already has the var set. After
5
+ switching the primary to claude_cli, an inherited `MEMORYMASTER_LLM_PROVIDER=google`
6
+ left the provider unchanged — but `MEMORYMASTER_LLM_MODEL` was set to the new
7
+ Claude model name, producing 50× HTTP 404 from the Gemini API per cycle before
8
+ the fallback chain saved it.
9
+
10
+ Fix: hook MUST use direct `os.environ["KEY"] = ...` assignment.
11
+
12
+ These tests assert against the SHIPPED template
13
+ (`memorymaster/config_templates/hooks/memorymaster-steward-cycle.py`) so a
14
+ reverted edit fails CI before the broken hook reaches a user via
15
+ `memorymaster-setup`.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ from pathlib import Path
20
+
21
+ import pytest
22
+
23
+ TEMPLATE = (
24
+ Path(__file__).resolve().parents[1]
25
+ / "memorymaster"
26
+ / "config_templates"
27
+ / "hooks"
28
+ / "memorymaster-steward-cycle.py"
29
+ )
30
+
31
+
32
+ @pytest.fixture(scope="module")
33
+ def template_text() -> str:
34
+ assert TEMPLATE.exists(), f"steward hook template missing: {TEMPLATE}"
35
+ return TEMPLATE.read_text(encoding="utf-8")
36
+
37
+
38
+ def test_template_does_not_use_setdefault_for_llm_provider(template_text: str) -> None:
39
+ """If this fails, someone reverted the v3.5.0 hook env-assignment fix."""
40
+ assert (
41
+ 'setdefault("MEMORYMASTER_LLM_PROVIDER"' not in template_text
42
+ ), "steward hook must use os.environ['KEY'] = ... assignment, not setdefault"
43
+
44
+
45
+ def test_template_does_not_use_setdefault_for_llm_model(template_text: str) -> None:
46
+ assert (
47
+ 'setdefault("MEMORYMASTER_LLM_MODEL"' not in template_text
48
+ ), "steward hook must use os.environ['KEY'] = ... assignment, not setdefault"
49
+
50
+
51
+ @pytest.mark.parametrize(
52
+ "key,expected_value",
53
+ [
54
+ ("MEMORYMASTER_LLM_PROVIDER", "claude_cli"),
55
+ ("MEMORYMASTER_LLM_MODEL", "claude-haiku-4-5-20251001"),
56
+ ("MEMORYMASTER_LLM_FALLBACK_PROVIDER", "ollama"),
57
+ ("MEMORYMASTER_LLM_FALLBACK_MODEL", "gemma4:e4b"),
58
+ ],
59
+ )
60
+ def test_template_assigns_env_directly(
61
+ template_text: str, key: str, expected_value: str
62
+ ) -> None:
63
+ """Each LLM env var must be set via direct assignment with the v3.5.0 default."""
64
+ needle = f'os.environ["{key}"] = "{expected_value}"'
65
+ assert (
66
+ needle in template_text
67
+ ), f"expected `{needle}` in template; either the assignment or value drifted"
68
+
69
+
70
+ def test_template_imports_only_from_stdlib_and_memorymaster(template_text: str) -> None:
71
+ """The hook is invoked from cron/Task Scheduler; it can only depend on
72
+ stdlib + the installed memorymaster package. A new third-party import here
73
+ would silently break user installs that didn't pip install the dep."""
74
+ forbidden = ("requests", "httpx", "aiohttp", "boto3")
75
+ for pkg in forbidden:
76
+ assert (
77
+ f"import {pkg}" not in template_text
78
+ and f"from {pkg}" not in template_text
79
+ ), f"steward hook must not import {pkg} (would break installs missing the dep)"
@@ -0,0 +1,186 @@
1
+ """Tests for the claude_cli LLM provider (memorymaster.llm_provider._call_claude_cli).
2
+
3
+ The provider shells out to the local `claude --print` binary. These tests mock
4
+ subprocess.run so they don't actually invoke the CLI — they cover the defensive
5
+ branches that the production user is most likely to hit:
6
+
7
+ - missing binary on PATH
8
+ - non-zero exit
9
+ - timeout
10
+ - UTF-8 round-trip including emoji
11
+ - MEMORYMASTER_CLAUDE_CLI_BIN override
12
+ - MEMORYMASTER_CLAUDE_CLI_TIMEOUT override
13
+ - aliases ("claude_cli", "claude-cli") resolve to the same provider
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import subprocess
18
+ from unittest.mock import patch
19
+
20
+ import pytest
21
+
22
+ from memorymaster import llm_provider
23
+
24
+
25
+ def _completed(stdout: str = "", stderr: str = "", returncode: int = 0):
26
+ return subprocess.CompletedProcess(
27
+ args=["claude", "--print", "--model", "x"],
28
+ returncode=returncode,
29
+ stdout=stdout,
30
+ stderr=stderr,
31
+ )
32
+
33
+
34
+ @pytest.fixture(autouse=True)
35
+ def _clean_env(monkeypatch):
36
+ """Strip any inherited claude_cli env vars so each test starts clean."""
37
+ for key in (
38
+ "MEMORYMASTER_CLAUDE_CLI_BIN",
39
+ "MEMORYMASTER_CLAUDE_CLI_TIMEOUT",
40
+ "MEMORYMASTER_LLM_MODEL",
41
+ ):
42
+ monkeypatch.delenv(key, raising=False)
43
+
44
+
45
+ def test_missing_binary_returns_empty(monkeypatch, caplog):
46
+ """When `claude` is not on PATH and no override is set, return '' and warn."""
47
+ monkeypatch.setattr(llm_provider.shutil, "which", lambda _: None)
48
+ with caplog.at_level("WARNING"):
49
+ out = llm_provider._call_claude_cli("prompt", "text")
50
+ assert out == ""
51
+ assert any("binary not found" in rec.message for rec in caplog.records)
52
+
53
+
54
+ def test_non_zero_exit_returns_empty(monkeypatch, caplog):
55
+ monkeypatch.setattr(llm_provider.shutil, "which", lambda _: "/fake/claude")
56
+ fake_run = lambda *a, **kw: _completed(stdout="", stderr="boom", returncode=2)
57
+ monkeypatch.setattr(llm_provider.subprocess, "run", fake_run)
58
+ with caplog.at_level("WARNING"):
59
+ out = llm_provider._call_claude_cli("prompt", "text")
60
+ assert out == ""
61
+ assert any("exit=2" in rec.message for rec in caplog.records)
62
+
63
+
64
+ def test_timeout_returns_empty(monkeypatch, caplog):
65
+ monkeypatch.setattr(llm_provider.shutil, "which", lambda _: "/fake/claude")
66
+
67
+ def _raise(*_a, **_kw):
68
+ raise subprocess.TimeoutExpired(cmd="claude", timeout=120)
69
+
70
+ monkeypatch.setattr(llm_provider.subprocess, "run", _raise)
71
+ with caplog.at_level("WARNING"):
72
+ out = llm_provider._call_claude_cli("prompt", "text")
73
+ assert out == ""
74
+ assert any("timed out" in rec.message for rec in caplog.records)
75
+
76
+
77
+ def test_oserror_returns_empty(monkeypatch, caplog):
78
+ monkeypatch.setattr(llm_provider.shutil, "which", lambda _: "/fake/claude")
79
+
80
+ def _raise(*_a, **_kw):
81
+ raise OSError("permission denied")
82
+
83
+ monkeypatch.setattr(llm_provider.subprocess, "run", _raise)
84
+ with caplog.at_level("WARNING"):
85
+ out = llm_provider._call_claude_cli("prompt", "text")
86
+ assert out == ""
87
+ assert any("subprocess failed" in rec.message for rec in caplog.records)
88
+
89
+
90
+ def test_utf8_emoji_roundtrip(monkeypatch):
91
+ monkeypatch.setattr(llm_provider.shutil, "which", lambda _: "/fake/claude")
92
+ captured = {}
93
+
94
+ def _fake(args, input, capture_output, text, timeout, encoding, errors):
95
+ captured["input"] = input
96
+ captured["encoding"] = encoding
97
+ return _completed(stdout="hola 👋 ñoño \n", returncode=0)
98
+
99
+ monkeypatch.setattr(llm_provider.subprocess, "run", _fake)
100
+ out = llm_provider._call_claude_cli("Saludá", "ño 🌍 emoji")
101
+ assert out == "hola 👋 ñoño" # strip() applied
102
+ assert captured["input"] == "Saludá\n\nño 🌍 emoji"
103
+ assert captured["encoding"] == "utf-8"
104
+
105
+
106
+ def test_bin_override_honored(monkeypatch):
107
+ monkeypatch.setenv("MEMORYMASTER_CLAUDE_CLI_BIN", "/custom/claude")
108
+ # `which` should NOT be consulted when override is set.
109
+ monkeypatch.setattr(
110
+ llm_provider.shutil,
111
+ "which",
112
+ lambda _: pytest.fail("which should not be called when override set"),
113
+ )
114
+ captured = {}
115
+
116
+ def _fake(args, **_kw):
117
+ captured["argv"] = args
118
+ return _completed(stdout="ok", returncode=0)
119
+
120
+ monkeypatch.setattr(llm_provider.subprocess, "run", _fake)
121
+ out = llm_provider._call_claude_cli("p", "t")
122
+ assert out == "ok"
123
+ assert captured["argv"][0] == "/custom/claude"
124
+
125
+
126
+ def test_timeout_override_honored(monkeypatch):
127
+ monkeypatch.setenv("MEMORYMASTER_CLAUDE_CLI_TIMEOUT", "30")
128
+ monkeypatch.setattr(llm_provider.shutil, "which", lambda _: "/fake/claude")
129
+ captured = {}
130
+
131
+ def _fake(args, **kw):
132
+ captured["timeout"] = kw["timeout"]
133
+ return _completed(stdout="ok", returncode=0)
134
+
135
+ monkeypatch.setattr(llm_provider.subprocess, "run", _fake)
136
+ llm_provider._call_claude_cli("p", "t")
137
+ assert captured["timeout"] == 30
138
+
139
+
140
+ def test_model_override_honored(monkeypatch):
141
+ monkeypatch.setenv("MEMORYMASTER_LLM_MODEL", "claude-sonnet-4-6-20251022")
142
+ monkeypatch.setattr(llm_provider.shutil, "which", lambda _: "/fake/claude")
143
+ captured = {}
144
+
145
+ def _fake(args, **_kw):
146
+ captured["argv"] = args
147
+ return _completed(stdout="ok", returncode=0)
148
+
149
+ monkeypatch.setattr(llm_provider.subprocess, "run", _fake)
150
+ llm_provider._call_claude_cli("p", "t")
151
+ assert "--model" in captured["argv"]
152
+ model_idx = captured["argv"].index("--model") + 1
153
+ assert captured["argv"][model_idx] == "claude-sonnet-4-6-20251022"
154
+
155
+
156
+ def test_default_model_is_haiku(monkeypatch):
157
+ monkeypatch.setattr(llm_provider.shutil, "which", lambda _: "/fake/claude")
158
+ captured = {}
159
+
160
+ def _fake(args, **_kw):
161
+ captured["argv"] = args
162
+ return _completed(stdout="ok", returncode=0)
163
+
164
+ monkeypatch.setattr(llm_provider.subprocess, "run", _fake)
165
+ llm_provider._call_claude_cli("p", "t")
166
+ model_idx = captured["argv"].index("--model") + 1
167
+ assert captured["argv"][model_idx] == "claude-haiku-4-5-20251001"
168
+
169
+
170
+ def test_provider_aliases_register_same_function():
171
+ """Both 'claude_cli' and 'claude-cli' must resolve to _call_claude_cli."""
172
+ assert llm_provider._PROVIDERS["claude_cli"] is llm_provider._call_claude_cli
173
+ assert llm_provider._PROVIDERS["claude-cli"] is llm_provider._call_claude_cli
174
+
175
+
176
+ def test_call_llm_dispatches_to_claude_cli(monkeypatch):
177
+ """End-to-end: call_llm with provider=claude_cli should route to _call_claude_cli."""
178
+ monkeypatch.setenv("MEMORYMASTER_LLM_PROVIDER", "claude_cli")
179
+ monkeypatch.setattr(llm_provider.shutil, "which", lambda _: "/fake/claude")
180
+
181
+ def _fake(args, **_kw):
182
+ return _completed(stdout='[{"answer": 42}]', returncode=0)
183
+
184
+ monkeypatch.setattr(llm_provider.subprocess, "run", _fake)
185
+ out = llm_provider.call_llm("Q:", "What is the answer?")
186
+ assert out == '[{"answer": 42}]'
File without changes
File without changes