claude_memory 0.7.0 → 0.8.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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/memory.sqlite3 +0 -0
  3. data/.claude/memory.sqlite3-shm +0 -0
  4. data/.claude/memory.sqlite3-wal +0 -0
  5. data/.claude/settings.json +78 -6
  6. data/.claude/settings.local.json +5 -2
  7. data/.claude/skills/improve/SKILL.md +113 -25
  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 +1 -1
  11. data/.claude-plugin/plugin.json +1 -2
  12. data/CHANGELOG.md +74 -1
  13. data/CLAUDE.md +32 -6
  14. data/README.md +1 -1
  15. data/docs/improvements.md +51 -91
  16. data/docs/influence/lossless-claw.md +409 -0
  17. data/docs/quality_review.md +119 -224
  18. data/hooks/hooks.json +39 -7
  19. data/lib/claude_memory/commands/checks/distill_check.rb +61 -0
  20. data/lib/claude_memory/commands/checks/hooks_check.rb +2 -2
  21. data/lib/claude_memory/commands/checks/vec_check.rb +2 -1
  22. data/lib/claude_memory/commands/completion_command.rb +179 -0
  23. data/lib/claude_memory/commands/doctor_command.rb +2 -0
  24. data/lib/claude_memory/commands/help_command.rb +4 -0
  25. data/lib/claude_memory/commands/hook_command.rb +2 -1
  26. data/lib/claude_memory/commands/index_command.rb +100 -65
  27. data/lib/claude_memory/commands/initializers/database_ensurer.rb +16 -0
  28. data/lib/claude_memory/commands/initializers/global_initializer.rb +2 -1
  29. data/lib/claude_memory/commands/initializers/hooks_configurator.rb +55 -11
  30. data/lib/claude_memory/commands/initializers/project_initializer.rb +2 -1
  31. data/lib/claude_memory/commands/install_skill_command.rb +78 -0
  32. data/lib/claude_memory/commands/registry.rb +3 -1
  33. data/lib/claude_memory/commands/skills/distill-transcripts.md +98 -0
  34. data/lib/claude_memory/commands/skills/memory-recall.md +67 -0
  35. data/lib/claude_memory/core/fact_ranker.rb +2 -2
  36. data/lib/claude_memory/core/rr_fusion.rb +23 -6
  37. data/lib/claude_memory/core/snippet_extractor.rb +7 -3
  38. data/lib/claude_memory/core/text_builder.rb +11 -0
  39. data/lib/claude_memory/domain/provenance.rb +0 -1
  40. data/lib/claude_memory/embeddings/api_adapter.rb +96 -0
  41. data/lib/claude_memory/embeddings/dimension_check.rb +23 -0
  42. data/lib/claude_memory/embeddings/fastembed_adapter.rb +4 -0
  43. data/lib/claude_memory/embeddings/generator.rb +4 -0
  44. data/lib/claude_memory/embeddings/resolver.rb +18 -0
  45. data/lib/claude_memory/hook/context_injector.rb +58 -2
  46. data/lib/claude_memory/hook/distillation_runner.rb +46 -0
  47. data/lib/claude_memory/hook/handler.rb +11 -2
  48. data/lib/claude_memory/index/vector_index.rb +15 -2
  49. data/lib/claude_memory/infrastructure/schema_validator.rb +3 -3
  50. data/lib/claude_memory/mcp/error_classifier.rb +171 -0
  51. data/lib/claude_memory/mcp/handlers/context_handlers.rb +38 -0
  52. data/lib/claude_memory/mcp/handlers/management_handlers.rb +145 -0
  53. data/lib/claude_memory/mcp/handlers/query_handlers.rb +115 -0
  54. data/lib/claude_memory/mcp/handlers/setup_handlers.rb +211 -0
  55. data/lib/claude_memory/mcp/handlers/shortcut_handlers.rb +37 -0
  56. data/lib/claude_memory/mcp/handlers/stats_handlers.rb +202 -0
  57. data/lib/claude_memory/mcp/instructions_builder.rb +64 -5
  58. data/lib/claude_memory/mcp/query_guide.rb +51 -22
  59. data/lib/claude_memory/mcp/response_formatter.rb +4 -1
  60. data/lib/claude_memory/mcp/server.rb +1 -0
  61. data/lib/claude_memory/mcp/text_summary.rb +28 -1
  62. data/lib/claude_memory/mcp/tool_definitions.rb +33 -3
  63. data/lib/claude_memory/mcp/tool_helpers.rb +43 -0
  64. data/lib/claude_memory/mcp/tools.rb +47 -681
  65. data/lib/claude_memory/recall/dual_engine.rb +105 -0
  66. data/lib/claude_memory/recall/legacy_engine.rb +138 -0
  67. data/lib/claude_memory/recall/query_core.rb +371 -0
  68. data/lib/claude_memory/recall.rb +29 -616
  69. data/lib/claude_memory/shortcuts.rb +4 -4
  70. data/lib/claude_memory/store/retry_handler.rb +61 -0
  71. data/lib/claude_memory/store/schema_manager.rb +68 -0
  72. data/lib/claude_memory/store/sqlite_store.rb +85 -201
  73. data/lib/claude_memory/sweep/maintenance.rb +126 -0
  74. data/lib/claude_memory/sweep/sweeper.rb +81 -75
  75. data/lib/claude_memory/templates/hooks.example.json +26 -7
  76. data/lib/claude_memory/version.rb +1 -1
  77. data/lib/claude_memory.rb +12 -0
  78. data/v0.6.0.ANNOUNCE +32 -0
  79. metadata +27 -1
@@ -28,6 +28,8 @@ module ClaudeMemory
28
28
  when "memory.recall_semantic" then summarize_semantic(result)
29
29
  when "memory.search_concepts" then summarize_concepts(result)
30
30
  when "memory.fact_graph" then summarize_fact_graph(result)
31
+ when "memory.undistilled" then summarize_undistilled(result)
32
+ when "memory.mark_distilled" then summarize_mark_distilled(result)
31
33
  when "memory.check_setup" then summarize_check_setup(result)
32
34
  else JSON.generate(result)
33
35
  end
@@ -104,7 +106,8 @@ module ClaudeMemory
104
106
  end
105
107
 
106
108
  def self.summarize_sweep(result)
107
- "Sweep (#{result[:scope]}): #{result[:proposed_expired]} proposed expired, " \
109
+ escalation = result[:escalation_level] ? " [#{result[:escalation_level]}]" : ""
110
+ "Sweep (#{result[:scope]})#{escalation}: #{result[:proposed_expired]} proposed expired, " \
108
111
  "#{result[:disputed_expired]} disputed expired, " \
109
112
  "#{result[:orphaned_deleted]} orphaned deleted, " \
110
113
  "#{result[:content_pruned]} content pruned " \
@@ -121,6 +124,10 @@ module ClaudeMemory
121
124
  "- #{name}: #{info[:facts_active]} active facts, #{info[:open_conflicts]} conflicts (schema v#{info[:schema_version]})"
122
125
  end
123
126
  end
127
+
128
+ pending = result[:pending_distillation] || 0
129
+ lines << "Pending distillation: #{pending}" if pending > 0
130
+
124
131
  lines.join("\n")
125
132
  end
126
133
 
@@ -232,6 +239,26 @@ module ClaudeMemory
232
239
  lines.join("\n")
233
240
  end
234
241
 
242
+ def self.summarize_undistilled(result)
243
+ items = result[:items] || []
244
+ return "No undistilled content items." if items.empty?
245
+
246
+ lines = ["#{result[:count]} undistilled content item(s):"]
247
+ items.each do |i|
248
+ ago = i[:occurred_ago] || "unknown"
249
+ lines << "- Item ##{i[:content_item_id]} (#{ago}): #{(i[:raw_text] || "")[0, 80]}..."
250
+ end
251
+ lines.join("\n")
252
+ end
253
+
254
+ def self.summarize_mark_distilled(result)
255
+ if result[:success]
256
+ "Marked content item ##{result[:content_item_id]} as distilled (#{result[:facts_extracted]} facts extracted)"
257
+ else
258
+ result[:error]
259
+ end
260
+ end
261
+
235
262
  def self.summarize_check_setup(result)
236
263
  lines = ["Setup status: #{result[:status]}"]
237
264
  lines << "Version: #{result[:version][:current]} (latest: #{result[:version][:latest]})"
@@ -25,6 +25,7 @@ module ClaudeMemory
25
25
  type: "object",
26
26
  properties: {
27
27
  query: {type: "string", description: "Search query for existing knowledge (e.g., 'authentication flow', 'error handling', 'database setup')"},
28
+ 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
29
  limit: {type: "integer", description: "Max results", default: 10},
29
30
  scope: {type: "string", enum: ["all", "global", "project"], description: "Filter by scope: 'all' (default), 'global', or 'project'", default: "all"},
30
31
  compact: {type: "boolean", description: "Omit provenance receipts for ~60% smaller responses (~800 → ~300 tokens/result)", default: false}
@@ -40,6 +41,7 @@ module ClaudeMemory
40
41
  type: "object",
41
42
  properties: {
42
43
  query: {type: "string", description: "Search query for existing knowledge (e.g., 'client errors', 'database choice')"},
44
+ 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
45
  limit: {type: "integer", description: "Maximum results to return", default: 20},
44
46
  scope: {type: "string", enum: ["all", "global", "project"], description: "Scope: 'all' (both), 'global' (user-wide), 'project' (current only)", default: "all"}
45
47
  },
@@ -99,12 +101,13 @@ module ClaudeMemory
99
101
  },
100
102
  {
101
103
  name: "memory.sweep_now",
102
- description: "Run maintenance sweep on a database",
104
+ description: "Run maintenance sweep on a database. Use escalate: true for guaranteed progress (normal → aggressive → fallback).",
103
105
  inputSchema: {
104
106
  type: "object",
105
107
  properties: {
106
108
  budget_seconds: {type: "integer", default: 5},
107
- scope: {type: "string", enum: ["global", "project"], default: "project"}
109
+ scope: {type: "string", enum: ["global", "project"], default: "project"},
110
+ escalate: {type: "boolean", default: false, description: "Enable three-level escalation (normal → aggressive → fallback) to guarantee progress"}
108
111
  }
109
112
  },
110
113
  annotations: WRITE
@@ -264,10 +267,12 @@ module ClaudeMemory
264
267
  type: "object",
265
268
  properties: {
266
269
  query: {type: "string", description: "Search query"},
270
+ 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."},
267
271
  mode: {type: "string", enum: ["vector", "text", "both"], default: "both", description: "Search mode: vector (embeddings), text (FTS), or both (hybrid)"},
268
272
  limit: {type: "integer", default: 10, description: "Maximum results to return"},
269
273
  scope: {type: "string", enum: ["all", "global", "project"], default: "all", description: "Filter by scope"},
270
- compact: {type: "boolean", description: "Omit provenance receipts for ~60% smaller responses (~800 → ~300 tokens/result)", default: false}
274
+ compact: {type: "boolean", description: "Omit provenance receipts for ~60% smaller responses (~800 → ~300 tokens/result)", default: false},
275
+ explain: {type: "boolean", description: "Include per-result score traces showing FTS rank, vector similarity, and RRF contribution", default: false}
271
276
  },
272
277
  required: ["query"]
273
278
  },
@@ -308,6 +313,31 @@ module ClaudeMemory
308
313
  },
309
314
  annotations: READ_ONLY
310
315
  },
316
+ {
317
+ name: "memory.undistilled",
318
+ description: "List content items not yet deeply distilled. Returns raw transcript text for knowledge extraction.",
319
+ inputSchema: {
320
+ type: "object",
321
+ properties: {
322
+ limit: {type: "integer", default: 3, description: "Max items to return"},
323
+ min_length: {type: "integer", default: 200, description: "Min text length (skip tiny deltas)"}
324
+ }
325
+ },
326
+ annotations: READ_ONLY
327
+ },
328
+ {
329
+ name: "memory.mark_distilled",
330
+ description: "Mark a content item as distilled after extracting facts from it.",
331
+ inputSchema: {
332
+ type: "object",
333
+ properties: {
334
+ content_item_id: {type: "integer", description: "ID of the distilled content item"},
335
+ facts_extracted: {type: "integer", default: 0, description: "Number of facts extracted"}
336
+ },
337
+ required: ["content_item_id"]
338
+ },
339
+ annotations: WRITE_IDEMPOTENT
340
+ },
311
341
  {
312
342
  name: "memory.check_setup",
313
343
  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