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.
- checksums.yaml +4 -4
- data/.claude/.mind.mv2.aLCUZd +0 -0
- data/.claude/memory.sqlite3 +0 -0
- data/.claude/rules/claude_memory.generated.md +7 -1
- data/.claude/settings.json +0 -4
- data/.claude/settings.local.json +4 -1
- data/.claude-plugin/plugin.json +1 -1
- data/.claude.json +11 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +62 -11
- data/CLAUDE.md +87 -24
- data/README.md +76 -159
- data/docs/EXAMPLES.md +436 -0
- data/docs/RELEASE_NOTES_v0.2.0.md +179 -0
- data/docs/RUBY_COMMUNITY_POST_v0.2.0.md +582 -0
- data/docs/SOCIAL_MEDIA_v0.2.0.md +420 -0
- data/docs/architecture.md +360 -0
- data/docs/expert_review.md +1718 -0
- data/docs/feature_adoption_plan.md +1241 -0
- data/docs/feature_adoption_plan_revised.md +2374 -0
- data/docs/improvements.md +1325 -0
- data/docs/quality_review.md +1544 -0
- data/docs/review_summary.md +480 -0
- data/lefthook.yml +10 -0
- data/lib/claude_memory/cli.rb +16 -844
- data/lib/claude_memory/commands/base_command.rb +95 -0
- data/lib/claude_memory/commands/changes_command.rb +39 -0
- data/lib/claude_memory/commands/conflicts_command.rb +37 -0
- data/lib/claude_memory/commands/db_init_command.rb +40 -0
- data/lib/claude_memory/commands/doctor_command.rb +147 -0
- data/lib/claude_memory/commands/explain_command.rb +65 -0
- data/lib/claude_memory/commands/help_command.rb +37 -0
- data/lib/claude_memory/commands/hook_command.rb +106 -0
- data/lib/claude_memory/commands/ingest_command.rb +47 -0
- data/lib/claude_memory/commands/init_command.rb +218 -0
- data/lib/claude_memory/commands/promote_command.rb +30 -0
- data/lib/claude_memory/commands/publish_command.rb +36 -0
- data/lib/claude_memory/commands/recall_command.rb +61 -0
- data/lib/claude_memory/commands/registry.rb +55 -0
- data/lib/claude_memory/commands/search_command.rb +43 -0
- data/lib/claude_memory/commands/serve_mcp_command.rb +16 -0
- data/lib/claude_memory/commands/sweep_command.rb +36 -0
- data/lib/claude_memory/commands/version_command.rb +13 -0
- data/lib/claude_memory/configuration.rb +38 -0
- data/lib/claude_memory/core/fact_id.rb +41 -0
- data/lib/claude_memory/core/null_explanation.rb +47 -0
- data/lib/claude_memory/core/null_fact.rb +30 -0
- data/lib/claude_memory/core/result.rb +143 -0
- data/lib/claude_memory/core/session_id.rb +37 -0
- data/lib/claude_memory/core/token_estimator.rb +33 -0
- data/lib/claude_memory/core/transcript_path.rb +37 -0
- data/lib/claude_memory/domain/conflict.rb +51 -0
- data/lib/claude_memory/domain/entity.rb +51 -0
- data/lib/claude_memory/domain/fact.rb +70 -0
- data/lib/claude_memory/domain/provenance.rb +48 -0
- data/lib/claude_memory/hook/exit_codes.rb +18 -0
- data/lib/claude_memory/hook/handler.rb +7 -2
- data/lib/claude_memory/index/index_query.rb +89 -0
- data/lib/claude_memory/index/index_query_logic.rb +41 -0
- data/lib/claude_memory/index/query_options.rb +67 -0
- data/lib/claude_memory/infrastructure/file_system.rb +29 -0
- data/lib/claude_memory/infrastructure/in_memory_file_system.rb +32 -0
- data/lib/claude_memory/ingest/content_sanitizer.rb +42 -0
- data/lib/claude_memory/ingest/ingester.rb +3 -0
- data/lib/claude_memory/ingest/privacy_tag.rb +48 -0
- data/lib/claude_memory/mcp/tools.rb +174 -1
- data/lib/claude_memory/publish.rb +29 -20
- data/lib/claude_memory/recall.rb +164 -16
- data/lib/claude_memory/resolve/resolver.rb +41 -37
- data/lib/claude_memory/shortcuts.rb +56 -0
- data/lib/claude_memory/store/store_manager.rb +35 -32
- data/lib/claude_memory/templates/hooks.example.json +0 -4
- data/lib/claude_memory/version.rb +1 -1
- data/lib/claude_memory.rb +59 -21
- metadata +55 -1
|
@@ -0,0 +1,1325 @@
|
|
|
1
|
+
# Improvements to Consider (Based on claude-mem Analysis)
|
|
2
|
+
|
|
3
|
+
*Generated: 2026-01-21*
|
|
4
|
+
*Source: Comparative analysis of [thedotmack/claude-mem](https://github.com/thedotmack/claude-mem)*
|
|
5
|
+
|
|
6
|
+
This document identifies design patterns, features, and architectural decisions from claude-mem that could improve claude_memory. Each section includes rationale, implementation considerations, and priority.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Executive Summary
|
|
11
|
+
|
|
12
|
+
Claude-mem (TypeScript/Node.js, v9.0.5) is a production-grade memory compression system with 6+ months of real-world usage. Key strengths:
|
|
13
|
+
|
|
14
|
+
- **Progressive Disclosure**: Token-efficient 3-layer retrieval workflow
|
|
15
|
+
- **ROI Metrics**: Tracks token costs and discovery efficiency
|
|
16
|
+
- **Slim Architecture**: Clean separation via service layer pattern
|
|
17
|
+
- **Dual Integration**: Plugin + MCP server for flexibility
|
|
18
|
+
- **Privacy-First**: User-controlled content exclusion via tags
|
|
19
|
+
- **Fail-Fast Philosophy**: Explicit error handling and exit codes
|
|
20
|
+
|
|
21
|
+
**Our Advantages**:
|
|
22
|
+
- Ruby ecosystem (simpler dependencies)
|
|
23
|
+
- Dual-database architecture (global + project scope)
|
|
24
|
+
- Fact-based knowledge graph (vs observation blobs)
|
|
25
|
+
- Truth maintenance system (conflict resolution)
|
|
26
|
+
- Predicate policies (single vs multi-value)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 1. Progressive Disclosure Pattern
|
|
31
|
+
|
|
32
|
+
### What claude-mem Does
|
|
33
|
+
|
|
34
|
+
**3-Layer Workflow** enforced at the tool level:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Layer 1: search → Get compact index with IDs (~50-100 tokens/result)
|
|
38
|
+
Layer 2: timeline → Get chronological context around IDs
|
|
39
|
+
Layer 3: get_observations → Fetch full details (~500-1,000 tokens/result)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Token savings**: ~10x reduction by filtering before fetching.
|
|
43
|
+
|
|
44
|
+
**MCP Tools**:
|
|
45
|
+
- `search` - Returns index format (titles, IDs, token counts)
|
|
46
|
+
- `timeline` - Returns context around specific observation
|
|
47
|
+
- `get_observations` - Returns full details only for filtered IDs
|
|
48
|
+
- `__IMPORTANT` - Workflow documentation (always visible)
|
|
49
|
+
|
|
50
|
+
**File**: `docs/public/progressive-disclosure.mdx` (673 lines of philosophy)
|
|
51
|
+
|
|
52
|
+
### What We Should Do
|
|
53
|
+
|
|
54
|
+
**Priority**: HIGH
|
|
55
|
+
|
|
56
|
+
**Implementation**:
|
|
57
|
+
|
|
58
|
+
1. **Add token count field to facts table**:
|
|
59
|
+
```ruby
|
|
60
|
+
alter table :facts do
|
|
61
|
+
add_column :token_count, Integer
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
2. **Create index format in Recall**:
|
|
66
|
+
```ruby
|
|
67
|
+
# lib/claude_memory/recall.rb
|
|
68
|
+
def recall_index(query, scope: :project, limit: 20)
|
|
69
|
+
facts = search_facts(query, scope:, limit:)
|
|
70
|
+
facts.map do |fact|
|
|
71
|
+
{
|
|
72
|
+
id: fact[:id],
|
|
73
|
+
subject: fact[:subject],
|
|
74
|
+
predicate: fact[:predicate],
|
|
75
|
+
object_preview: fact[:object_value][0..50],
|
|
76
|
+
scope: fact[:scope],
|
|
77
|
+
token_count: fact[:token_count] || estimate_tokens(fact)
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
3. **Add MCP tool for fetching details**:
|
|
84
|
+
```ruby
|
|
85
|
+
# lib/claude_memory/mcp/tools.rb
|
|
86
|
+
TOOLS["memory.recall_index"] = {
|
|
87
|
+
description: "Layer 1: Search for facts. Returns compact index with IDs.",
|
|
88
|
+
input_schema: {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
query: { type: "string" },
|
|
92
|
+
scope: { type: "string", enum: ["global", "project", "both"] },
|
|
93
|
+
limit: { type: "integer", default: 20 }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
TOOLS["memory.recall_details"] = {
|
|
99
|
+
description: "Layer 2: Fetch full fact details by IDs.",
|
|
100
|
+
input_schema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
fact_ids: { type: "array", items: { type: "integer" } }
|
|
104
|
+
},
|
|
105
|
+
required: ["fact_ids"]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
4. **Update publish format** to show costs:
|
|
111
|
+
```markdown
|
|
112
|
+
## Recent Facts
|
|
113
|
+
|
|
114
|
+
| ID | Subject | Predicate | Preview | Tokens |
|
|
115
|
+
|----|---------|-----------|---------|--------|
|
|
116
|
+
| #123 | project | uses_database | PostgreSQL | ~45 |
|
|
117
|
+
| #124 | project | has_constraint | API rate lim... | ~120 |
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Benefits**:
|
|
121
|
+
- Reduces context waste in published snapshots
|
|
122
|
+
- Gives Claude control over retrieval depth
|
|
123
|
+
- Makes token costs visible for informed decisions
|
|
124
|
+
|
|
125
|
+
**Trade-offs**:
|
|
126
|
+
- More complex MCP interface
|
|
127
|
+
- Requires token estimation logic
|
|
128
|
+
- May confuse users who expect full details
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 2. ROI Metrics and Token Economics
|
|
133
|
+
|
|
134
|
+
### What claude-mem Does
|
|
135
|
+
|
|
136
|
+
**Discovery Token Tracking**:
|
|
137
|
+
- `discovery_tokens` field on observations table
|
|
138
|
+
- Tracks tokens spent discovering each piece of knowledge
|
|
139
|
+
- Cumulative metrics in session summaries
|
|
140
|
+
- Footer displays ROI: "Access 10k tokens for 2,500t"
|
|
141
|
+
|
|
142
|
+
**File**: `src/services/sqlite/Database.ts`
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
observations: {
|
|
146
|
+
id: INTEGER PRIMARY KEY,
|
|
147
|
+
title: TEXT,
|
|
148
|
+
narrative: TEXT,
|
|
149
|
+
discovery_tokens: INTEGER, // ← Cost tracking
|
|
150
|
+
created_at_epoch: INTEGER
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
session_summaries: {
|
|
154
|
+
cumulative_discovery_tokens: INTEGER, // ← Running total
|
|
155
|
+
observation_count: INTEGER
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Context Footer Example**:
|
|
160
|
+
```markdown
|
|
161
|
+
💡 **Token Economics:**
|
|
162
|
+
- Context shown: 2,500 tokens
|
|
163
|
+
- Research captured: 10,000 tokens
|
|
164
|
+
- ROI: 4x compression
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### What We Should Do
|
|
168
|
+
|
|
169
|
+
**Priority**: MEDIUM
|
|
170
|
+
|
|
171
|
+
**Implementation**:
|
|
172
|
+
|
|
173
|
+
1. **Add metrics table**:
|
|
174
|
+
```ruby
|
|
175
|
+
create_table :ingestion_metrics do
|
|
176
|
+
primary_key :id
|
|
177
|
+
foreign_key :content_item_id, :content_items
|
|
178
|
+
Integer :input_tokens
|
|
179
|
+
Integer :output_tokens
|
|
180
|
+
Integer :facts_extracted
|
|
181
|
+
DateTime :created_at
|
|
182
|
+
end
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
2. **Track during distillation**:
|
|
186
|
+
```ruby
|
|
187
|
+
# lib/claude_memory/distill/distiller.rb
|
|
188
|
+
def distill(content)
|
|
189
|
+
response = api_call(content)
|
|
190
|
+
facts = extract_facts(response)
|
|
191
|
+
|
|
192
|
+
store_metrics(
|
|
193
|
+
input_tokens: response.usage.input_tokens,
|
|
194
|
+
output_tokens: response.usage.output_tokens,
|
|
195
|
+
facts_extracted: facts.size
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
facts
|
|
199
|
+
end
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
3. **Display in CLI**:
|
|
203
|
+
```ruby
|
|
204
|
+
# claude-memory stats
|
|
205
|
+
def stats_cmd
|
|
206
|
+
metrics = store.aggregate_metrics
|
|
207
|
+
puts "Token Economics:"
|
|
208
|
+
puts " Input: #{metrics[:input_tokens]} tokens"
|
|
209
|
+
puts " Output: #{metrics[:output_tokens]} tokens"
|
|
210
|
+
puts " Facts: #{metrics[:facts_extracted]}"
|
|
211
|
+
puts " Efficiency: #{metrics[:facts_extracted] / metrics[:input_tokens].to_f} facts/token"
|
|
212
|
+
end
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
4. **Add to published snapshot**:
|
|
216
|
+
```markdown
|
|
217
|
+
<!-- At bottom of .claude/rules/claude_memory.generated.md -->
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
*Memory stats: 145 facts from 12,500 ingested tokens (86 facts/1k tokens)*
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Benefits**:
|
|
225
|
+
- Visibility into memory system efficiency
|
|
226
|
+
- Justifies API costs (shows compression ratio)
|
|
227
|
+
- Helps tune distillation prompts for better extraction
|
|
228
|
+
|
|
229
|
+
**Trade-offs**:
|
|
230
|
+
- Requires API usage tracking
|
|
231
|
+
- Adds database complexity
|
|
232
|
+
- May not be meaningful for all distiller implementations
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 3. Privacy Tag System
|
|
237
|
+
|
|
238
|
+
### What claude-mem Does
|
|
239
|
+
|
|
240
|
+
**Dual-Tag Architecture** for content exclusion:
|
|
241
|
+
|
|
242
|
+
1. **`<claude-mem-context>`** (system tag):
|
|
243
|
+
- Prevents recursive storage when context is auto-injected
|
|
244
|
+
- Strips at hook layer before worker sees it
|
|
245
|
+
|
|
246
|
+
2. **`<private>`** (user tag):
|
|
247
|
+
- Manual privacy control
|
|
248
|
+
- Users wrap sensitive content to exclude from storage
|
|
249
|
+
- Example: `API key: <private>sk-abc123</private>`
|
|
250
|
+
|
|
251
|
+
**File**: `src/utils/tag-stripping.ts`
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
export function stripPrivateTags(text: string): string {
|
|
255
|
+
const MAX_TAG_COUNT = 100; // ReDoS protection
|
|
256
|
+
|
|
257
|
+
return text
|
|
258
|
+
.replace(/<claude-mem-context>[\s\S]*?<\/claude-mem-context>/g, '')
|
|
259
|
+
.replace(/<private>[\s\S]*?<\/private>/g, '');
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Edge Processing Philosophy**: Stripping happens at hook layer (before data reaches database).
|
|
264
|
+
|
|
265
|
+
### What We Should Do
|
|
266
|
+
|
|
267
|
+
**Priority**: HIGH
|
|
268
|
+
|
|
269
|
+
**Implementation**:
|
|
270
|
+
|
|
271
|
+
1. **Add tag stripping to ingester**:
|
|
272
|
+
```ruby
|
|
273
|
+
# lib/claude_memory/ingest/transcript_reader.rb
|
|
274
|
+
class TranscriptReader
|
|
275
|
+
SYSTEM_TAGS = ['claude-memory-context'].freeze
|
|
276
|
+
USER_TAGS = ['private', 'no-memory'].freeze
|
|
277
|
+
MAX_TAG_COUNT = 100
|
|
278
|
+
|
|
279
|
+
def strip_tags(text)
|
|
280
|
+
validate_tag_count(text)
|
|
281
|
+
|
|
282
|
+
ALL_TAGS = SYSTEM_TAGS + USER_TAGS
|
|
283
|
+
ALL_TAGS.each do |tag|
|
|
284
|
+
text = text.gsub(/<#{tag}>.*?<\/#{tag}>/m, '')
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
text
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def validate_tag_count(text)
|
|
291
|
+
count = text.scan(/<(?:#{ALL_TAGS.join('|')})>/).size
|
|
292
|
+
raise "Too many tags (#{count}), possible ReDoS" if count > MAX_TAG_COUNT
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
2. **Document in README**:
|
|
298
|
+
```markdown
|
|
299
|
+
## Privacy Control
|
|
300
|
+
|
|
301
|
+
Wrap sensitive content in `<private>` tags to exclude from storage:
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
API endpoint: https://api.example.com
|
|
305
|
+
API key: <private>sk-abc123def456</private>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
System tags (auto-stripped):
|
|
309
|
+
- `<claude-memory-context>` - Prevents recursive storage of published memory
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
3. **Add to hook handler**:
|
|
313
|
+
```ruby
|
|
314
|
+
# lib/claude_memory/hook/handler.rb
|
|
315
|
+
def ingest_hook
|
|
316
|
+
input = read_stdin
|
|
317
|
+
transcript = input[:transcript_delta]
|
|
318
|
+
|
|
319
|
+
# Strip tags before processing
|
|
320
|
+
transcript = strip_privacy_tags(transcript)
|
|
321
|
+
|
|
322
|
+
ingester.ingest(transcript)
|
|
323
|
+
end
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
4. **Test edge cases**:
|
|
327
|
+
```ruby
|
|
328
|
+
# spec/claude_memory/ingest/transcript_reader_spec.rb
|
|
329
|
+
it "strips nested private tags" do
|
|
330
|
+
text = "Public <private>Secret <private>Nested</private></private> Public"
|
|
331
|
+
expect(strip_tags(text)).to eq("Public Public")
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
it "prevents ReDoS with many tags" do
|
|
335
|
+
text = "<private>" * 101
|
|
336
|
+
expect { strip_tags(text) }.to raise_error(/Too many tags/)
|
|
337
|
+
end
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Benefits**:
|
|
341
|
+
- User control over sensitive data
|
|
342
|
+
- Prevents credential leakage
|
|
343
|
+
- Protects recursive context injection
|
|
344
|
+
- Security-conscious design
|
|
345
|
+
|
|
346
|
+
**Trade-offs**:
|
|
347
|
+
- Users must remember to tag sensitive content
|
|
348
|
+
- May create false sense of security
|
|
349
|
+
- Regex-based (could miss edge cases)
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## 4. Slim Orchestrator Pattern
|
|
354
|
+
|
|
355
|
+
### What claude-mem Does
|
|
356
|
+
|
|
357
|
+
**Worker Service Evolution**: Refactored from 2,000 lines → 300 lines orchestrator.
|
|
358
|
+
|
|
359
|
+
**File Structure**:
|
|
360
|
+
```
|
|
361
|
+
src/services/worker-service.ts (300 lines - orchestrator)
|
|
362
|
+
↓ delegates to
|
|
363
|
+
src/server/Server.ts (Express setup)
|
|
364
|
+
src/services/sqlite/Database.ts (data layer)
|
|
365
|
+
src/services/worker/ (business logic)
|
|
366
|
+
├── SDKAgent.ts (agent management)
|
|
367
|
+
├── SessionManager.ts (session lifecycle)
|
|
368
|
+
└── search/SearchOrchestrator.ts (search strategies)
|
|
369
|
+
src/infrastructure/ (process management)
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Benefit**: Testability, readability, separation of concerns.
|
|
373
|
+
|
|
374
|
+
### What We Should Do
|
|
375
|
+
|
|
376
|
+
**Priority**: MEDIUM
|
|
377
|
+
|
|
378
|
+
**Current State**:
|
|
379
|
+
- `lib/claude_memory/cli.rb`: 800+ lines (all commands)
|
|
380
|
+
- Logic mixed with CLI parsing
|
|
381
|
+
- Hard to test individual commands
|
|
382
|
+
|
|
383
|
+
**Implementation**:
|
|
384
|
+
|
|
385
|
+
1. **Extract command classes**:
|
|
386
|
+
```ruby
|
|
387
|
+
# lib/claude_memory/commands/
|
|
388
|
+
├── base_command.rb
|
|
389
|
+
├── ingest_command.rb
|
|
390
|
+
├── recall_command.rb
|
|
391
|
+
├── publish_command.rb
|
|
392
|
+
├── promote_command.rb
|
|
393
|
+
└── sweep_command.rb
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
2. **Slim CLI to routing**:
|
|
397
|
+
```ruby
|
|
398
|
+
# lib/claude_memory/cli.rb (150 lines)
|
|
399
|
+
module ClaudeMemory
|
|
400
|
+
class CLI
|
|
401
|
+
def run(args)
|
|
402
|
+
command_name = args[0]
|
|
403
|
+
command = command_for(command_name)
|
|
404
|
+
command.run(args[1..])
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
private
|
|
408
|
+
|
|
409
|
+
def command_for(name)
|
|
410
|
+
case name
|
|
411
|
+
when "ingest" then Commands::IngestCommand.new
|
|
412
|
+
when "recall" then Commands::RecallCommand.new
|
|
413
|
+
# ...
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
3. **Command base class**:
|
|
421
|
+
```ruby
|
|
422
|
+
# lib/claude_memory/commands/base_command.rb
|
|
423
|
+
module ClaudeMemory
|
|
424
|
+
module Commands
|
|
425
|
+
class BaseCommand
|
|
426
|
+
def initialize
|
|
427
|
+
@store_manager = Store::StoreManager.new
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def run(args)
|
|
431
|
+
options = parse_options(args)
|
|
432
|
+
validate_options(options)
|
|
433
|
+
execute(options)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
private
|
|
437
|
+
|
|
438
|
+
def parse_options(args)
|
|
439
|
+
raise NotImplementedError
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def execute(options)
|
|
443
|
+
raise NotImplementedError
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
4. **Example command**:
|
|
451
|
+
```ruby
|
|
452
|
+
# lib/claude_memory/commands/recall_command.rb
|
|
453
|
+
module ClaudeMemory
|
|
454
|
+
module Commands
|
|
455
|
+
class RecallCommand < BaseCommand
|
|
456
|
+
def parse_options(args)
|
|
457
|
+
OptionParser.new do |opts|
|
|
458
|
+
opts.on("--query QUERY") { |q| options[:query] = q }
|
|
459
|
+
opts.on("--scope SCOPE") { |s| options[:scope] = s }
|
|
460
|
+
end.parse!(args)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def execute(options)
|
|
464
|
+
results = Recall.search(
|
|
465
|
+
options[:query],
|
|
466
|
+
scope: options[:scope]
|
|
467
|
+
)
|
|
468
|
+
puts format_results(results)
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
**Benefits**:
|
|
476
|
+
- Each command is independently testable
|
|
477
|
+
- CLI.rb becomes simple router
|
|
478
|
+
- Easier to add new commands
|
|
479
|
+
- Clear separation of parsing vs execution
|
|
480
|
+
|
|
481
|
+
**Trade-offs**:
|
|
482
|
+
- More files to navigate
|
|
483
|
+
- Slightly more boilerplate
|
|
484
|
+
- May be overkill for small CLI
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## 5. Health Monitoring and Process Management
|
|
489
|
+
|
|
490
|
+
### What claude-mem Does
|
|
491
|
+
|
|
492
|
+
**Worker Service Management**:
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
// Health check endpoint
|
|
496
|
+
app.get('/health', (req, res) => {
|
|
497
|
+
res.json({
|
|
498
|
+
status: 'ok',
|
|
499
|
+
uptime: process.uptime(),
|
|
500
|
+
port: WORKER_PORT,
|
|
501
|
+
memory: process.memoryUsage(),
|
|
502
|
+
version: packageJson.version
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// Smart startup
|
|
507
|
+
async function ensureWorkerHealthy(timeout = 10000) {
|
|
508
|
+
const healthy = await checkHealth();
|
|
509
|
+
if (!healthy) {
|
|
510
|
+
await startWorker();
|
|
511
|
+
await waitForHealth(timeout);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Process Management**:
|
|
517
|
+
- PID file tracking (`~/.claude-mem/worker.pid`)
|
|
518
|
+
- Port conflict detection
|
|
519
|
+
- Version mismatch warnings
|
|
520
|
+
- Graceful shutdown handlers
|
|
521
|
+
- Platform-aware timeouts (Windows vs Unix)
|
|
522
|
+
|
|
523
|
+
**File**: `src/infrastructure/ProcessManager.ts`
|
|
524
|
+
|
|
525
|
+
### What We Should Do
|
|
526
|
+
|
|
527
|
+
**Priority**: LOW (we use MCP server, not background worker)
|
|
528
|
+
|
|
529
|
+
**Implementation** (if we add background worker):
|
|
530
|
+
|
|
531
|
+
1. **Health endpoint in MCP server**:
|
|
532
|
+
```ruby
|
|
533
|
+
# lib/claude_memory/mcp/server.rb
|
|
534
|
+
def handle_ping
|
|
535
|
+
{
|
|
536
|
+
status: "ok",
|
|
537
|
+
version: ClaudeMemory::VERSION,
|
|
538
|
+
databases: {
|
|
539
|
+
global: File.exist?(global_db_path),
|
|
540
|
+
project: File.exist?(project_db_path)
|
|
541
|
+
},
|
|
542
|
+
uptime: Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start_time
|
|
543
|
+
}
|
|
544
|
+
end
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
2. **PID file management**:
|
|
548
|
+
```ruby
|
|
549
|
+
# lib/claude_memory/daemon.rb
|
|
550
|
+
class Daemon
|
|
551
|
+
PID_FILE = File.expand_path("~/.claude/memory_server.pid")
|
|
552
|
+
|
|
553
|
+
def start
|
|
554
|
+
check_existing_process
|
|
555
|
+
fork_and_daemonize
|
|
556
|
+
write_pid_file
|
|
557
|
+
setup_signal_handlers
|
|
558
|
+
run_server
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def stop
|
|
562
|
+
pid = read_pid_file
|
|
563
|
+
Process.kill("TERM", pid)
|
|
564
|
+
wait_for_shutdown
|
|
565
|
+
remove_pid_file
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
**Benefits**:
|
|
571
|
+
- Reliable server lifecycle
|
|
572
|
+
- Easy debugging (health checks)
|
|
573
|
+
- Prevents duplicate processes
|
|
574
|
+
|
|
575
|
+
**Trade-offs**:
|
|
576
|
+
- Complexity we may not need
|
|
577
|
+
- Ruby daemons are tricky on Windows
|
|
578
|
+
- MCP stdio transport doesn't need health checks
|
|
579
|
+
|
|
580
|
+
**Verdict**: Skip unless we switch to HTTP-based MCP transport.
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
## 6. Semantic Shortcuts and Search Strategies
|
|
585
|
+
|
|
586
|
+
### What claude-mem Does
|
|
587
|
+
|
|
588
|
+
**Semantic Shortcuts** (pre-configured queries):
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
// File: src/services/worker/http/routes/SearchRoutes.ts
|
|
592
|
+
app.get('/api/decisions', (req, res) => {
|
|
593
|
+
const results = await search({ type: 'decision' });
|
|
594
|
+
res.json(results);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
app.get('/api/changes', (req, res) => {
|
|
598
|
+
const results = await search({ type: ['feature', 'change'] });
|
|
599
|
+
res.json(results);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
app.get('/api/how-it-works', (req, res) => {
|
|
603
|
+
const results = await search({ type: 'how-it-works' });
|
|
604
|
+
res.json(results);
|
|
605
|
+
});
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**Search Strategy Pattern**:
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
// File: src/services/worker/search/SearchOrchestrator.ts
|
|
612
|
+
class SearchOrchestrator {
|
|
613
|
+
strategies: [
|
|
614
|
+
ChromaSearchStrategy, // Vector search (if available)
|
|
615
|
+
SQLiteSearchStrategy, // FTS5 fallback
|
|
616
|
+
HybridSearchStrategy // Combine both
|
|
617
|
+
]
|
|
618
|
+
|
|
619
|
+
async search(query, options) {
|
|
620
|
+
const strategy = selectStrategy(options);
|
|
621
|
+
return strategy.execute(query);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
**Fallback Logic**:
|
|
627
|
+
1. Try Chroma vector search (semantic)
|
|
628
|
+
2. Fall back to SQLite FTS5 (keyword)
|
|
629
|
+
3. Merge and re-rank results if both available
|
|
630
|
+
|
|
631
|
+
### What We Should Do
|
|
632
|
+
|
|
633
|
+
**Priority**: MEDIUM
|
|
634
|
+
|
|
635
|
+
**Implementation**:
|
|
636
|
+
|
|
637
|
+
1. **Add shortcut methods to Recall**:
|
|
638
|
+
```ruby
|
|
639
|
+
# lib/claude_memory/recall.rb
|
|
640
|
+
module ClaudeMemory
|
|
641
|
+
class Recall
|
|
642
|
+
class << self
|
|
643
|
+
def recent_decisions(limit: 10)
|
|
644
|
+
search("decision constraint rule", limit:)
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
def architecture_choices(limit: 10)
|
|
648
|
+
search("uses framework implements architecture", limit:)
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
def conventions(limit: 20)
|
|
652
|
+
search("convention style format pattern", scope: :global, limit:)
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def project_config(limit: 10)
|
|
656
|
+
search("uses requires depends_on", scope: :project, limit:)
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
2. **Add MCP tools for shortcuts**:
|
|
664
|
+
```ruby
|
|
665
|
+
# lib/claude_memory/mcp/tools.rb
|
|
666
|
+
TOOLS["memory.decisions"] = {
|
|
667
|
+
description: "Quick access to architectural decisions and constraints",
|
|
668
|
+
input_schema: { type: "object", properties: { limit: { type: "integer" } } }
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
TOOLS["memory.conventions"] = {
|
|
672
|
+
description: "Quick access to coding conventions and preferences",
|
|
673
|
+
input_schema: { type: "object", properties: { limit: { type: "integer" } } }
|
|
674
|
+
}
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
3. **Search strategy pattern** (future: if we add vector search):
|
|
678
|
+
```ruby
|
|
679
|
+
# lib/claude_memory/index/search_strategy.rb
|
|
680
|
+
module ClaudeMemory
|
|
681
|
+
module Index
|
|
682
|
+
class SearchStrategy
|
|
683
|
+
def self.select(options)
|
|
684
|
+
if options[:semantic] && vector_db_available?
|
|
685
|
+
VectorSearchStrategy.new
|
|
686
|
+
else
|
|
687
|
+
LexicalSearchStrategy.new
|
|
688
|
+
end
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
class LexicalSearchStrategy < SearchStrategy
|
|
693
|
+
def search(query)
|
|
694
|
+
LexicalFTS.search(query)
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
class VectorSearchStrategy < SearchStrategy
|
|
699
|
+
def search(query)
|
|
700
|
+
# Future: vector embeddings
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
end
|
|
704
|
+
end
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
**Benefits**:
|
|
708
|
+
- Common queries are one command
|
|
709
|
+
- Reduces cognitive load
|
|
710
|
+
- Pre-optimized for specific use cases
|
|
711
|
+
- Strategy pattern enables future enhancements
|
|
712
|
+
|
|
713
|
+
**Trade-offs**:
|
|
714
|
+
- Need to pick right shortcuts (user research)
|
|
715
|
+
- May not cover all use cases
|
|
716
|
+
- Shortcuts can become stale
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
## 7. Web-Based Viewer UI
|
|
721
|
+
|
|
722
|
+
### What claude-mem Does
|
|
723
|
+
|
|
724
|
+
**Real-Time Memory Viewer** at `http://localhost:37777`:
|
|
725
|
+
|
|
726
|
+
- React-based web UI
|
|
727
|
+
- Server-Sent Events (SSE) for real-time updates
|
|
728
|
+
- Infinite scroll pagination
|
|
729
|
+
- Project filtering
|
|
730
|
+
- Settings persistence (sidebar state, theme)
|
|
731
|
+
- Auto-reconnection with exponential backoff
|
|
732
|
+
- Single-file HTML bundle (esbuild)
|
|
733
|
+
|
|
734
|
+
**File**: `src/ui/viewer/` (React components)
|
|
735
|
+
|
|
736
|
+
**Features**:
|
|
737
|
+
- See observations as they're captured
|
|
738
|
+
- Search historical observations
|
|
739
|
+
- Filter by project
|
|
740
|
+
- Export/share observations
|
|
741
|
+
- Theme toggle (light/dark)
|
|
742
|
+
|
|
743
|
+
**Build**:
|
|
744
|
+
```typescript
|
|
745
|
+
esbuild.build({
|
|
746
|
+
entryPoints: ['src/ui/viewer/index.tsx'],
|
|
747
|
+
bundle: true,
|
|
748
|
+
outfile: 'plugin/ui/viewer.html',
|
|
749
|
+
loader: { '.tsx': 'tsx', '.woff2': 'dataurl' },
|
|
750
|
+
});
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### What We Should Do
|
|
754
|
+
|
|
755
|
+
**Priority**: LOW (nice-to-have)
|
|
756
|
+
|
|
757
|
+
**Implementation** (if we want it):
|
|
758
|
+
|
|
759
|
+
1. **Add Sinatra web server**:
|
|
760
|
+
```ruby
|
|
761
|
+
# lib/claude_memory/web/server.rb
|
|
762
|
+
require 'sinatra/base'
|
|
763
|
+
require 'json'
|
|
764
|
+
|
|
765
|
+
module ClaudeMemory
|
|
766
|
+
module Web
|
|
767
|
+
class Server < Sinatra::Base
|
|
768
|
+
get '/' do
|
|
769
|
+
erb :index
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
get '/api/facts' do
|
|
773
|
+
facts = Recall.search(params[:query], limit: 100)
|
|
774
|
+
json facts
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
get '/api/stream' do
|
|
778
|
+
stream :keep_open do |out|
|
|
779
|
+
# SSE for real-time updates
|
|
780
|
+
EventMachine.add_periodic_timer(1) do
|
|
781
|
+
out << "data: #{recent_facts.to_json}\n\n"
|
|
782
|
+
end
|
|
783
|
+
end
|
|
784
|
+
end
|
|
785
|
+
end
|
|
786
|
+
end
|
|
787
|
+
end
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
2. **Add to MCP server** (optional HTTP endpoint):
|
|
791
|
+
```ruby
|
|
792
|
+
# claude-memory serve --web
|
|
793
|
+
def serve_with_web
|
|
794
|
+
Thread.new { Web::Server.run!(port: 37778) }
|
|
795
|
+
serve_mcp # Main MCP server
|
|
796
|
+
end
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
3. **Simple HTML viewer**:
|
|
800
|
+
```html
|
|
801
|
+
<!-- lib/claude_memory/web/views/index.erb -->
|
|
802
|
+
<!DOCTYPE html>
|
|
803
|
+
<html>
|
|
804
|
+
<head>
|
|
805
|
+
<title>ClaudeMemory Viewer</title>
|
|
806
|
+
<style>/* Minimal CSS */</style>
|
|
807
|
+
</head>
|
|
808
|
+
<body>
|
|
809
|
+
<div id="facts-list"></div>
|
|
810
|
+
<script>
|
|
811
|
+
// Fetch and display facts
|
|
812
|
+
fetch('/api/facts')
|
|
813
|
+
.then(r => r.json())
|
|
814
|
+
.then(facts => render(facts));
|
|
815
|
+
</script>
|
|
816
|
+
</body>
|
|
817
|
+
</html>
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
**Benefits**:
|
|
821
|
+
- Visibility into memory system
|
|
822
|
+
- Debugging tool
|
|
823
|
+
- User trust (transparency)
|
|
824
|
+
|
|
825
|
+
**Trade-offs**:
|
|
826
|
+
- Significant development effort
|
|
827
|
+
- Need to bundle web assets
|
|
828
|
+
- Another dependency (web server)
|
|
829
|
+
- Maintenance burden
|
|
830
|
+
|
|
831
|
+
**Verdict**: Skip for MVP. Consider if users request it.
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
## 8. Dual-Integration Strategy
|
|
836
|
+
|
|
837
|
+
### What claude-mem Does
|
|
838
|
+
|
|
839
|
+
**Plugin + MCP Server Hybrid**:
|
|
840
|
+
|
|
841
|
+
1. **Claude Code Plugin** (primary):
|
|
842
|
+
- Hooks for lifecycle events
|
|
843
|
+
- Worker service for AI processing
|
|
844
|
+
- Installed via marketplace
|
|
845
|
+
|
|
846
|
+
2. **MCP Server** (secondary):
|
|
847
|
+
- Thin wrapper delegating to worker HTTP API
|
|
848
|
+
- Enables Claude Desktop integration
|
|
849
|
+
- Same backend, different frontend
|
|
850
|
+
|
|
851
|
+
**File**: `src/servers/mcp-server.ts` (thin wrapper)
|
|
852
|
+
|
|
853
|
+
```typescript
|
|
854
|
+
// MCP server delegates to worker HTTP API
|
|
855
|
+
const mcpServer = new McpServer({
|
|
856
|
+
name: "claude-mem",
|
|
857
|
+
version: packageJson.version
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
861
|
+
// Fetch tools from worker
|
|
862
|
+
const tools = await fetch('http://localhost:37777/api/mcp/tools');
|
|
863
|
+
return tools.json();
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
867
|
+
// Forward to worker
|
|
868
|
+
const result = await fetch('http://localhost:37777/api/mcp/call', {
|
|
869
|
+
method: 'POST',
|
|
870
|
+
body: JSON.stringify(request.params)
|
|
871
|
+
});
|
|
872
|
+
return result.json();
|
|
873
|
+
});
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
**Benefit**: One backend, multiple frontends.
|
|
877
|
+
|
|
878
|
+
### What We Should Do
|
|
879
|
+
|
|
880
|
+
**Priority**: LOW
|
|
881
|
+
|
|
882
|
+
**Current State**: We only have MCP server (no plugin hooks yet).
|
|
883
|
+
|
|
884
|
+
**Implementation** (if we add Claude Code hooks):
|
|
885
|
+
|
|
886
|
+
1. **Keep MCP server as primary**:
|
|
887
|
+
```ruby
|
|
888
|
+
# lib/claude_memory/mcp/server.rb
|
|
889
|
+
# Current implementation - keep as-is
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
2. **Add hook handlers**:
|
|
893
|
+
```ruby
|
|
894
|
+
# lib/claude_memory/hook/handler.rb
|
|
895
|
+
# Delegate to same store manager
|
|
896
|
+
def ingest_hook
|
|
897
|
+
store_manager = Store::StoreManager.new
|
|
898
|
+
ingester = Ingest::Ingester.new(store_manager)
|
|
899
|
+
ingester.ingest(read_stdin[:transcript_delta])
|
|
900
|
+
end
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
3. **Shared backend**:
|
|
904
|
+
```
|
|
905
|
+
MCP Server (stdio) ──┐
|
|
906
|
+
├──> Store::StoreManager ──> SQLite
|
|
907
|
+
Hook Handler (stdin) ─┘
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
**Benefits**:
|
|
911
|
+
- Works with both Claude Code and Claude Desktop
|
|
912
|
+
- No duplicate logic
|
|
913
|
+
- Clean separation of transport vs business logic
|
|
914
|
+
|
|
915
|
+
**Trade-offs**:
|
|
916
|
+
- More integration points to maintain
|
|
917
|
+
- Hook contract is Claude Code-specific
|
|
918
|
+
|
|
919
|
+
**Verdict**: Consider if we add Claude Code hooks (not urgent).
|
|
920
|
+
|
|
921
|
+
---
|
|
922
|
+
|
|
923
|
+
## 9. Exit Code Strategy for Hooks
|
|
924
|
+
|
|
925
|
+
### What claude-mem Does
|
|
926
|
+
|
|
927
|
+
**Hook Exit Code Contract**:
|
|
928
|
+
|
|
929
|
+
```typescript
|
|
930
|
+
// Success or graceful shutdown
|
|
931
|
+
process.exit(0); // Windows Terminal closes tab
|
|
932
|
+
|
|
933
|
+
// Non-blocking error (show to user, continue)
|
|
934
|
+
console.error("Warning: ...");
|
|
935
|
+
process.exit(1);
|
|
936
|
+
|
|
937
|
+
// Blocking error (feed to Claude for processing)
|
|
938
|
+
console.error("ERROR: ...");
|
|
939
|
+
process.exit(2);
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
**Philosophy**: Worker/hook errors exit with 0 to prevent Windows Terminal tab accumulation.
|
|
943
|
+
|
|
944
|
+
**File**: `docs/context/claude-code/exit-codes.md`
|
|
945
|
+
|
|
946
|
+
### What We Should Do
|
|
947
|
+
|
|
948
|
+
**Priority**: MEDIUM (if we add hooks)
|
|
949
|
+
|
|
950
|
+
**Implementation**:
|
|
951
|
+
|
|
952
|
+
1. **Define exit code constants**:
|
|
953
|
+
```ruby
|
|
954
|
+
# lib/claude_memory/hook/exit_codes.rb
|
|
955
|
+
module ClaudeMemory
|
|
956
|
+
module Hook
|
|
957
|
+
module ExitCodes
|
|
958
|
+
SUCCESS = 0
|
|
959
|
+
WARNING = 1 # Non-blocking error
|
|
960
|
+
ERROR = 2 # Blocking error
|
|
961
|
+
end
|
|
962
|
+
end
|
|
963
|
+
end
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
2. **Use in hook handler**:
|
|
967
|
+
```ruby
|
|
968
|
+
# lib/claude_memory/hook/handler.rb
|
|
969
|
+
def run
|
|
970
|
+
handle_hook(ARGV[0])
|
|
971
|
+
exit ExitCodes::SUCCESS
|
|
972
|
+
rescue NonBlockingError => e
|
|
973
|
+
warn e.message
|
|
974
|
+
exit ExitCodes::WARNING
|
|
975
|
+
rescue => e
|
|
976
|
+
$stderr.puts "ERROR: #{e.message}"
|
|
977
|
+
exit ExitCodes::ERROR
|
|
978
|
+
end
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
3. **Document in CLAUDE.md**:
|
|
982
|
+
```markdown
|
|
983
|
+
## Hook Exit Codes
|
|
984
|
+
|
|
985
|
+
- **0**: Success or graceful shutdown
|
|
986
|
+
- **1**: Non-blocking error (shown to user, session continues)
|
|
987
|
+
- **2**: Blocking error (fed to Claude for processing)
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
**Benefits**:
|
|
991
|
+
- Clear contract with Claude Code
|
|
992
|
+
- Predictable behavior
|
|
993
|
+
- Better error handling
|
|
994
|
+
|
|
995
|
+
**Trade-offs**:
|
|
996
|
+
- Hook-specific pattern
|
|
997
|
+
- Not applicable to MCP server
|
|
998
|
+
|
|
999
|
+
---
|
|
1000
|
+
|
|
1001
|
+
## 10. Configuration-Driven Context Injection
|
|
1002
|
+
|
|
1003
|
+
### What claude-mem Does
|
|
1004
|
+
|
|
1005
|
+
**Context Config File**: `~/.claude-mem/settings.json`
|
|
1006
|
+
|
|
1007
|
+
```json
|
|
1008
|
+
{
|
|
1009
|
+
"context": {
|
|
1010
|
+
"mode": "reader", // reader | chat | inference
|
|
1011
|
+
"observations": {
|
|
1012
|
+
"enabled": true,
|
|
1013
|
+
"limit": 10,
|
|
1014
|
+
"types": ["decision", "gotcha", "trade-off"]
|
|
1015
|
+
},
|
|
1016
|
+
"summaries": {
|
|
1017
|
+
"enabled": true,
|
|
1018
|
+
"fields": ["request", "learned", "completed"]
|
|
1019
|
+
},
|
|
1020
|
+
"timeline": {
|
|
1021
|
+
"depth": 5
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
**File**: `src/services/context/ContextConfigLoader.ts`
|
|
1028
|
+
|
|
1029
|
+
**Benefit**: Users can fine-tune what gets injected.
|
|
1030
|
+
|
|
1031
|
+
### What We Should Do
|
|
1032
|
+
|
|
1033
|
+
**Priority**: LOW
|
|
1034
|
+
|
|
1035
|
+
**Implementation**:
|
|
1036
|
+
|
|
1037
|
+
1. **Add config file**:
|
|
1038
|
+
```ruby
|
|
1039
|
+
# ~/.claude/memory_config.yml
|
|
1040
|
+
publish:
|
|
1041
|
+
mode: shared # shared | local | home
|
|
1042
|
+
facts:
|
|
1043
|
+
limit: 50
|
|
1044
|
+
scopes: [global, project]
|
|
1045
|
+
predicates: [uses_*, depends_on, has_constraint]
|
|
1046
|
+
entities:
|
|
1047
|
+
limit: 20
|
|
1048
|
+
conflicts:
|
|
1049
|
+
show: true
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
2. **Load in publisher**:
|
|
1053
|
+
```ruby
|
|
1054
|
+
# lib/claude_memory/publish.rb
|
|
1055
|
+
class Publisher
|
|
1056
|
+
def initialize
|
|
1057
|
+
@config = load_config
|
|
1058
|
+
end
|
|
1059
|
+
|
|
1060
|
+
def load_config
|
|
1061
|
+
path = File.expand_path("~/.claude/memory_config.yml")
|
|
1062
|
+
YAML.load_file(path) if File.exist?(path)
|
|
1063
|
+
rescue
|
|
1064
|
+
default_config
|
|
1065
|
+
end
|
|
1066
|
+
end
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
3. **Apply during publish**:
|
|
1070
|
+
```ruby
|
|
1071
|
+
def build_snapshot
|
|
1072
|
+
config = @config[:publish]
|
|
1073
|
+
|
|
1074
|
+
facts = store.facts(
|
|
1075
|
+
limit: config[:facts][:limit],
|
|
1076
|
+
scopes: config[:facts][:scopes]
|
|
1077
|
+
)
|
|
1078
|
+
|
|
1079
|
+
format_snapshot(facts, config)
|
|
1080
|
+
end
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
**Benefits**:
|
|
1084
|
+
- User control over published content
|
|
1085
|
+
- Environment-specific configs
|
|
1086
|
+
- Reduces noise in generated files
|
|
1087
|
+
|
|
1088
|
+
**Trade-offs**:
|
|
1089
|
+
- Another config file to document
|
|
1090
|
+
- May confuse users
|
|
1091
|
+
- Publish should be opinionated by default
|
|
1092
|
+
|
|
1093
|
+
**Verdict**: Skip for MVP. Default config is sufficient.
|
|
1094
|
+
|
|
1095
|
+
---
|
|
1096
|
+
|
|
1097
|
+
## Features We're Already Doing Better
|
|
1098
|
+
|
|
1099
|
+
### 1. Dual-Database Architecture (Global + Project)
|
|
1100
|
+
|
|
1101
|
+
**Our Advantage**: `Store::StoreManager` with global + project scopes.
|
|
1102
|
+
|
|
1103
|
+
Claude-mem has a single database with project filtering. Our approach is cleaner:
|
|
1104
|
+
|
|
1105
|
+
```ruby
|
|
1106
|
+
# We separate global vs project knowledge
|
|
1107
|
+
@global_store = Store::SqliteStore.new(global_db_path)
|
|
1108
|
+
@project_store = Store::SqliteStore.new(project_db_path)
|
|
1109
|
+
|
|
1110
|
+
# Claude-mem filters post-query
|
|
1111
|
+
SELECT * FROM observations WHERE project = ?
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
**Keep this.** It's a better design.
|
|
1115
|
+
|
|
1116
|
+
### 2. Fact-Based Knowledge Graph
|
|
1117
|
+
|
|
1118
|
+
**Our Advantage**: Subject-predicate-object triples with provenance.
|
|
1119
|
+
|
|
1120
|
+
Claude-mem stores blob observations. We store structured facts:
|
|
1121
|
+
|
|
1122
|
+
```ruby
|
|
1123
|
+
# Ours (structured)
|
|
1124
|
+
{ subject: "project", predicate: "uses_database", object: "PostgreSQL" }
|
|
1125
|
+
|
|
1126
|
+
# Theirs (blob)
|
|
1127
|
+
{ title: "Uses PostgreSQL", narrative: "The project uses..." }
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
**Keep this.** Enables richer queries and inference.
|
|
1131
|
+
|
|
1132
|
+
### 3. Truth Maintenance System
|
|
1133
|
+
|
|
1134
|
+
**Our Advantage**: `Resolve::Resolver` with supersession and conflicts.
|
|
1135
|
+
|
|
1136
|
+
Claude-mem doesn't resolve contradictions. We do:
|
|
1137
|
+
|
|
1138
|
+
```ruby
|
|
1139
|
+
# We detect when facts supersede each other
|
|
1140
|
+
old: { subject: "api", predicate: "uses_auth", object: "JWT" }
|
|
1141
|
+
new: { subject: "api", predicate: "uses_auth", object: "OAuth2" }
|
|
1142
|
+
# → Creates supersession link
|
|
1143
|
+
|
|
1144
|
+
# We detect conflicts
|
|
1145
|
+
fact1: { subject: "api", predicate: "rate_limit", object: "100/min" }
|
|
1146
|
+
fact2: { subject: "api", predicate: "rate_limit", object: "1000/min" }
|
|
1147
|
+
# → Creates conflict record
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
**Keep this.** It's a core differentiator.
|
|
1151
|
+
|
|
1152
|
+
### 4. Predicate Policies
|
|
1153
|
+
|
|
1154
|
+
**Our Advantage**: `Resolve::PredicatePolicy` for single vs multi-value.
|
|
1155
|
+
|
|
1156
|
+
Claude-mem doesn't distinguish. We do:
|
|
1157
|
+
|
|
1158
|
+
```ruby
|
|
1159
|
+
# Single-value (supersedes)
|
|
1160
|
+
"uses_database" → only one database at a time
|
|
1161
|
+
|
|
1162
|
+
# Multi-value (accumulates)
|
|
1163
|
+
"depends_on" → many dependencies
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
**Keep this.** Prevents false conflicts.
|
|
1167
|
+
|
|
1168
|
+
### 5. Ruby Ecosystem (Simpler)
|
|
1169
|
+
|
|
1170
|
+
**Our Advantage**: Fewer dependencies, easier install.
|
|
1171
|
+
|
|
1172
|
+
```ruby
|
|
1173
|
+
# Ours
|
|
1174
|
+
gem install claude_memory # Done
|
|
1175
|
+
|
|
1176
|
+
# Theirs
|
|
1177
|
+
npm install # Needs Node.js
|
|
1178
|
+
npm install chromadb # Needs Python + pip
|
|
1179
|
+
npm install better-sqlite3 # Needs node-gyp + build tools
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
**Keep this.** Ruby's stdlib is excellent.
|
|
1183
|
+
|
|
1184
|
+
---
|
|
1185
|
+
|
|
1186
|
+
## Features to Avoid
|
|
1187
|
+
|
|
1188
|
+
### 1. Chroma Vector Database
|
|
1189
|
+
|
|
1190
|
+
**Their Approach**: Hybrid SQLite FTS5 + Chroma vector search.
|
|
1191
|
+
|
|
1192
|
+
**Our Take**: **Skip it.** Adds significant complexity:
|
|
1193
|
+
|
|
1194
|
+
- Python dependency
|
|
1195
|
+
- ChromaDB server
|
|
1196
|
+
- Embedding generation
|
|
1197
|
+
- Sync overhead
|
|
1198
|
+
|
|
1199
|
+
**Alternative**: Stick with SQLite FTS5. Add embeddings only if users request semantic search.
|
|
1200
|
+
|
|
1201
|
+
### 2. Claude Agent SDK for Distillation
|
|
1202
|
+
|
|
1203
|
+
**Their Approach**: Use `@anthropic-ai/claude-agent-sdk` for observation compression.
|
|
1204
|
+
|
|
1205
|
+
**Our Take**: **Skip it.** We already have `Distill::Distiller` interface. SDK adds:
|
|
1206
|
+
|
|
1207
|
+
- Node.js dependency
|
|
1208
|
+
- Subprocess management
|
|
1209
|
+
- Complex event loop
|
|
1210
|
+
|
|
1211
|
+
**Alternative**: Direct API calls via `anthropic-rb` gem (if we implement distiller).
|
|
1212
|
+
|
|
1213
|
+
### 3. Worker Service Background Process
|
|
1214
|
+
|
|
1215
|
+
**Their Approach**: Long-running worker with HTTP API + MCP wrapper.
|
|
1216
|
+
|
|
1217
|
+
**Our Take**: **Skip it.** We use MCP server directly:
|
|
1218
|
+
|
|
1219
|
+
- No background process to manage
|
|
1220
|
+
- No port conflicts
|
|
1221
|
+
- No PID files
|
|
1222
|
+
- Simpler deployment
|
|
1223
|
+
|
|
1224
|
+
**Alternative**: Keep stdio-based MCP server. Add HTTP transport only if needed.
|
|
1225
|
+
|
|
1226
|
+
### 4. Web Viewer UI
|
|
1227
|
+
|
|
1228
|
+
**Their Approach**: React-based web UI at `http://localhost:37777`.
|
|
1229
|
+
|
|
1230
|
+
**Our Take**: **Skip for MVP.** Significant effort for uncertain value:
|
|
1231
|
+
|
|
1232
|
+
- React + esbuild
|
|
1233
|
+
- SSE implementation
|
|
1234
|
+
- State management
|
|
1235
|
+
- CSS/theming
|
|
1236
|
+
|
|
1237
|
+
**Alternative**: CLI output is sufficient. Add web UI if users request it.
|
|
1238
|
+
|
|
1239
|
+
---
|
|
1240
|
+
|
|
1241
|
+
## Implementation Priorities
|
|
1242
|
+
|
|
1243
|
+
### High Priority (Next Sprint)
|
|
1244
|
+
|
|
1245
|
+
1. **Progressive Disclosure Pattern** - Add index format to Recall, update MCP tools
|
|
1246
|
+
2. **Privacy Tag System** - Implement `<private>` tag stripping
|
|
1247
|
+
3. **Exit Code Strategy** - Define exit codes for future hooks
|
|
1248
|
+
|
|
1249
|
+
### Medium Priority (Next Quarter)
|
|
1250
|
+
|
|
1251
|
+
4. **ROI Metrics** - Track token economics
|
|
1252
|
+
5. **Slim Orchestrator Pattern** - Extract commands from CLI
|
|
1253
|
+
6. **Semantic Shortcuts** - Add convenience methods to Recall
|
|
1254
|
+
7. **Search Strategies** - Prepare for future vector search
|
|
1255
|
+
|
|
1256
|
+
### Low Priority (Future)
|
|
1257
|
+
|
|
1258
|
+
8. **Health Monitoring** - Only if we add background worker
|
|
1259
|
+
9. **Dual Integration** - Only if we add Claude Code hooks
|
|
1260
|
+
10. **Config-Driven Context** - Only if users request customization
|
|
1261
|
+
11. **Web Viewer UI** - Only if users request visualization
|
|
1262
|
+
|
|
1263
|
+
---
|
|
1264
|
+
|
|
1265
|
+
## Migration Path
|
|
1266
|
+
|
|
1267
|
+
### Phase 1: Quick Wins (1-2 weeks)
|
|
1268
|
+
|
|
1269
|
+
- [ ] Implement `<private>` tag stripping in ingester
|
|
1270
|
+
- [ ] Add token count estimation to facts
|
|
1271
|
+
- [ ] Create index format in Recall
|
|
1272
|
+
- [ ] Add `memory.recall_index` MCP tool
|
|
1273
|
+
- [ ] Document progressive disclosure pattern
|
|
1274
|
+
|
|
1275
|
+
### Phase 2: Structural (1 month)
|
|
1276
|
+
|
|
1277
|
+
- [ ] Extract command classes from CLI
|
|
1278
|
+
- [ ] Add metrics table for token tracking
|
|
1279
|
+
- [ ] Implement semantic shortcuts
|
|
1280
|
+
- [ ] Add search strategy pattern (prep for vector search)
|
|
1281
|
+
|
|
1282
|
+
### Phase 3: Advanced (3+ months)
|
|
1283
|
+
|
|
1284
|
+
- [ ] Add vector embeddings (if requested)
|
|
1285
|
+
- [ ] Build web viewer (if requested)
|
|
1286
|
+
- [ ] Add Claude Code hooks (if requested)
|
|
1287
|
+
- [ ] Implement background worker (if needed)
|
|
1288
|
+
|
|
1289
|
+
---
|
|
1290
|
+
|
|
1291
|
+
## Key Takeaways
|
|
1292
|
+
|
|
1293
|
+
**What claude-mem does exceptionally well**:
|
|
1294
|
+
1. Progressive disclosure (token efficiency)
|
|
1295
|
+
2. ROI metrics (visibility)
|
|
1296
|
+
3. Privacy controls (user trust)
|
|
1297
|
+
4. Clean architecture (maintainability)
|
|
1298
|
+
5. Production polish (error handling, logging, health checks)
|
|
1299
|
+
|
|
1300
|
+
**What we do better**:
|
|
1301
|
+
1. Dual-database architecture (global + project)
|
|
1302
|
+
2. Fact-based knowledge graph (structured)
|
|
1303
|
+
3. Truth maintenance (conflict resolution)
|
|
1304
|
+
4. Predicate policies (semantic understanding)
|
|
1305
|
+
5. Simpler dependencies (Ruby ecosystem)
|
|
1306
|
+
|
|
1307
|
+
**Our path forward**:
|
|
1308
|
+
- Adopt their token efficiency patterns
|
|
1309
|
+
- Keep our knowledge graph architecture
|
|
1310
|
+
- Add privacy controls
|
|
1311
|
+
- Improve observability (metrics)
|
|
1312
|
+
- Maintain simplicity (avoid over-engineering)
|
|
1313
|
+
|
|
1314
|
+
---
|
|
1315
|
+
|
|
1316
|
+
## References
|
|
1317
|
+
|
|
1318
|
+
- [claude-mem GitHub](https://github.com/thedotmack/claude-mem)
|
|
1319
|
+
- [Architecture Evolution](../claude-mem/docs/public/architecture-evolution.mdx)
|
|
1320
|
+
- [Progressive Disclosure Philosophy](../claude-mem/docs/public/progressive-disclosure.mdx)
|
|
1321
|
+
- [ClaudeMemory Updated Plan](updated_plan.md)
|
|
1322
|
+
|
|
1323
|
+
---
|
|
1324
|
+
|
|
1325
|
+
*This analysis represents a critical review of production-grade patterns that have proven effective in real-world usage. Our goal is to learn from claude-mem's strengths while preserving the unique advantages of our fact-based approach.*
|