claude_memory 0.5.0 → 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.
- checksums.yaml +4 -4
- data/.claude/CLAUDE.md +1 -1
- data/.claude/rules/claude_memory.generated.md +1 -1
- data/.claude/settings.json +11 -6
- data/.claude/settings.local.json +9 -1
- data/.claude-plugin/marketplace.json +5 -2
- data/.claude-plugin/plugin.json +16 -3
- data/.mcp.json +9 -0
- data/CHANGELOG.md +69 -0
- data/CLAUDE.md +27 -13
- data/README.md +6 -2
- data/Rakefile +22 -0
- data/db/migrations/011_add_tool_call_summaries.rb +18 -0
- data/db/migrations/012_add_vec_indexing_support.rb +19 -0
- data/docs/improvements.md +86 -66
- data/docs/influence/claude-mem.md +253 -0
- data/docs/influence/claude-supermemory.md +158 -430
- data/docs/influence/episodic-memory.md +217 -0
- data/docs/influence/grepai.md +163 -839
- data/docs/influence/kbs.md +437 -0
- data/docs/influence/qmd.md +139 -481
- data/hooks/hooks.json +19 -15
- data/lefthook.yml +4 -0
- data/lib/claude_memory/commands/checks/vec_check.rb +73 -0
- data/lib/claude_memory/commands/compact_command.rb +94 -0
- data/lib/claude_memory/commands/doctor_command.rb +1 -0
- data/lib/claude_memory/commands/export_command.rb +108 -0
- data/lib/claude_memory/commands/help_command.rb +2 -0
- data/lib/claude_memory/commands/hook_command.rb +110 -9
- data/lib/claude_memory/commands/index_command.rb +63 -8
- data/lib/claude_memory/commands/initializers/global_initializer.rb +26 -7
- data/lib/claude_memory/commands/initializers/project_initializer.rb +35 -12
- data/lib/claude_memory/commands/registry.rb +3 -1
- data/lib/claude_memory/hook/context_injector.rb +75 -0
- data/lib/claude_memory/hook/error_classifier.rb +67 -0
- data/lib/claude_memory/hook/handler.rb +21 -1
- data/lib/claude_memory/index/vector_index.rb +171 -0
- data/lib/claude_memory/infrastructure/schema_validator.rb +5 -1
- data/lib/claude_memory/ingest/ingester.rb +26 -1
- data/lib/claude_memory/ingest/observation_compressor.rb +177 -0
- data/lib/claude_memory/mcp/instructions_builder.rb +76 -0
- data/lib/claude_memory/mcp/server.rb +3 -1
- data/lib/claude_memory/mcp/tool_definitions.rb +15 -7
- data/lib/claude_memory/mcp/tools.rb +125 -2
- data/lib/claude_memory/publish.rb +28 -27
- data/lib/claude_memory/recall/dual_query_template.rb +1 -12
- data/lib/claude_memory/recall.rb +71 -17
- data/lib/claude_memory/store/sqlite_store.rb +95 -29
- data/lib/claude_memory/sweep/sweeper.rb +30 -0
- data/lib/claude_memory/version.rb +1 -1
- data/lib/claude_memory.rb +8 -0
- data/scripts/hook-runner.sh +14 -0
- data/scripts/serve-mcp.sh +14 -0
- data/skills/setup-memory/SKILL.md +6 -0
- metadata +32 -2
data/lib/claude_memory/recall.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
588
|
+
ranked_results = fts.search_with_ranks(query_text, limit: limit * 2)
|
|
562
589
|
|
|
563
|
-
return [] if
|
|
590
|
+
return [] if ranked_results.empty?
|
|
564
591
|
|
|
565
|
-
|
|
566
|
-
|
|
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
|
-
.
|
|
570
|
-
.map { |row| row[:fact_id] }
|
|
597
|
+
.select(:fact_id, :content_item_id)
|
|
598
|
+
.all
|
|
571
599
|
|
|
572
|
-
|
|
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
|
-
|
|
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
|
-
|
|
579
|
-
|
|
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
|
-
|
|
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 =
|
|
13
|
+
SCHEMA_VERSION = 12
|
|
14
14
|
|
|
15
15
|
attr_reader :db
|
|
16
16
|
|
|
@@ -21,17 +21,65 @@ module ClaudeMemory
|
|
|
21
21
|
ensure_schema!
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# Retry configuration for database operations
|
|
25
|
+
# SQLite's busy_timeout doesn't reliably detect lock release, so we use
|
|
26
|
+
# shorter timeouts with application-level retry for better responsiveness
|
|
27
|
+
MAX_RETRIES = 5
|
|
28
|
+
RETRY_BASE_DELAY = 0.1 # seconds, with exponential backoff
|
|
29
|
+
|
|
30
|
+
# Execute a block with retry logic for busy/locked errors
|
|
31
|
+
# This handles concurrent access from multiple hook processes
|
|
32
|
+
def with_retry(operation_name = "database operation")
|
|
33
|
+
retries = 0
|
|
34
|
+
begin
|
|
35
|
+
yield
|
|
36
|
+
rescue Sequel::DatabaseError, Extralite::Error, Extralite::BusyError => e
|
|
37
|
+
if retryable_error?(e) && retries < MAX_RETRIES
|
|
38
|
+
retries += 1
|
|
39
|
+
delay = RETRY_BASE_DELAY * (2**retries) # Exponential backoff
|
|
40
|
+
sleep(delay)
|
|
41
|
+
retry
|
|
42
|
+
end
|
|
43
|
+
raise
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Execute a transaction with retry logic for concurrent access
|
|
48
|
+
# Use this instead of @db.transaction when concurrent writes are expected
|
|
49
|
+
def transaction_with_retry(&block)
|
|
50
|
+
with_retry("transaction") do
|
|
51
|
+
@db.transaction(&block)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
24
55
|
private
|
|
25
56
|
|
|
57
|
+
def retryable_error?(error)
|
|
58
|
+
message = error.message.downcase
|
|
59
|
+
message.include?("busy") || message.include?("locked")
|
|
60
|
+
end
|
|
61
|
+
|
|
26
62
|
def connect_database(db_path)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
63
|
+
retries = 0
|
|
64
|
+
begin
|
|
65
|
+
Sequel.connect(
|
|
66
|
+
"extralite:#{db_path}",
|
|
67
|
+
# Use shorter busy_timeout since we handle retry at app level
|
|
68
|
+
# This allows faster detection of lock release between retries
|
|
69
|
+
connect_sqls: [
|
|
70
|
+
"PRAGMA busy_timeout = 1000",
|
|
71
|
+
"PRAGMA journal_mode = WAL",
|
|
72
|
+
"PRAGMA synchronous = NORMAL"
|
|
73
|
+
]
|
|
74
|
+
)
|
|
75
|
+
rescue Sequel::DatabaseConnectionError, Extralite::Error => e
|
|
76
|
+
retries += 1
|
|
77
|
+
if retries <= MAX_RETRIES && retryable_error?(e)
|
|
78
|
+
sleep(RETRY_BASE_DELAY * (2**retries))
|
|
79
|
+
retry
|
|
80
|
+
end
|
|
81
|
+
raise
|
|
82
|
+
end
|
|
35
83
|
end
|
|
36
84
|
|
|
37
85
|
public
|
|
@@ -40,6 +88,10 @@ module ClaudeMemory
|
|
|
40
88
|
@db.disconnect
|
|
41
89
|
end
|
|
42
90
|
|
|
91
|
+
def vector_index
|
|
92
|
+
@vector_index ||= Index::VectorIndex.new(self)
|
|
93
|
+
end
|
|
94
|
+
|
|
43
95
|
# Checkpoint the WAL file to prevent unlimited growth
|
|
44
96
|
# This truncates the WAL after checkpointing
|
|
45
97
|
# Should be called periodically during maintenance/sweep operations
|
|
@@ -106,27 +158,29 @@ module ClaudeMemory
|
|
|
106
158
|
def upsert_content_item(source:, text_hash:, byte_len:, session_id: nil, transcript_path: nil,
|
|
107
159
|
project_path: nil, occurred_at: nil, raw_text: nil, metadata: nil,
|
|
108
160
|
git_branch: nil, cwd: nil, claude_version: nil, thinking_level: nil, source_mtime: nil)
|
|
109
|
-
|
|
110
|
-
|
|
161
|
+
with_retry("upsert_content_item") do
|
|
162
|
+
existing = content_items.where(text_hash: text_hash, session_id: session_id).get(:id)
|
|
163
|
+
return existing if existing
|
|
111
164
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
165
|
+
now = Time.now.utc.iso8601
|
|
166
|
+
content_items.insert(
|
|
167
|
+
source: source,
|
|
168
|
+
session_id: session_id,
|
|
169
|
+
transcript_path: transcript_path,
|
|
170
|
+
project_path: project_path,
|
|
171
|
+
occurred_at: occurred_at || now,
|
|
172
|
+
ingested_at: now,
|
|
173
|
+
text_hash: text_hash,
|
|
174
|
+
byte_len: byte_len,
|
|
175
|
+
raw_text: raw_text,
|
|
176
|
+
metadata_json: metadata&.to_json,
|
|
177
|
+
git_branch: git_branch,
|
|
178
|
+
cwd: cwd,
|
|
179
|
+
claude_version: claude_version,
|
|
180
|
+
thinking_level: thinking_level,
|
|
181
|
+
source_mtime: source_mtime
|
|
182
|
+
)
|
|
183
|
+
end
|
|
130
184
|
end
|
|
131
185
|
|
|
132
186
|
def content_item_by_transcript_and_mtime(transcript_path, mtime_iso8601)
|
|
@@ -142,6 +196,7 @@ module ClaudeMemory
|
|
|
142
196
|
tool_name: tc[:tool_name],
|
|
143
197
|
tool_input: tc[:tool_input],
|
|
144
198
|
tool_result: tc[:tool_result],
|
|
199
|
+
compressed_summary: tc[:compressed_summary],
|
|
145
200
|
is_error: tc[:is_error] || false,
|
|
146
201
|
timestamp: tc[:timestamp]
|
|
147
202
|
)
|
|
@@ -412,6 +467,12 @@ module ClaudeMemory
|
|
|
412
467
|
# Handle backward compatibility: databases created with old migration system
|
|
413
468
|
sync_legacy_schema_version!
|
|
414
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
|
+
|
|
415
476
|
# Run Sequel migrations to bring database to target version
|
|
416
477
|
Sequel::Migrator.run(@db, migrations_path, target: SCHEMA_VERSION)
|
|
417
478
|
|
|
@@ -451,6 +512,11 @@ module ClaudeMemory
|
|
|
451
512
|
end
|
|
452
513
|
end
|
|
453
514
|
|
|
515
|
+
def current_schema_version
|
|
516
|
+
return nil unless @db.table_exists?(:schema_info)
|
|
517
|
+
@db[:schema_info].get(:version)
|
|
518
|
+
end
|
|
519
|
+
|
|
454
520
|
def set_meta(key, value)
|
|
455
521
|
@db[:meta].insert_conflict(target: :key, update: {value: value}).insert(key: key, value: value)
|
|
456
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
|
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.
|
|
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:
|
|
@@ -70,6 +84,7 @@ files:
|
|
|
70
84
|
- ".claude/skills/study-repo/analysis-template.md"
|
|
71
85
|
- ".claude/skills/study-repo/focus-examples.md"
|
|
72
86
|
- ".lefthook/map_specs.rb"
|
|
87
|
+
- ".mcp.json"
|
|
73
88
|
- ".ruby-version"
|
|
74
89
|
- CHANGELOG.md
|
|
75
90
|
- CLAUDE.md
|
|
@@ -91,6 +106,8 @@ files:
|
|
|
91
106
|
- db/migrations/008_add_provenance_line_range.rb
|
|
92
107
|
- db/migrations/009_add_docid.rb
|
|
93
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
|
|
94
111
|
- docs/EXAMPLES.md
|
|
95
112
|
- docs/GETTING_STARTED.md
|
|
96
113
|
- docs/RELEASE_NOTES_v0.2.0.md
|
|
@@ -106,8 +123,11 @@ files:
|
|
|
106
123
|
- docs/expert_review.md
|
|
107
124
|
- docs/improvements.md
|
|
108
125
|
- docs/influence/.gitkeep
|
|
126
|
+
- docs/influence/claude-mem.md
|
|
109
127
|
- docs/influence/claude-supermemory.md
|
|
128
|
+
- docs/influence/episodic-memory.md
|
|
110
129
|
- docs/influence/grepai.md
|
|
130
|
+
- docs/influence/kbs.md
|
|
111
131
|
- docs/influence/qmd.md
|
|
112
132
|
- docs/organizational_memory_playbook.md
|
|
113
133
|
- docs/plans/feature_adoption_plan.md
|
|
@@ -129,10 +149,13 @@ files:
|
|
|
129
149
|
- lib/claude_memory/commands/checks/hooks_check.rb
|
|
130
150
|
- lib/claude_memory/commands/checks/reporter.rb
|
|
131
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
|
|
132
154
|
- lib/claude_memory/commands/conflicts_command.rb
|
|
133
155
|
- lib/claude_memory/commands/db_init_command.rb
|
|
134
156
|
- lib/claude_memory/commands/doctor_command.rb
|
|
135
157
|
- lib/claude_memory/commands/explain_command.rb
|
|
158
|
+
- lib/claude_memory/commands/export_command.rb
|
|
136
159
|
- lib/claude_memory/commands/help_command.rb
|
|
137
160
|
- lib/claude_memory/commands/hook_command.rb
|
|
138
161
|
- lib/claude_memory/commands/index_command.rb
|
|
@@ -188,12 +211,15 @@ files:
|
|
|
188
211
|
- lib/claude_memory/embeddings/fastembed_adapter.rb
|
|
189
212
|
- lib/claude_memory/embeddings/generator.rb
|
|
190
213
|
- lib/claude_memory/embeddings/similarity.rb
|
|
214
|
+
- lib/claude_memory/hook/context_injector.rb
|
|
215
|
+
- lib/claude_memory/hook/error_classifier.rb
|
|
191
216
|
- lib/claude_memory/hook/exit_codes.rb
|
|
192
217
|
- lib/claude_memory/hook/handler.rb
|
|
193
218
|
- lib/claude_memory/index/index_query.rb
|
|
194
219
|
- lib/claude_memory/index/index_query_logic.rb
|
|
195
220
|
- lib/claude_memory/index/lexical_fts.rb
|
|
196
221
|
- lib/claude_memory/index/query_options.rb
|
|
222
|
+
- lib/claude_memory/index/vector_index.rb
|
|
197
223
|
- lib/claude_memory/infrastructure/file_system.rb
|
|
198
224
|
- lib/claude_memory/infrastructure/in_memory_file_system.rb
|
|
199
225
|
- lib/claude_memory/infrastructure/operation_tracker.rb
|
|
@@ -201,11 +227,13 @@ files:
|
|
|
201
227
|
- lib/claude_memory/ingest/content_sanitizer.rb
|
|
202
228
|
- lib/claude_memory/ingest/ingester.rb
|
|
203
229
|
- lib/claude_memory/ingest/metadata_extractor.rb
|
|
230
|
+
- lib/claude_memory/ingest/observation_compressor.rb
|
|
204
231
|
- lib/claude_memory/ingest/privacy_tag.rb
|
|
205
232
|
- lib/claude_memory/ingest/tool_extractor.rb
|
|
206
233
|
- lib/claude_memory/ingest/tool_filter.rb
|
|
207
234
|
- lib/claude_memory/ingest/transcript_reader.rb
|
|
208
235
|
- lib/claude_memory/logging/logger.rb
|
|
236
|
+
- lib/claude_memory/mcp/instructions_builder.rb
|
|
209
237
|
- lib/claude_memory/mcp/query_guide.rb
|
|
210
238
|
- lib/claude_memory/mcp/response_formatter.rb
|
|
211
239
|
- lib/claude_memory/mcp/server.rb
|
|
@@ -228,6 +256,8 @@ files:
|
|
|
228
256
|
- lib/claude_memory/templates/output-styles/memory-aware.md
|
|
229
257
|
- lib/claude_memory/version.rb
|
|
230
258
|
- output-styles/memory-aware.md
|
|
259
|
+
- scripts/hook-runner.sh
|
|
260
|
+
- scripts/serve-mcp.sh
|
|
231
261
|
- sig/claude_memory.rbs
|
|
232
262
|
- skills/analyze/SKILL.md
|
|
233
263
|
- skills/debug-memory/SKILL.md
|
|
@@ -255,7 +285,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
255
285
|
- !ruby/object:Gem::Version
|
|
256
286
|
version: '0'
|
|
257
287
|
requirements: []
|
|
258
|
-
rubygems_version: 4.0.
|
|
288
|
+
rubygems_version: 4.0.6
|
|
259
289
|
specification_version: 4
|
|
260
290
|
summary: Long-term, self-managed memory for Claude Code
|
|
261
291
|
test_files: []
|