claude_memory 0.7.1 → 0.9.0

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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/memory.sqlite3 +0 -0
  3. data/.claude/rules/claude_memory.generated.md +32 -2
  4. data/.claude/settings.json +65 -15
  5. data/.claude/settings.local.json +5 -2
  6. data/.claude/skills/improve/SKILL.md +113 -25
  7. data/.claude/skills/upgrade-dependencies/SKILL.md +154 -0
  8. data/.claude-plugin/commands/distill-transcripts.md +98 -0
  9. data/.claude-plugin/commands/memory-recall.md +67 -0
  10. data/.claude-plugin/marketplace.json +2 -2
  11. data/.claude-plugin/plugin.json +3 -3
  12. data/.claude-plugin/scripts/hook-runner.sh +14 -0
  13. data/.claude-plugin/scripts/serve-mcp.sh +14 -0
  14. data/.ruby-version +1 -1
  15. data/CHANGELOG.md +90 -1
  16. data/CLAUDE.md +56 -18
  17. data/README.md +35 -0
  18. data/db/migrations/013_add_mcp_tool_calls.rb +26 -0
  19. data/db/migrations/014_canonicalize_predicates.rb +30 -0
  20. data/docs/improvements.md +74 -74
  21. data/docs/influence/claude-mem.md +1 -0
  22. data/docs/influence/claude-supermemory.md +1 -0
  23. data/docs/influence/episodic-memory.md +1 -0
  24. data/docs/influence/grepai.md +1 -0
  25. data/docs/influence/kbs.md +1 -0
  26. data/docs/influence/lossless-claw.md +1 -0
  27. data/docs/influence/qmd.md +1 -0
  28. data/docs/quality_review.md +119 -224
  29. data/hooks/hooks.json +39 -7
  30. data/lib/claude_memory/commands/checks/distill_check.rb +61 -0
  31. data/lib/claude_memory/commands/checks/hooks_check.rb +2 -2
  32. data/lib/claude_memory/commands/checks/vec_check.rb +2 -1
  33. data/lib/claude_memory/commands/completion_command.rb +149 -0
  34. data/lib/claude_memory/commands/doctor_command.rb +2 -0
  35. data/lib/claude_memory/commands/embeddings_command.rb +198 -0
  36. data/lib/claude_memory/commands/help_command.rb +12 -1
  37. data/lib/claude_memory/commands/hook_command.rb +2 -1
  38. data/lib/claude_memory/commands/index_command.rb +85 -78
  39. data/lib/claude_memory/commands/initializers/database_ensurer.rb +16 -0
  40. data/lib/claude_memory/commands/initializers/global_initializer.rb +2 -1
  41. data/lib/claude_memory/commands/initializers/hooks_configurator.rb +55 -11
  42. data/lib/claude_memory/commands/initializers/project_initializer.rb +2 -1
  43. data/lib/claude_memory/commands/install_skill_command.rb +78 -0
  44. data/lib/claude_memory/commands/registry.rb +47 -32
  45. data/lib/claude_memory/commands/reject_command.rb +62 -0
  46. data/lib/claude_memory/commands/restore_command.rb +77 -0
  47. data/lib/claude_memory/commands/skills/distill-transcripts.md +102 -0
  48. data/lib/claude_memory/commands/skills/memory-recall.md +67 -0
  49. data/lib/claude_memory/commands/stats_command.rb +98 -2
  50. data/lib/claude_memory/configuration.rb +14 -1
  51. data/lib/claude_memory/core/fact_ranker.rb +2 -2
  52. data/lib/claude_memory/core/rr_fusion.rb +23 -6
  53. data/lib/claude_memory/core/snippet_extractor.rb +7 -3
  54. data/lib/claude_memory/core/text_builder.rb +11 -0
  55. data/lib/claude_memory/distill/json_schema.md +8 -4
  56. data/lib/claude_memory/distill/null_distiller.rb +2 -0
  57. data/lib/claude_memory/domain/entity.rb +13 -1
  58. data/lib/claude_memory/domain/fact.rb +26 -2
  59. data/lib/claude_memory/domain/provenance.rb +0 -1
  60. data/lib/claude_memory/embeddings/api_adapter.rb +97 -0
  61. data/lib/claude_memory/embeddings/dimension_check.rb +23 -0
  62. data/lib/claude_memory/embeddings/fastembed_adapter.rb +46 -12
  63. data/lib/claude_memory/embeddings/generator.rb +4 -0
  64. data/lib/claude_memory/embeddings/inspector.rb +91 -0
  65. data/lib/claude_memory/embeddings/model_registry.rb +210 -0
  66. data/lib/claude_memory/embeddings/resolver.rb +44 -0
  67. data/lib/claude_memory/hook/context_injector.rb +58 -2
  68. data/lib/claude_memory/hook/distillation_runner.rb +46 -0
  69. data/lib/claude_memory/hook/handler.rb +11 -2
  70. data/lib/claude_memory/index/vector_index.rb +15 -2
  71. data/lib/claude_memory/infrastructure/schema_validator.rb +3 -3
  72. data/lib/claude_memory/ingest/ingester.rb +17 -0
  73. data/lib/claude_memory/mcp/handlers/context_handlers.rb +38 -0
  74. data/lib/claude_memory/mcp/handlers/management_handlers.rb +169 -0
  75. data/lib/claude_memory/mcp/handlers/query_handlers.rb +115 -0
  76. data/lib/claude_memory/mcp/handlers/setup_handlers.rb +211 -0
  77. data/lib/claude_memory/mcp/handlers/shortcut_handlers.rb +37 -0
  78. data/lib/claude_memory/mcp/handlers/stats_handlers.rb +205 -0
  79. data/lib/claude_memory/mcp/instructions_builder.rb +19 -1
  80. data/lib/claude_memory/mcp/query_guide.rb +10 -0
  81. data/lib/claude_memory/mcp/response_formatter.rb +1 -0
  82. data/lib/claude_memory/mcp/server.rb +22 -1
  83. data/lib/claude_memory/mcp/telemetry.rb +86 -0
  84. data/lib/claude_memory/mcp/text_summary.rb +26 -0
  85. data/lib/claude_memory/mcp/tool_definitions.rb +116 -4
  86. data/lib/claude_memory/mcp/tool_helpers.rb +43 -0
  87. data/lib/claude_memory/mcp/tools.rb +50 -679
  88. data/lib/claude_memory/publish.rb +40 -5
  89. data/lib/claude_memory/recall/dual_engine.rb +105 -0
  90. data/lib/claude_memory/recall/legacy_engine.rb +138 -0
  91. data/lib/claude_memory/recall/query_core.rb +371 -0
  92. data/lib/claude_memory/recall.rb +121 -673
  93. data/lib/claude_memory/resolve/predicate_policy.rb +63 -3
  94. data/lib/claude_memory/resolve/resolver.rb +43 -0
  95. data/lib/claude_memory/shortcuts.rb +4 -4
  96. data/lib/claude_memory/store/retry_handler.rb +61 -0
  97. data/lib/claude_memory/store/schema_manager.rb +68 -0
  98. data/lib/claude_memory/store/sqlite_store.rb +334 -201
  99. data/lib/claude_memory/store/store_manager.rb +50 -1
  100. data/lib/claude_memory/sweep/maintenance.rb +115 -1
  101. data/lib/claude_memory/sweep/sweeper.rb +3 -0
  102. data/lib/claude_memory/templates/hooks.example.json +26 -7
  103. data/lib/claude_memory/version.rb +1 -1
  104. data/lib/claude_memory.rb +16 -0
  105. metadata +48 -8
  106. data/.claude/memory.sqlite3-shm +0 -0
  107. data/.claude/memory.sqlite3-wal +0 -0
@@ -14,6 +14,59 @@ module ClaudeMemory
14
14
  # Annotations for idempotent writes (safe to retry)
15
15
  WRITE_IDEMPOTENT = {readOnlyHint: false, idempotentHint: true, destructiveHint: false}.freeze
16
16
 
17
+ # Schema for {predicate, count} entries
18
+ PREDICATE_COUNT_SCHEMA = {
19
+ type: "object",
20
+ properties: {
21
+ predicate: {type: "string"},
22
+ count: {type: "integer"}
23
+ },
24
+ required: ["predicate", "count"]
25
+ }.freeze
26
+
27
+ # Schema for per-database stats block returned by memory.stats
28
+ DATABASE_STATS_SCHEMA = {
29
+ type: "object",
30
+ properties: {
31
+ exists: {type: "boolean"},
32
+ schema_version: {type: "integer"},
33
+ facts: {
34
+ type: "object",
35
+ properties: {
36
+ total: {type: "integer"},
37
+ active: {type: "integer"},
38
+ superseded: {type: "integer"},
39
+ top_predicates: {
40
+ type: "array",
41
+ description: "Top 10 predicates by count (known + novel combined)",
42
+ items: PREDICATE_COUNT_SCHEMA
43
+ },
44
+ predicates_known: {
45
+ type: "array",
46
+ description: "Predicates with explicit cardinality policies in PredicatePolicy::POLICIES, sorted by count desc",
47
+ items: PREDICATE_COUNT_SCHEMA
48
+ },
49
+ predicates_novel: {
50
+ type: "array",
51
+ description: "Predicates not in PredicatePolicy::POLICIES, sorted by count desc. Novel predicates with high counts are candidates for promotion to known status with explicit cardinality policies (canonicalization signal).",
52
+ items: PREDICATE_COUNT_SCHEMA
53
+ }
54
+ }
55
+ },
56
+ entities: {
57
+ type: "object",
58
+ properties: {
59
+ total: {type: "integer"},
60
+ by_type: {type: "array", items: {type: "object"}}
61
+ }
62
+ },
63
+ content_items: {type: "object"},
64
+ provenance: {type: "object"},
65
+ conflicts: {type: "object"},
66
+ vec: {type: "object"}
67
+ }
68
+ }.freeze
69
+
17
70
  # Returns array of tool definitions for MCP protocol
18
71
  # @return [Array<Hash>] Tool definitions with name, description, and inputSchema
19
72
  def self.all
@@ -25,6 +78,7 @@ module ClaudeMemory
25
78
  type: "object",
26
79
  properties: {
27
80
  query: {type: "string", description: "Search query for existing knowledge (e.g., 'authentication flow', 'error handling', 'database setup')"},
81
+ intent: {type: "string", description: "Optional intent to disambiguate the query (e.g., 'migration' or 'performance' when query is 'database'). Steers search without replacing the query."},
28
82
  limit: {type: "integer", description: "Max results", default: 10},
29
83
  scope: {type: "string", enum: ["all", "global", "project"], description: "Filter by scope: 'all' (default), 'global', or 'project'", default: "all"},
30
84
  compact: {type: "boolean", description: "Omit provenance receipts for ~60% smaller responses (~800 → ~300 tokens/result)", default: false}
@@ -40,6 +94,7 @@ module ClaudeMemory
40
94
  type: "object",
41
95
  properties: {
42
96
  query: {type: "string", description: "Search query for existing knowledge (e.g., 'client errors', 'database choice')"},
97
+ intent: {type: "string", description: "Optional intent to disambiguate the query (e.g., 'schema' or 'optimization' when query is 'database'). Steers search without replacing the query."},
43
98
  limit: {type: "integer", description: "Maximum results to return", default: 20},
44
99
  scope: {type: "string", enum: ["all", "global", "project"], description: "Scope: 'all' (both), 'global' (user-wide), 'project' (current only)", default: "all"}
45
100
  },
@@ -121,13 +176,29 @@ module ClaudeMemory
121
176
  },
122
177
  {
123
178
  name: "memory.stats",
124
- description: "Get detailed statistics about the memory system (facts by predicate, entities by type, provenance coverage, conflicts, database sizes)",
179
+ description: "Get detailed statistics about the memory system (facts by predicate, entities by type, provenance coverage, conflicts, database sizes).",
125
180
  inputSchema: {
126
181
  type: "object",
127
182
  properties: {
128
183
  scope: {type: "string", enum: ["all", "global", "project"], description: "Show stats for: all (default), global, or project", default: "all"}
129
184
  }
130
185
  },
186
+ outputSchema: {
187
+ type: "object",
188
+ properties: {
189
+ scope: {type: "string", enum: ["all", "global", "project"]},
190
+ databases: {
191
+ type: "object",
192
+ description: "Per-database stats. Keys are 'global', 'project', or 'legacy' depending on connection mode.",
193
+ properties: {
194
+ global: DATABASE_STATS_SCHEMA,
195
+ project: DATABASE_STATS_SCHEMA,
196
+ legacy: DATABASE_STATS_SCHEMA
197
+ }
198
+ }
199
+ },
200
+ required: ["scope", "databases"]
201
+ },
131
202
  annotations: READ_ONLY
132
203
  },
133
204
  {
@@ -142,6 +213,20 @@ module ClaudeMemory
142
213
  },
143
214
  annotations: WRITE_IDEMPOTENT
144
215
  },
216
+ {
217
+ name: "memory.reject_fact",
218
+ description: "Mark a fact as rejected (e.g. a distiller hallucination). Sets status to 'rejected' and closes any open conflicts involving the fact. Use when the user confirms a fact is wrong.",
219
+ inputSchema: {
220
+ type: "object",
221
+ properties: {
222
+ fact_id: {type: "integer", description: "Fact ID to reject"},
223
+ docid: {type: "string", description: "8-char docid (alternative to fact_id)"},
224
+ reason: {type: "string", description: "Why the fact is wrong (recorded in conflict notes)"},
225
+ scope: {type: "string", enum: ["project", "global"], description: "Database scope", default: "project"}
226
+ }
227
+ },
228
+ annotations: WRITE_IDEMPOTENT
229
+ },
145
230
  {
146
231
  name: "memory.store_extraction",
147
232
  description: "Store extracted facts, entities, and decisions from a conversation. Call this to persist knowledge you've learned during the session.",
@@ -154,7 +239,7 @@ module ClaudeMemory
154
239
  items: {
155
240
  type: "object",
156
241
  properties: {
157
- type: {type: "string", description: "Entity type: database, framework, language, platform, repo, module, person, service"},
242
+ type: {type: "string", description: "Entity type. Common types: database, framework, language, platform, repo, module, person, service, tool, library, concept. You may use other types if needed."},
158
243
  name: {type: "string", description: "Canonical name"},
159
244
  confidence: {type: "number", description: "0.0-1.0 extraction confidence"}
160
245
  },
@@ -168,7 +253,7 @@ module ClaudeMemory
168
253
  type: "object",
169
254
  properties: {
170
255
  subject: {type: "string", description: "Entity name or 'repo' for project-level facts"},
171
- predicate: {type: "string", description: "Relationship type: uses_database, uses_framework, convention, decision, auth_method, deployment_platform"},
256
+ predicate: {type: "string", description: "Relationship type. Known predicates: #{ClaudeMemory::Resolve::PredicatePolicy.known_predicates.join(", ")}. You may use other snake_case predicates for relations that don't fit these — be specific and reuse existing predicates when possible."},
172
257
  object: {type: "string", description: "The value or target entity"},
173
258
  confidence: {type: "number", description: "0.0-1.0 how confident"},
174
259
  quote: {type: "string", description: "Source text excerpt (max 200 chars)"},
@@ -265,10 +350,12 @@ module ClaudeMemory
265
350
  type: "object",
266
351
  properties: {
267
352
  query: {type: "string", description: "Search query"},
353
+ intent: {type: "string", description: "Optional intent to disambiguate the query (e.g., 'security' when query is 'authentication'). Disables BM25 shortcut to ensure vector search runs."},
268
354
  mode: {type: "string", enum: ["vector", "text", "both"], default: "both", description: "Search mode: vector (embeddings), text (FTS), or both (hybrid)"},
269
355
  limit: {type: "integer", default: 10, description: "Maximum results to return"},
270
356
  scope: {type: "string", enum: ["all", "global", "project"], default: "all", description: "Filter by scope"},
271
- compact: {type: "boolean", description: "Omit provenance receipts for ~60% smaller responses (~800 → ~300 tokens/result)", default: false}
357
+ compact: {type: "boolean", description: "Omit provenance receipts for ~60% smaller responses (~800 → ~300 tokens/result)", default: false},
358
+ explain: {type: "boolean", description: "Include per-result score traces showing FTS rank, vector similarity, and RRF contribution", default: false}
272
359
  },
273
360
  required: ["query"]
274
361
  },
@@ -309,6 +396,31 @@ module ClaudeMemory
309
396
  },
310
397
  annotations: READ_ONLY
311
398
  },
399
+ {
400
+ name: "memory.undistilled",
401
+ description: "List content items not yet deeply distilled. Returns raw transcript text for knowledge extraction.",
402
+ inputSchema: {
403
+ type: "object",
404
+ properties: {
405
+ limit: {type: "integer", default: 3, description: "Max items to return"},
406
+ min_length: {type: "integer", default: 200, description: "Min text length (skip tiny deltas)"}
407
+ }
408
+ },
409
+ annotations: READ_ONLY
410
+ },
411
+ {
412
+ name: "memory.mark_distilled",
413
+ description: "Mark a content item as distilled after extracting facts from it.",
414
+ inputSchema: {
415
+ type: "object",
416
+ properties: {
417
+ content_item_id: {type: "integer", description: "ID of the distilled content item"},
418
+ facts_extracted: {type: "integer", default: 0, description: "Number of facts extracted"}
419
+ },
420
+ required: ["content_item_id"]
421
+ },
422
+ annotations: WRITE_IDEMPOTENT
423
+ },
312
424
  {
313
425
  name: "memory.check_setup",
314
426
  description: "Check ClaudeMemory initialization status. Returns version info, issues found, and recommendations.",
@@ -75,6 +75,49 @@ module ClaudeMemory
75
75
  def extract_limit(args, default: 10)
76
76
  args["limit"] || default
77
77
  end
78
+
79
+ # Extract optional intent parameter for query disambiguation
80
+ # @param args [Hash] Tool arguments
81
+ # @return [String, nil] Intent string or nil if not provided/blank
82
+ def extract_intent(args)
83
+ intent = args["intent"]
84
+ (intent.nil? || intent.to_s.strip.empty?) ? nil : intent.to_s.strip
85
+ end
86
+
87
+ # Collect undistilled content items from both stores (or legacy store)
88
+ # @param limit [Integer] Maximum items to return
89
+ # @param min_length [Integer] Minimum byte_len to include
90
+ # @return [Array<Hash>] Undistilled items sorted by recency
91
+ def collect_undistilled_items(limit:, min_length: 200)
92
+ if @manager
93
+ stores = []
94
+ stores << @manager.project_store if @manager.project_exists?
95
+ stores << @manager.global_store if @manager.global_exists?
96
+ items = stores.flat_map { |s| s.undistilled_content_items(limit: limit, min_length: min_length) }
97
+ items.sort_by { |i| i[:occurred_at] || "" }.reverse.first(limit)
98
+ elsif @legacy_store
99
+ @legacy_store.undistilled_content_items(limit: limit, min_length: min_length)
100
+ else
101
+ []
102
+ end
103
+ end
104
+
105
+ # Find the store containing a given content item
106
+ # @param content_item_id [Integer] Content item ID to locate
107
+ # @return [Store::SQLiteStore, nil] The store containing the item, or nil
108
+ def find_store_for_content_item(content_item_id)
109
+ if @manager
110
+ if @manager.project_store&.content_items&.where(id: content_item_id)&.any?
111
+ @manager.project_store
112
+ elsif @manager.global_store&.content_items&.where(id: content_item_id)&.any?
113
+ @manager.global_store
114
+ end
115
+ elsif @legacy_store
116
+ if @legacy_store.content_items.where(id: content_item_id).any?
117
+ @legacy_store
118
+ end
119
+ end
120
+ end
78
121
  end
79
122
  end
80
123
  end