claude_memory 0.5.1 → 0.6.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/CLAUDE.md +1 -1
  3. data/.claude/rules/claude_memory.generated.md +1 -1
  4. data/.claude/settings.json +5 -0
  5. data/.claude/settings.local.json +9 -1
  6. data/.claude-plugin/marketplace.json +5 -2
  7. data/.claude-plugin/plugin.json +16 -3
  8. data/CHANGELOG.md +55 -0
  9. data/CLAUDE.md +27 -13
  10. data/README.md +6 -2
  11. data/Rakefile +22 -0
  12. data/db/migrations/011_add_tool_call_summaries.rb +18 -0
  13. data/db/migrations/012_add_vec_indexing_support.rb +19 -0
  14. data/docs/improvements.md +86 -66
  15. data/docs/influence/claude-mem.md +253 -0
  16. data/docs/influence/claude-supermemory.md +158 -430
  17. data/docs/influence/episodic-memory.md +217 -0
  18. data/docs/influence/grepai.md +163 -839
  19. data/docs/influence/kbs.md +437 -0
  20. data/docs/influence/qmd.md +139 -481
  21. data/hooks/hooks.json +19 -15
  22. data/lefthook.yml +4 -0
  23. data/lib/claude_memory/commands/checks/vec_check.rb +73 -0
  24. data/lib/claude_memory/commands/compact_command.rb +94 -0
  25. data/lib/claude_memory/commands/doctor_command.rb +1 -0
  26. data/lib/claude_memory/commands/export_command.rb +108 -0
  27. data/lib/claude_memory/commands/help_command.rb +2 -0
  28. data/lib/claude_memory/commands/hook_command.rb +110 -9
  29. data/lib/claude_memory/commands/index_command.rb +63 -8
  30. data/lib/claude_memory/commands/initializers/global_initializer.rb +26 -7
  31. data/lib/claude_memory/commands/initializers/project_initializer.rb +35 -12
  32. data/lib/claude_memory/commands/registry.rb +3 -1
  33. data/lib/claude_memory/hook/context_injector.rb +75 -0
  34. data/lib/claude_memory/hook/error_classifier.rb +67 -0
  35. data/lib/claude_memory/hook/handler.rb +21 -1
  36. data/lib/claude_memory/index/vector_index.rb +171 -0
  37. data/lib/claude_memory/infrastructure/schema_validator.rb +5 -1
  38. data/lib/claude_memory/ingest/ingester.rb +26 -1
  39. data/lib/claude_memory/ingest/observation_compressor.rb +177 -0
  40. data/lib/claude_memory/mcp/instructions_builder.rb +76 -0
  41. data/lib/claude_memory/mcp/server.rb +3 -1
  42. data/lib/claude_memory/mcp/tool_definitions.rb +15 -7
  43. data/lib/claude_memory/mcp/tools.rb +125 -2
  44. data/lib/claude_memory/publish.rb +28 -27
  45. data/lib/claude_memory/recall/dual_query_template.rb +1 -12
  46. data/lib/claude_memory/recall.rb +71 -17
  47. data/lib/claude_memory/store/sqlite_store.rb +17 -1
  48. data/lib/claude_memory/sweep/sweeper.rb +30 -0
  49. data/lib/claude_memory/version.rb +1 -1
  50. data/lib/claude_memory.rb +8 -0
  51. data/scripts/hook-runner.sh +14 -0
  52. data/scripts/serve-mcp.sh +14 -0
  53. data/skills/setup-memory/SKILL.md +6 -0
  54. metadata +31 -2
@@ -499,7 +499,8 @@ module ClaudeMemory
499
499
  results = template.execute(scope: scope, limit: limit) do |store, source|
500
500
  query_semantic_single(store, text, limit: limit * 3, mode: mode, source: source)
501
501
  end
502
- dedupe_and_sort(results, limit)
502
+ # Use similarity-preserving dedupe (not source/time sort) to keep RRF ordering
503
+ Core::FactRanker.dedupe_by_fact_id(results, limit)
503
504
  end
504
505
 
505
506
  def query_semantic_legacy(text, limit:, scope:, mode:)
@@ -529,6 +530,33 @@ module ClaudeMemory
529
530
  # Generate query embedding
530
531
  query_embedding = @embedding_generator.generate(query_text)
531
532
 
533
+ # Fast path: use sqlite-vec KNN when available
534
+ vec_index = store.vector_index
535
+ if vec_index.available?
536
+ return search_by_vector_native(store, vec_index, query_embedding, limit, source)
537
+ end
538
+
539
+ # Fallback: JSON + Ruby cosine similarity
540
+ search_by_vector_fallback(store, query_embedding, limit, source)
541
+ end
542
+
543
+ def search_by_vector_native(store, vec_index, query_embedding, limit, source)
544
+ matches = vec_index.search(query_embedding, k: limit)
545
+ return [] if matches.empty?
546
+
547
+ fact_ids = matches.map { |m| m[:fact_id] }
548
+ facts_by_id = batch_find_facts(store, fact_ids)
549
+ receipts_by_fact_id = batch_find_receipts(store, fact_ids)
550
+
551
+ Core::ResultBuilder.build_results_with_scores(
552
+ matches,
553
+ facts_by_id: facts_by_id,
554
+ receipts_by_fact_id: receipts_by_fact_id,
555
+ source: source
556
+ )
557
+ end
558
+
559
+ def search_by_vector_fallback(store, query_embedding, limit, source)
532
560
  # Load facts with embeddings
533
561
  facts_data = store.facts_with_embeddings(limit: 5000)
534
562
  return [] if facts_data.empty?
@@ -556,33 +584,59 @@ module ClaudeMemory
556
584
  end
557
585
 
558
586
  def search_by_fts(store, query_text, limit, source)
559
- # Use existing FTS search infrastructure
560
587
  fts = Index::LexicalFTS.new(store)
561
- content_ids = fts.search(query_text, limit: limit * 2)
588
+ ranked_results = fts.search_with_ranks(query_text, limit: limit * 2)
562
589
 
563
- return [] if content_ids.empty?
590
+ return [] if ranked_results.empty?
564
591
 
565
- # Find facts from content items
566
- fact_ids = store.provenance
592
+ content_ids = ranked_results.map { |r| r[:content_item_id] }
593
+
594
+ # Map content_item_ids to fact_ids, preserving FTS rank order
595
+ provenance_rows = store.provenance
567
596
  .where(content_item_id: content_ids)
568
- .select(:fact_id)
569
- .distinct
570
- .map { |row| row[:fact_id] }
597
+ .select(:fact_id, :content_item_id)
598
+ .all
571
599
 
572
- return [] if fact_ids.empty?
600
+ content_to_facts = provenance_rows.group_by { |r| r[:content_item_id] }
601
+
602
+ # Build ordered fact list with normalized BM25 scores
603
+ # FTS5 rank values are negative (more negative = better match)
604
+ ranks = ranked_results.map { |r| r[:rank] }
605
+ min_rank = ranks.min # Most negative = best
606
+ max_rank = ranks.max # Least negative = worst
607
+ range = (max_rank - min_rank).abs
608
+
609
+ seen_fact_ids = Set.new
610
+ scored_matches = []
611
+
612
+ ranked_results.each do |r|
613
+ similarity = if range > 0
614
+ # Normalize: best rank → 1.0, worst rank → 0.1
615
+ 0.1 + 0.9 * ((max_rank - r[:rank]).abs / range)
616
+ else
617
+ 0.8 # Single result gets a reasonable score
618
+ end
573
619
 
574
- # Batch fetch facts
620
+ fact_ids = content_to_facts[r[:content_item_id]]&.map { |p| p[:fact_id] } || []
621
+ fact_ids.each do |fid|
622
+ next if seen_fact_ids.include?(fid)
623
+ seen_fact_ids.add(fid)
624
+ scored_matches << {fact_id: fid, similarity: similarity}
625
+ end
626
+ end
627
+
628
+ return [] if scored_matches.empty?
629
+
630
+ fact_ids = scored_matches.map { |m| m[:fact_id] }
575
631
  facts_by_id = batch_find_facts(store, fact_ids)
576
632
  receipts_by_fact_id = batch_find_receipts(store, fact_ids)
577
633
 
578
- results = Core::ResultBuilder.build_results(
579
- fact_ids,
634
+ Core::ResultBuilder.build_results_with_scores(
635
+ scored_matches,
580
636
  facts_by_id: facts_by_id,
581
637
  receipts_by_fact_id: receipts_by_fact_id,
582
- source: source,
583
- similarity: 0.5 # Default score for FTS results
584
- )
585
- results.take(limit)
638
+ source: source
639
+ ).take(limit)
586
640
  end
587
641
 
588
642
  def merge_search_results(vector_results, text_results, limit)
@@ -10,7 +10,7 @@ require "sequel/adapters/extralite"
10
10
  module ClaudeMemory
11
11
  module Store
12
12
  class SQLiteStore
13
- SCHEMA_VERSION = 10
13
+ SCHEMA_VERSION = 12
14
14
 
15
15
  attr_reader :db
16
16
 
@@ -88,6 +88,10 @@ module ClaudeMemory
88
88
  @db.disconnect
89
89
  end
90
90
 
91
+ def vector_index
92
+ @vector_index ||= Index::VectorIndex.new(self)
93
+ end
94
+
91
95
  # Checkpoint the WAL file to prevent unlimited growth
92
96
  # This truncates the WAL after checkpointing
93
97
  # Should be called periodically during maintenance/sweep operations
@@ -192,6 +196,7 @@ module ClaudeMemory
192
196
  tool_name: tc[:tool_name],
193
197
  tool_input: tc[:tool_input],
194
198
  tool_result: tc[:tool_result],
199
+ compressed_summary: tc[:compressed_summary],
195
200
  is_error: tc[:is_error] || false,
196
201
  timestamp: tc[:timestamp]
197
202
  )
@@ -462,6 +467,12 @@ module ClaudeMemory
462
467
  # Handle backward compatibility: databases created with old migration system
463
468
  sync_legacy_schema_version!
464
469
 
470
+ # Skip migration if the database is already ahead of this gem's version.
471
+ # This happens when a newer gem version migrated the DB and an older
472
+ # installed gem (e.g. via hooks) tries to open it.
473
+ current = current_schema_version
474
+ return if current && current > SCHEMA_VERSION
475
+
465
476
  # Run Sequel migrations to bring database to target version
466
477
  Sequel::Migrator.run(@db, migrations_path, target: SCHEMA_VERSION)
467
478
 
@@ -501,6 +512,11 @@ module ClaudeMemory
501
512
  end
502
513
  end
503
514
 
515
+ def current_schema_version
516
+ return nil unless @db.table_exists?(:schema_info)
517
+ @db[:schema_info].get(:version)
518
+ end
519
+
504
520
  def set_meta(key, value)
505
521
  @db[:meta].insert_conflict(target: :key, update: {value: value}).insert(key: key, value: value)
506
522
  end
@@ -31,6 +31,8 @@ module ClaudeMemory
31
31
  expire_disputed_facts if within_budget?
32
32
  prune_orphaned_provenance if within_budget?
33
33
  prune_old_content if within_budget?
34
+ backfill_vec_index if within_budget?
35
+ cleanup_vec_expired if within_budget?
34
36
  checkpoint_wal if within_budget?
35
37
 
36
38
  @stats[:elapsed_seconds] = Time.now - @start_time
@@ -83,6 +85,34 @@ module ClaudeMemory
83
85
  .delete
84
86
  end
85
87
 
88
+ def with_vec_index
89
+ vec_index = @store.vector_index
90
+ return unless vec_index.available?
91
+ yield vec_index
92
+ end
93
+
94
+ def backfill_vec_index
95
+ with_vec_index do |vec_index|
96
+ @stats[:vec_backfilled] = vec_index.backfill_batch!(limit: 100)
97
+ end
98
+ end
99
+
100
+ def cleanup_vec_expired
101
+ with_vec_index do |vec_index|
102
+ # Remove vec0 entries for superseded/expired facts
103
+ # (remove_embedding manages vec_indexed_at)
104
+ stale_ids = @store.facts
105
+ .where(status: %w[superseded expired])
106
+ .where(Sequel.~(vec_indexed_at: nil))
107
+ .select(:id)
108
+ .limit(100)
109
+ .map { |r| r[:id] }
110
+
111
+ stale_ids.each { |fact_id| vec_index.remove_embedding(fact_id) }
112
+ @stats[:vec_cleaned] = stale_ids.size
113
+ end
114
+ end
115
+
86
116
  def checkpoint_wal
87
117
  @store.checkpoint_wal
88
118
  @stats[:wal_checkpointed] = true
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeMemory
4
- VERSION = "0.5.1"
4
+ VERSION = "0.6.0"
5
5
  end
data/lib/claude_memory.rb CHANGED
@@ -31,6 +31,7 @@ require_relative "claude_memory/commands/checks/snapshot_check"
31
31
  require_relative "claude_memory/commands/checks/claude_md_check"
32
32
  require_relative "claude_memory/commands/checks/hooks_check"
33
33
  require_relative "claude_memory/commands/checks/reporter"
34
+ require_relative "claude_memory/commands/checks/vec_check"
34
35
  require_relative "claude_memory/commands/help_command"
35
36
  require_relative "claude_memory/commands/version_command"
36
37
  require_relative "claude_memory/commands/doctor_command"
@@ -57,6 +58,8 @@ require_relative "claude_memory/commands/serve_mcp_command"
57
58
  require_relative "claude_memory/commands/hook_command"
58
59
  require_relative "claude_memory/commands/index_command"
59
60
  require_relative "claude_memory/commands/recover_command"
61
+ require_relative "claude_memory/commands/compact_command"
62
+ require_relative "claude_memory/commands/export_command"
60
63
  require_relative "claude_memory/commands/registry"
61
64
  require_relative "claude_memory/cli"
62
65
  require_relative "claude_memory/configuration"
@@ -69,15 +72,19 @@ require_relative "claude_memory/domain/provenance"
69
72
  require_relative "claude_memory/domain/conflict"
70
73
  require_relative "claude_memory/embeddings/generator"
71
74
  require_relative "claude_memory/embeddings/similarity"
75
+ require_relative "claude_memory/hook/context_injector"
72
76
  require_relative "claude_memory/hook/exit_codes"
73
77
  require_relative "claude_memory/hook/handler"
78
+ require_relative "claude_memory/hook/error_classifier"
74
79
  require_relative "claude_memory/index/query_options"
75
80
  require_relative "claude_memory/index/index_query_logic"
76
81
  require_relative "claude_memory/index/index_query"
77
82
  require_relative "claude_memory/index/lexical_fts"
83
+ require_relative "claude_memory/index/vector_index"
78
84
  require_relative "claude_memory/ingest/privacy_tag"
79
85
  require_relative "claude_memory/ingest/content_sanitizer"
80
86
  require_relative "claude_memory/ingest/metadata_extractor"
87
+ require_relative "claude_memory/ingest/observation_compressor"
81
88
  require_relative "claude_memory/ingest/tool_extractor"
82
89
  require_relative "claude_memory/ingest/tool_filter"
83
90
  require_relative "claude_memory/ingest/ingester"
@@ -87,6 +94,7 @@ require_relative "claude_memory/infrastructure/file_system"
87
94
  require_relative "claude_memory/infrastructure/in_memory_file_system"
88
95
  require_relative "claude_memory/infrastructure/operation_tracker"
89
96
  require_relative "claude_memory/infrastructure/schema_validator"
97
+ require_relative "claude_memory/mcp/instructions_builder"
90
98
  require_relative "claude_memory/mcp/query_guide"
91
99
  require_relative "claude_memory/mcp/text_summary"
92
100
  require_relative "claude_memory/mcp/tool_helpers"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bash
2
+ # Generic hook delegate for claude-memory.
3
+ # Used by the Claude Code plugin to run hook subcommands.
4
+ # Usage: hook-runner.sh <subcommand> [args...]
5
+ # Exit 0 on missing binary to avoid blocking Claude Code.
6
+
7
+ set -uo pipefail
8
+
9
+ if command -v claude-memory > /dev/null 2>&1; then
10
+ exec claude-memory hook "$@"
11
+ else
12
+ echo "claude-memory gem not found. Install with: gem install claude_memory" >&2
13
+ exit 0
14
+ fi
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bash
2
+ # Wrapper script for claude-memory MCP server.
3
+ # Used by the Claude Code plugin to launch the MCP server.
4
+ # Falls back to a JSON-RPC error if the gem is not installed.
5
+
6
+ set -euo pipefail
7
+
8
+ if command -v claude-memory > /dev/null 2>&1; then
9
+ exec claude-memory serve-mcp
10
+ else
11
+ # Return a JSON-RPC error so Claude Code surfaces it to the user
12
+ echo '{"jsonrpc":"2.0","id":1,"error":{"code":-32603,"message":"claude-memory gem not found. Install with: gem install claude_memory"}}'
13
+ exit 1
14
+ fi
@@ -38,12 +38,18 @@ When invoked, it will:
38
38
  ### Step 1: Check Installation Status
39
39
 
40
40
  ```bash
41
+ gem list claude_memory
41
42
  claude-memory --version
42
43
  claude-memory doctor
43
44
  ```
44
45
 
45
46
  Analyze the output to determine current state.
46
47
 
48
+ - If `gem list` shows no `claude_memory` entry, the gem is not installed.
49
+ Guide the user: `gem install claude_memory`
50
+ - If running in plugin mode (`CLAUDE_PLUGIN_ROOT` is set), hooks and MCP
51
+ are managed by the plugin — only databases and memory instructions need setup.
52
+
47
53
  ### Step 2: Determine Action Required
48
54
 
49
55
  - **Not installed**: Databases don't exist
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: claude_memory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Valentino Stoll
@@ -37,6 +37,20 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: '2.14'
40
+ - !ruby/object:Gem::Dependency
41
+ name: sqlite-vec
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.1'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.1'
40
54
  description: Turn-key Ruby gem providing Claude Code with instant, high-quality, long-term,
41
55
  self-managed memory using Claude Code Hooks + MCP + Output Style.
42
56
  email:
@@ -92,6 +106,8 @@ files:
92
106
  - db/migrations/008_add_provenance_line_range.rb
93
107
  - db/migrations/009_add_docid.rb
94
108
  - db/migrations/010_add_llm_cache.rb
109
+ - db/migrations/011_add_tool_call_summaries.rb
110
+ - db/migrations/012_add_vec_indexing_support.rb
95
111
  - docs/EXAMPLES.md
96
112
  - docs/GETTING_STARTED.md
97
113
  - docs/RELEASE_NOTES_v0.2.0.md
@@ -107,8 +123,11 @@ files:
107
123
  - docs/expert_review.md
108
124
  - docs/improvements.md
109
125
  - docs/influence/.gitkeep
126
+ - docs/influence/claude-mem.md
110
127
  - docs/influence/claude-supermemory.md
128
+ - docs/influence/episodic-memory.md
111
129
  - docs/influence/grepai.md
130
+ - docs/influence/kbs.md
112
131
  - docs/influence/qmd.md
113
132
  - docs/organizational_memory_playbook.md
114
133
  - docs/plans/feature_adoption_plan.md
@@ -130,10 +149,13 @@ files:
130
149
  - lib/claude_memory/commands/checks/hooks_check.rb
131
150
  - lib/claude_memory/commands/checks/reporter.rb
132
151
  - lib/claude_memory/commands/checks/snapshot_check.rb
152
+ - lib/claude_memory/commands/checks/vec_check.rb
153
+ - lib/claude_memory/commands/compact_command.rb
133
154
  - lib/claude_memory/commands/conflicts_command.rb
134
155
  - lib/claude_memory/commands/db_init_command.rb
135
156
  - lib/claude_memory/commands/doctor_command.rb
136
157
  - lib/claude_memory/commands/explain_command.rb
158
+ - lib/claude_memory/commands/export_command.rb
137
159
  - lib/claude_memory/commands/help_command.rb
138
160
  - lib/claude_memory/commands/hook_command.rb
139
161
  - lib/claude_memory/commands/index_command.rb
@@ -189,12 +211,15 @@ files:
189
211
  - lib/claude_memory/embeddings/fastembed_adapter.rb
190
212
  - lib/claude_memory/embeddings/generator.rb
191
213
  - lib/claude_memory/embeddings/similarity.rb
214
+ - lib/claude_memory/hook/context_injector.rb
215
+ - lib/claude_memory/hook/error_classifier.rb
192
216
  - lib/claude_memory/hook/exit_codes.rb
193
217
  - lib/claude_memory/hook/handler.rb
194
218
  - lib/claude_memory/index/index_query.rb
195
219
  - lib/claude_memory/index/index_query_logic.rb
196
220
  - lib/claude_memory/index/lexical_fts.rb
197
221
  - lib/claude_memory/index/query_options.rb
222
+ - lib/claude_memory/index/vector_index.rb
198
223
  - lib/claude_memory/infrastructure/file_system.rb
199
224
  - lib/claude_memory/infrastructure/in_memory_file_system.rb
200
225
  - lib/claude_memory/infrastructure/operation_tracker.rb
@@ -202,11 +227,13 @@ files:
202
227
  - lib/claude_memory/ingest/content_sanitizer.rb
203
228
  - lib/claude_memory/ingest/ingester.rb
204
229
  - lib/claude_memory/ingest/metadata_extractor.rb
230
+ - lib/claude_memory/ingest/observation_compressor.rb
205
231
  - lib/claude_memory/ingest/privacy_tag.rb
206
232
  - lib/claude_memory/ingest/tool_extractor.rb
207
233
  - lib/claude_memory/ingest/tool_filter.rb
208
234
  - lib/claude_memory/ingest/transcript_reader.rb
209
235
  - lib/claude_memory/logging/logger.rb
236
+ - lib/claude_memory/mcp/instructions_builder.rb
210
237
  - lib/claude_memory/mcp/query_guide.rb
211
238
  - lib/claude_memory/mcp/response_formatter.rb
212
239
  - lib/claude_memory/mcp/server.rb
@@ -229,6 +256,8 @@ files:
229
256
  - lib/claude_memory/templates/output-styles/memory-aware.md
230
257
  - lib/claude_memory/version.rb
231
258
  - output-styles/memory-aware.md
259
+ - scripts/hook-runner.sh
260
+ - scripts/serve-mcp.sh
232
261
  - sig/claude_memory.rbs
233
262
  - skills/analyze/SKILL.md
234
263
  - skills/debug-memory/SKILL.md
@@ -256,7 +285,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
256
285
  - !ruby/object:Gem::Version
257
286
  version: '0'
258
287
  requirements: []
259
- rubygems_version: 4.0.3
288
+ rubygems_version: 4.0.6
260
289
  specification_version: 4
261
290
  summary: Long-term, self-managed memory for Claude Code
262
291
  test_files: []