claude_memory 0.1.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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/CLAUDE.md +3 -0
  3. data/.claude/memory.sqlite3 +0 -0
  4. data/.claude/output-styles/memory-aware.md +21 -0
  5. data/.claude/rules/claude_memory.generated.md +21 -0
  6. data/.claude/settings.json +62 -0
  7. data/.claude/settings.local.json +21 -0
  8. data/.claude-plugin/marketplace.json +13 -0
  9. data/.claude-plugin/plugin.json +10 -0
  10. data/.mcp.json +11 -0
  11. data/CHANGELOG.md +36 -0
  12. data/CLAUDE.md +224 -0
  13. data/CODE_OF_CONDUCT.md +10 -0
  14. data/LICENSE.txt +21 -0
  15. data/README.md +212 -0
  16. data/Rakefile +10 -0
  17. data/commands/analyze.md +29 -0
  18. data/commands/recall.md +17 -0
  19. data/commands/remember.md +26 -0
  20. data/docs/demo.md +126 -0
  21. data/docs/organizational_memory_playbook.md +291 -0
  22. data/docs/plan.md +411 -0
  23. data/docs/plugin.md +202 -0
  24. data/docs/updated_plan.md +453 -0
  25. data/exe/claude-memory +8 -0
  26. data/hooks/hooks.json +59 -0
  27. data/lib/claude_memory/cli.rb +869 -0
  28. data/lib/claude_memory/distill/distiller.rb +11 -0
  29. data/lib/claude_memory/distill/extraction.rb +29 -0
  30. data/lib/claude_memory/distill/json_schema.md +78 -0
  31. data/lib/claude_memory/distill/null_distiller.rb +123 -0
  32. data/lib/claude_memory/hook/handler.rb +49 -0
  33. data/lib/claude_memory/index/lexical_fts.rb +58 -0
  34. data/lib/claude_memory/ingest/ingester.rb +46 -0
  35. data/lib/claude_memory/ingest/transcript_reader.rb +21 -0
  36. data/lib/claude_memory/mcp/server.rb +127 -0
  37. data/lib/claude_memory/mcp/tools.rb +409 -0
  38. data/lib/claude_memory/publish.rb +201 -0
  39. data/lib/claude_memory/recall.rb +360 -0
  40. data/lib/claude_memory/resolve/predicate_policy.rb +30 -0
  41. data/lib/claude_memory/resolve/resolver.rb +152 -0
  42. data/lib/claude_memory/store/sqlite_store.rb +340 -0
  43. data/lib/claude_memory/store/store_manager.rb +139 -0
  44. data/lib/claude_memory/sweep/sweeper.rb +80 -0
  45. data/lib/claude_memory/templates/hooks.example.json +74 -0
  46. data/lib/claude_memory/templates/output-styles/memory-aware.md +21 -0
  47. data/lib/claude_memory/version.rb +5 -0
  48. data/lib/claude_memory.rb +36 -0
  49. data/sig/claude_memory.rbs +4 -0
  50. data/skills/analyze/SKILL.md +126 -0
  51. data/skills/memory/SKILL.md +82 -0
  52. metadata +123 -0
@@ -0,0 +1,340 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sequel"
4
+ require "json"
5
+
6
+ module ClaudeMemory
7
+ module Store
8
+ class SQLiteStore
9
+ SCHEMA_VERSION = 2
10
+
11
+ attr_reader :db
12
+
13
+ def initialize(db_path)
14
+ @db_path = db_path
15
+ @db = Sequel.sqlite(db_path)
16
+ ensure_schema!
17
+ end
18
+
19
+ def close
20
+ @db.disconnect
21
+ end
22
+
23
+ def schema_version
24
+ @db[:meta].where(key: "schema_version").get(:value)&.to_i
25
+ end
26
+
27
+ def content_items
28
+ @db[:content_items]
29
+ end
30
+
31
+ def delta_cursors
32
+ @db[:delta_cursors]
33
+ end
34
+
35
+ def entities
36
+ @db[:entities]
37
+ end
38
+
39
+ def entity_aliases
40
+ @db[:entity_aliases]
41
+ end
42
+
43
+ def facts
44
+ @db[:facts]
45
+ end
46
+
47
+ def provenance
48
+ @db[:provenance]
49
+ end
50
+
51
+ def fact_links
52
+ @db[:fact_links]
53
+ end
54
+
55
+ def conflicts
56
+ @db[:conflicts]
57
+ end
58
+
59
+ private
60
+
61
+ def ensure_schema!
62
+ create_tables!
63
+ run_migrations!
64
+ set_meta("schema_version", SCHEMA_VERSION.to_s)
65
+ set_meta("created_at", Time.now.utc.iso8601) unless get_meta("created_at")
66
+ end
67
+
68
+ def run_migrations!
69
+ current = get_meta("schema_version")&.to_i || 0
70
+
71
+ migrate_to_v2! if current < 2
72
+ end
73
+
74
+ def migrate_to_v2!
75
+ columns = @db.schema(:content_items).map(&:first)
76
+ unless columns.include?(:project_path)
77
+ @db.alter_table(:content_items) do
78
+ add_column :project_path, String
79
+ end
80
+ end
81
+
82
+ columns = @db.schema(:facts).map(&:first)
83
+ unless columns.include?(:scope)
84
+ @db.alter_table(:facts) do
85
+ add_column :scope, String, default: "project"
86
+ add_column :project_path, String
87
+ add_index :scope, name: :idx_facts_scope
88
+ add_index :project_path, name: :idx_facts_project
89
+ end
90
+ end
91
+ end
92
+
93
+ def create_tables!
94
+ @db.create_table?(:meta) do
95
+ String :key, primary_key: true
96
+ String :value
97
+ end
98
+
99
+ @db.create_table?(:content_items) do
100
+ primary_key :id
101
+ String :source, null: false
102
+ String :session_id
103
+ String :transcript_path
104
+ String :project_path
105
+ String :occurred_at
106
+ String :ingested_at, null: false
107
+ String :text_hash, null: false
108
+ Integer :byte_len, null: false
109
+ String :raw_text, text: true
110
+ String :metadata_json, text: true
111
+ end
112
+
113
+ @db.create_table?(:delta_cursors) do
114
+ primary_key :id
115
+ String :session_id, null: false
116
+ String :transcript_path, null: false
117
+ Integer :last_byte_offset, null: false, default: 0
118
+ String :updated_at, null: false
119
+ unique [:session_id, :transcript_path]
120
+ end
121
+
122
+ @db.create_table?(:entities) do
123
+ primary_key :id
124
+ String :type, null: false
125
+ String :canonical_name, null: false
126
+ String :slug, null: false, unique: true
127
+ String :created_at, null: false
128
+ end
129
+
130
+ @db.create_table?(:entity_aliases) do
131
+ primary_key :id
132
+ foreign_key :entity_id, :entities, null: false
133
+ String :source
134
+ String :alias, null: false
135
+ Float :confidence, default: 1.0
136
+ end
137
+
138
+ @db.create_table?(:facts) do
139
+ primary_key :id
140
+ foreign_key :subject_entity_id, :entities
141
+ String :predicate, null: false
142
+ foreign_key :object_entity_id, :entities
143
+ String :object_literal
144
+ String :datatype
145
+ String :polarity, default: "positive"
146
+ String :valid_from
147
+ String :valid_to
148
+ String :status, default: "active"
149
+ Float :confidence, default: 1.0
150
+ String :created_from
151
+ String :created_at, null: false
152
+ String :scope, default: "project"
153
+ String :project_path
154
+ end
155
+
156
+ @db.create_table?(:provenance) do
157
+ primary_key :id
158
+ foreign_key :fact_id, :facts, null: false
159
+ foreign_key :content_item_id, :content_items
160
+ String :quote, text: true
161
+ foreign_key :attribution_entity_id, :entities
162
+ String :strength, default: "stated"
163
+ end
164
+
165
+ @db.create_table?(:fact_links) do
166
+ primary_key :id
167
+ foreign_key :from_fact_id, :facts, null: false
168
+ foreign_key :to_fact_id, :facts, null: false
169
+ String :link_type, null: false
170
+ end
171
+
172
+ @db.create_table?(:conflicts) do
173
+ primary_key :id
174
+ foreign_key :fact_a_id, :facts, null: false
175
+ foreign_key :fact_b_id, :facts, null: false
176
+ String :status, default: "open"
177
+ String :detected_at, null: false
178
+ String :notes, text: true
179
+ end
180
+
181
+ create_index_if_not_exists(:facts, :predicate, :idx_facts_predicate)
182
+ create_index_if_not_exists(:facts, :subject_entity_id, :idx_facts_subject)
183
+ create_index_if_not_exists(:facts, :status, :idx_facts_status)
184
+ create_index_if_not_exists(:facts, :scope, :idx_facts_scope)
185
+ create_index_if_not_exists(:facts, :project_path, :idx_facts_project)
186
+ create_index_if_not_exists(:provenance, :fact_id, :idx_provenance_fact)
187
+ create_index_if_not_exists(:entity_aliases, :entity_id, :idx_entity_aliases_entity)
188
+ create_index_if_not_exists(:content_items, :session_id, :idx_content_items_session)
189
+ create_index_if_not_exists(:content_items, :project_path, :idx_content_items_project)
190
+ end
191
+
192
+ def create_index_if_not_exists(table, column, name)
193
+ @db.run("CREATE INDEX IF NOT EXISTS #{name} ON #{table}(#{column})")
194
+ end
195
+
196
+ def set_meta(key, value)
197
+ @db[:meta].insert_conflict(target: :key, update: {value: value}).insert(key: key, value: value)
198
+ end
199
+
200
+ def get_meta(key)
201
+ @db[:meta].where(key: key).get(:value)
202
+ end
203
+
204
+ public
205
+
206
+ def upsert_content_item(source:, text_hash:, byte_len:, session_id: nil, transcript_path: nil,
207
+ project_path: nil, occurred_at: nil, raw_text: nil, metadata: nil)
208
+ existing = content_items.where(text_hash: text_hash, session_id: session_id).get(:id)
209
+ return existing if existing
210
+
211
+ now = Time.now.utc.iso8601
212
+ content_items.insert(
213
+ source: source,
214
+ session_id: session_id,
215
+ transcript_path: transcript_path,
216
+ project_path: project_path,
217
+ occurred_at: occurred_at || now,
218
+ ingested_at: now,
219
+ text_hash: text_hash,
220
+ byte_len: byte_len,
221
+ raw_text: raw_text,
222
+ metadata_json: metadata&.to_json
223
+ )
224
+ end
225
+
226
+ def get_delta_cursor(session_id, transcript_path)
227
+ delta_cursors.where(session_id: session_id, transcript_path: transcript_path).get(:last_byte_offset)
228
+ end
229
+
230
+ def update_delta_cursor(session_id, transcript_path, offset)
231
+ now = Time.now.utc.iso8601
232
+ delta_cursors
233
+ .insert_conflict(
234
+ target: [:session_id, :transcript_path],
235
+ update: {last_byte_offset: offset, updated_at: now}
236
+ )
237
+ .insert(
238
+ session_id: session_id,
239
+ transcript_path: transcript_path,
240
+ last_byte_offset: offset,
241
+ updated_at: now
242
+ )
243
+ end
244
+
245
+ def find_or_create_entity(type:, name:)
246
+ slug = slugify(type, name)
247
+ existing = entities.where(slug: slug).get(:id)
248
+ return existing if existing
249
+
250
+ now = Time.now.utc.iso8601
251
+ entities.insert(type: type, canonical_name: name, slug: slug, created_at: now)
252
+ end
253
+
254
+ def insert_fact(subject_entity_id:, predicate:, object_entity_id: nil, object_literal: nil,
255
+ datatype: nil, polarity: "positive", valid_from: nil, status: "active",
256
+ confidence: 1.0, created_from: nil, scope: "project", project_path: nil)
257
+ now = Time.now.utc.iso8601
258
+ facts.insert(
259
+ subject_entity_id: subject_entity_id,
260
+ predicate: predicate,
261
+ object_entity_id: object_entity_id,
262
+ object_literal: object_literal,
263
+ datatype: datatype,
264
+ polarity: polarity,
265
+ valid_from: valid_from || now,
266
+ status: status,
267
+ confidence: confidence,
268
+ created_from: created_from,
269
+ created_at: now,
270
+ scope: scope,
271
+ project_path: project_path
272
+ )
273
+ end
274
+
275
+ def update_fact(fact_id, status: nil, valid_to: nil, scope: nil, project_path: nil)
276
+ updates = {}
277
+ updates[:status] = status if status
278
+ updates[:valid_to] = valid_to if valid_to
279
+
280
+ if scope
281
+ updates[:scope] = scope
282
+ updates[:project_path] = (scope == "global") ? nil : project_path
283
+ end
284
+
285
+ return false if updates.empty?
286
+
287
+ facts.where(id: fact_id).update(updates)
288
+ true
289
+ end
290
+
291
+ def facts_for_slot(subject_entity_id, predicate, status: "active")
292
+ facts
293
+ .where(subject_entity_id: subject_entity_id, predicate: predicate, status: status)
294
+ .select(:id, :subject_entity_id, :predicate, :object_entity_id, :object_literal,
295
+ :datatype, :polarity, :valid_from, :valid_to, :status, :confidence,
296
+ :created_from, :created_at)
297
+ .all
298
+ end
299
+
300
+ def insert_provenance(fact_id:, content_item_id: nil, quote: nil, attribution_entity_id: nil, strength: "stated")
301
+ provenance.insert(
302
+ fact_id: fact_id,
303
+ content_item_id: content_item_id,
304
+ quote: quote,
305
+ attribution_entity_id: attribution_entity_id,
306
+ strength: strength
307
+ )
308
+ end
309
+
310
+ def provenance_for_fact(fact_id)
311
+ provenance.where(fact_id: fact_id).all
312
+ end
313
+
314
+ def insert_conflict(fact_a_id:, fact_b_id:, status: "open", notes: nil)
315
+ now = Time.now.utc.iso8601
316
+ conflicts.insert(
317
+ fact_a_id: fact_a_id,
318
+ fact_b_id: fact_b_id,
319
+ status: status,
320
+ detected_at: now,
321
+ notes: notes
322
+ )
323
+ end
324
+
325
+ def open_conflicts
326
+ conflicts.where(status: "open").all
327
+ end
328
+
329
+ def insert_fact_link(from_fact_id:, to_fact_id:, link_type:)
330
+ fact_links.insert(from_fact_id: from_fact_id, to_fact_id: to_fact_id, link_type: link_type)
331
+ end
332
+
333
+ private
334
+
335
+ def slugify(type, name)
336
+ "#{type}:#{name.downcase.gsub(/[^a-z0-9]+/, "_").gsub(/^_|_$/, "")}"
337
+ end
338
+ end
339
+ end
340
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module ClaudeMemory
6
+ module Store
7
+ class StoreManager
8
+ attr_reader :global_store, :project_store, :project_path
9
+
10
+ def initialize(global_db_path: nil, project_db_path: nil, project_path: nil, env: ENV)
11
+ @project_path = project_path || env["CLAUDE_PROJECT_DIR"] || Dir.pwd
12
+ @global_db_path = global_db_path || self.class.default_global_db_path(env)
13
+ @project_db_path = project_db_path || self.class.default_project_db_path(@project_path)
14
+
15
+ @global_store = nil
16
+ @project_store = nil
17
+ end
18
+
19
+ def self.default_global_db_path(env = ENV)
20
+ home = env["HOME"] || File.expand_path("~")
21
+ File.join(home, ".claude", "memory.sqlite3")
22
+ end
23
+
24
+ def self.default_project_db_path(project_path = Dir.pwd)
25
+ File.join(project_path, ".claude", "memory.sqlite3")
26
+ end
27
+
28
+ def ensure_global!
29
+ return @global_store if @global_store
30
+
31
+ FileUtils.mkdir_p(File.dirname(@global_db_path))
32
+ @global_store = SQLiteStore.new(@global_db_path)
33
+ end
34
+
35
+ def ensure_project!
36
+ return @project_store if @project_store
37
+
38
+ FileUtils.mkdir_p(File.dirname(@project_db_path))
39
+ @project_store = SQLiteStore.new(@project_db_path)
40
+ end
41
+
42
+ def ensure_both!
43
+ ensure_global!
44
+ ensure_project!
45
+ end
46
+
47
+ attr_reader :global_db_path
48
+
49
+ attr_reader :project_db_path
50
+
51
+ def global_exists?
52
+ File.exist?(@global_db_path)
53
+ end
54
+
55
+ def project_exists?
56
+ File.exist?(@project_db_path)
57
+ end
58
+
59
+ def close
60
+ @global_store&.close
61
+ @project_store&.close
62
+ @global_store = nil
63
+ @project_store = nil
64
+ end
65
+
66
+ def store_for_scope(scope)
67
+ case scope
68
+ when "global"
69
+ ensure_global!
70
+ @global_store
71
+ when "project"
72
+ ensure_project!
73
+ @project_store
74
+ else
75
+ raise ArgumentError, "Invalid scope: #{scope}. Use 'global' or 'project'"
76
+ end
77
+ end
78
+
79
+ def promote_fact(fact_id)
80
+ ensure_both!
81
+
82
+ fact = @project_store.facts.where(id: fact_id).first
83
+ return nil unless fact
84
+
85
+ subject = @project_store.entities.where(id: fact[:subject_entity_id]).first
86
+ return nil unless subject
87
+
88
+ global_subject_id = @global_store.find_or_create_entity(
89
+ type: subject[:type],
90
+ name: subject[:canonical_name]
91
+ )
92
+
93
+ global_object_id = nil
94
+ if fact[:object_entity_id]
95
+ object = @project_store.entities.where(id: fact[:object_entity_id]).first
96
+ if object
97
+ global_object_id = @global_store.find_or_create_entity(
98
+ type: object[:type],
99
+ name: object[:canonical_name]
100
+ )
101
+ end
102
+ end
103
+
104
+ global_fact_id = @global_store.insert_fact(
105
+ subject_entity_id: global_subject_id,
106
+ predicate: fact[:predicate],
107
+ object_entity_id: global_object_id,
108
+ object_literal: fact[:object_literal],
109
+ datatype: fact[:datatype],
110
+ polarity: fact[:polarity],
111
+ valid_from: fact[:valid_from],
112
+ status: fact[:status],
113
+ confidence: fact[:confidence],
114
+ created_from: "promoted:#{@project_path}:#{fact_id}",
115
+ scope: "global",
116
+ project_path: nil
117
+ )
118
+
119
+ copy_provenance(fact_id, global_fact_id)
120
+
121
+ global_fact_id
122
+ end
123
+
124
+ private
125
+
126
+ def copy_provenance(source_fact_id, target_fact_id)
127
+ @project_store.provenance.where(fact_id: source_fact_id).each do |prov|
128
+ @global_store.insert_provenance(
129
+ fact_id: target_fact_id,
130
+ content_item_id: nil,
131
+ quote: prov[:quote],
132
+ attribution_entity_id: nil,
133
+ strength: prov[:strength]
134
+ )
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeMemory
4
+ module Sweep
5
+ class Sweeper
6
+ DEFAULT_CONFIG = {
7
+ proposed_fact_ttl_days: 14,
8
+ disputed_fact_ttl_days: 30,
9
+ content_retention_days: 30,
10
+ default_budget_seconds: 5
11
+ }.freeze
12
+
13
+ def initialize(store, config: {})
14
+ @store = store
15
+ @config = DEFAULT_CONFIG.merge(config)
16
+ @start_time = nil
17
+ @stats = nil
18
+ end
19
+
20
+ def run!(budget_seconds: nil)
21
+ budget = budget_seconds || @config[:default_budget_seconds]
22
+ @start_time = Time.now
23
+ @stats = {
24
+ proposed_facts_expired: 0,
25
+ disputed_facts_expired: 0,
26
+ orphaned_provenance_deleted: 0,
27
+ old_content_pruned: 0
28
+ }
29
+
30
+ expire_proposed_facts if within_budget?
31
+ expire_disputed_facts if within_budget?
32
+ prune_orphaned_provenance if within_budget?
33
+ prune_old_content if within_budget?
34
+
35
+ @stats[:elapsed_seconds] = Time.now - @start_time
36
+ @stats[:budget_honored] = @stats[:elapsed_seconds] <= budget
37
+ @stats
38
+ end
39
+
40
+ private
41
+
42
+ def within_budget?
43
+ budget = @config[:default_budget_seconds]
44
+ (Time.now - @start_time) < budget
45
+ end
46
+
47
+ def expire_proposed_facts
48
+ cutoff = (Time.now - @config[:proposed_fact_ttl_days] * 86400).utc.iso8601
49
+ @stats[:proposed_facts_expired] = @store.facts
50
+ .where(status: "proposed")
51
+ .where { created_at < cutoff }
52
+ .update(status: "expired")
53
+ end
54
+
55
+ def expire_disputed_facts
56
+ cutoff = (Time.now - @config[:disputed_fact_ttl_days] * 86400).utc.iso8601
57
+ @stats[:disputed_facts_expired] = @store.facts
58
+ .where(status: "disputed")
59
+ .where { created_at < cutoff }
60
+ .update(status: "expired")
61
+ end
62
+
63
+ def prune_orphaned_provenance
64
+ fact_ids = @store.facts.select(:id)
65
+ @stats[:orphaned_provenance_deleted] = @store.provenance
66
+ .exclude(fact_id: fact_ids)
67
+ .delete
68
+ end
69
+
70
+ def prune_old_content
71
+ cutoff = (Time.now - @config[:content_retention_days] * 86400).utc.iso8601
72
+ referenced_ids = @store.provenance.exclude(content_item_id: nil).select(:content_item_id)
73
+ @stats[:old_content_pruned] = @store.content_items
74
+ .where { ingested_at < cutoff }
75
+ .exclude(id: referenced_ids)
76
+ .delete
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,74 @@
1
+ {
2
+ "hooks": {
3
+ "Stop": [
4
+ {
5
+ "matcher": "",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "claude-memory hook ingest",
10
+ "timeout": 10
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "SessionStart": [
16
+ {
17
+ "matcher": "",
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "claude-memory hook ingest",
22
+ "timeout": 10
23
+ }
24
+ ]
25
+ }
26
+ ],
27
+ "PreCompact": [
28
+ {
29
+ "matcher": "",
30
+ "hooks": [
31
+ {
32
+ "type": "command",
33
+ "command": "claude-memory hook ingest",
34
+ "timeout": 30
35
+ },
36
+ {
37
+ "type": "command",
38
+ "command": "claude-memory hook sweep",
39
+ "timeout": 30
40
+ }
41
+ ]
42
+ }
43
+ ],
44
+ "SessionEnd": [
45
+ {
46
+ "matcher": "",
47
+ "hooks": [
48
+ {
49
+ "type": "command",
50
+ "command": "claude-memory hook ingest",
51
+ "timeout": 30
52
+ },
53
+ {
54
+ "type": "command",
55
+ "command": "claude-memory hook sweep",
56
+ "timeout": 30
57
+ }
58
+ ]
59
+ }
60
+ ],
61
+ "Notification": [
62
+ {
63
+ "matcher": "idle_prompt",
64
+ "hooks": [
65
+ {
66
+ "type": "command",
67
+ "command": "claude-memory hook sweep",
68
+ "timeout": 10
69
+ }
70
+ ]
71
+ }
72
+ ]
73
+ }
74
+ }
@@ -0,0 +1,21 @@
1
+ ---
2
+ keep-coding-instructions: true
3
+ ---
4
+
5
+ # Memory-Aware Output Style
6
+
7
+ When making decisions or establishing conventions:
8
+ - State decisions clearly with "We decided to..." or "We agreed to..."
9
+ - Be explicit about technology choices: "We use PostgreSQL for..."
10
+ - Clarify when replacing previous decisions: "We no longer use X, switching to Y"
11
+ - Note conventions with "Convention:" or "Standard:"
12
+
13
+ When recalling past context:
14
+ - Use the memory.recall MCP tool to find relevant past decisions
15
+ - Cite specific facts when referencing previous work
16
+ - If unsure, use memory.explain to get provenance for a fact
17
+
18
+ When conflicts arise:
19
+ - Acknowledge contradictions explicitly
20
+ - Use memory.conflicts to see open disputes
21
+ - Help resolve conflicts by providing clear supersession signals
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeMemory
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeMemory
4
+ class Error < StandardError; end
5
+
6
+ LEGACY_DB_PATH = ".claude_memory.sqlite3"
7
+ PROJECT_DB_PATH = ".claude/memory.sqlite3"
8
+
9
+ def self.global_db_path(env = ENV)
10
+ home = env["HOME"] || File.expand_path("~")
11
+ File.join(home, ".claude", "memory.sqlite3")
12
+ end
13
+
14
+ def self.project_db_path(project_path = Dir.pwd)
15
+ File.join(project_path, ".claude", "memory.sqlite3")
16
+ end
17
+ end
18
+
19
+ require_relative "claude_memory/version"
20
+ require_relative "claude_memory/cli"
21
+ require_relative "claude_memory/store/sqlite_store"
22
+ require_relative "claude_memory/store/store_manager"
23
+ require_relative "claude_memory/ingest/transcript_reader"
24
+ require_relative "claude_memory/ingest/ingester"
25
+ require_relative "claude_memory/index/lexical_fts"
26
+ require_relative "claude_memory/distill/extraction"
27
+ require_relative "claude_memory/distill/distiller"
28
+ require_relative "claude_memory/distill/null_distiller"
29
+ require_relative "claude_memory/resolve/predicate_policy"
30
+ require_relative "claude_memory/resolve/resolver"
31
+ require_relative "claude_memory/recall"
32
+ require_relative "claude_memory/sweep/sweeper"
33
+ require_relative "claude_memory/mcp/tools"
34
+ require_relative "claude_memory/mcp/server"
35
+ require_relative "claude_memory/publish"
36
+ require_relative "claude_memory/hook/handler"
@@ -0,0 +1,4 @@
1
+ module ClaudeMemory
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end