claude_memory 0.1.0 → 0.2.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/.mind.mv2.aLCUZd +0 -0
  3. data/.claude/memory.sqlite3 +0 -0
  4. data/.claude/rules/claude_memory.generated.md +7 -1
  5. data/.claude/settings.json +0 -4
  6. data/.claude/settings.local.json +4 -1
  7. data/.claude-plugin/plugin.json +1 -1
  8. data/.claude.json +11 -0
  9. data/.ruby-version +1 -0
  10. data/CHANGELOG.md +62 -11
  11. data/CLAUDE.md +87 -24
  12. data/README.md +76 -159
  13. data/docs/EXAMPLES.md +436 -0
  14. data/docs/RELEASE_NOTES_v0.2.0.md +179 -0
  15. data/docs/RUBY_COMMUNITY_POST_v0.2.0.md +582 -0
  16. data/docs/SOCIAL_MEDIA_v0.2.0.md +420 -0
  17. data/docs/architecture.md +360 -0
  18. data/docs/expert_review.md +1718 -0
  19. data/docs/feature_adoption_plan.md +1241 -0
  20. data/docs/feature_adoption_plan_revised.md +2374 -0
  21. data/docs/improvements.md +1325 -0
  22. data/docs/quality_review.md +1544 -0
  23. data/docs/review_summary.md +480 -0
  24. data/lefthook.yml +10 -0
  25. data/lib/claude_memory/cli.rb +16 -844
  26. data/lib/claude_memory/commands/base_command.rb +95 -0
  27. data/lib/claude_memory/commands/changes_command.rb +39 -0
  28. data/lib/claude_memory/commands/conflicts_command.rb +37 -0
  29. data/lib/claude_memory/commands/db_init_command.rb +40 -0
  30. data/lib/claude_memory/commands/doctor_command.rb +147 -0
  31. data/lib/claude_memory/commands/explain_command.rb +65 -0
  32. data/lib/claude_memory/commands/help_command.rb +37 -0
  33. data/lib/claude_memory/commands/hook_command.rb +106 -0
  34. data/lib/claude_memory/commands/ingest_command.rb +47 -0
  35. data/lib/claude_memory/commands/init_command.rb +218 -0
  36. data/lib/claude_memory/commands/promote_command.rb +30 -0
  37. data/lib/claude_memory/commands/publish_command.rb +36 -0
  38. data/lib/claude_memory/commands/recall_command.rb +61 -0
  39. data/lib/claude_memory/commands/registry.rb +55 -0
  40. data/lib/claude_memory/commands/search_command.rb +43 -0
  41. data/lib/claude_memory/commands/serve_mcp_command.rb +16 -0
  42. data/lib/claude_memory/commands/sweep_command.rb +36 -0
  43. data/lib/claude_memory/commands/version_command.rb +13 -0
  44. data/lib/claude_memory/configuration.rb +38 -0
  45. data/lib/claude_memory/core/fact_id.rb +41 -0
  46. data/lib/claude_memory/core/null_explanation.rb +47 -0
  47. data/lib/claude_memory/core/null_fact.rb +30 -0
  48. data/lib/claude_memory/core/result.rb +143 -0
  49. data/lib/claude_memory/core/session_id.rb +37 -0
  50. data/lib/claude_memory/core/token_estimator.rb +33 -0
  51. data/lib/claude_memory/core/transcript_path.rb +37 -0
  52. data/lib/claude_memory/domain/conflict.rb +51 -0
  53. data/lib/claude_memory/domain/entity.rb +51 -0
  54. data/lib/claude_memory/domain/fact.rb +70 -0
  55. data/lib/claude_memory/domain/provenance.rb +48 -0
  56. data/lib/claude_memory/hook/exit_codes.rb +18 -0
  57. data/lib/claude_memory/hook/handler.rb +7 -2
  58. data/lib/claude_memory/index/index_query.rb +89 -0
  59. data/lib/claude_memory/index/index_query_logic.rb +41 -0
  60. data/lib/claude_memory/index/query_options.rb +67 -0
  61. data/lib/claude_memory/infrastructure/file_system.rb +29 -0
  62. data/lib/claude_memory/infrastructure/in_memory_file_system.rb +32 -0
  63. data/lib/claude_memory/ingest/content_sanitizer.rb +42 -0
  64. data/lib/claude_memory/ingest/ingester.rb +3 -0
  65. data/lib/claude_memory/ingest/privacy_tag.rb +48 -0
  66. data/lib/claude_memory/mcp/tools.rb +174 -1
  67. data/lib/claude_memory/publish.rb +29 -20
  68. data/lib/claude_memory/recall.rb +164 -16
  69. data/lib/claude_memory/resolve/resolver.rb +41 -37
  70. data/lib/claude_memory/shortcuts.rb +56 -0
  71. data/lib/claude_memory/store/store_manager.rb +35 -32
  72. data/lib/claude_memory/templates/hooks.example.json +0 -4
  73. data/lib/claude_memory/version.rb +1 -1
  74. data/lib/claude_memory.rb +59 -21
  75. metadata +55 -1
@@ -0,0 +1,1241 @@
1
+ # ClaudeMemory Feature Adoption Plan
2
+ ## Based on claude-mem Analysis
3
+
4
+ ## Executive Summary
5
+
6
+ This plan incrementally adopts proven patterns from claude-mem (a production-grade memory system with 6+ months of real-world usage) while preserving ClaudeMemory's unique advantages (dual-database architecture, fact-based knowledge graph, truth maintenance system).
7
+
8
+ **Timeline:** 4-6 weeks across 3 phases
9
+ **Approach:** TDD, backward compatible, high-impact features first
10
+ **Risk Level:** Low
11
+
12
+ ### Features Already Complete ✅
13
+ - **Slim Orchestrator Pattern** - CLI decomposed into 16 command classes (Phase 2 of previous refactoring)
14
+ - **Domain-Driven Design** - Rich domain models with business logic
15
+ - **Transaction Safety** - Multi-step operations wrapped in transactions
16
+ - **FileSystem Abstraction** - In-memory testing without disk I/O
17
+
18
+ ---
19
+
20
+ ## Phase 1: Privacy & Token Economics (Weeks 1-2)
21
+ ### High-impact features with security and observability benefits
22
+
23
+ ### 1.1 Privacy Tag System (Days 1-3)
24
+
25
+ **Priority:** HIGH - Security and user trust
26
+
27
+ **Goal:** Allow users to exclude sensitive content from storage using `<private>` tags
28
+
29
+ #### Implementation Steps
30
+
31
+ **1. Create Content Sanitizer (Day 1)**
32
+
33
+ **New file:** `lib/claude_memory/ingest/content_sanitizer.rb`
34
+
35
+ ```ruby
36
+ # frozen_string_literal: true
37
+
38
+ module ClaudeMemory
39
+ module Ingest
40
+ class ContentSanitizer
41
+ SYSTEM_TAGS = ["claude-memory-context"].freeze
42
+ USER_TAGS = ["private", "no-memory", "secret"].freeze
43
+ MAX_TAG_COUNT = 100 # ReDoS protection
44
+
45
+ def self.strip_tags(text)
46
+ validate_tag_count!(text)
47
+
48
+ all_tags = SYSTEM_TAGS + USER_TAGS
49
+ all_tags.each do |tag|
50
+ # Match opening and closing tags, including multiline content
51
+ text = text.gsub(/<#{Regexp.escape(tag)}>.*?<\/#{Regexp.escape(tag)}>/m, "")
52
+ end
53
+
54
+ text
55
+ end
56
+
57
+ def self.validate_tag_count!(text)
58
+ all_tags = SYSTEM_TAGS + USER_TAGS
59
+ pattern = /<(?:#{all_tags.join("|")})>/
60
+ count = text.scan(pattern).size
61
+
62
+ raise Error, "Too many privacy tags (#{count}), possible ReDoS attack" if count > MAX_TAG_COUNT
63
+ end
64
+ end
65
+ end
66
+ end
67
+ ```
68
+
69
+ **Tests:** `spec/claude_memory/ingest/content_sanitizer_spec.rb`
70
+ ```ruby
71
+ RSpec.describe ClaudeMemory::Ingest::ContentSanitizer do
72
+ describe ".strip_tags" do
73
+ it "strips <private> tags and content" do
74
+ text = "Public <private>Secret</private> Public"
75
+ expect(described_class.strip_tags(text)).to eq("Public Public")
76
+ end
77
+
78
+ it "strips multiple tag types" do
79
+ text = "A <private>X</private> B <no-memory>Y</no-memory> C"
80
+ expect(described_class.strip_tags(text)).to eq("A B C")
81
+ end
82
+
83
+ it "handles nested tags" do
84
+ text = "Public <private>Outer <private>Inner</private></private> End"
85
+ expect(described_class.strip_tags(text)).to eq("Public End")
86
+ end
87
+
88
+ it "preserves multiline content structure" do
89
+ text = "Line 1\n<private>Line 2\nLine 3</private>\nLine 4"
90
+ result = described_class.strip_tags(text)
91
+ expect(result).to eq("Line 1\n\nLine 4")
92
+ end
93
+
94
+ it "raises error on excessive tags (ReDoS protection)" do
95
+ text = "<private>" * 101
96
+ expect { described_class.strip_tags(text) }.to raise_error(ClaudeMemory::Error, /Too many privacy tags/)
97
+ end
98
+
99
+ it "strips claude-memory-context system tags" do
100
+ text = "Before <claude-memory-context>Context</claude-memory-context> After"
101
+ expect(described_class.strip_tags(text)).to eq("Before After")
102
+ end
103
+ end
104
+
105
+ describe ".validate_tag_count!" do
106
+ it "accepts reasonable tag counts" do
107
+ text = "<private>x</private>" * 50
108
+ expect { described_class.validate_tag_count!(text) }.not_to raise_error
109
+ end
110
+
111
+ it "rejects excessive tag counts" do
112
+ text = "<private>x</private>" * 101
113
+ expect { described_class.validate_tag_count!(text) }.to raise_error(ClaudeMemory::Error)
114
+ end
115
+ end
116
+ end
117
+ ```
118
+
119
+ **Commit:** "Add ContentSanitizer for privacy tag stripping with ReDoS protection"
120
+
121
+ **2. Integrate into Ingester (Day 2)**
122
+
123
+ **Modify:** `lib/claude_memory/ingest/ingester.rb` (after line 22)
124
+
125
+ ```ruby
126
+ def ingest(source:, session_id:, transcript_path:, project_path: nil)
127
+ current_offset = @store.get_delta_cursor(session_id, transcript_path) || 0
128
+ delta, new_offset = TranscriptReader.read_delta(transcript_path, current_offset)
129
+
130
+ # NEW: Strip privacy tags before processing
131
+ delta = ContentSanitizer.strip_tags(delta)
132
+
133
+ return {status: :empty, message: "No content after cursor #{current_offset}"} if delta.empty?
134
+
135
+ # ... rest of method unchanged
136
+ end
137
+ ```
138
+
139
+ **Tests:** Add to `spec/claude_memory/ingest/ingester_spec.rb`
140
+ ```ruby
141
+ it "strips privacy tags from ingested content" do
142
+ File.write(transcript_path, "Public <private>Secret API key</private> Public")
143
+
144
+ ingester.ingest(
145
+ source: "test",
146
+ session_id: "sess-123",
147
+ transcript_path: transcript_path
148
+ )
149
+
150
+ # Verify stored content is sanitized
151
+ item = store.content_items.first
152
+ expect(item[:raw_text]).to eq("Public Public")
153
+ expect(item[:raw_text]).not_to include("Secret API key")
154
+ end
155
+
156
+ it "strips claude-memory-context tags" do
157
+ File.write(transcript_path, "New <claude-memory-context>Old context</claude-memory-context> Content")
158
+
159
+ ingester.ingest(
160
+ source: "test",
161
+ session_id: "sess-123",
162
+ transcript_path: transcript_path
163
+ )
164
+
165
+ item = store.content_items.first
166
+ expect(item[:raw_text]).to eq("New Content")
167
+ end
168
+ ```
169
+
170
+ **Commit:** "Integrate ContentSanitizer into Ingester"
171
+
172
+ **3. Update Documentation (Day 3)**
173
+
174
+ **Modify:** `README.md` - Add "Privacy Control" section after "Usage Examples"
175
+
176
+ ```markdown
177
+ ## Privacy Control
178
+
179
+ ClaudeMemory respects user privacy through content exclusion tags. Wrap sensitive information in `<private>` tags to prevent storage:
180
+
181
+ ### Example
182
+
183
+ \`\`\`
184
+ API Configuration:
185
+ - Endpoint: https://api.example.com
186
+ - API Key: <private>sk-abc123def456789</private>
187
+ - Rate Limit: 1000/hour
188
+ \`\`\`
189
+
190
+ The API key will be stripped before storage, while other information is preserved.
191
+
192
+ ### Supported Tags
193
+
194
+ - `<private>...</private>` - User-controlled privacy (recommended)
195
+ - `<no-memory>...</no-memory>` - Alternative privacy tag
196
+ - `<secret>...</secret>` - Alternative privacy tag
197
+
198
+ ### System Tags
199
+
200
+ - `<claude-memory-context>...</claude-memory-context>` - Auto-stripped to prevent recursive storage of published memory
201
+
202
+ ### Security Notes
203
+
204
+ - Tags are stripped at ingestion time (edge processing)
205
+ - Protected against ReDoS attacks (max 100 tags per ingestion)
206
+ - Content within tags is never stored or indexed
207
+ ```
208
+
209
+ **Modify:** `CLAUDE.md` - Add to "Hook Integration" section
210
+
211
+ ```markdown
212
+ ### Privacy Tag Handling
213
+
214
+ ClaudeMemory automatically strips privacy tags during ingestion:
215
+
216
+ \`\`\`ruby
217
+ # User input:
218
+ "Database: postgresql, Password: <private>secret123</private>"
219
+
220
+ # Stored content:
221
+ "Database: postgresql, Password: "
222
+ \`\`\`
223
+
224
+ This happens at the hook layer before content reaches the database. Supported tags:
225
+ - `<private>` - User privacy control
226
+ - `<no-memory>` - Alternative syntax
227
+ - `<secret>` - Alternative syntax
228
+ - `<claude-memory-context>` - System tag (prevents recursive context injection)
229
+
230
+ ReDoS protection: Max 100 tags per ingestion.
231
+ ```
232
+
233
+ **Commit:** "Document privacy tag system in README and CLAUDE.md"
234
+
235
+ ---
236
+
237
+ ### 1.2 Progressive Disclosure Pattern (Days 4-7)
238
+
239
+ **Priority:** HIGH - Token efficiency and cost reduction
240
+
241
+ **Goal:** Enable 2-tier retrieval (lightweight index → detailed fetch) to reduce context waste
242
+
243
+ #### Implementation Steps
244
+
245
+ **1. Add Token Estimation (Day 4)**
246
+
247
+ **New file:** `lib/claude_memory/core/token_estimator.rb`
248
+
249
+ ```ruby
250
+ # frozen_string_literal: true
251
+
252
+ module ClaudeMemory
253
+ module Core
254
+ class TokenEstimator
255
+ # Approximation: ~4 characters per token for English text
256
+ # More accurate for Claude's tokenizer than simple word count
257
+ CHARS_PER_TOKEN = 4.0
258
+
259
+ def self.estimate(text)
260
+ return 0 if text.nil? || text.empty?
261
+
262
+ # Remove extra whitespace and count characters
263
+ normalized = text.strip.gsub(/\s+/, " ")
264
+ chars = normalized.length
265
+
266
+ # Return ceiling to avoid underestimation
267
+ (chars / CHARS_PER_TOKEN).ceil
268
+ end
269
+
270
+ def self.estimate_fact(fact)
271
+ # Estimate tokens for a fact record
272
+ text = [
273
+ fact[:subject_name],
274
+ fact[:predicate],
275
+ fact[:object_literal]
276
+ ].compact.join(" ")
277
+
278
+ estimate(text)
279
+ end
280
+ end
281
+ end
282
+ end
283
+ ```
284
+
285
+ **Tests:** `spec/claude_memory/core/token_estimator_spec.rb`
286
+ ```ruby
287
+ RSpec.describe ClaudeMemory::Core::TokenEstimator do
288
+ describe ".estimate" do
289
+ it "estimates tokens for short text" do
290
+ expect(described_class.estimate("hello world")).to eq(3)
291
+ end
292
+
293
+ it "estimates tokens for longer text" do
294
+ text = "The quick brown fox jumps over the lazy dog"
295
+ expect(described_class.estimate(text)).to be_between(10, 12)
296
+ end
297
+
298
+ it "handles empty text" do
299
+ expect(described_class.estimate("")).to eq(0)
300
+ expect(described_class.estimate(nil)).to eq(0)
301
+ end
302
+
303
+ it "normalizes whitespace" do
304
+ expect(described_class.estimate("a b c")).to eq(described_class.estimate("a b c"))
305
+ end
306
+ end
307
+
308
+ describe ".estimate_fact" do
309
+ it "estimates tokens for fact" do
310
+ fact = {
311
+ subject_name: "project",
312
+ predicate: "uses_database",
313
+ object_literal: "PostgreSQL"
314
+ }
315
+
316
+ tokens = described_class.estimate_fact(fact)
317
+ expect(tokens).to be > 0
318
+ expect(tokens).to be < 10
319
+ end
320
+ end
321
+ end
322
+ ```
323
+
324
+ **Commit:** "Add TokenEstimator for progressive disclosure"
325
+
326
+ **2. Add Index Format to Recall (Day 5)**
327
+
328
+ **Modify:** `lib/claude_memory/recall.rb` - Add new method after line 28
329
+
330
+ ```ruby
331
+ # Returns lightweight index format (no full content)
332
+ def query_index(query_text, limit: 20, scope: SCOPE_ALL)
333
+ if @legacy_mode
334
+ query_index_legacy(query_text, limit: limit, scope: scope)
335
+ else
336
+ query_index_dual(query_text, limit: limit, scope: scope)
337
+ end
338
+ end
339
+
340
+ private
341
+
342
+ def query_index_dual(query_text, limit:, scope:)
343
+ results = []
344
+
345
+ if scope == SCOPE_ALL || scope == SCOPE_PROJECT
346
+ @manager.ensure_project! if @manager.project_exists?
347
+ if @manager.project_store
348
+ project_results = query_index_single_store(@manager.project_store, query_text, limit: limit, source: :project)
349
+ results.concat(project_results)
350
+ end
351
+ end
352
+
353
+ if scope == SCOPE_ALL || scope == SCOPE_GLOBAL
354
+ @manager.ensure_global! if @manager.global_exists?
355
+ if @manager.global_store
356
+ global_results = query_index_single_store(@manager.global_store, query_text, limit: limit, source: :global)
357
+ results.concat(global_results)
358
+ end
359
+ end
360
+
361
+ dedupe_and_sort(results, limit)
362
+ end
363
+
364
+ def query_index_single_store(store, query_text, limit:, source:)
365
+ fts = Index::LexicalFTS.new(store)
366
+ content_ids = fts.search(query_text, limit: limit * 3)
367
+ return [] if content_ids.empty?
368
+
369
+ # Collect fact IDs (same as query_single_store)
370
+ seen_fact_ids = Set.new
371
+ ordered_fact_ids = []
372
+
373
+ content_ids.each do |content_id|
374
+ provenance_records = store.provenance
375
+ .select(:fact_id)
376
+ .where(content_item_id: content_id)
377
+ .all
378
+
379
+ provenance_records.each do |prov|
380
+ fact_id = prov[:fact_id]
381
+ next if seen_fact_ids.include?(fact_id)
382
+
383
+ seen_fact_ids.add(fact_id)
384
+ ordered_fact_ids << fact_id
385
+ break if ordered_fact_ids.size >= limit
386
+ end
387
+ break if ordered_fact_ids.size >= limit
388
+ end
389
+
390
+ return [] if ordered_fact_ids.empty?
391
+
392
+ # Batch query facts but return INDEX format (lightweight)
393
+ store.facts
394
+ .left_join(:entities, id: :subject_entity_id)
395
+ .select(
396
+ Sequel[:facts][:id],
397
+ Sequel[:facts][:predicate],
398
+ Sequel[:facts][:object_literal],
399
+ Sequel[:facts][:status],
400
+ Sequel[:entities][:canonical_name].as(:subject_name),
401
+ Sequel[:facts][:scope],
402
+ Sequel[:facts][:confidence]
403
+ )
404
+ .where(Sequel[:facts][:id] => ordered_fact_ids)
405
+ .all
406
+ .map do |fact|
407
+ {
408
+ id: fact[:id],
409
+ subject: fact[:subject_name],
410
+ predicate: fact[:predicate],
411
+ object_preview: fact[:object_literal]&.slice(0, 50), # Truncate for preview
412
+ status: fact[:status],
413
+ scope: fact[:scope],
414
+ confidence: fact[:confidence],
415
+ token_estimate: Core::TokenEstimator.estimate_fact(fact),
416
+ source: source
417
+ }
418
+ end
419
+ end
420
+ ```
421
+
422
+ **Tests:** Add to `spec/claude_memory/recall_spec.rb`
423
+ ```ruby
424
+ describe "#query_index" do
425
+ it "returns lightweight index format" do
426
+ fact_id = create_fact("uses_database", "PostgreSQL with extensive configuration")
427
+
428
+ results = recall.query_index("database", limit: 10, scope: :all)
429
+
430
+ expect(results).not_to be_empty
431
+ result = results.first
432
+
433
+ # Has essential fields
434
+ expect(result[:id]).to eq(fact_id)
435
+ expect(result[:predicate]).to eq("uses_database")
436
+ expect(result[:subject]).to be_present
437
+
438
+ # Has preview (truncated)
439
+ expect(result[:object_preview].length).to be <= 50
440
+
441
+ # Has token estimate
442
+ expect(result[:token_estimate]).to be > 0
443
+
444
+ # Does NOT have full provenance
445
+ expect(result).not_to have_key(:receipts)
446
+ expect(result).not_to have_key(:valid_from)
447
+ end
448
+
449
+ it "includes token estimates" do
450
+ create_fact("uses_framework", "React")
451
+
452
+ results = recall.query_index("framework", limit: 10)
453
+
454
+ expect(results.first[:token_estimate]).to be_between(1, 10)
455
+ end
456
+ end
457
+ ```
458
+
459
+ **Commit:** "Add query_index method for progressive disclosure pattern"
460
+
461
+ **3. Add MCP Tools for Progressive Disclosure (Days 6-7)**
462
+
463
+ **Modify:** `lib/claude_memory/mcp/tools.rb`
464
+
465
+ Add to `#definitions` method (around line 150):
466
+ ```ruby
467
+ {
468
+ name: "memory.recall_index",
469
+ description: "Layer 1: Search for facts and get lightweight index (IDs, previews, token counts). Use this first before fetching full details.",
470
+ inputSchema: {
471
+ type: "object",
472
+ properties: {
473
+ query: {
474
+ type: "string",
475
+ description: "Search query for fact discovery"
476
+ },
477
+ limit: {
478
+ type: "integer",
479
+ description: "Maximum results to return",
480
+ default: 20
481
+ },
482
+ scope: {
483
+ type: "string",
484
+ enum: ["all", "global", "project"],
485
+ default: "all",
486
+ description: "Scope: 'all' (both), 'global' (user-wide), 'project' (current only)"
487
+ }
488
+ },
489
+ required: ["query"]
490
+ }
491
+ },
492
+ {
493
+ name: "memory.recall_details",
494
+ description: "Layer 2: Fetch full details for specific fact IDs from the index. Use after memory.recall_index to get complete information.",
495
+ inputSchema: {
496
+ type: "object",
497
+ properties: {
498
+ fact_ids: {
499
+ type: "array",
500
+ items: {type: "integer"},
501
+ description: "Fact IDs from memory.recall_index"
502
+ },
503
+ scope: {
504
+ type: "string",
505
+ enum: ["project", "global"],
506
+ default: "project",
507
+ description: "Database to query"
508
+ }
509
+ },
510
+ required: ["fact_ids"]
511
+ }
512
+ }
513
+ ```
514
+
515
+ Add to `#call` method (around line 175):
516
+ ```ruby
517
+ when "memory.recall_index"
518
+ recall_index(arguments)
519
+ when "memory.recall_details"
520
+ recall_details(arguments)
521
+ ```
522
+
523
+ Add private methods (around line 360):
524
+ ```ruby
525
+ def recall_index(args)
526
+ scope = args["scope"] || "all"
527
+ results = @recall.query_index(args["query"], limit: args["limit"] || 20, scope: scope)
528
+
529
+ total_tokens = results.sum { |r| r[:token_estimate] }
530
+
531
+ {
532
+ query: args["query"],
533
+ scope: scope,
534
+ result_count: results.size,
535
+ total_estimated_tokens: total_tokens,
536
+ facts: results.map do |r|
537
+ {
538
+ id: r[:id],
539
+ subject: r[:subject],
540
+ predicate: r[:predicate],
541
+ object_preview: r[:object_preview],
542
+ status: r[:status],
543
+ scope: r[:scope],
544
+ confidence: r[:confidence],
545
+ tokens: r[:token_estimate],
546
+ source: r[:source]
547
+ }
548
+ end
549
+ }
550
+ end
551
+
552
+ def recall_details(args)
553
+ fact_ids = args["fact_ids"]
554
+ scope = args["scope"] || "project"
555
+
556
+ # Batch fetch detailed explanations
557
+ explanations = fact_ids.map do |fact_id|
558
+ explanation = @recall.explain(fact_id, scope: scope)
559
+ next nil if explanation.is_a?(Core::NullExplanation)
560
+
561
+ {
562
+ fact: {
563
+ id: explanation[:fact][:id],
564
+ subject: explanation[:fact][:subject_name],
565
+ predicate: explanation[:fact][:predicate],
566
+ object: explanation[:fact][:object_literal],
567
+ status: explanation[:fact][:status],
568
+ confidence: explanation[:fact][:confidence],
569
+ scope: explanation[:fact][:scope],
570
+ valid_from: explanation[:fact][:valid_from],
571
+ valid_to: explanation[:fact][:valid_to]
572
+ },
573
+ receipts: explanation[:receipts].map { |r|
574
+ {
575
+ quote: r[:quote],
576
+ strength: r[:strength],
577
+ session_id: r[:session_id],
578
+ occurred_at: r[:occurred_at]
579
+ }
580
+ },
581
+ relationships: {
582
+ supersedes: explanation[:supersedes],
583
+ superseded_by: explanation[:superseded_by],
584
+ conflicts: explanation[:conflicts].map { |c| {id: c[:id], status: c[:status]} }
585
+ }
586
+ }
587
+ end.compact
588
+
589
+ {
590
+ fact_count: explanations.size,
591
+ facts: explanations
592
+ }
593
+ end
594
+ ```
595
+
596
+ **Tests:** Add to `spec/claude_memory/mcp/tools_spec.rb`
597
+ ```ruby
598
+ describe "memory.recall_index" do
599
+ it "returns lightweight index" do
600
+ create_fact("uses_database", "PostgreSQL")
601
+
602
+ result = tools.call("memory.recall_index", {"query" => "database", "limit" => 10})
603
+
604
+ expect(result[:result_count]).to be > 0
605
+ expect(result[:total_estimated_tokens]).to be > 0
606
+
607
+ fact = result[:facts].first
608
+ expect(fact[:id]).to be_present
609
+ expect(fact[:object_preview].length).to be <= 50
610
+ expect(fact[:tokens]).to be > 0
611
+ end
612
+ end
613
+
614
+ describe "memory.recall_details" do
615
+ it "fetches full details for fact IDs" do
616
+ fact_id = create_fact("uses_framework", "React with hooks")
617
+
618
+ result = tools.call("memory.recall_details", {
619
+ "fact_ids" => [fact_id],
620
+ "scope" => "project"
621
+ })
622
+
623
+ expect(result[:fact_count]).to eq(1)
624
+
625
+ fact = result[:facts].first
626
+ expect(fact[:fact][:id]).to eq(fact_id)
627
+ expect(fact[:fact][:object]).to eq("React with hooks") # Full content
628
+ expect(fact[:receipts]).to be_an(Array)
629
+ expect(fact[:relationships]).to be_present
630
+ end
631
+
632
+ it "handles multiple fact IDs" do
633
+ id1 = create_fact("uses_database", "PostgreSQL")
634
+ id2 = create_fact("uses_framework", "Rails")
635
+
636
+ result = tools.call("memory.recall_details", {
637
+ "fact_ids" => [id1, id2]
638
+ })
639
+
640
+ expect(result[:fact_count]).to eq(2)
641
+ end
642
+ end
643
+ ```
644
+
645
+ **Commit:** "Add progressive disclosure MCP tools (recall_index, recall_details)"
646
+
647
+ **4. Update Documentation (Day 7)**
648
+
649
+ **Modify:** `README.md` - Update "MCP Tools" section
650
+
651
+ ```markdown
652
+ ### MCP Tools
653
+
654
+ When configured, these tools are available in Claude Code:
655
+
656
+ #### Progressive Disclosure Tools (Recommended)
657
+
658
+ - `memory.recall_index` - **Layer 1**: Search for facts, returns lightweight index (IDs, previews, token estimates)
659
+ - `memory.recall_details` - **Layer 2**: Fetch full details for specific fact IDs
660
+
661
+ **Workflow:**
662
+ \`\`\`
663
+ 1. memory.recall_index("database")
664
+ → Returns 10 facts with previews (~50 tokens)
665
+
666
+ 2. User/Claude selects relevant IDs (e.g., [123, 456])
667
+
668
+ 3. memory.recall_details([123, 456])
669
+ → Returns complete information (~500 tokens)
670
+ \`\`\`
671
+
672
+ **Benefits:** 10x token reduction for initial search, user control over detail retrieval
673
+
674
+ #### Full-Content Tools (Legacy)
675
+
676
+ - `memory.recall` - Search for relevant facts (returns full details immediately)
677
+ - `memory.explain` - Get detailed fact provenance
678
+ - `memory.promote` - Promote a project fact to global memory
679
+ - `memory.store_extraction` - Store extracted facts from a conversation
680
+ - `memory.changes` - Recent fact updates
681
+ - `memory.conflicts` - Open contradictions
682
+ - `memory.sweep_now` - Run maintenance
683
+ - `memory.status` - System health check
684
+ ```
685
+
686
+ **Modify:** `CLAUDE.md` - Update "MCP Integration" section
687
+
688
+ ```markdown
689
+ ## MCP Integration
690
+
691
+ ### Progressive Disclosure Workflow
692
+
693
+ ClaudeMemory uses a 2-layer retrieval pattern for token efficiency:
694
+
695
+ **Layer 1 - Discovery (`memory.recall_index`)**
696
+ Returns lightweight index with:
697
+ - Fact IDs and previews (50 char max)
698
+ - Token estimates per fact
699
+ - Scope and confidence
700
+ - Total estimated cost for full retrieval
701
+
702
+ **Layer 2 - Detail (`memory.recall_details`)**
703
+ Returns complete information for selected IDs:
704
+ - Full fact content
705
+ - Complete provenance with quotes
706
+ - Relationship graph (supersession, conflicts)
707
+ - Temporal validity
708
+
709
+ Example usage in Claude Code:
710
+
711
+ \`\`\`
712
+ Claude: Let me search your memory for database configuration
713
+ Tool: memory.recall_index(query="database", limit=10)
714
+ Result: Found 5 facts (~150 tokens if retrieved)
715
+
716
+ Claude: I'll fetch details for the 2 most relevant facts
717
+ Tool: memory.recall_details(fact_ids=[123, 124])
718
+ Result: Full details for PostgreSQL configuration
719
+ \`\`\`
720
+
721
+ This reduces initial context by ~10x compared to fetching all details immediately.
722
+ ```
723
+
724
+ **Commit:** "Document progressive disclosure pattern in README and CLAUDE.md"
725
+
726
+ ---
727
+
728
+ ## Phase 2: Semantic Enhancements (Weeks 3-4)
729
+ ### Improved query patterns and shortcuts
730
+
731
+ ### 2.1 Semantic Shortcut Methods (Days 8-10)
732
+
733
+ **Priority:** MEDIUM - Developer convenience
734
+
735
+ **Goal:** Pre-configured queries for common use cases
736
+
737
+ #### Implementation Steps
738
+
739
+ **1. Add Shortcut Methods to Recall (Day 8)**
740
+
741
+ **Modify:** `lib/claude_memory/recall.rb` - Add class methods after line 55
742
+
743
+ ```ruby
744
+ class << self
745
+ def recent_decisions(manager, limit: 10)
746
+ recall = new(manager)
747
+ recall.query("decision constraint rule requirement", limit: limit, scope: SCOPE_ALL)
748
+ end
749
+
750
+ def architecture_choices(manager, limit: 10)
751
+ recall = new(manager)
752
+ recall.query("uses framework implements architecture pattern", limit: limit, scope: SCOPE_ALL)
753
+ end
754
+
755
+ def conventions(manager, limit: 20)
756
+ recall = new(manager)
757
+ recall.query("convention style format pattern prefer", limit: limit, scope: SCOPE_GLOBAL)
758
+ end
759
+
760
+ def project_config(manager, limit: 10)
761
+ recall = new(manager)
762
+ recall.query("uses requires depends_on configuration", limit: limit, scope: SCOPE_PROJECT)
763
+ end
764
+
765
+ def recent_changes(manager, days: 7, limit: 20)
766
+ recall = new(manager)
767
+ since = Time.now - (days * 24 * 60 * 60)
768
+ recall.changes(since: since, limit: limit, scope: SCOPE_ALL)
769
+ end
770
+ end
771
+ ```
772
+
773
+ **Tests:** Add to `spec/claude_memory/recall_spec.rb`
774
+ ```ruby
775
+ describe ".recent_decisions" do
776
+ it "returns decision-related facts" do
777
+ create_fact("decision", "Use PostgreSQL for primary database")
778
+ create_fact("constraint", "API rate limit 1000/min")
779
+
780
+ results = described_class.recent_decisions(manager, limit: 10)
781
+
782
+ expect(results.size).to be >= 2
783
+ expect(results.map { |r| r[:fact][:predicate] }).to include("decision", "constraint")
784
+ end
785
+ end
786
+
787
+ describe ".conventions" do
788
+ it "returns only global scope conventions" do
789
+ create_global_fact("convention", "Use 4-space indentation")
790
+ create_project_fact("convention", "Project uses tabs")
791
+
792
+ results = described_class.conventions(manager, limit: 10)
793
+
794
+ # Should only return global convention
795
+ expect(results.size).to eq(1)
796
+ expect(results.first[:fact][:object_literal]).to eq("Use 4-space indentation")
797
+ end
798
+ end
799
+ ```
800
+
801
+ **Commit:** "Add semantic shortcut methods to Recall"
802
+
803
+ **2. Add MCP Tools for Shortcuts (Day 9)**
804
+
805
+ **Modify:** `lib/claude_memory/mcp/tools.rb`
806
+
807
+ Add definitions (around line 150):
808
+ ```ruby
809
+ {
810
+ name: "memory.decisions",
811
+ description: "Quick access to architectural decisions, constraints, and rules",
812
+ inputSchema: {
813
+ type: "object",
814
+ properties: {
815
+ limit: {type: "integer", default: 10}
816
+ }
817
+ }
818
+ },
819
+ {
820
+ name: "memory.conventions",
821
+ description: "Quick access to coding conventions and style preferences (global scope)",
822
+ inputSchema: {
823
+ type: "object",
824
+ properties: {
825
+ limit: {type: "integer", default: 20}
826
+ }
827
+ }
828
+ },
829
+ {
830
+ name: "memory.architecture",
831
+ description: "Quick access to framework choices and architectural patterns",
832
+ inputSchema: {
833
+ type: "object",
834
+ properties: {
835
+ limit: {type: "integer", default: 10}
836
+ }
837
+ }
838
+ }
839
+ ```
840
+
841
+ Add handlers (around line 175):
842
+ ```ruby
843
+ when "memory.decisions"
844
+ decisions(arguments)
845
+ when "memory.conventions"
846
+ conventions(arguments)
847
+ when "memory.architecture"
848
+ architecture(arguments)
849
+
850
+ # ... private methods (around line 400):
851
+
852
+ def decisions(args)
853
+ results = Recall.recent_decisions(@manager, limit: args["limit"] || 10)
854
+ format_shortcut_results(results, "decisions")
855
+ end
856
+
857
+ def conventions(args)
858
+ results = Recall.conventions(@manager, limit: args["limit"] || 20)
859
+ format_shortcut_results(results, "conventions")
860
+ end
861
+
862
+ def architecture(args)
863
+ results = Recall.architecture_choices(@manager, limit: args["limit"] || 10)
864
+ format_shortcut_results(results, "architecture")
865
+ end
866
+
867
+ def format_shortcut_results(results, category)
868
+ {
869
+ category: category,
870
+ count: results.size,
871
+ facts: results.map do |r|
872
+ {
873
+ id: r[:fact][:id],
874
+ subject: r[:fact][:subject_name],
875
+ predicate: r[:fact][:predicate],
876
+ object: r[:fact][:object_literal],
877
+ scope: r[:fact][:scope],
878
+ source: r[:source]
879
+ }
880
+ end
881
+ }
882
+ end
883
+ ```
884
+
885
+ **Commit:** "Add semantic shortcut MCP tools (decisions, conventions, architecture)"
886
+
887
+ **3. Update Documentation (Day 10)**
888
+
889
+ **Modify:** `README.md` - Add "Semantic Shortcuts" section
890
+
891
+ ```markdown
892
+ ### Semantic Shortcuts
893
+
894
+ Quick access to common queries via MCP tools:
895
+
896
+ - `memory.decisions` - Architectural decisions, constraints, and rules
897
+ - `memory.conventions` - Coding conventions and style preferences (global scope)
898
+ - `memory.architecture` - Framework choices and architectural patterns
899
+
900
+ These shortcuts use optimized queries for specific use cases, reducing the need for manual query construction.
901
+
902
+ #### CLI Usage
903
+
904
+ \`\`\`bash
905
+ # Get all architectural decisions
906
+ claude-memory recall "decision constraint rule"
907
+
908
+ # Get global conventions only
909
+ claude-memory recall "convention style format" --scope global
910
+
911
+ # Get project architecture
912
+ claude-memory recall "uses framework architecture" --scope project
913
+ \`\`\`
914
+ ```
915
+
916
+ **Commit:** "Document semantic shortcuts in README"
917
+
918
+ ---
919
+
920
+ ### 2.2 Exit Code Strategy for Hooks (Day 11)
921
+
922
+ **Priority:** MEDIUM - Better error handling for Claude Code integration
923
+
924
+ **Goal:** Define clear exit code contract for hook commands
925
+
926
+ #### Implementation Steps
927
+
928
+ **1. Create Exit Code Constants**
929
+
930
+ **New file:** `lib/claude_memory/hook/exit_codes.rb`
931
+
932
+ ```ruby
933
+ # frozen_string_literal: true
934
+
935
+ module ClaudeMemory
936
+ module Hook
937
+ module ExitCodes
938
+ # Success or graceful shutdown
939
+ SUCCESS = 0
940
+
941
+ # Non-blocking error (shown to user, session continues)
942
+ # Example: Missing transcript file, database not initialized
943
+ WARNING = 1
944
+
945
+ # Blocking error (fed to Claude for processing)
946
+ # Example: Database corruption, schema mismatch
947
+ ERROR = 2
948
+ end
949
+ end
950
+ end
951
+ ```
952
+
953
+ **Modify:** `lib/claude_memory/hook/handler.rb` - Add error classes
954
+
955
+ ```ruby
956
+ class Handler
957
+ class NonBlockingError < StandardError; end
958
+ class BlockingError < StandardError; end
959
+
960
+ # ... existing code ...
961
+
962
+ def handle_error(error)
963
+ case error
964
+ when NonBlockingError
965
+ warn "Warning: #{error.message}"
966
+ ExitCodes::WARNING
967
+ when BlockingError
968
+ $stderr.puts "ERROR: #{error.message}"
969
+ ExitCodes::ERROR
970
+ else
971
+ # Unknown errors are blocking by default (safer)
972
+ $stderr.puts "ERROR: #{error.class}: #{error.message}"
973
+ ExitCodes::ERROR
974
+ end
975
+ end
976
+ end
977
+ ```
978
+
979
+ **Modify:** `lib/claude_memory/commands/hook_command.rb` - Return proper exit codes
980
+
981
+ ```ruby
982
+ def call(args)
983
+ # ... existing code ...
984
+
985
+ case subcommand
986
+ when "ingest"
987
+ result = handler.ingest(payload)
988
+ result[:status] == :skipped ? Hook::ExitCodes::WARNING : Hook::ExitCodes::SUCCESS
989
+ when "sweep"
990
+ handler.sweep(payload)
991
+ Hook::ExitCodes::SUCCESS
992
+ when "publish"
993
+ handler.publish(payload)
994
+ Hook::ExitCodes::SUCCESS
995
+ else
996
+ stderr.puts "Unknown hook subcommand: #{subcommand}"
997
+ Hook::ExitCodes::ERROR
998
+ end
999
+ rescue Handler::NonBlockingError => e
1000
+ handler.handle_error(e)
1001
+ rescue => e
1002
+ handler.handle_error(e)
1003
+ end
1004
+ ```
1005
+
1006
+ **Tests:** Add to `spec/claude_memory/commands/hook_command_spec.rb`
1007
+ ```ruby
1008
+ it "returns SUCCESS exit code for successful ingest" do
1009
+ payload = {
1010
+ "subcommand" => "ingest",
1011
+ "session_id" => "sess-123",
1012
+ "transcript_path" => transcript_path
1013
+ }
1014
+
1015
+ exit_code = command.call(["ingest"], stdin: StringIO.new(JSON.generate(payload)))
1016
+ expect(exit_code).to eq(Hook::ExitCodes::SUCCESS)
1017
+ end
1018
+
1019
+ it "returns WARNING exit code for skipped ingest" do
1020
+ payload = {
1021
+ "subcommand" => "ingest",
1022
+ "session_id" => "sess-123",
1023
+ "transcript_path" => "/nonexistent/file"
1024
+ }
1025
+
1026
+ exit_code = command.call(["ingest"], stdin: StringIO.new(JSON.generate(payload)))
1027
+ expect(exit_code).to eq(Hook::ExitCodes::WARNING)
1028
+ end
1029
+ ```
1030
+
1031
+ **Update:** `CLAUDE.md`
1032
+
1033
+ ```markdown
1034
+ ## Hook Exit Codes
1035
+
1036
+ ClaudeMemory hooks follow a standardized exit code contract:
1037
+
1038
+ - **0 (SUCCESS)**: Hook completed successfully or gracefully shut down
1039
+ - **1 (WARNING)**: Non-blocking error (shown to user, session continues)
1040
+ - Examples: Missing transcript file, database not initialized, empty delta
1041
+ - **2 (ERROR)**: Blocking error (fed to Claude for processing)
1042
+ - Examples: Database corruption, schema version mismatch, critical failures
1043
+
1044
+ This ensures predictable behavior when integrated with Claude Code hooks system.
1045
+ ```
1046
+
1047
+ **Commit:** "Add exit code strategy for hook commands"
1048
+
1049
+ ---
1050
+
1051
+ ## Phase 3: Future Enhancements (Optional)
1052
+ ### Lower priority features for later consideration
1053
+
1054
+ ### 3.1 Token Economics Tracking (Days 12-15, Optional)
1055
+
1056
+ **Priority:** LOW-MEDIUM - Observability
1057
+
1058
+ **Goal:** Track token usage metrics to demonstrate memory system efficiency
1059
+
1060
+ **Note:** This requires distiller integration (currently a stub). Skip if distiller not implemented.
1061
+
1062
+ #### High-Level Steps
1063
+
1064
+ 1. Add `ingestion_metrics` table to schema
1065
+ 2. Track tokens during distillation
1066
+ 3. Add `stats` CLI command
1067
+ 4. Add metrics footer to publish output
1068
+
1069
+ **Deferred:** Wait for distiller implementation
1070
+
1071
+ ---
1072
+
1073
+ ## Critical Files Reference
1074
+
1075
+ ### Phase 1: Privacy & Token Economics
1076
+
1077
+ #### New Files
1078
+ - `lib/claude_memory/ingest/content_sanitizer.rb` - Privacy tag stripping
1079
+ - `lib/claude_memory/core/token_estimator.rb` - Token estimation
1080
+ - `spec/claude_memory/ingest/content_sanitizer_spec.rb` - Tests
1081
+ - `spec/claude_memory/core/token_estimator_spec.rb` - Tests
1082
+
1083
+ #### Modified Files
1084
+ - `lib/claude_memory/ingest/ingester.rb` - Integrate ContentSanitizer
1085
+ - `lib/claude_memory/recall.rb` - Add query_index method
1086
+ - `lib/claude_memory/mcp/tools.rb` - Add progressive disclosure tools
1087
+ - `README.md` - Document privacy tags and progressive disclosure
1088
+ - `CLAUDE.md` - Document privacy tags and MCP tools
1089
+
1090
+ ### Phase 2: Semantic Enhancements
1091
+
1092
+ #### New Files
1093
+ - `lib/claude_memory/hook/exit_codes.rb` - Exit code constants
1094
+
1095
+ #### Modified Files
1096
+ - `lib/claude_memory/recall.rb` - Add shortcut class methods
1097
+ - `lib/claude_memory/mcp/tools.rb` - Add shortcut MCP tools
1098
+ - `lib/claude_memory/hook/handler.rb` - Use exit codes
1099
+ - `lib/claude_memory/commands/hook_command.rb` - Return exit codes
1100
+ - `README.md` - Document semantic shortcuts
1101
+ - `CLAUDE.md` - Document exit codes
1102
+
1103
+ ---
1104
+
1105
+ ## Testing Strategy
1106
+
1107
+ ### Test-First Workflow
1108
+ 1. Write failing test for new behavior
1109
+ 2. Implement minimal code to pass
1110
+ 3. Refactor while keeping tests green
1111
+ 4. Commit with tests + implementation
1112
+
1113
+ ### Coverage Goals
1114
+ - Maintain >80% coverage throughout
1115
+ - 100% coverage for ContentSanitizer (security-critical)
1116
+ - 100% coverage for TokenEstimator (accuracy-critical)
1117
+
1118
+ ### Integration Testing
1119
+ - Test progressive disclosure end-to-end (recall_index → recall_details)
1120
+ - Test privacy tag stripping with various edge cases
1121
+ - Test exit codes in hook commands
1122
+
1123
+ ---
1124
+
1125
+ ## Success Metrics
1126
+
1127
+ ### Phase 1 Metrics
1128
+ - ✅ Privacy tags stripped at ingestion (zero sensitive data stored)
1129
+ - ✅ Progressive disclosure reduces initial context by ~10x
1130
+ - ✅ New MCP tools: recall_index, recall_details
1131
+ - ✅ Token estimation accurate within 20%
1132
+
1133
+ ### Phase 2 Metrics
1134
+ - ✅ Semantic shortcuts reduce query complexity
1135
+ - ✅ Exit codes standardized for hooks
1136
+ - ✅ 3 new shortcut MCP tools (decisions, conventions, architecture)
1137
+
1138
+ ---
1139
+
1140
+ ## Verification Plan
1141
+
1142
+ ### After Phase 1
1143
+
1144
+ ```bash
1145
+ # Test privacy tag stripping
1146
+ echo "Public <private>secret</private> text" > /tmp/test.txt
1147
+ ./exe/claude-memory ingest --source test --session test-1 --transcript /tmp/test.txt --db /tmp/test.sqlite3
1148
+ # Verify "secret" not stored
1149
+
1150
+ # Test progressive disclosure
1151
+ ./exe/claude-memory recall "database" --limit 5
1152
+ # Should see full results (no index format in CLI yet)
1153
+
1154
+ # Test MCP tools
1155
+ ./exe/claude-memory serve-mcp
1156
+ # Send test requests for recall_index and recall_details
1157
+ ```
1158
+
1159
+ ### After Phase 2
1160
+
1161
+ ```bash
1162
+ # Test semantic shortcuts via MCP
1163
+ ./exe/claude-memory serve-mcp
1164
+ # Test memory.decisions, memory.conventions, memory.architecture
1165
+
1166
+ # Test exit codes
1167
+ echo '{"subcommand":"ingest","session_id":"test"}' | ./exe/claude-memory hook ingest
1168
+ echo $? # Should be 1 (WARNING) for missing transcript
1169
+ ```
1170
+
1171
+ ---
1172
+
1173
+ ## Migration Path
1174
+
1175
+ ### Week 1-2: Foundation (Phase 1)
1176
+ - Days 1-3: Privacy tag system (HIGH priority)
1177
+ - Days 4-7: Progressive disclosure (HIGH priority)
1178
+
1179
+ ### Week 3-4: Enhancements (Phase 2)
1180
+ - Days 8-10: Semantic shortcuts (MEDIUM priority)
1181
+ - Day 11: Exit code strategy (MEDIUM priority)
1182
+
1183
+ ### Week 5-6: Optional (Phase 3)
1184
+ - Days 12-15: Token economics tracking (LOW, requires distiller)
1185
+
1186
+ ---
1187
+
1188
+ ## What We're NOT Doing (And Why)
1189
+
1190
+ ### ❌ Chroma Vector Database
1191
+ **Reason:** Adds Python dependency, embedding generation, sync overhead. SQLite FTS5 is sufficient.
1192
+
1193
+ ### ❌ Background Worker Process
1194
+ **Reason:** MCP stdio transport works well. No need for HTTP server, PID files, port management.
1195
+
1196
+ ### ❌ Web Viewer UI
1197
+ **Reason:** Significant effort (React, SSE, state management) for uncertain value. CLI is sufficient.
1198
+
1199
+ ### ❌ Slim Orchestrator Pattern
1200
+ **Reason:** ALREADY COMPLETE! Previous refactoring extracted all 16 commands.
1201
+
1202
+ ---
1203
+
1204
+ ## Architecture Advantages We're Preserving
1205
+
1206
+ ### ✅ Dual-Database Architecture (Global + Project)
1207
+ Better than claude-mem's single database with filtering.
1208
+
1209
+ ### ✅ Fact-Based Knowledge Graph
1210
+ Structured triples enable richer queries vs. observation blobs.
1211
+
1212
+ ### ✅ Truth Maintenance System
1213
+ Conflict resolution and supersession not present in claude-mem.
1214
+
1215
+ ### ✅ Predicate Policies
1216
+ Single-value vs multi-value predicates prevent false conflicts.
1217
+
1218
+ ### ✅ Ruby Ecosystem
1219
+ Simpler dependencies, easier install vs. Node.js + Python stack.
1220
+
1221
+ ---
1222
+
1223
+ ## Next Steps
1224
+
1225
+ 1. **Review and approve this plan**
1226
+ 2. **Create feature branch:** `feature/claude-mem-adoption`
1227
+ 3. **Start Phase 1, Step 1.1:** Add ContentSanitizer for privacy tags
1228
+ 4. **Commit early, commit often:** Small, focused changes
1229
+ 5. **Review progress:** Weekly checkpoint after each phase
1230
+
1231
+ ---
1232
+
1233
+ ## Notes
1234
+
1235
+ - All features maintain backward compatibility
1236
+ - Tests are updated/added with each change
1237
+ - Code style follows Standard Ruby
1238
+ - Frozen string literals maintained throughout
1239
+ - Ruby 3.2+ idioms used where appropriate
1240
+ - Privacy tag stripping is non-reversible by design (security-first)
1241
+ - Progressive disclosure is optional (legacy recall tool still works)