claude_memory 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.claude/CLAUDE.md +1 -1
- data/.claude/rules/claude_memory.generated.md +14 -1
- data/.claude/skills/check-memory/SKILL.md +10 -0
- data/.claude/skills/improve/SKILL.md +12 -1
- data/.claude-plugin/plugin.json +1 -1
- data/CHANGELOG.md +70 -0
- data/db/migrations/008_add_provenance_line_range.rb +21 -0
- data/db/migrations/009_add_docid.rb +39 -0
- data/db/migrations/010_add_llm_cache.rb +30 -0
- data/docs/improvements.md +72 -1084
- data/docs/influence/claude-supermemory.md +498 -0
- data/docs/influence/qmd.md +424 -2022
- data/docs/quality_review.md +64 -705
- data/lib/claude_memory/commands/doctor_command.rb +45 -4
- data/lib/claude_memory/commands/explain_command.rb +11 -6
- data/lib/claude_memory/commands/stats_command.rb +1 -1
- data/lib/claude_memory/core/fact_graph.rb +122 -0
- data/lib/claude_memory/core/fact_query_builder.rb +34 -14
- data/lib/claude_memory/core/fact_ranker.rb +3 -20
- data/lib/claude_memory/core/relative_time.rb +45 -0
- data/lib/claude_memory/core/result_sorter.rb +2 -2
- data/lib/claude_memory/core/rr_fusion.rb +57 -0
- data/lib/claude_memory/core/snippet_extractor.rb +97 -0
- data/lib/claude_memory/domain/fact.rb +3 -1
- data/lib/claude_memory/index/index_query.rb +2 -0
- data/lib/claude_memory/index/lexical_fts.rb +18 -0
- data/lib/claude_memory/infrastructure/operation_tracker.rb +7 -21
- data/lib/claude_memory/infrastructure/schema_validator.rb +30 -25
- data/lib/claude_memory/ingest/content_sanitizer.rb +8 -1
- data/lib/claude_memory/ingest/ingester.rb +67 -56
- data/lib/claude_memory/ingest/tool_extractor.rb +1 -1
- data/lib/claude_memory/ingest/tool_filter.rb +55 -0
- data/lib/claude_memory/logging/logger.rb +112 -0
- data/lib/claude_memory/mcp/query_guide.rb +96 -0
- data/lib/claude_memory/mcp/response_formatter.rb +86 -23
- data/lib/claude_memory/mcp/server.rb +34 -4
- data/lib/claude_memory/mcp/text_summary.rb +257 -0
- data/lib/claude_memory/mcp/tool_definitions.rb +20 -4
- data/lib/claude_memory/mcp/tools.rb +133 -120
- data/lib/claude_memory/publish.rb +12 -2
- data/lib/claude_memory/recall/expansion_detector.rb +44 -0
- data/lib/claude_memory/recall.rb +93 -41
- data/lib/claude_memory/resolve/resolver.rb +72 -40
- data/lib/claude_memory/store/sqlite_store.rb +99 -24
- data/lib/claude_memory/sweep/sweeper.rb +6 -0
- data/lib/claude_memory/version.rb +1 -1
- data/lib/claude_memory.rb +21 -0
- metadata +14 -2
- data/docs/remaining_improvements.md +0 -330
data/docs/quality_review.md
CHANGED
|
@@ -1,743 +1,102 @@
|
|
|
1
1
|
# Code Quality Review - Ruby Best Practices
|
|
2
2
|
|
|
3
|
-
**Review Date:** 2026-
|
|
4
|
-
|
|
5
|
-
**
|
|
3
|
+
**Review Date:** 2026-02-04
|
|
4
|
+
**Previous Review:** 2026-01-29
|
|
5
|
+
**Last Quality Update:** 2026-02-04 (21/24 items completed)
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## Executive Summary
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
### Major Wins Since Last Review ✅
|
|
14
|
-
|
|
15
|
-
1. **Recall.rb Reduced 24%**: 754 → 575 lines, 58 → 11 visible public methods
|
|
16
|
-
2. **MCP Tools.rb Refactored 43%**: 1,039 → 592 lines with proper extractions
|
|
17
|
-
3. **MCP Modules Extracted**: 683 lines properly separated into 3 new modules:
|
|
18
|
-
- ResponseFormatter (331 lines) - Pure formatting logic
|
|
19
|
-
- ToolDefinitions (279 lines) - Tool schemas as data
|
|
20
|
-
- SetupStatusAnalyzer (73 lines) - Pure status analysis
|
|
21
|
-
4. **OperationTracker Fixed**: JSON functions replaced with Ruby JSON handling ✅
|
|
22
|
-
5. **DualQueryTemplate Added**: 64 lines, eliminates dual-database query duplication
|
|
23
|
-
6. **More Pure Core Classes**: ConceptRanker (74 lines), FactQueryBuilder (154 lines)
|
|
24
|
-
|
|
25
|
-
### Critical Achievements
|
|
26
|
-
|
|
27
|
-
The codebase has crossed a major quality threshold:
|
|
28
|
-
- **God objects resolved**: Both Recall and MCP Tools dramatically reduced
|
|
29
|
-
- **Functional core growing**: 17+ pure logic classes in Core/
|
|
30
|
-
- **Proper extractions**: ResponseFormatter, ToolDefinitions, SetupStatusAnalyzer
|
|
31
|
-
- **Strategy emerging**: DualQueryTemplate shows path to full strategy pattern
|
|
32
|
-
|
|
33
|
-
### Remaining Work
|
|
34
|
-
|
|
35
|
-
Despite excellent progress, some refinements remain:
|
|
36
|
-
1. Complete strategy pattern extraction in Recall (legacy mode conditionals still present)
|
|
37
|
-
2. Individual tool classes for MCP (optional improvement)
|
|
38
|
-
3. String timestamps to DateTime migration
|
|
39
|
-
4. Result objects for consistent returns
|
|
40
|
-
5. Constructor side effects in LexicalFTS
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## 1. Sandi Metz Perspective (POODR)
|
|
45
|
-
|
|
46
|
-
### What's Been Fixed Since Last Review ✅
|
|
47
|
-
|
|
48
|
-
- **Recall.rb reduced 24%**: 754 → 575 lines, method count 58 → visible public methods ~11
|
|
49
|
-
- **MCP Tools.rb refactored 43%**: 1,039 → 592 lines
|
|
50
|
-
- **Three major extractions**:
|
|
51
|
-
- ResponseFormatter (331 lines) - Pure formatting logic
|
|
52
|
-
- ToolDefinitions (279 lines) - Tool schemas as data
|
|
53
|
-
- SetupStatusAnalyzer (73 lines) - Pure status analysis
|
|
54
|
-
- **DualQueryTemplate extracted**: 64 lines, eliminates duplication
|
|
55
|
-
- **FactQueryBuilder extracted**: 154 lines, pure query construction
|
|
56
|
-
|
|
57
|
-
**Evidence of Progress:**
|
|
58
|
-
|
|
59
|
-
```ruby
|
|
60
|
-
# recall.rb:575 total lines (down from 754)
|
|
61
|
-
# Now clearly organized:
|
|
62
|
-
# - 11 public query methods (lines 42-124)
|
|
63
|
-
# - Private implementation methods well-separated
|
|
64
|
-
# - Uses DualQueryTemplate to eliminate duplication
|
|
65
|
-
|
|
66
|
-
# mcp/tools.rb:592 total lines (down from 1,039)
|
|
67
|
-
# Clean delegation:
|
|
68
|
-
def recall(args)
|
|
69
|
-
results = @recall.query(args["query"], limit: limit, scope: scope)
|
|
70
|
-
ResponseFormatter.format_recall_results(results) # Extracted!
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# New extractions show proper SRP:
|
|
74
|
-
# response_formatter.rb:331 lines - ONLY formatting
|
|
75
|
-
# tool_definitions.rb:279 lines - ONLY schemas
|
|
76
|
-
# setup_status_analyzer.rb:73 lines - ONLY status logic
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
### Issues Remaining
|
|
80
|
-
|
|
81
|
-
#### 🟡 Medium Priority: Complete Strategy Pattern in Recall
|
|
82
|
-
|
|
83
|
-
**Status**: Recall still has legacy mode conditional routing, but impact is now minor:
|
|
84
|
-
|
|
85
|
-
```ruby
|
|
86
|
-
# recall.rb still has legacy mode checks
|
|
87
|
-
def query(query_text, limit: 10, scope: SCOPE_ALL)
|
|
88
|
-
if @legacy_mode
|
|
89
|
-
query_legacy(query_text, limit: limit, scope: scope)
|
|
90
|
-
else
|
|
91
|
-
query_dual(query_text, limit: limit, scope: scope)
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
**However**, this is now much less problematic because:
|
|
97
|
-
- Only 10 routing conditionals (down from dozens)
|
|
98
|
-
- DualQueryTemplate handles dual-mode elegantly
|
|
99
|
-
- Legacy mode is for backwards compatibility only
|
|
100
|
-
- File size is reasonable (575 lines)
|
|
101
|
-
|
|
102
|
-
**Sandi Metz Says:** "This is now acceptable. Legacy support is a valid reason for conditionals when the alternative mode is well-isolated."
|
|
103
|
-
|
|
104
|
-
**Recommended (Optional) Fix:**
|
|
105
|
-
|
|
106
|
-
```ruby
|
|
107
|
-
# Could complete strategy pattern, but not urgent
|
|
108
|
-
class Recall
|
|
109
|
-
def initialize(store_or_manager, **options)
|
|
110
|
-
@strategy = build_strategy(store_or_manager, options)
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def query(query_text, limit: 10, scope: SCOPE_ALL)
|
|
114
|
-
@strategy.query(query_text, limit: limit, scope: scope)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
private
|
|
118
|
-
|
|
119
|
-
def build_strategy(store_or_manager, options)
|
|
120
|
-
if store_or_manager.is_a?(Store::StoreManager)
|
|
121
|
-
Recall::DualStoreStrategy.new(store_or_manager, options)
|
|
122
|
-
else
|
|
123
|
-
Recall::LegacyStoreStrategy.new(store_or_manager, options)
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
**Estimated Effort:** 1-2 days (optional refinement)
|
|
130
|
-
|
|
131
|
-
**Priority:** 🟡 Medium (system works well as-is)
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## 2. Jeremy Evans Perspective (Sequel Expert)
|
|
136
|
-
|
|
137
|
-
### What's Been Fixed Since Last Review ✅
|
|
138
|
-
|
|
139
|
-
- **OperationTracker JSON functions FIXED**: Now uses Ruby JSON handling! ✅
|
|
140
|
-
- **WAL checkpoint added**: `checkpoint_wal` method implemented ✅
|
|
141
|
-
- **Migrations stable**: 7 proper Sequel migration files
|
|
142
|
-
- **Transaction safety**: Used consistently in critical operations
|
|
143
|
-
|
|
144
|
-
**Evidence:**
|
|
145
|
-
|
|
146
|
-
```ruby
|
|
147
|
-
# operation_tracker.rb:113-125 - NOW FIXED!
|
|
148
|
-
stuck.all.each do |op|
|
|
149
|
-
checkpoint = op[:checkpoint_data] ? JSON.parse(op[:checkpoint_data]) : {}
|
|
150
|
-
checkpoint["error"] = error_message # Ruby hash manipulation!
|
|
151
|
-
|
|
152
|
-
@store.db[:operation_progress]
|
|
153
|
-
.where(id: op[:id])
|
|
154
|
-
.update(
|
|
155
|
-
status: "failed",
|
|
156
|
-
completed_at: now,
|
|
157
|
-
checkpoint_data: JSON.generate(checkpoint) # Ruby JSON!
|
|
158
|
-
)
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
# sqlite_store.rb:40-42 - WAL checkpoint added!
|
|
162
|
-
def checkpoint_wal
|
|
163
|
-
@db.run("PRAGMA wal_checkpoint(TRUNCATE)")
|
|
164
|
-
end
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
**Jeremy Evans Would Say:** "Excellent! This is how you handle JSON in Ruby applications."
|
|
168
|
-
|
|
169
|
-
### Issues Remaining
|
|
170
|
-
|
|
171
|
-
#### 🟡 Medium Priority: String Timestamps Throughout
|
|
172
|
-
|
|
173
|
-
**Problem**: Still using ISO8601 strings instead of DateTime columns:
|
|
174
|
-
|
|
175
|
-
```ruby
|
|
176
|
-
# sqlite_store.rb:102
|
|
177
|
-
now = Time.now.utc.iso8601
|
|
178
|
-
|
|
179
|
-
# Found 17 occurrences of Time.now.utc.iso8601 pattern
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
**Jeremy Evans Would Say:** "Use DateTime columns for proper date operations."
|
|
183
|
-
|
|
184
|
-
**Recommended Fix:**
|
|
185
|
-
|
|
186
|
-
```ruby
|
|
187
|
-
# Migration to convert to DateTime
|
|
188
|
-
Sequel.migration do
|
|
189
|
-
up do
|
|
190
|
-
alter_table(:content_items) do
|
|
191
|
-
add_column :occurred_at_dt, DateTime
|
|
192
|
-
add_column :ingested_at_dt, DateTime
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
# Batch convert
|
|
196
|
-
self[:content_items].all.each do |row|
|
|
197
|
-
self[:content_items].where(id: row[:id]).update(
|
|
198
|
-
occurred_at_dt: Time.parse(row[:occurred_at]),
|
|
199
|
-
ingested_at_dt: Time.parse(row[:ingested_at])
|
|
200
|
-
)
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
alter_table(:content_items) do
|
|
204
|
-
drop_column :occurred_at
|
|
205
|
-
drop_column :ingested_at
|
|
206
|
-
rename_column :occurred_at_dt, :occurred_at
|
|
207
|
-
rename_column :ingested_at_dt, :ingested_at
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
# Then enable Sequel timestamps plugin
|
|
213
|
-
plugin :timestamps, update_on_create: true
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
**Estimated Effort:** 1-2 days
|
|
217
|
-
|
|
218
|
-
**Priority:** 🟡 Medium (current approach works, but DateTime is better practice)
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## 3. Kent Beck Perspective (TDD, Simple Design)
|
|
223
|
-
|
|
224
|
-
### What's Been Fixed Since Last Review ✅
|
|
225
|
-
|
|
226
|
-
- **DualQueryTemplate**: Beautiful extraction eliminating conditional duplication
|
|
227
|
-
- **DoctorCommand**: Still exemplary at 31 lines
|
|
228
|
-
- **OperationTracker**: Now has clean Ruby logic
|
|
229
|
-
- **Check classes**: 5 specialized classes, each focused
|
|
230
|
-
|
|
231
|
-
**Evidence:**
|
|
232
|
-
|
|
233
|
-
```ruby
|
|
234
|
-
# dual_query_template.rb:22-34 - Simple and elegant!
|
|
235
|
-
def execute(scope:, limit: nil, &operation)
|
|
236
|
-
results = []
|
|
237
|
-
|
|
238
|
-
if should_query_project?(scope)
|
|
239
|
-
results.concat(query_store(:project, &operation))
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
if should_query_global?(scope)
|
|
243
|
-
results.concat(query_store(:global, &operation))
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
results
|
|
247
|
-
end
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
**Kent Beck Would Say:** "This is what simple design looks like. Clear intent, no clever tricks."
|
|
251
|
-
|
|
252
|
-
### Issues Remaining
|
|
253
|
-
|
|
254
|
-
#### 🔵 Low Priority: Constructor Side Effects
|
|
255
|
-
|
|
256
|
-
**Problem**: LexicalFTS still has side effect in constructor:
|
|
257
|
-
|
|
258
|
-
```ruby
|
|
259
|
-
# index/lexical_fts.rb:6-10
|
|
260
|
-
def initialize(store)
|
|
261
|
-
@store = store
|
|
262
|
-
@db = store.db
|
|
263
|
-
@fts_table_ensured = false # Good: now uses flag!
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
# lexical_fts.rb:12-13
|
|
267
|
-
def index_content_item(content_item_id, text)
|
|
268
|
-
ensure_fts_table! # Side effect on first use
|
|
269
|
-
# ...
|
|
270
|
-
end
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
**Note**: This has been improved with lazy initialization flag, but table creation is still a side effect.
|
|
274
|
-
|
|
275
|
-
**Kent Beck Would Say:** "Better with the flag, but consider extracting schema setup entirely."
|
|
276
|
-
|
|
277
|
-
**Recommended Fix:**
|
|
278
|
-
|
|
279
|
-
```ruby
|
|
280
|
-
# Option 1: Keep current lazy approach (acceptable)
|
|
281
|
-
# Already improved with @fts_table_ensured flag
|
|
282
|
-
|
|
283
|
-
# Option 2: Explicit schema setup (more explicit)
|
|
284
|
-
class LexicalFTS
|
|
285
|
-
def self.setup_schema(db)
|
|
286
|
-
db.run(<<~SQL)
|
|
287
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS content_fts
|
|
288
|
-
USING fts5(content_item_id UNINDEXED, text, tokenize='porter unicode61')
|
|
289
|
-
SQL
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
# Then in migrations or initialization:
|
|
294
|
-
Index::LexicalFTS.setup_schema(db)
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
**Estimated Effort:** 0.5 days
|
|
298
|
-
|
|
299
|
-
**Priority:** 🔵 Low (current approach is acceptable with flag)
|
|
300
|
-
|
|
301
|
-
---
|
|
302
|
-
|
|
303
|
-
## 4. Avdi Grimm Perspective (Confident Ruby)
|
|
304
|
-
|
|
305
|
-
### What's Been Fixed Since Last Review ✅
|
|
306
|
-
|
|
307
|
-
- **ResponseFormatter**: Pure formatting, no mixed concerns
|
|
308
|
-
- **SetupStatusAnalyzer**: Pure status logic, returns clear values
|
|
309
|
-
- **Core modules**: Growing collection of well-behaved objects
|
|
310
|
-
- **OperationTracker**: Now returns consistent values
|
|
311
|
-
|
|
312
|
-
### Issues Remaining
|
|
313
|
-
|
|
314
|
-
#### 🟡 Medium Priority: Inconsistent Return Values
|
|
315
|
-
|
|
316
|
-
**Problem**: Methods still return different types on success vs failure:
|
|
317
|
-
|
|
318
|
-
```ruby
|
|
319
|
-
# recall.rb - Returns array or specific result
|
|
320
|
-
def explain(fact_id, scope: nil)
|
|
321
|
-
if @legacy_mode
|
|
322
|
-
explain_from_store(@legacy_store, fact_id)
|
|
323
|
-
else
|
|
324
|
-
scope ||= SCOPE_PROJECT
|
|
325
|
-
store = @manager.store_for_scope(scope)
|
|
326
|
-
explain_from_store(store, fact_id)
|
|
327
|
-
end
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
# explain_from_store returns hash or NullExplanation
|
|
331
|
-
# But some methods return nil, others return empty arrays
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
**Avdi Grimm Would Say:** "Use Result objects consistently to make success/failure explicit."
|
|
335
|
-
|
|
336
|
-
**Recommended Fix:**
|
|
337
|
-
|
|
338
|
-
```ruby
|
|
339
|
-
module ClaudeMemory
|
|
340
|
-
module Domain
|
|
341
|
-
class QueryResult
|
|
342
|
-
def self.success(value)
|
|
343
|
-
Success.new(value)
|
|
344
|
-
end
|
|
11
|
+
The codebase is in strong shape after a comprehensive quality pass on Feb 4. All critical and high-priority issues from the review have been resolved: N+1 queries eliminated, bare rescues replaced with specific exception types, mutation patterns fixed in functional core, and long methods decomposed into focused helpers.
|
|
345
12
|
|
|
346
|
-
|
|
347
|
-
NotFound.new(message)
|
|
348
|
-
end
|
|
13
|
+
**Remaining work:** 9 items (1 medium, 8 low priority). No critical or high-priority issues remain.
|
|
349
14
|
|
|
350
|
-
|
|
351
|
-
Error.new(message)
|
|
352
|
-
end
|
|
353
|
-
end
|
|
15
|
+
### Current Strengths
|
|
354
16
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
class NotFound < QueryResult
|
|
364
|
-
attr_reader :message
|
|
365
|
-
def initialize(message) = @message = message
|
|
366
|
-
def success? = false
|
|
367
|
-
def not_found? = true
|
|
368
|
-
def error? = false
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
class Error < QueryResult
|
|
372
|
-
attr_reader :message
|
|
373
|
-
def initialize(message) = @message = message
|
|
374
|
-
def success? = false
|
|
375
|
-
def not_found? = false
|
|
376
|
-
def error? = true
|
|
377
|
-
end
|
|
378
|
-
end
|
|
379
|
-
end
|
|
380
|
-
|
|
381
|
-
# Usage:
|
|
382
|
-
def explain(fact_id, scope: nil)
|
|
383
|
-
result = explain_from_store(store, fact_id)
|
|
384
|
-
return QueryResult.not_found("Fact #{fact_id} not found") if result.is_a?(Core::NullExplanation)
|
|
385
|
-
QueryResult.success(result)
|
|
386
|
-
end
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
**Estimated Effort:** 1-2 days
|
|
390
|
-
|
|
391
|
-
**Priority:** 🟡 Medium (would improve error handling clarity)
|
|
17
|
+
- Functional core: 20+ pure logic classes with zero I/O
|
|
18
|
+
- Domain objects: properly frozen and self-validating
|
|
19
|
+
- Null object pattern: NullFact, NullExplanation
|
|
20
|
+
- Result monad: Core::Result for Success/Failure
|
|
21
|
+
- 100% frozen_string_literal compliance (104 files)
|
|
22
|
+
- 1.77:1 test-to-code ratio (17,693 spec : 9,982 lib)
|
|
23
|
+
- Zero bare rescues, zero N+1 queries
|
|
392
24
|
|
|
393
25
|
---
|
|
394
26
|
|
|
395
|
-
##
|
|
396
|
-
|
|
397
|
-
### What's Been Fixed Since Last Review ✅
|
|
398
|
-
|
|
399
|
-
- **ConceptRanker**: New pure logic class (74 lines)! ✅
|
|
400
|
-
- **FactQueryBuilder**: Pure query construction (154 lines)! ✅
|
|
401
|
-
- **SetupStatusAnalyzer**: Pure status analysis (73 lines)! ✅
|
|
402
|
-
- **ResponseFormatter**: Pure formatting (331 lines)! ✅
|
|
403
|
-
- **ToolDefinitions**: Pure data structures (279 lines)! ✅
|
|
404
|
-
|
|
405
|
-
**Evidence:**
|
|
406
|
-
|
|
407
|
-
```ruby
|
|
408
|
-
# concept_ranker.rb:13-19 - Perfect functional core!
|
|
409
|
-
def self.rank_by_concepts(concept_results, limit)
|
|
410
|
-
fact_map = build_fact_map(concept_results)
|
|
411
|
-
multi_concept_facts = filter_by_all_concepts(fact_map, concept_results.size)
|
|
412
|
-
return [] if multi_concept_facts.empty?
|
|
413
|
-
|
|
414
|
-
rank_by_average_similarity(multi_concept_facts, limit)
|
|
415
|
-
end
|
|
416
|
-
|
|
417
|
-
# fact_query_builder.rb:13-21 - Pure query construction!
|
|
418
|
-
def self.batch_find_facts(store, fact_ids)
|
|
419
|
-
return {} if fact_ids.empty?
|
|
420
|
-
|
|
421
|
-
results = build_facts_dataset(store)
|
|
422
|
-
.where(Sequel[:facts][:id] => fact_ids)
|
|
423
|
-
.all
|
|
424
|
-
|
|
425
|
-
results.each_with_object({}) { |row, hash| hash[row[:id]] = row }
|
|
426
|
-
end
|
|
427
|
-
|
|
428
|
-
# setup_status_analyzer.rb:13-25 - Pure decision logic!
|
|
429
|
-
def self.determine_status(global_db_exists, claude_md_exists, version_status)
|
|
430
|
-
initialized = global_db_exists && claude_md_exists
|
|
431
|
-
|
|
432
|
-
if initialized && version_status == "up_to_date"
|
|
433
|
-
"healthy"
|
|
434
|
-
elsif initialized && version_status == "outdated"
|
|
435
|
-
"needs_upgrade"
|
|
436
|
-
elsif global_db_exists && !claude_md_exists
|
|
437
|
-
"partially_initialized"
|
|
438
|
-
else
|
|
439
|
-
"not_initialized"
|
|
440
|
-
end
|
|
441
|
-
end
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
**Gary Bernhardt Would Say:** "This is EXACTLY right. Pure logic, no I/O, instant tests, composable functions."
|
|
445
|
-
|
|
446
|
-
### Core Module Growth
|
|
447
|
-
|
|
448
|
-
**Pure Logic Classes (No I/O):**
|
|
449
|
-
- `Core::FactRanker` (114 lines)
|
|
450
|
-
- `Core::ConceptRanker` (74 lines)
|
|
451
|
-
- `Core::FactQueryBuilder` (154 lines)
|
|
452
|
-
- `Core::ScopeFilter`
|
|
453
|
-
- `Core::FactCollector`
|
|
454
|
-
- `Core::ResultBuilder`
|
|
455
|
-
- `Core::ResultSorter`
|
|
456
|
-
- `Core::TextBuilder`
|
|
457
|
-
- `Core::EmbeddingCandidateBuilder`
|
|
458
|
-
- `Core::TokenEstimator`
|
|
459
|
-
- `MCP::ResponseFormatter` (331 lines)
|
|
460
|
-
- `MCP::ToolDefinitions` (279 lines)
|
|
461
|
-
- `MCP::SetupStatusAnalyzer` (73 lines)
|
|
462
|
-
|
|
463
|
-
**Total: 17+ pure logic classes!**
|
|
464
|
-
|
|
465
|
-
### Issues Remaining
|
|
466
|
-
|
|
467
|
-
#### 🔵 Low Priority: Mutable State in Resolver
|
|
468
|
-
|
|
469
|
-
**Problem**: Still uses mutable instance variables for context:
|
|
470
|
-
|
|
471
|
-
```ruby
|
|
472
|
-
# resolver.rb:10-13
|
|
473
|
-
def apply(extraction, content_item_id: nil, occurred_at: nil, project_path: nil, scope: "project")
|
|
474
|
-
occurred_at ||= Time.now.utc.iso8601
|
|
475
|
-
@current_project_path = project_path # Mutable state
|
|
476
|
-
@current_scope = scope # Mutable state
|
|
477
|
-
# ...
|
|
478
|
-
end
|
|
479
|
-
```
|
|
27
|
+
## Remaining Items
|
|
480
28
|
|
|
481
|
-
|
|
29
|
+
### Medium Priority
|
|
482
30
|
|
|
483
|
-
|
|
31
|
+
| # | Issue | File:Line | Expert |
|
|
32
|
+
|---|-------|-----------|--------|
|
|
33
|
+
| 16 | Resolver mutable state after init | `resolve/resolver.rb:10-13` | Gary Bernhardt |
|
|
484
34
|
|
|
485
|
-
|
|
486
|
-
class ResolutionContext
|
|
487
|
-
attr_reader :project_path, :scope, :occurred_at, :content_item_id
|
|
35
|
+
`@current_project_path` and `@current_scope` are set in `apply()` rather than threaded as parameters. Should pass through method chain instead of mutable instance state.
|
|
488
36
|
|
|
489
|
-
|
|
490
|
-
@project_path = project_path
|
|
491
|
-
@scope = scope
|
|
492
|
-
@occurred_at = occurred_at
|
|
493
|
-
@content_item_id = content_item_id
|
|
494
|
-
freeze # Immutable
|
|
495
|
-
end
|
|
496
|
-
end
|
|
37
|
+
### Low Priority
|
|
497
38
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
**Estimated Effort:** 0.5 days
|
|
511
|
-
|
|
512
|
-
**Priority:** 🔵 Low (current approach works, improvement is stylistic)
|
|
39
|
+
| # | Issue | File:Line | Expert |
|
|
40
|
+
|---|-------|-----------|--------|
|
|
41
|
+
| 17 | DateTime migration (string timestamps) | Multiple files | Jeremy Evans |
|
|
42
|
+
| 18 | Strategy pattern in Recall (608 lines) | `recall.rb` | Sandi Metz |
|
|
43
|
+
| 19 | Command manager helper (`with_manager`) | `commands/*.rb` | Kent Beck |
|
|
44
|
+
| 20 | release_connections polymorphism | `mcp/server.rb:148-156` | Gary Bernhardt |
|
|
45
|
+
| 21 | Sweeper mutable state | `sweep/sweeper.rb:16-17` | Gary Bernhardt |
|
|
46
|
+
| 22 | Provenance batch insert (`multi_insert`) | `store/store_manager.rb:129-139` | Jeremy Evans |
|
|
47
|
+
| 23 | Individual MCP tool classes | `mcp/tools.rb` | Sandi Metz |
|
|
48
|
+
| 24 | Result objects for all queries | Multiple files | Avdi Grimm |
|
|
513
49
|
|
|
514
50
|
---
|
|
515
51
|
|
|
516
|
-
##
|
|
52
|
+
## Risk Assessment
|
|
517
53
|
|
|
518
|
-
|
|
|
519
|
-
|
|
520
|
-
| **
|
|
521
|
-
| **
|
|
522
|
-
| **
|
|
523
|
-
| **
|
|
524
|
-
| **
|
|
54
|
+
| Area | Risk Level | Notes |
|
|
55
|
+
|------|-----------|-------|
|
|
56
|
+
| **Performance** | ✅ Low | N+1 queries fixed |
|
|
57
|
+
| **Maintainability** | ✅ Low | Long methods decomposed |
|
|
58
|
+
| **Correctness** | ✅ Low | databases_exist? fixed, ResultSorter non-mutating |
|
|
59
|
+
| **Error Handling** | ✅ Low | All bare rescues replaced with specific types |
|
|
60
|
+
| **Architecture** | ✅ Low | Strong functional core, proper layering |
|
|
61
|
+
| **Testing** | ✅ Low | 1.77:1 ratio, 98 spec files |
|
|
525
62
|
|
|
526
63
|
---
|
|
527
64
|
|
|
528
|
-
##
|
|
529
|
-
|
|
530
|
-
### Optional Improvements (Low-Medium Priority)
|
|
531
|
-
|
|
532
|
-
The codebase is now in excellent shape. These are refinements, not critical fixes:
|
|
533
|
-
|
|
534
|
-
#### 1. Complete Strategy Pattern in Recall (Optional)
|
|
535
|
-
|
|
536
|
-
**Target**: Remove legacy mode conditionals
|
|
537
|
-
|
|
538
|
-
**Benefit**: Cleaner architecture, easier testing
|
|
539
|
-
|
|
540
|
-
**Effort**: 1-2 days
|
|
541
|
-
|
|
542
|
-
**Priority:** 🟡 Medium (system works well as-is)
|
|
543
|
-
|
|
544
|
-
#### 2. DateTime Migration (Recommended)
|
|
545
|
-
|
|
546
|
-
**Target**: Convert string timestamps to DateTime columns
|
|
547
|
-
|
|
548
|
-
**Benefit**: Better date operations, database best practice
|
|
549
|
-
|
|
550
|
-
**Effort**: 1-2 days
|
|
551
|
-
|
|
552
|
-
**Priority:** 🟡 Medium (improvement, not fix)
|
|
553
|
-
|
|
554
|
-
#### 3. Result Objects (Nice to Have)
|
|
555
|
-
|
|
556
|
-
**Target**: Consistent return values across query methods
|
|
557
|
-
|
|
558
|
-
**Benefit**: Clearer error handling, explicit success/failure
|
|
559
|
-
|
|
560
|
-
**Effort**: 1-2 days
|
|
561
|
-
|
|
562
|
-
**Priority:** 🟡 Medium (stylistic improvement)
|
|
563
|
-
|
|
564
|
-
#### 4. Individual Tool Classes (Optional)
|
|
565
|
-
|
|
566
|
-
**Target**: Split MCP tools.rb into individual tool classes
|
|
65
|
+
## Metrics
|
|
567
66
|
|
|
568
|
-
|
|
67
|
+
| Metric | Jan 29 | Feb 4 |
|
|
68
|
+
|--------|--------|-------|
|
|
69
|
+
| Ruby files (lib) | ~85 | 104 |
|
|
70
|
+
| LOC (lib) | ~8,000 | 9,982 |
|
|
71
|
+
| Pure logic classes | 17+ | 20+ |
|
|
72
|
+
| Test files | 74+ | 98 |
|
|
73
|
+
| Test-to-code ratio | ~1.5:1 | 1.77:1 |
|
|
74
|
+
| Files >500 lines | 0 | 2 (tools, recall) 🟡 |
|
|
75
|
+
| Bare rescues | 0 | 0 ✅ |
|
|
76
|
+
| N+1 patterns | 0 | 0 ✅ |
|
|
569
77
|
|
|
570
|
-
|
|
78
|
+
## File Size Watch List
|
|
571
79
|
|
|
572
|
-
|
|
80
|
+
| File | Lines | Concern |
|
|
81
|
+
|------|-------|---------|
|
|
82
|
+
| `mcp/tools.rb` | ~610 | Consider individual tool classes (#23) |
|
|
83
|
+
| `recall.rb` | ~608 | Consider strategy pattern extraction (#18) |
|
|
84
|
+
| `store/sqlite_store.rb` | 481 | Trending up — watch for 500 |
|
|
573
85
|
|
|
574
86
|
---
|
|
575
87
|
|
|
576
|
-
##
|
|
88
|
+
## Completed (Feb 4, 2026)
|
|
577
89
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
| Recall lines | 754 | 575 | ✅ -24% |
|
|
581
|
-
| Recall public methods | 58 | ~11 | ✅ Excellent |
|
|
582
|
-
| MCP Tools lines | 1,039 | 592 | ✅ -43% |
|
|
583
|
-
| MCP extracted modules | 0 | 3 (683 lines) | ✅ +683 |
|
|
584
|
-
| SQLiteStore lines | 383 | 389 | ✅ Stable |
|
|
585
|
-
| DoctorCommand lines | 31 | 31 | ✅ Stable |
|
|
586
|
-
| Pure logic classes | 14 | 17+ | ✅ +3 |
|
|
587
|
-
| God objects | 2 | 0 | ✅ Resolved! |
|
|
588
|
-
| Migration files | 7 | 7 | ✅ Stable |
|
|
589
|
-
| Command classes | 16 | 21 | ✅ +5 |
|
|
590
|
-
| Test files | 64+ | 74+ | ✅ +10 |
|
|
591
|
-
| OperationTracker JSON | SQLite funcs | Ruby JSON | ✅ Fixed! |
|
|
90
|
+
<details>
|
|
91
|
+
<summary>21 items completed in 7 atomic commits</summary>
|
|
592
92
|
|
|
593
|
-
**
|
|
594
|
-
- ✅ Both god objects resolved through proper extraction
|
|
595
|
-
- ✅ Functional core growing rapidly (17+ pure classes)
|
|
596
|
-
- ✅ MCP modules properly separated (683 lines extracted)
|
|
597
|
-
- ✅ Test coverage improving
|
|
598
|
-
- ✅ Architecture is sound and maintainable
|
|
93
|
+
**Quick Wins (6):** bare rescue in server.rb, tool_extractor.rb, stats_command.rb; ResultSorter mutation; RRFusion mutation; databases_exist? logic
|
|
599
94
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
## 9. Positive Observations
|
|
603
|
-
|
|
604
|
-
### Architectural Excellence
|
|
605
|
-
|
|
606
|
-
1. **Functional Core Growing**: 17+ pure logic classes with zero I/O
|
|
607
|
-
2. **Proper Extractions**: ResponseFormatter, ToolDefinitions, SetupStatusAnalyzer
|
|
608
|
-
3. **DualQueryTemplate**: Elegant solution to dual-database queries
|
|
609
|
-
4. **FactQueryBuilder**: Clean separation of query construction
|
|
610
|
-
5. **ConceptRanker**: Perfect example of pure business logic
|
|
611
|
-
|
|
612
|
-
### Code Quality Wins
|
|
613
|
-
|
|
614
|
-
- **DoctorCommand**: Still exemplary at 31 lines
|
|
615
|
-
- **OperationTracker**: Fixed JSON functions, now uses Ruby properly
|
|
616
|
-
- **WAL Checkpoint**: Implemented for database maintenance
|
|
617
|
-
- **Transaction Safety**: Consistently used in critical operations
|
|
618
|
-
- **Check Classes**: 5 specialized, focused classes
|
|
619
|
-
- **Core Module**: Well-organized pure logic (17+ classes)
|
|
620
|
-
|
|
621
|
-
### Testing & Maintenance
|
|
622
|
-
|
|
623
|
-
- **74+ spec files**: Growing test coverage
|
|
624
|
-
- **7 migrations**: Proper Sequel migration system
|
|
625
|
-
- **Standard Ruby**: Consistent linting
|
|
626
|
-
- **Good documentation**: Clear inline comments
|
|
627
|
-
- **FileSystem abstraction**: Testable without I/O
|
|
628
|
-
|
|
629
|
-
---
|
|
630
|
-
|
|
631
|
-
## 10. Conclusion
|
|
632
|
-
|
|
633
|
-
**The codebase has reached production-quality standards!**
|
|
634
|
-
|
|
635
|
-
### Major Achievements (Jan 27 → Jan 29)
|
|
636
|
-
|
|
637
|
-
1. ✅ Recall reduced 24% (754 → 575 lines)
|
|
638
|
-
2. ✅ MCP Tools reduced 43% (1,039 → 592 lines)
|
|
639
|
-
3. ✅ 3 major extractions (683 lines properly separated)
|
|
640
|
-
4. ✅ OperationTracker JSON functions fixed
|
|
641
|
-
5. ✅ DualQueryTemplate eliminates duplication
|
|
642
|
-
6. ✅ 17+ pure logic classes in functional core
|
|
643
|
-
|
|
644
|
-
### Current State: Excellent
|
|
645
|
-
|
|
646
|
-
**God objects**: ✅ Resolved through proper extraction
|
|
647
|
-
**Architecture**: ✅ Sound with clear boundaries
|
|
648
|
-
**Testing**: ✅ 74+ spec files, growing coverage
|
|
649
|
-
**Code quality**: ✅ Consistently high across modules
|
|
650
|
-
**Maintainability**: ✅ Excellent with clear patterns
|
|
651
|
-
|
|
652
|
-
### Remaining Work: Optional Refinements
|
|
653
|
-
|
|
654
|
-
The remaining recommendations are **improvements, not fixes**:
|
|
655
|
-
- Complete strategy pattern (optional architectural refinement)
|
|
656
|
-
- DateTime migration (database best practice)
|
|
657
|
-
- Result objects (error handling clarity)
|
|
658
|
-
- Individual tool classes (minor organizational improvement)
|
|
659
|
-
|
|
660
|
-
None of these are critical. The codebase is production-ready.
|
|
661
|
-
|
|
662
|
-
### Recommendation
|
|
663
|
-
|
|
664
|
-
**Ship it!** The architecture is solid, patterns are clear, and code quality is high. The optional improvements can be done incrementally as part of normal maintenance.
|
|
665
|
-
|
|
666
|
-
The team has done outstanding work transforming this codebase from having god objects to having a beautiful functional core with clear boundaries.
|
|
95
|
+
**High Priority (8):** N+1 provenance query; N+1 legacy query; check_setup extraction; detailed_stats extraction; resolve_fact decomposition; ingester transaction body extraction
|
|
667
96
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
**Review completed:** 2026-01-29
|
|
671
|
-
**Reviewed by:** Claude Code (comprehensive analysis through expert perspectives)
|
|
672
|
-
**Next review:** Recommend after 2-3 months of production use
|
|
673
|
-
|
|
674
|
-
**Overall Assessment:** ✅ PRODUCTION READY
|
|
675
|
-
|
|
676
|
-
---
|
|
677
|
-
|
|
678
|
-
## Appendix A: Quick Wins (COMPLETED ✅)
|
|
679
|
-
|
|
680
|
-
All quick wins from the previous review have been completed:
|
|
681
|
-
|
|
682
|
-
1. ✅ **Fix JSON functions in OperationTracker** - DONE
|
|
683
|
-
- Replaced `Sequel.function(:json_set)` with Ruby JSON handling
|
|
684
|
-
- Lines 114-117 and 143-154 now use Ruby JSON.parse/generate
|
|
685
|
-
|
|
686
|
-
2. ✅ **Add WAL checkpoint management** - DONE
|
|
687
|
-
- Added `checkpoint_wal` method to SQLiteStore (lines 40-42)
|
|
688
|
-
- Available for sweep operations
|
|
689
|
-
|
|
690
|
-
3. ✅ **Extract ResponseFormatter from Tools** - DONE
|
|
691
|
-
- Created `MCP::ResponseFormatter` class (331 lines)
|
|
692
|
-
- All formatting logic properly separated
|
|
693
|
-
|
|
694
|
-
4. ✅ **Extract ToolDefinitions** - DONE
|
|
695
|
-
- Created `MCP::ToolDefinitions` module (279 lines)
|
|
696
|
-
- Tool schemas as pure data
|
|
697
|
-
|
|
698
|
-
5. ✅ **Add ConceptRanker to Core** - DONE
|
|
699
|
-
- Created `Core::ConceptRanker` (74 lines)
|
|
700
|
-
- Pure logic with fast tests
|
|
701
|
-
|
|
702
|
-
**All quick wins completed!**
|
|
97
|
+
**Medium Priority (7):** RRFusion mutation; OperationTracker DRY; ToolExtractor bare rescue; databases_exist?; stats_command bare rescue; SchemaValidator.validate extraction; FactGraph.build decomposition
|
|
98
|
+
</details>
|
|
703
99
|
|
|
704
100
|
---
|
|
705
101
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
**No files > 500 lines!** 🎉
|
|
709
|
-
|
|
710
|
-
**Medium Files (200-600 lines):**
|
|
711
|
-
- `lib/claude_memory/mcp/tools.rb` - 592 lines ✅ (down 43%)
|
|
712
|
-
- `lib/claude_memory/recall.rb` - 575 lines ✅ (down 24%)
|
|
713
|
-
- `lib/claude_memory/store/sqlite_store.rb` - 389 lines ✅
|
|
714
|
-
- `lib/claude_memory/mcp/response_formatter.rb` - 331 lines ✅
|
|
715
|
-
- `lib/claude_memory/mcp/tool_definitions.rb` - 279 lines ✅
|
|
716
|
-
|
|
717
|
-
**Well-Sized Files (< 200 lines):**
|
|
718
|
-
- `lib/claude_memory/cli.rb` - 41 lines ✅
|
|
719
|
-
- `lib/claude_memory/commands/doctor_command.rb` - 31 lines ✅
|
|
720
|
-
- `lib/claude_memory/core/fact_ranker.rb` - 114 lines ✅
|
|
721
|
-
- `lib/claude_memory/core/fact_query_builder.rb` - 154 lines ✅
|
|
722
|
-
- `lib/claude_memory/core/concept_ranker.rb` - 74 lines ✅
|
|
723
|
-
- `lib/claude_memory/mcp/setup_status_analyzer.rb` - 73 lines ✅
|
|
724
|
-
- `lib/claude_memory/recall/dual_query_template.rb` - 64 lines ✅
|
|
725
|
-
- Most command files - 30-115 lines ✅
|
|
726
|
-
- Check classes - 30-115 lines each ✅
|
|
727
|
-
- Domain objects - 30-80 lines ✅
|
|
728
|
-
- Value objects - 20-40 lines ✅
|
|
729
|
-
|
|
730
|
-
**Migration Files:**
|
|
731
|
-
- `db/migrations/*.rb` - 7 files ✅
|
|
732
|
-
|
|
733
|
-
---
|
|
734
|
-
|
|
735
|
-
## Appendix C: Critical Files for Implementation
|
|
736
|
-
|
|
737
|
-
Based on this comprehensive review, the most critical files for implementing the remaining optional improvements are:
|
|
738
|
-
|
|
739
|
-
- `/Users/valentinostoll/src/claude_memory/lib/claude_memory/recall.rb` - Main query coordinator (575 lines, could complete strategy pattern)
|
|
740
|
-
- `/Users/valentinostoll/src/claude_memory/lib/claude_memory/mcp/tools.rb` - Tool handler (592 lines, well-structured, could split further)
|
|
741
|
-
- `/Users/valentinostoll/src/claude_memory/lib/claude_memory/store/sqlite_store.rb` - Database layer (389 lines, good for DateTime migration)
|
|
742
|
-
- `/Users/valentinostoll/src/claude_memory/lib/claude_memory/resolve/resolver.rb` - Resolution logic (156 lines, uses mutable state)
|
|
743
|
-
- `/Users/valentinostoll/src/claude_memory/lib/claude_memory/index/lexical_fts.rb` - FTS indexer (63 lines, has constructor side effect with flag)
|
|
102
|
+
**Next review:** After recall.rb strategy pattern or sqlite_store.rb extraction
|