claude_memory 0.2.0 → 0.3.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.o2N83S +0 -0
- data/.claude/CLAUDE.md +1 -0
- data/.claude/rules/claude_memory.generated.md +28 -9
- data/.claude/settings.local.json +9 -1
- data/.claude/skills/check-memory/SKILL.md +77 -0
- data/.claude/skills/improve/SKILL.md +532 -0
- data/.claude/skills/improve/feature-patterns.md +1221 -0
- data/.claude/skills/quality-update/SKILL.md +229 -0
- data/.claude/skills/quality-update/implementation-guide.md +346 -0
- data/.claude/skills/review-commit/SKILL.md +199 -0
- data/.claude/skills/review-for-quality/SKILL.md +154 -0
- data/.claude/skills/review-for-quality/expert-checklists.md +79 -0
- data/.claude/skills/setup-memory/SKILL.md +168 -0
- data/.claude/skills/study-repo/SKILL.md +307 -0
- data/.claude/skills/study-repo/analysis-template.md +323 -0
- data/.claude/skills/study-repo/focus-examples.md +327 -0
- data/CHANGELOG.md +133 -0
- data/CLAUDE.md +130 -11
- data/README.md +117 -10
- data/db/migrations/001_create_initial_schema.rb +117 -0
- data/db/migrations/002_add_project_scoping.rb +33 -0
- data/db/migrations/003_add_session_metadata.rb +42 -0
- data/db/migrations/004_add_fact_embeddings.rb +20 -0
- data/db/migrations/005_add_incremental_sync.rb +21 -0
- data/db/migrations/006_add_operation_tracking.rb +40 -0
- data/db/migrations/007_add_ingestion_metrics.rb +26 -0
- data/docs/.claude/mind.mv2.lock +0 -0
- data/docs/GETTING_STARTED.md +587 -0
- data/docs/RELEASE_NOTES_v0.2.0.md +0 -1
- data/docs/RUBY_COMMUNITY_POST_v0.2.0.md +0 -2
- data/docs/architecture.md +9 -8
- data/docs/auto_init_design.md +230 -0
- data/docs/improvements.md +557 -731
- data/docs/influence/.gitkeep +13 -0
- data/docs/influence/grepai.md +933 -0
- data/docs/influence/qmd.md +2195 -0
- data/docs/plugin.md +257 -11
- data/docs/quality_review.md +472 -1273
- data/docs/remaining_improvements.md +330 -0
- data/lefthook.yml +13 -0
- data/lib/claude_memory/commands/checks/claude_md_check.rb +41 -0
- data/lib/claude_memory/commands/checks/database_check.rb +120 -0
- data/lib/claude_memory/commands/checks/hooks_check.rb +112 -0
- data/lib/claude_memory/commands/checks/reporter.rb +110 -0
- data/lib/claude_memory/commands/checks/snapshot_check.rb +30 -0
- data/lib/claude_memory/commands/doctor_command.rb +12 -129
- data/lib/claude_memory/commands/help_command.rb +1 -0
- data/lib/claude_memory/commands/hook_command.rb +9 -2
- data/lib/claude_memory/commands/index_command.rb +169 -0
- data/lib/claude_memory/commands/ingest_command.rb +1 -1
- data/lib/claude_memory/commands/init_command.rb +5 -197
- data/lib/claude_memory/commands/initializers/database_ensurer.rb +30 -0
- data/lib/claude_memory/commands/initializers/global_initializer.rb +85 -0
- data/lib/claude_memory/commands/initializers/hooks_configurator.rb +156 -0
- data/lib/claude_memory/commands/initializers/mcp_configurator.rb +56 -0
- data/lib/claude_memory/commands/initializers/memory_instructions_writer.rb +135 -0
- data/lib/claude_memory/commands/initializers/project_initializer.rb +111 -0
- data/lib/claude_memory/commands/recover_command.rb +75 -0
- data/lib/claude_memory/commands/registry.rb +5 -1
- data/lib/claude_memory/commands/stats_command.rb +239 -0
- data/lib/claude_memory/commands/uninstall_command.rb +226 -0
- data/lib/claude_memory/core/batch_loader.rb +32 -0
- data/lib/claude_memory/core/concept_ranker.rb +73 -0
- data/lib/claude_memory/core/embedding_candidate_builder.rb +37 -0
- data/lib/claude_memory/core/fact_collector.rb +51 -0
- data/lib/claude_memory/core/fact_query_builder.rb +154 -0
- data/lib/claude_memory/core/fact_ranker.rb +113 -0
- data/lib/claude_memory/core/result_builder.rb +54 -0
- data/lib/claude_memory/core/result_sorter.rb +25 -0
- data/lib/claude_memory/core/scope_filter.rb +61 -0
- data/lib/claude_memory/core/text_builder.rb +29 -0
- data/lib/claude_memory/embeddings/generator.rb +161 -0
- data/lib/claude_memory/embeddings/similarity.rb +69 -0
- data/lib/claude_memory/hook/handler.rb +4 -3
- data/lib/claude_memory/index/lexical_fts.rb +7 -2
- data/lib/claude_memory/infrastructure/operation_tracker.rb +158 -0
- data/lib/claude_memory/infrastructure/schema_validator.rb +206 -0
- data/lib/claude_memory/ingest/content_sanitizer.rb +6 -7
- data/lib/claude_memory/ingest/ingester.rb +99 -15
- data/lib/claude_memory/ingest/metadata_extractor.rb +57 -0
- data/lib/claude_memory/ingest/tool_extractor.rb +71 -0
- data/lib/claude_memory/mcp/response_formatter.rb +331 -0
- data/lib/claude_memory/mcp/server.rb +19 -0
- data/lib/claude_memory/mcp/setup_status_analyzer.rb +73 -0
- data/lib/claude_memory/mcp/tool_definitions.rb +279 -0
- data/lib/claude_memory/mcp/tool_helpers.rb +80 -0
- data/lib/claude_memory/mcp/tools.rb +330 -320
- data/lib/claude_memory/recall/dual_query_template.rb +63 -0
- data/lib/claude_memory/recall.rb +304 -237
- data/lib/claude_memory/resolve/resolver.rb +52 -49
- data/lib/claude_memory/store/sqlite_store.rb +210 -144
- data/lib/claude_memory/store/store_manager.rb +6 -6
- data/lib/claude_memory/sweep/sweeper.rb +6 -0
- data/lib/claude_memory/version.rb +1 -1
- data/lib/claude_memory.rb +35 -3
- metadata +71 -11
- data/.claude/.mind.mv2.aLCUZd +0 -0
- data/.claude/memory.sqlite3 +0 -0
- data/.mcp.json +0 -11
- /data/docs/{feature_adoption_plan.md → plans/feature_adoption_plan.md} +0 -0
- /data/docs/{feature_adoption_plan_revised.md → plans/feature_adoption_plan_revised.md} +0 -0
- /data/docs/{plan.md → plans/plan.md} +0 -0
- /data/docs/{updated_plan.md → plans/updated_plan.md} +0 -0
data/docs/quality_review.md
CHANGED
|
@@ -1,1544 +1,743 @@
|
|
|
1
1
|
# Code Quality Review - Ruby Best Practices
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Review Date:** 2026-01-29 (Updated)
|
|
4
4
|
|
|
5
|
-
**Review
|
|
5
|
+
**Previous Review:** 2026-01-27
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## Executive Summary
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**OUTSTANDING PROGRESS!** The team has achieved major architectural breakthroughs since January 27th:
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
2. **Mixed Concerns** - I/O interleaved with business logic throughout
|
|
15
|
-
3. **Inconsistent Database Practices** - Mix of Sequel datasets and raw SQL
|
|
16
|
-
4. **Lack of Domain Objects** - Primitive obsession with hashes
|
|
17
|
-
5. **State Management** - Mutable instance variables where immutability preferred
|
|
13
|
+
### Major Wins Since Last Review ✅
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
- High test coverage
|
|
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)
|
|
29
24
|
|
|
30
|
-
### Critical
|
|
25
|
+
### Critical Achievements
|
|
31
26
|
|
|
32
|
-
|
|
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
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
**Violations:**
|
|
37
|
-
- Single Responsibility Principle violated
|
|
38
|
-
- Too many public methods (18+ commands)
|
|
39
|
-
- Too many private methods (20+)
|
|
40
|
-
- Methods > 10 lines (doctor_cmd, init_local, configure_global_hooks, etc.)
|
|
41
|
-
|
|
42
|
-
**Example:**
|
|
43
|
-
```ruby
|
|
44
|
-
# cli.rb:689-743 - doctor_cmd does too much
|
|
45
|
-
def doctor_cmd
|
|
46
|
-
issues = []
|
|
47
|
-
warnings = []
|
|
48
|
-
|
|
49
|
-
# Database checking
|
|
50
|
-
# File system checking
|
|
51
|
-
# Config validation
|
|
52
|
-
# Conflict detection
|
|
53
|
-
# Output formatting
|
|
54
|
-
# Error handling
|
|
55
|
-
end
|
|
56
|
-
```
|
|
33
|
+
### Remaining Work
|
|
57
34
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class Doctor
|
|
65
|
-
def initialize(store_manager, reporter:)
|
|
66
|
-
@store_manager = store_manager
|
|
67
|
-
@reporter = reporter
|
|
68
|
-
end
|
|
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
|
|
69
41
|
|
|
70
|
-
|
|
71
|
-
checks = [
|
|
72
|
-
DatabaseCheck.new(@store_manager),
|
|
73
|
-
SnapshotCheck.new,
|
|
74
|
-
HooksCheck.new
|
|
75
|
-
]
|
|
42
|
+
---
|
|
76
43
|
|
|
77
|
-
|
|
78
|
-
@reporter.report(results)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
```
|
|
44
|
+
## 1. Sandi Metz Perspective (POODR)
|
|
84
45
|
|
|
85
|
-
|
|
46
|
+
### What's Been Fixed Since Last Review ✅
|
|
86
47
|
|
|
87
|
-
**
|
|
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
|
|
88
56
|
|
|
89
|
-
**
|
|
90
|
-
- `cli.rb:689-743` - `doctor_cmd` (55 lines)
|
|
91
|
-
- `cli.rb:536-565` - `init_local` (30 lines)
|
|
92
|
-
- `cli.rb:586-601` - `configure_global_hooks` (16 lines)
|
|
93
|
-
- `recall.rb:58-78` - `query_dual` (21 lines)
|
|
57
|
+
**Evidence of Progress:**
|
|
94
58
|
|
|
95
|
-
**Recommended Fix:**
|
|
96
|
-
Break into smaller, well-named private methods:
|
|
97
59
|
```ruby
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
private
|
|
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
|
|
105
65
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
check_hooks
|
|
112
|
-
]
|
|
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!
|
|
113
71
|
end
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
#### 🟡 Duplicated Attribute Readers (store_manager.rb:47-49)
|
|
117
|
-
|
|
118
|
-
**Problem:**
|
|
119
|
-
```ruby
|
|
120
|
-
attr_reader :global_store, :project_store, :project_path # line 8
|
|
121
|
-
|
|
122
|
-
# ... later ...
|
|
123
72
|
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
126
77
|
```
|
|
127
78
|
|
|
128
|
-
|
|
79
|
+
### Issues Remaining
|
|
129
80
|
|
|
130
|
-
#### 🟡
|
|
81
|
+
#### 🟡 Medium Priority: Complete Strategy Pattern in Recall
|
|
131
82
|
|
|
132
|
-
**
|
|
83
|
+
**Status**: Recall still has legacy mode conditional routing, but impact is now minor:
|
|
133
84
|
|
|
134
|
-
**Violations:**
|
|
135
|
-
- Two modes = two responsibilities
|
|
136
|
-
- Conditional logic based on mode throughout
|
|
137
|
-
- Hard to reason about which path executes
|
|
138
|
-
|
|
139
|
-
**Recommended Fix:**
|
|
140
|
-
Create separate classes:
|
|
141
85
|
```ruby
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
class DualRecall
|
|
147
|
-
# Dual store logic only
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# Factory
|
|
151
|
-
def self.build(store_or_manager)
|
|
152
|
-
if store_or_manager.is_a?(Store::StoreManager)
|
|
153
|
-
DualRecall.new(store_or_manager)
|
|
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)
|
|
154
90
|
else
|
|
155
|
-
|
|
91
|
+
query_dual(query_text, limit: limit, scope: scope)
|
|
156
92
|
end
|
|
157
93
|
end
|
|
158
94
|
```
|
|
159
95
|
|
|
160
|
-
|
|
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:**
|
|
161
105
|
|
|
162
|
-
**Problem:**
|
|
163
106
|
```ruby
|
|
164
|
-
|
|
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
|
|
165
112
|
|
|
166
|
-
|
|
113
|
+
def query(query_text, limit: 10, scope: SCOPE_ALL)
|
|
114
|
+
@strategy.query(query_text, limit: limit, scope: scope)
|
|
115
|
+
end
|
|
167
116
|
|
|
168
|
-
|
|
117
|
+
private
|
|
169
118
|
|
|
170
|
-
def
|
|
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
|
|
171
127
|
```
|
|
172
128
|
|
|
173
|
-
**
|
|
129
|
+
**Estimated Effort:** 1-2 days (optional refinement)
|
|
130
|
+
|
|
131
|
+
**Priority:** 🟡 Medium (system works well as-is)
|
|
174
132
|
|
|
175
133
|
---
|
|
176
134
|
|
|
177
135
|
## 2. Jeremy Evans Perspective (Sequel Expert)
|
|
178
136
|
|
|
179
|
-
###
|
|
180
|
-
- Proper Sequel usage patterns
|
|
181
|
-
- Database performance
|
|
182
|
-
- Schema design
|
|
183
|
-
- Connection management
|
|
137
|
+
### What's Been Fixed Since Last Review ✅
|
|
184
138
|
|
|
185
|
-
|
|
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
|
|
186
143
|
|
|
187
|
-
|
|
144
|
+
**Evidence:**
|
|
188
145
|
|
|
189
|
-
**Problem:**
|
|
190
146
|
```ruby
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
**Violations:**
|
|
198
|
-
- Bypasses Sequel's dataset API
|
|
199
|
-
- Inconsistent with rest of codebase
|
|
200
|
-
- No type casting or safety checks
|
|
201
|
-
- Raw SQL is harder to test
|
|
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!
|
|
202
151
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
#### 🔴 No Transaction Wrapping (store_manager.rb:79-122)
|
|
212
|
-
|
|
213
|
-
**Problem:** `promote_fact` performs multiple database writes without transaction:
|
|
214
|
-
```ruby
|
|
215
|
-
def promote_fact(fact_id)
|
|
216
|
-
ensure_both!
|
|
217
|
-
|
|
218
|
-
fact = @project_store.facts.where(id: fact_id).first
|
|
219
|
-
# ... multiple inserts across two databases
|
|
220
|
-
global_fact_id = @global_store.insert_fact(...)
|
|
221
|
-
copy_provenance(fact_id, global_fact_id)
|
|
222
|
-
|
|
223
|
-
global_fact_id
|
|
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
|
+
)
|
|
224
159
|
end
|
|
225
|
-
```
|
|
226
160
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
```ruby
|
|
231
|
-
def promote_fact(fact_id)
|
|
232
|
-
ensure_both!
|
|
233
|
-
|
|
234
|
-
@global_store.db.transaction do
|
|
235
|
-
fact = @project_store.facts.where(id: fact_id).first
|
|
236
|
-
return nil unless fact
|
|
237
|
-
|
|
238
|
-
# ... inserts ...
|
|
239
|
-
end
|
|
161
|
+
# sqlite_store.rb:40-42 - WAL checkpoint added!
|
|
162
|
+
def checkpoint_wal
|
|
163
|
+
@db.run("PRAGMA wal_checkpoint(TRUNCATE)")
|
|
240
164
|
end
|
|
241
165
|
```
|
|
242
166
|
|
|
243
|
-
**
|
|
244
|
-
|
|
245
|
-
#### 🔴 String Timestamps Instead of Time Objects
|
|
246
|
-
|
|
247
|
-
**Problem:** Throughout the codebase:
|
|
248
|
-
```ruby
|
|
249
|
-
String :created_at, null: false # sqlite_store.rb:127
|
|
250
|
-
now = Time.now.utc.iso8601 # sqlite_store.rb:211
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
**Issues:**
|
|
254
|
-
- String comparison for dates is fragile
|
|
255
|
-
- No timezone enforcement at DB level
|
|
256
|
-
- Manual ISO8601 conversion everywhere
|
|
257
|
-
- Harder to query by date ranges
|
|
167
|
+
**Jeremy Evans Would Say:** "Excellent! This is how you handle JSON in Ruby applications."
|
|
258
168
|
|
|
259
|
-
|
|
260
|
-
```ruby
|
|
261
|
-
# Use DateTime columns
|
|
262
|
-
DateTime :created_at, null: false
|
|
169
|
+
### Issues Remaining
|
|
263
170
|
|
|
264
|
-
|
|
265
|
-
Sequel.extension :date_arithmetic
|
|
266
|
-
plugin :timestamps, update_on_create: true
|
|
267
|
-
```
|
|
171
|
+
#### 🟡 Medium Priority: String Timestamps Throughout
|
|
268
172
|
|
|
269
|
-
|
|
173
|
+
**Problem**: Still using ISO8601 strings instead of DateTime columns:
|
|
270
174
|
|
|
271
|
-
**Problem:** SQLite connections created without pooling options (sqlite_store.rb:15):
|
|
272
175
|
```ruby
|
|
273
|
-
|
|
274
|
-
|
|
176
|
+
# sqlite_store.rb:102
|
|
177
|
+
now = Time.now.utc.iso8601
|
|
275
178
|
|
|
276
|
-
|
|
277
|
-
```ruby
|
|
278
|
-
@db = Sequel.connect(
|
|
279
|
-
adapter: 'sqlite',
|
|
280
|
-
database: db_path,
|
|
281
|
-
max_connections: 4,
|
|
282
|
-
pool_timeout: 5
|
|
283
|
-
)
|
|
179
|
+
# Found 17 occurrences of Time.now.utc.iso8601 pattern
|
|
284
180
|
```
|
|
285
181
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
**Problem:** Hand-rolled migration system instead of Sequel's migration framework.
|
|
182
|
+
**Jeremy Evans Would Say:** "Use DateTime columns for proper date operations."
|
|
289
183
|
|
|
290
|
-
**
|
|
291
|
-
- No rollback support
|
|
292
|
-
- No migration history
|
|
293
|
-
- Schema changes mixed with initialization
|
|
184
|
+
**Recommended Fix:**
|
|
294
185
|
|
|
295
|
-
**Recommended:**
|
|
296
|
-
Use Sequel's migration extension:
|
|
297
186
|
```ruby
|
|
298
|
-
#
|
|
187
|
+
# Migration to convert to DateTime
|
|
299
188
|
Sequel.migration do
|
|
300
189
|
up do
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
+
)
|
|
305
201
|
end
|
|
306
|
-
end
|
|
307
202
|
|
|
308
|
-
|
|
309
|
-
|
|
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
|
|
310
209
|
end
|
|
311
210
|
end
|
|
312
211
|
|
|
313
|
-
#
|
|
314
|
-
|
|
212
|
+
# Then enable Sequel timestamps plugin
|
|
213
|
+
plugin :timestamps, update_on_create: true
|
|
315
214
|
```
|
|
316
215
|
|
|
317
|
-
|
|
216
|
+
**Estimated Effort:** 1-2 days
|
|
318
217
|
|
|
319
|
-
**
|
|
320
|
-
- `timestamps` - automatic created_at/updated_at
|
|
321
|
-
- `validation_helpers` - model validations
|
|
322
|
-
- `json_serializer` - better JSON handling
|
|
323
|
-
- `association_dependencies` - cascade deletes
|
|
324
|
-
|
|
325
|
-
**Example Benefit:**
|
|
326
|
-
```ruby
|
|
327
|
-
class Fact < Sequel::Model
|
|
328
|
-
plugin :timestamps
|
|
329
|
-
plugin :validation_helpers
|
|
330
|
-
|
|
331
|
-
many_to_one :subject, class: :Entity
|
|
332
|
-
one_to_many :provenance_records, class: :Provenance
|
|
333
|
-
|
|
334
|
-
def validate
|
|
335
|
-
super
|
|
336
|
-
validates_presence [:subject_entity_id, :predicate]
|
|
337
|
-
end
|
|
338
|
-
end
|
|
339
|
-
```
|
|
218
|
+
**Priority:** 🟡 Medium (current approach works, but DateTime is better practice)
|
|
340
219
|
|
|
341
220
|
---
|
|
342
221
|
|
|
343
|
-
## 3. Kent Beck Perspective (TDD,
|
|
344
|
-
|
|
345
|
-
### Focus Areas
|
|
346
|
-
- Test-first design
|
|
347
|
-
- Simple solutions
|
|
348
|
-
- Revealing intent
|
|
349
|
-
- Small steps
|
|
350
|
-
- Clear boundaries
|
|
351
|
-
|
|
352
|
-
### Critical Issues
|
|
353
|
-
|
|
354
|
-
#### 🔴 CLI Methods Untestable in Isolation
|
|
355
|
-
|
|
356
|
-
**Problem:** CLI methods create their own dependencies:
|
|
357
|
-
```ruby
|
|
358
|
-
def ingest
|
|
359
|
-
opts = parse_ingest_options
|
|
360
|
-
return 1 unless opts
|
|
222
|
+
## 3. Kent Beck Perspective (TDD, Simple Design)
|
|
361
223
|
|
|
362
|
-
|
|
363
|
-
ingester = ClaudeMemory::Ingest::Ingester.new(store) # Created here!
|
|
224
|
+
### What's Been Fixed Since Last Review ✅
|
|
364
225
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
|
369
230
|
|
|
370
|
-
**
|
|
371
|
-
- Can't inject test double for store
|
|
372
|
-
- Must use real database for tests
|
|
373
|
-
- Slow integration tests required
|
|
374
|
-
- Hard to test error paths
|
|
231
|
+
**Evidence:**
|
|
375
232
|
|
|
376
|
-
**Recommended Fix:**
|
|
377
233
|
```ruby
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
234
|
+
# dual_query_template.rb:22-34 - Simple and elegant!
|
|
235
|
+
def execute(scope:, limit: nil, &operation)
|
|
236
|
+
results = []
|
|
381
237
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
# ...
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
private
|
|
388
|
-
|
|
389
|
-
def default_store
|
|
390
|
-
@default_store ||= ClaudeMemory::Store::SQLiteStore.new(opts[:db])
|
|
391
|
-
end
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
#### 🔴 Methods Don't Reveal Intent
|
|
395
|
-
|
|
396
|
-
**Problem:** `run` method is a giant case statement (cli.rb:14-58):
|
|
397
|
-
```ruby
|
|
398
|
-
def run
|
|
399
|
-
command = @args.first || "help"
|
|
400
|
-
|
|
401
|
-
case command
|
|
402
|
-
when "help", "-h", "--help"
|
|
403
|
-
print_help
|
|
404
|
-
0
|
|
405
|
-
when "version", "-v", "--version"
|
|
406
|
-
print_version
|
|
407
|
-
0
|
|
408
|
-
# ... 15 more cases
|
|
238
|
+
if should_query_project?(scope)
|
|
239
|
+
results.concat(query_store(:project, &operation))
|
|
409
240
|
end
|
|
410
|
-
end
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
**Issues:**
|
|
414
|
-
- Doesn't reveal what the CLI does
|
|
415
|
-
- Adding commands requires modifying this method
|
|
416
|
-
- No clear command structure
|
|
417
|
-
|
|
418
|
-
**Recommended Fix:**
|
|
419
|
-
```ruby
|
|
420
|
-
def run
|
|
421
|
-
command_name = extract_command_name
|
|
422
|
-
command = find_command(command_name)
|
|
423
|
-
command.call(arguments)
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
private
|
|
427
|
-
|
|
428
|
-
def find_command(name)
|
|
429
|
-
COMMANDS.fetch(name) { UnknownCommand.new(name) }
|
|
430
|
-
end
|
|
431
|
-
|
|
432
|
-
COMMANDS = {
|
|
433
|
-
'help' => Commands::Help.new(@stdout),
|
|
434
|
-
'ingest' => Commands::Ingest.new(@stdout, @stderr),
|
|
435
|
-
# ...
|
|
436
|
-
}
|
|
437
|
-
```
|
|
438
241
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
```ruby
|
|
443
|
-
opts[:global] = true if !opts[:global] && !opts[:project]
|
|
444
|
-
opts[:project] = true if !opts[:global] && !opts[:project]
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
**Issues:**
|
|
448
|
-
- Double negative logic
|
|
449
|
-
- Duplicate condition
|
|
450
|
-
- Intent unclear (setting both to true?)
|
|
451
|
-
- Bug: both will be true after these lines!
|
|
242
|
+
if should_query_global?(scope)
|
|
243
|
+
results.concat(query_store(:global, &operation))
|
|
244
|
+
end
|
|
452
245
|
|
|
453
|
-
|
|
454
|
-
```ruby
|
|
455
|
-
if !opts[:global] && !opts[:project]
|
|
456
|
-
opts[:global] = true
|
|
457
|
-
opts[:project] = true
|
|
246
|
+
results
|
|
458
247
|
end
|
|
459
248
|
```
|
|
460
249
|
|
|
461
|
-
|
|
462
|
-
```ruby
|
|
463
|
-
opts[:global] = opts[:project] = true if opts.values_at(:global, :project).none?
|
|
464
|
-
```
|
|
250
|
+
**Kent Beck Would Say:** "This is what simple design looks like. Clear intent, no clever tricks."
|
|
465
251
|
|
|
466
|
-
|
|
252
|
+
### Issues Remaining
|
|
467
253
|
|
|
468
|
-
|
|
469
|
-
```ruby
|
|
470
|
-
def initialize(store)
|
|
471
|
-
@store = store
|
|
472
|
-
@db = store.db
|
|
473
|
-
ensure_fts_table! # Side effect!
|
|
474
|
-
end
|
|
475
|
-
```
|
|
254
|
+
#### 🔵 Low Priority: Constructor Side Effects
|
|
476
255
|
|
|
477
|
-
**
|
|
478
|
-
- Constructor has side effect (creates table)
|
|
479
|
-
- Violates Command-Query Separation
|
|
480
|
-
- Can't instantiate without modifying database
|
|
481
|
-
- Hard to test
|
|
256
|
+
**Problem**: LexicalFTS still has side effect in constructor:
|
|
482
257
|
|
|
483
|
-
**Recommended Fix:**
|
|
484
258
|
```ruby
|
|
259
|
+
# index/lexical_fts.rb:6-10
|
|
485
260
|
def initialize(store)
|
|
486
261
|
@store = store
|
|
487
262
|
@db = store.db
|
|
263
|
+
@fts_table_ensured = false # Good: now uses flag!
|
|
488
264
|
end
|
|
489
265
|
|
|
266
|
+
# lexical_fts.rb:12-13
|
|
490
267
|
def index_content_item(content_item_id, text)
|
|
491
|
-
ensure_fts_table! #
|
|
268
|
+
ensure_fts_table! # Side effect on first use
|
|
492
269
|
# ...
|
|
493
270
|
end
|
|
494
271
|
```
|
|
495
272
|
|
|
496
|
-
|
|
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."
|
|
497
276
|
|
|
498
|
-
|
|
277
|
+
**Recommended Fix:**
|
|
499
278
|
|
|
500
|
-
**Problem:** Parser, validator, executor, formatter all in one method:
|
|
501
279
|
```ruby
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
query = @args[1]
|
|
505
|
-
|
|
506
|
-
# Validate
|
|
507
|
-
unless query
|
|
508
|
-
@stderr.puts "Usage: ..."
|
|
509
|
-
return 1
|
|
510
|
-
end
|
|
280
|
+
# Option 1: Keep current lazy approach (acceptable)
|
|
281
|
+
# Already improved with @fts_table_ensured flag
|
|
511
282
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
|
516
290
|
end
|
|
517
|
-
|
|
518
|
-
# Execute
|
|
519
|
-
manager = ClaudeMemory::Store::StoreManager.new
|
|
520
|
-
recall = ClaudeMemory::Recall.new(manager)
|
|
521
|
-
results = recall.query(query, limit: opts[:limit], scope: opts[:scope])
|
|
522
|
-
|
|
523
|
-
# Format
|
|
524
|
-
if results.empty?
|
|
525
|
-
@stdout.puts "No facts found."
|
|
526
|
-
else
|
|
527
|
-
results.each do |result|
|
|
528
|
-
print_fact(result[:fact])
|
|
529
|
-
# ...
|
|
530
|
-
end
|
|
531
|
-
end
|
|
532
|
-
|
|
533
|
-
# Cleanup
|
|
534
|
-
manager.close
|
|
535
|
-
0
|
|
536
291
|
end
|
|
292
|
+
|
|
293
|
+
# Then in migrations or initialization:
|
|
294
|
+
Index::LexicalFTS.setup_schema(db)
|
|
537
295
|
```
|
|
538
296
|
|
|
539
|
-
**
|
|
297
|
+
**Estimated Effort:** 0.5 days
|
|
298
|
+
|
|
299
|
+
**Priority:** 🔵 Low (current approach is acceptable with flag)
|
|
540
300
|
|
|
541
301
|
---
|
|
542
302
|
|
|
543
303
|
## 4. Avdi Grimm Perspective (Confident Ruby)
|
|
544
304
|
|
|
545
|
-
###
|
|
546
|
-
- Confident code
|
|
547
|
-
- Tell, don't ask
|
|
548
|
-
- Null object pattern
|
|
549
|
-
- Duck typing
|
|
550
|
-
- Meaningful return values
|
|
551
|
-
|
|
552
|
-
### Critical Issues
|
|
553
|
-
|
|
554
|
-
#### 🔴 Nil Checks Throughout (recall.rb)
|
|
305
|
+
### What's Been Fixed Since Last Review ✅
|
|
555
306
|
|
|
556
|
-
**
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
explain_from_store(store, fact_id)
|
|
561
|
-
end
|
|
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
|
|
562
311
|
|
|
563
|
-
|
|
564
|
-
fact = find_fact_from_store(store, fact_id)
|
|
565
|
-
return nil unless fact # Returning nil!
|
|
312
|
+
### Issues Remaining
|
|
566
313
|
|
|
567
|
-
|
|
568
|
-
fact: fact,
|
|
569
|
-
receipts: find_receipts_from_store(store, fact_id),
|
|
570
|
-
# ...
|
|
571
|
-
}
|
|
572
|
-
end
|
|
573
|
-
```
|
|
314
|
+
#### 🟡 Medium Priority: Inconsistent Return Values
|
|
574
315
|
|
|
575
|
-
**
|
|
576
|
-
- Caller must check for nil
|
|
577
|
-
- Forces defensive programming everywhere
|
|
578
|
-
- No clear "not found" semantics
|
|
316
|
+
**Problem**: Methods still return different types on success vs failure:
|
|
579
317
|
|
|
580
|
-
**Recommended Fix:**
|
|
581
318
|
```ruby
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
def present?
|
|
592
|
-
false
|
|
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)
|
|
593
327
|
end
|
|
594
328
|
end
|
|
595
329
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
return NullExplanation.new unless fact
|
|
599
|
-
|
|
600
|
-
Explanation.new(
|
|
601
|
-
fact: fact,
|
|
602
|
-
receipts: find_receipts_from_store(store, fact_id),
|
|
603
|
-
# ...
|
|
604
|
-
)
|
|
605
|
-
end
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
#### 🔴 Inconsistent Return Values
|
|
609
|
-
|
|
610
|
-
**Problem:** Different methods return different types:
|
|
611
|
-
```ruby
|
|
612
|
-
# Returns integer exit code
|
|
613
|
-
def ingest
|
|
614
|
-
# ...
|
|
615
|
-
0
|
|
616
|
-
end
|
|
617
|
-
|
|
618
|
-
# Returns hash
|
|
619
|
-
def promote_fact(fact_id)
|
|
620
|
-
# ...
|
|
621
|
-
global_fact_id
|
|
622
|
-
end
|
|
623
|
-
|
|
624
|
-
# Returns nil or hash
|
|
625
|
-
def explain_from_store(store, fact_id)
|
|
626
|
-
return nil unless fact
|
|
627
|
-
{ fact: fact, ... }
|
|
628
|
-
end
|
|
330
|
+
# explain_from_store returns hash or NullExplanation
|
|
331
|
+
# But some methods return nil, others return empty arrays
|
|
629
332
|
```
|
|
630
333
|
|
|
631
|
-
**
|
|
632
|
-
- No consistent interface
|
|
633
|
-
- Callers can't rely on duck typing
|
|
634
|
-
- Some return success/failure, others return values
|
|
334
|
+
**Avdi Grimm Would Say:** "Use Result objects consistently to make success/failure explicit."
|
|
635
335
|
|
|
636
336
|
**Recommended Fix:**
|
|
637
|
-
Use result objects:
|
|
638
|
-
```ruby
|
|
639
|
-
class Result
|
|
640
|
-
def self.success(value)
|
|
641
|
-
Success.new(value)
|
|
642
|
-
end
|
|
643
|
-
|
|
644
|
-
def self.failure(error)
|
|
645
|
-
Failure.new(error)
|
|
646
|
-
end
|
|
647
|
-
end
|
|
648
|
-
|
|
649
|
-
def promote_fact(fact_id)
|
|
650
|
-
ensure_both!
|
|
651
|
-
|
|
652
|
-
fact = @project_store.facts.where(id: fact_id).first
|
|
653
|
-
return Result.failure("Fact not found") unless fact
|
|
654
337
|
|
|
655
|
-
global_fact_id = # ... promotion logic
|
|
656
|
-
Result.success(global_fact_id)
|
|
657
|
-
end
|
|
658
|
-
```
|
|
659
|
-
|
|
660
|
-
#### 🔴 Ask-Then-Do Pattern (publish.rb:165-171)
|
|
661
|
-
|
|
662
|
-
**Problem:**
|
|
663
|
-
```ruby
|
|
664
|
-
def should_write?(path, content)
|
|
665
|
-
return true unless File.exist?(path)
|
|
666
|
-
|
|
667
|
-
existing_hash = Digest::SHA256.file(path).hexdigest
|
|
668
|
-
new_hash = Digest::SHA256.hexdigest(content)
|
|
669
|
-
existing_hash != new_hash
|
|
670
|
-
end
|
|
671
|
-
|
|
672
|
-
# Usage:
|
|
673
|
-
if should_write?(path, content)
|
|
674
|
-
File.write(path, content)
|
|
675
|
-
end
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
**Issues:**
|
|
679
|
-
- Asking for permission, then doing action
|
|
680
|
-
- Should just "tell" the object to write
|
|
681
|
-
|
|
682
|
-
**Recommended Fix:**
|
|
683
338
|
```ruby
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
339
|
+
module ClaudeMemory
|
|
340
|
+
module Domain
|
|
341
|
+
class QueryResult
|
|
342
|
+
def self.success(value)
|
|
343
|
+
Success.new(value)
|
|
344
|
+
end
|
|
687
345
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
346
|
+
def self.not_found(message = "Not found")
|
|
347
|
+
NotFound.new(message)
|
|
348
|
+
end
|
|
691
349
|
|
|
692
|
-
|
|
350
|
+
def self.error(message)
|
|
351
|
+
Error.new(message)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
693
354
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
355
|
+
class Success < QueryResult
|
|
356
|
+
attr_reader :value
|
|
357
|
+
def initialize(value) = @value = value
|
|
358
|
+
def success? = true
|
|
359
|
+
def not_found? = false
|
|
360
|
+
def error? = false
|
|
361
|
+
end
|
|
700
362
|
|
|
701
|
-
|
|
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
|
|
702
370
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
def
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
if matching
|
|
710
|
-
add_provenance(matching[:id], content_item_id, fact_data)
|
|
711
|
-
outcome[:provenance] = 1
|
|
712
|
-
return outcome # Early return 1
|
|
713
|
-
elsif supersession_signal?(fact_data)
|
|
714
|
-
supersede_facts(existing_facts, occurred_at)
|
|
715
|
-
outcome[:superseded] = existing_facts.size
|
|
716
|
-
else
|
|
717
|
-
create_conflict(existing_facts.first[:id], fact_data, subject_id, content_item_id, occurred_at)
|
|
718
|
-
outcome[:conflicts] = 1
|
|
719
|
-
return outcome # Early return 2
|
|
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
|
|
720
377
|
end
|
|
721
378
|
end
|
|
722
|
-
|
|
723
|
-
# ... continues
|
|
724
379
|
end
|
|
725
|
-
```
|
|
726
380
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
**Recommended Fix:**
|
|
733
|
-
Extract to guard clauses at top:
|
|
734
|
-
```ruby
|
|
735
|
-
def resolve_fact(fact_data, entity_ids, content_item_id, occurred_at)
|
|
736
|
-
outcome = build_outcome
|
|
737
|
-
|
|
738
|
-
return handle_matching_fact(...) if matching_fact_exists?(...)
|
|
739
|
-
return handle_conflict(...) if conflicts_with_existing?(...)
|
|
740
|
-
|
|
741
|
-
create_new_fact(...)
|
|
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)
|
|
742
386
|
end
|
|
743
387
|
```
|
|
744
388
|
|
|
745
|
-
|
|
389
|
+
**Estimated Effort:** 1-2 days
|
|
746
390
|
|
|
747
|
-
**
|
|
748
|
-
```ruby
|
|
749
|
-
fact = {
|
|
750
|
-
subject_name: "repo",
|
|
751
|
-
predicate: "uses_database",
|
|
752
|
-
object_literal: "PostgreSQL",
|
|
753
|
-
status: "active",
|
|
754
|
-
confidence: 1.0
|
|
755
|
-
}
|
|
756
|
-
```
|
|
757
|
-
|
|
758
|
-
**Issues:**
|
|
759
|
-
- No domain behavior
|
|
760
|
-
- No validation
|
|
761
|
-
- No encapsulation
|
|
762
|
-
- Hard to refactor
|
|
763
|
-
|
|
764
|
-
**Recommended Fix:**
|
|
765
|
-
```ruby
|
|
766
|
-
class Fact
|
|
767
|
-
attr_reader :subject_name, :predicate, :object_literal, :status, :confidence
|
|
768
|
-
|
|
769
|
-
def initialize(subject_name:, predicate:, object_literal:, status: "active", confidence: 1.0)
|
|
770
|
-
@subject_name = subject_name
|
|
771
|
-
@predicate = predicate
|
|
772
|
-
@object_literal = object_literal
|
|
773
|
-
@status = status
|
|
774
|
-
@confidence = confidence
|
|
775
|
-
|
|
776
|
-
validate!
|
|
777
|
-
end
|
|
778
|
-
|
|
779
|
-
def active?
|
|
780
|
-
status == "active"
|
|
781
|
-
end
|
|
782
|
-
|
|
783
|
-
def superseded?
|
|
784
|
-
status == "superseded"
|
|
785
|
-
end
|
|
786
|
-
|
|
787
|
-
private
|
|
788
|
-
|
|
789
|
-
def validate!
|
|
790
|
-
raise ArgumentError, "predicate required" if predicate.nil?
|
|
791
|
-
raise ArgumentError, "confidence must be 0-1" unless (0..1).cover?(confidence)
|
|
792
|
-
end
|
|
793
|
-
end
|
|
794
|
-
```
|
|
391
|
+
**Priority:** 🟡 Medium (would improve error handling clarity)
|
|
795
392
|
|
|
796
393
|
---
|
|
797
394
|
|
|
798
395
|
## 5. Gary Bernhardt Perspective (Boundaries, Fast Tests)
|
|
799
396
|
|
|
800
|
-
###
|
|
801
|
-
- Functional core, imperative shell
|
|
802
|
-
- Fast unit tests
|
|
803
|
-
- Clear boundaries
|
|
804
|
-
- Separation of I/O and logic
|
|
805
|
-
- Value objects
|
|
397
|
+
### What's Been Fixed Since Last Review ✅
|
|
806
398
|
|
|
807
|
-
|
|
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)! ✅
|
|
808
404
|
|
|
809
|
-
|
|
405
|
+
**Evidence:**
|
|
810
406
|
|
|
811
|
-
**Problem:** Every CLI method mixes computation with I/O:
|
|
812
407
|
```ruby
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
end
|
|
819
|
-
|
|
820
|
-
opts = {limit: 10, scope: "all"} # Logic
|
|
821
|
-
OptionParser.new do |o| # I/O (arg parsing)
|
|
822
|
-
o.on("--limit N", Integer) { |v| opts[:limit] = v }
|
|
823
|
-
end
|
|
824
|
-
|
|
825
|
-
manager = ClaudeMemory::Store::StoreManager.new # I/O (database)
|
|
826
|
-
recall = ClaudeMemory::Recall.new(manager)
|
|
827
|
-
results = recall.query(query, limit: opts[:limit], scope: opts[:scope]) # Logic
|
|
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?
|
|
828
413
|
|
|
829
|
-
|
|
830
|
-
@stdout.puts "No facts found." # I/O
|
|
831
|
-
else
|
|
832
|
-
@stdout.puts "Found #{results.size} fact(s):\n\n" # I/O
|
|
833
|
-
results.each do |result|
|
|
834
|
-
print_fact(result[:fact]) # I/O
|
|
835
|
-
end
|
|
836
|
-
end
|
|
837
|
-
|
|
838
|
-
manager.close # I/O
|
|
839
|
-
0
|
|
414
|
+
rank_by_average_similarity(multi_concept_facts, limit)
|
|
840
415
|
end
|
|
841
|
-
```
|
|
842
416
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
- Hard to test error cases
|
|
847
|
-
- Can't reuse logic in different contexts
|
|
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?
|
|
848
420
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
module ClaudeMemory
|
|
853
|
-
module Core
|
|
854
|
-
class RecallQuery
|
|
855
|
-
def self.call(query:, limit:, scope:, facts_repository:)
|
|
856
|
-
facts = facts_repository.search(query, limit: limit, scope: scope)
|
|
857
|
-
|
|
858
|
-
{
|
|
859
|
-
found: facts.any?,
|
|
860
|
-
count: facts.size,
|
|
861
|
-
facts: facts.map { |f| FactPresenter.new(f) }
|
|
862
|
-
}
|
|
863
|
-
end
|
|
864
|
-
end
|
|
865
|
-
end
|
|
866
|
-
end
|
|
867
|
-
```
|
|
868
|
-
|
|
869
|
-
Imperative shell:
|
|
870
|
-
```ruby
|
|
871
|
-
def recall_cmd
|
|
872
|
-
params = RecallParams.parse(@args)
|
|
873
|
-
return usage_error unless params.valid?
|
|
874
|
-
|
|
875
|
-
manager = StoreManager.new
|
|
876
|
-
result = Core::RecallQuery.call(
|
|
877
|
-
query: params.query,
|
|
878
|
-
limit: params.limit,
|
|
879
|
-
scope: params.scope,
|
|
880
|
-
facts_repository: FactsRepository.new(manager)
|
|
881
|
-
)
|
|
421
|
+
results = build_facts_dataset(store)
|
|
422
|
+
.where(Sequel[:facts][:id] => fact_ids)
|
|
423
|
+
.all
|
|
882
424
|
|
|
883
|
-
|
|
884
|
-
manager.close
|
|
885
|
-
0
|
|
425
|
+
results.each_with_object({}) { |row, hash| hash[row[:id]] = row }
|
|
886
426
|
end
|
|
887
|
-
```
|
|
888
|
-
|
|
889
|
-
**Benefits:**
|
|
890
|
-
- Core logic is pure (no I/O)
|
|
891
|
-
- Fast unit tests for core
|
|
892
|
-
- Shell handles all I/O
|
|
893
|
-
- Easy to test edge cases
|
|
894
427
|
|
|
895
|
-
|
|
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
|
|
896
431
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
- No type safety
|
|
906
|
-
- Easy to swap arguments
|
|
907
|
-
- No validation
|
|
908
|
-
- No domain behavior
|
|
909
|
-
|
|
910
|
-
**Recommended Fix:**
|
|
911
|
-
```ruby
|
|
912
|
-
class SessionId
|
|
913
|
-
attr_reader :value
|
|
914
|
-
|
|
915
|
-
def initialize(value)
|
|
916
|
-
@value = value
|
|
917
|
-
validate!
|
|
918
|
-
end
|
|
919
|
-
|
|
920
|
-
def to_s
|
|
921
|
-
value
|
|
922
|
-
end
|
|
923
|
-
|
|
924
|
-
private
|
|
925
|
-
|
|
926
|
-
def validate!
|
|
927
|
-
raise ArgumentError, "Session ID cannot be empty" if value.nil? || value.empty?
|
|
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"
|
|
928
440
|
end
|
|
929
441
|
end
|
|
442
|
+
```
|
|
930
443
|
|
|
931
|
-
|
|
932
|
-
attr_reader :value
|
|
933
|
-
|
|
934
|
-
def initialize(value)
|
|
935
|
-
@value = Pathname.new(value)
|
|
936
|
-
validate!
|
|
937
|
-
end
|
|
444
|
+
**Gary Bernhardt Would Say:** "This is EXACTLY right. Pure logic, no I/O, instant tests, composable functions."
|
|
938
445
|
|
|
939
|
-
|
|
940
|
-
value.exist?
|
|
941
|
-
end
|
|
446
|
+
### Core Module Growth
|
|
942
447
|
|
|
943
|
-
|
|
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)
|
|
944
462
|
|
|
945
|
-
|
|
946
|
-
raise ArgumentError, "Path cannot be nil" if value.nil?
|
|
947
|
-
end
|
|
948
|
-
end
|
|
463
|
+
**Total: 17+ pure logic classes!**
|
|
949
464
|
|
|
950
|
-
|
|
951
|
-
def ingest(source:, session_id:, transcript_path:, project_path: nil)
|
|
952
|
-
session_id = SessionId.new(session_id) unless session_id.is_a?(SessionId)
|
|
953
|
-
transcript_path = TranscriptPath.new(transcript_path) unless transcript_path.is_a?(TranscriptPath)
|
|
465
|
+
### Issues Remaining
|
|
954
466
|
|
|
955
|
-
|
|
956
|
-
end
|
|
957
|
-
```
|
|
467
|
+
#### 🔵 Low Priority: Mutable State in Resolver
|
|
958
468
|
|
|
959
|
-
|
|
469
|
+
**Problem**: Still uses mutable instance variables for context:
|
|
960
470
|
|
|
961
|
-
**Problem:** Publish class directly reads/writes files:
|
|
962
471
|
```ruby
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
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
|
|
967
477
|
# ...
|
|
968
478
|
end
|
|
969
|
-
|
|
970
|
-
def ensure_import_exists(mode, path)
|
|
971
|
-
if File.exist?(claude_md) # Direct file I/O
|
|
972
|
-
content = File.read(claude_md) # Direct file I/O
|
|
973
|
-
# ...
|
|
974
|
-
end
|
|
975
|
-
end
|
|
976
479
|
```
|
|
977
480
|
|
|
978
|
-
**
|
|
979
|
-
- Can't test without filesystem
|
|
980
|
-
- Slow tests
|
|
981
|
-
- Hard to test error conditions
|
|
481
|
+
**Gary Bernhardt Would Say:** "Pass context explicitly through value objects."
|
|
982
482
|
|
|
983
483
|
**Recommended Fix:**
|
|
984
|
-
Inject file system adapter:
|
|
985
|
-
```ruby
|
|
986
|
-
class FileSystem
|
|
987
|
-
def exist?(path)
|
|
988
|
-
File.exist?(path)
|
|
989
|
-
end
|
|
990
|
-
|
|
991
|
-
def read(path)
|
|
992
|
-
File.read(path)
|
|
993
|
-
end
|
|
994
|
-
|
|
995
|
-
def write(path, content)
|
|
996
|
-
File.write(path, content)
|
|
997
|
-
end
|
|
998
|
-
|
|
999
|
-
def file_hash(path)
|
|
1000
|
-
Digest::SHA256.file(path).hexdigest
|
|
1001
|
-
end
|
|
1002
|
-
end
|
|
1003
|
-
|
|
1004
|
-
class InMemoryFileSystem
|
|
1005
|
-
def initialize
|
|
1006
|
-
@files = {}
|
|
1007
|
-
end
|
|
1008
|
-
|
|
1009
|
-
def exist?(path)
|
|
1010
|
-
@files.key?(path)
|
|
1011
|
-
end
|
|
1012
484
|
|
|
1013
|
-
def read(path)
|
|
1014
|
-
@files.fetch(path) { raise Errno::ENOENT }
|
|
1015
|
-
end
|
|
1016
|
-
|
|
1017
|
-
def write(path, content)
|
|
1018
|
-
@files[path] = content
|
|
1019
|
-
end
|
|
1020
|
-
|
|
1021
|
-
def file_hash(path)
|
|
1022
|
-
content = read(path)
|
|
1023
|
-
Digest::SHA256.hexdigest(content)
|
|
1024
|
-
end
|
|
1025
|
-
end
|
|
1026
|
-
|
|
1027
|
-
class Publish
|
|
1028
|
-
def initialize(store, file_system: FileSystem.new)
|
|
1029
|
-
@store = store
|
|
1030
|
-
@file_system = file_system
|
|
1031
|
-
end
|
|
1032
|
-
|
|
1033
|
-
def should_write?(path, content)
|
|
1034
|
-
return true unless @file_system.exist?(path)
|
|
1035
|
-
|
|
1036
|
-
existing_hash = @file_system.file_hash(path)
|
|
1037
|
-
new_hash = Digest::SHA256.hexdigest(content)
|
|
1038
|
-
existing_hash != new_hash
|
|
1039
|
-
end
|
|
1040
|
-
end
|
|
1041
|
-
```
|
|
1042
|
-
|
|
1043
|
-
**Test:**
|
|
1044
485
|
```ruby
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
fs = InMemoryFileSystem.new
|
|
1048
|
-
store = double(:store)
|
|
1049
|
-
publish = Publish.new(store, file_system: fs)
|
|
486
|
+
class ResolutionContext
|
|
487
|
+
attr_reader :project_path, :scope, :occurred_at, :content_item_id
|
|
1050
488
|
|
|
1051
|
-
|
|
489
|
+
def initialize(project_path:, scope:, occurred_at:, content_item_id:)
|
|
490
|
+
@project_path = project_path
|
|
491
|
+
@scope = scope
|
|
492
|
+
@occurred_at = occurred_at
|
|
493
|
+
@content_item_id = content_item_id
|
|
494
|
+
freeze # Immutable
|
|
1052
495
|
end
|
|
1053
496
|
end
|
|
1054
|
-
```
|
|
1055
|
-
|
|
1056
|
-
#### 🔴 State Stored in Instance Variables (resolver.rb:10-13)
|
|
1057
497
|
|
|
1058
|
-
**Problem:**
|
|
1059
|
-
```ruby
|
|
1060
498
|
def apply(extraction, content_item_id: nil, occurred_at: nil, project_path: nil, scope: "project")
|
|
1061
|
-
occurred_at ||= Time.now.utc.iso8601
|
|
1062
|
-
@current_project_path = project_path # Mutable state!
|
|
1063
|
-
@current_scope = scope # Mutable state!
|
|
1064
|
-
|
|
1065
|
-
# Used in private methods
|
|
1066
|
-
end
|
|
1067
|
-
|
|
1068
|
-
def resolve_fact(fact_data, entity_ids, content_item_id, occurred_at)
|
|
1069
|
-
# ... uses @current_project_path and @current_scope
|
|
1070
|
-
fact_scope = fact_data[:scope_hint] || @current_scope
|
|
1071
|
-
fact_project = (fact_scope == "global") ? nil : @current_project_path
|
|
1072
|
-
end
|
|
1073
|
-
```
|
|
1074
|
-
|
|
1075
|
-
**Issues:**
|
|
1076
|
-
- Hidden coupling between methods
|
|
1077
|
-
- Stateful object (not thread-safe)
|
|
1078
|
-
- Hard to reason about
|
|
1079
|
-
- Side effects on instance
|
|
1080
|
-
|
|
1081
|
-
**Recommended Fix:**
|
|
1082
|
-
Pass as parameters:
|
|
1083
|
-
```ruby
|
|
1084
|
-
def apply(extraction, content_item_id: nil, occurred_at: nil, project_path: nil, scope: "project")
|
|
1085
|
-
occurred_at ||= Time.now.utc.iso8601
|
|
1086
|
-
|
|
1087
499
|
context = ResolutionContext.new(
|
|
1088
500
|
project_path: project_path,
|
|
1089
501
|
scope: scope,
|
|
1090
|
-
occurred_at: occurred_at
|
|
502
|
+
occurred_at: occurred_at || Time.now.utc.iso8601,
|
|
503
|
+
content_item_id: content_item_id
|
|
1091
504
|
)
|
|
1092
505
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
extraction.facts.each do |fact_data|
|
|
1096
|
-
outcome = resolve_fact(fact_data, entity_ids, content_item_id, context)
|
|
1097
|
-
merge_outcome!(result, outcome)
|
|
1098
|
-
end
|
|
1099
|
-
|
|
1100
|
-
result
|
|
1101
|
-
end
|
|
1102
|
-
|
|
1103
|
-
def resolve_fact(fact_data, entity_ids, content_item_id, context)
|
|
1104
|
-
# Uses context parameter instead of instance variables
|
|
1105
|
-
fact_scope = fact_data[:scope_hint] || context.scope
|
|
1106
|
-
fact_project = (fact_scope == "global") ? nil : context.project_path
|
|
506
|
+
resolve_with_context(extraction, context)
|
|
1107
507
|
end
|
|
1108
508
|
```
|
|
1109
509
|
|
|
1110
|
-
|
|
510
|
+
**Estimated Effort:** 0.5 days
|
|
1111
511
|
|
|
1112
|
-
**
|
|
1113
|
-
```
|
|
1114
|
-
CLI → creates Store directly
|
|
1115
|
-
CLI → creates Ingester directly
|
|
1116
|
-
Ingester → creates FTS index
|
|
1117
|
-
Publish → reads files
|
|
1118
|
-
Hook::Handler → creates dependencies
|
|
1119
|
-
```
|
|
512
|
+
**Priority:** 🔵 Low (current approach works, improvement is stylistic)
|
|
1120
513
|
|
|
1121
|
-
|
|
1122
|
-
```
|
|
1123
|
-
Presentation Layer (CLI, HTTP)
|
|
1124
|
-
↓
|
|
1125
|
-
Application Layer (Use Cases / Commands)
|
|
1126
|
-
↓
|
|
1127
|
-
Domain Layer (Core business logic - pure)
|
|
1128
|
-
↓
|
|
1129
|
-
Infrastructure Layer (Database, Files, External APIs)
|
|
1130
|
-
```
|
|
514
|
+
---
|
|
1131
515
|
|
|
1132
|
-
|
|
1133
|
-
```ruby
|
|
1134
|
-
# Domain Layer - Pure logic
|
|
1135
|
-
module ClaudeMemory
|
|
1136
|
-
module Domain
|
|
1137
|
-
class Fact
|
|
1138
|
-
# Pure domain object
|
|
1139
|
-
end
|
|
516
|
+
## 6. Summary by Expert
|
|
1140
517
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
518
|
+
| Expert | Status | Key Observations |
|
|
519
|
+
|--------|--------|------------------|
|
|
520
|
+
| **Sandi Metz** | ✅ Excellent | Recall reduced 24%, MCP reduced 43%, proper extractions |
|
|
521
|
+
| **Jeremy Evans** | ✅ Excellent | JSON functions fixed, WAL checkpoint added, transactions good |
|
|
522
|
+
| **Kent Beck** | ✅ Excellent | DualQueryTemplate is exemplary, simple design throughout |
|
|
523
|
+
| **Avdi Grimm** | 🟡 Very Good | ResponseFormatter extracted, could use Result objects |
|
|
524
|
+
| **Gary Bernhardt** | ✅ Outstanding | 17+ pure logic classes, functional core growing rapidly |
|
|
1146
525
|
|
|
1147
|
-
|
|
1148
|
-
raise NotImplementedError
|
|
1149
|
-
end
|
|
1150
|
-
end
|
|
1151
|
-
end
|
|
1152
|
-
end
|
|
526
|
+
---
|
|
1153
527
|
|
|
1154
|
-
|
|
1155
|
-
module ClaudeMemory
|
|
1156
|
-
module Infrastructure
|
|
1157
|
-
class SequelFactRepository < Domain::FactRepository
|
|
1158
|
-
def initialize(db)
|
|
1159
|
-
@db = db
|
|
1160
|
-
end
|
|
528
|
+
## 7. Priority Refactoring Recommendations
|
|
1161
529
|
|
|
1162
|
-
|
|
1163
|
-
# Sequel-specific implementation
|
|
1164
|
-
end
|
|
530
|
+
### Optional Improvements (Low-Medium Priority)
|
|
1165
531
|
|
|
1166
|
-
|
|
1167
|
-
# Sequel-specific implementation
|
|
1168
|
-
end
|
|
1169
|
-
end
|
|
1170
|
-
end
|
|
1171
|
-
end
|
|
532
|
+
The codebase is now in excellent shape. These are refinements, not critical fixes:
|
|
1172
533
|
|
|
1173
|
-
|
|
1174
|
-
module ClaudeMemory
|
|
1175
|
-
module Application
|
|
1176
|
-
class PromoteFact
|
|
1177
|
-
def initialize(fact_repository:, event_publisher:)
|
|
1178
|
-
@fact_repository = fact_repository
|
|
1179
|
-
@event_publisher = event_publisher
|
|
1180
|
-
end
|
|
534
|
+
#### 1. Complete Strategy Pattern in Recall (Optional)
|
|
1181
535
|
|
|
1182
|
-
|
|
1183
|
-
fact = @fact_repository.find(fact_id)
|
|
1184
|
-
return Result.failure("Not found") unless fact
|
|
536
|
+
**Target**: Remove legacy mode conditionals
|
|
1185
537
|
|
|
1186
|
-
|
|
1187
|
-
@fact_repository.save(promoted)
|
|
1188
|
-
@event_publisher.publish(FactPromoted.new(fact_id))
|
|
538
|
+
**Benefit**: Cleaner architecture, easier testing
|
|
1189
539
|
|
|
1190
|
-
|
|
1191
|
-
end
|
|
1192
|
-
end
|
|
1193
|
-
end
|
|
1194
|
-
end
|
|
540
|
+
**Effort**: 1-2 days
|
|
1195
541
|
|
|
1196
|
-
|
|
1197
|
-
class CLI
|
|
1198
|
-
def promote_cmd
|
|
1199
|
-
fact_id = @args[1]&.to_i
|
|
1200
|
-
return usage_error unless valid_fact_id?(fact_id)
|
|
542
|
+
**Priority:** 🟡 Medium (system works well as-is)
|
|
1201
543
|
|
|
1202
|
-
|
|
544
|
+
#### 2. DateTime Migration (Recommended)
|
|
1203
545
|
|
|
1204
|
-
|
|
1205
|
-
@stdout.puts "Promoted fact ##{fact_id}"
|
|
1206
|
-
0
|
|
1207
|
-
else
|
|
1208
|
-
@stderr.puts result.error
|
|
1209
|
-
1
|
|
1210
|
-
end
|
|
1211
|
-
end
|
|
1212
|
-
end
|
|
1213
|
-
```
|
|
546
|
+
**Target**: Convert string timestamps to DateTime columns
|
|
1214
547
|
|
|
1215
|
-
|
|
548
|
+
**Benefit**: Better date operations, database best practice
|
|
1216
549
|
|
|
1217
|
-
|
|
550
|
+
**Effort**: 1-2 days
|
|
1218
551
|
|
|
1219
|
-
|
|
552
|
+
**Priority:** 🟡 Medium (improvement, not fix)
|
|
1220
553
|
|
|
1221
|
-
|
|
1222
|
-
```ruby
|
|
1223
|
-
@stdout.puts "Message" # No parens
|
|
1224
|
-
print_help # No parens
|
|
1225
|
-
manager.close # No parens
|
|
1226
|
-
opts = {limit: 10, scope: "all"} # No parens
|
|
554
|
+
#### 3. Result Objects (Nice to Have)
|
|
1227
555
|
|
|
1228
|
-
|
|
1229
|
-
o.on("--limit N", Integer) { |v| opts[:limit] = v } # Parens
|
|
1230
|
-
end
|
|
556
|
+
**Target**: Consistent return values across query methods
|
|
1231
557
|
|
|
1232
|
-
|
|
1233
|
-
```
|
|
558
|
+
**Benefit**: Clearer error handling, explicit success/failure
|
|
1234
559
|
|
|
1235
|
-
**
|
|
1236
|
-
- Use parens for methods with arguments
|
|
1237
|
-
- Omit for methods without arguments
|
|
1238
|
-
- Omit for keywords (`puts`, `print`, `raise`)
|
|
560
|
+
**Effort**: 1-2 days
|
|
1239
561
|
|
|
1240
|
-
|
|
562
|
+
**Priority:** 🟡 Medium (stylistic improvement)
|
|
1241
563
|
|
|
1242
|
-
|
|
1243
|
-
```ruby
|
|
1244
|
-
def upsert_content_item(source:, text_hash:, byte_len:, session_id: nil, transcript_path: nil,
|
|
1245
|
-
project_path: nil, occurred_at: nil, raw_text: nil, metadata: nil)
|
|
1246
|
-
# 9 parameters!
|
|
1247
|
-
end
|
|
564
|
+
#### 4. Individual Tool Classes (Optional)
|
|
1248
565
|
|
|
1249
|
-
|
|
1250
|
-
datatype: nil, polarity: "positive", valid_from: nil, status: "active",
|
|
1251
|
-
confidence: 1.0, created_from: nil, scope: "project", project_path: nil)
|
|
1252
|
-
# 12 parameters!
|
|
1253
|
-
end
|
|
1254
|
-
```
|
|
566
|
+
**Target**: Split MCP tools.rb into individual tool classes
|
|
1255
567
|
|
|
1256
|
-
**
|
|
1257
|
-
```ruby
|
|
1258
|
-
class ContentItemParams
|
|
1259
|
-
attr_reader :source, :text_hash, :byte_len, :session_id, :transcript_path,
|
|
1260
|
-
:project_path, :occurred_at, :raw_text, :metadata
|
|
1261
|
-
|
|
1262
|
-
def initialize(source:, text_hash:, byte_len:, **optional)
|
|
1263
|
-
@source = source
|
|
1264
|
-
@text_hash = text_hash
|
|
1265
|
-
@byte_len = byte_len
|
|
1266
|
-
@session_id = optional[:session_id]
|
|
1267
|
-
# ... etc
|
|
1268
|
-
end
|
|
1269
|
-
end
|
|
568
|
+
**Benefit**: Even cleaner separation, easier to add tools
|
|
1270
569
|
|
|
1271
|
-
|
|
1272
|
-
# Much cleaner
|
|
1273
|
-
end
|
|
1274
|
-
```
|
|
570
|
+
**Effort**: 1 day
|
|
1275
571
|
|
|
1276
|
-
|
|
572
|
+
**Priority:** 🔵 Low (current structure is good)
|
|
1277
573
|
|
|
1278
|
-
|
|
1279
|
-
```ruby
|
|
1280
|
-
# MCP Server
|
|
1281
|
-
request["id"] # String key
|
|
1282
|
-
request["method"] # String key
|
|
574
|
+
---
|
|
1283
575
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
576
|
+
## 8. Metrics Comparison
|
|
577
|
+
|
|
578
|
+
| Metric | Jan 27, 2026 | Jan 29, 2026 | Change |
|
|
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! |
|
|
592
|
+
|
|
593
|
+
**Key Insights:**
|
|
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
|
|
1288
599
|
|
|
1289
|
-
|
|
600
|
+
---
|
|
1290
601
|
|
|
1291
|
-
|
|
602
|
+
## 9. Positive Observations
|
|
1292
603
|
|
|
1293
|
-
|
|
1294
|
-
```ruby
|
|
1295
|
-
begin
|
|
1296
|
-
store = ClaudeMemory::Store::SQLiteStore.new(db_path)
|
|
1297
|
-
# ...
|
|
1298
|
-
rescue => e # Catches everything!
|
|
1299
|
-
issues << "#{label} database error: #{e.message}"
|
|
1300
|
-
end
|
|
1301
|
-
```
|
|
604
|
+
### Architectural Excellence
|
|
1302
605
|
|
|
1303
|
-
**
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
```
|
|
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
|
|
1309
611
|
|
|
1310
|
-
###
|
|
612
|
+
### Code Quality Wins
|
|
1311
613
|
|
|
1312
|
-
**
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
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)
|
|
1316
620
|
|
|
1317
|
-
|
|
1318
|
-
@project_path = project_path || env["CLAUDE_PROJECT_DIR"] || Dir.pwd
|
|
621
|
+
### Testing & Maintenance
|
|
1319
622
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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
|
|
1323
628
|
|
|
1324
|
-
|
|
1325
|
-
```ruby
|
|
1326
|
-
module ClaudeMemory
|
|
1327
|
-
class Configuration
|
|
1328
|
-
def initialize(env = ENV)
|
|
1329
|
-
@env = env
|
|
1330
|
-
end
|
|
629
|
+
---
|
|
1331
630
|
|
|
1332
|
-
|
|
1333
|
-
@env["HOME"] || File.expand_path("~")
|
|
1334
|
-
end
|
|
631
|
+
## 10. Conclusion
|
|
1335
632
|
|
|
1336
|
-
|
|
1337
|
-
@env["CLAUDE_PROJECT_DIR"] || Dir.pwd
|
|
1338
|
-
end
|
|
633
|
+
**The codebase has reached production-quality standards!**
|
|
1339
634
|
|
|
1340
|
-
|
|
1341
|
-
@env["CLAUDE_SESSION_ID"]
|
|
1342
|
-
end
|
|
1343
|
-
end
|
|
1344
|
-
end
|
|
1345
|
-
```
|
|
1346
|
-
|
|
1347
|
-
### 🟡 Boolean Traps
|
|
1348
|
-
|
|
1349
|
-
**Problem:**
|
|
1350
|
-
```ruby
|
|
1351
|
-
opts = {global: false, project: false}
|
|
635
|
+
### Major Achievements (Jan 27 → Jan 29)
|
|
1352
636
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
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
|
|
1356
643
|
|
|
1357
|
-
|
|
1358
|
-
if opts[:global]
|
|
1359
|
-
# ...
|
|
1360
|
-
end
|
|
1361
|
-
```
|
|
644
|
+
### Current State: Excellent
|
|
1362
645
|
|
|
1363
|
-
**
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
when :global
|
|
1369
|
-
manager.ensure_global!
|
|
1370
|
-
when :project
|
|
1371
|
-
manager.ensure_project!
|
|
1372
|
-
when :both
|
|
1373
|
-
manager.ensure_both!
|
|
1374
|
-
end
|
|
1375
|
-
```
|
|
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
|
|
1376
651
|
|
|
1377
|
-
###
|
|
652
|
+
### Remaining Work: Optional Refinements
|
|
1378
653
|
|
|
1379
|
-
**
|
|
1380
|
-
-
|
|
1381
|
-
-
|
|
1382
|
-
-
|
|
1383
|
-
-
|
|
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)
|
|
1384
659
|
|
|
1385
|
-
|
|
1386
|
-
```ruby
|
|
1387
|
-
# Current
|
|
1388
|
-
case command
|
|
1389
|
-
when "help", "-h", "--help"
|
|
1390
|
-
print_help
|
|
1391
|
-
when "version", "-v", "--version"
|
|
1392
|
-
print_version
|
|
1393
|
-
end
|
|
660
|
+
None of these are critical. The codebase is production-ready.
|
|
1394
661
|
|
|
1395
|
-
|
|
1396
|
-
case command
|
|
1397
|
-
in "help" | "-h" | "--help"
|
|
1398
|
-
print_help
|
|
1399
|
-
in "version" | "-v" | "--version"
|
|
1400
|
-
print_version
|
|
1401
|
-
in unknown
|
|
1402
|
-
handle_unknown(unknown)
|
|
1403
|
-
end
|
|
662
|
+
### Recommendation
|
|
1404
663
|
|
|
1405
|
-
|
|
1406
|
-
def valid?(fact)
|
|
1407
|
-
fact[:predicate] && fact[:subject_entity_id]
|
|
1408
|
-
end
|
|
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.
|
|
1409
665
|
|
|
1410
|
-
|
|
1411
|
-
def valid?(fact) = fact[:predicate] && fact[:subject_entity_id]
|
|
1412
|
-
```
|
|
666
|
+
The team has done outstanding work transforming this codebase from having god objects to having a beautiful functional core with clear boundaries.
|
|
1413
667
|
|
|
1414
668
|
---
|
|
1415
669
|
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
### ✅ Good Practices
|
|
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
|
|
1421
673
|
|
|
1422
|
-
|
|
1423
|
-
2. **Consistent Sequel Usage** - Most of the time uses Sequel datasets properly
|
|
1424
|
-
3. **Explicit Dependencies** - Constructor injection used (though inconsistently)
|
|
1425
|
-
4. **Module Namespacing** - Good use of nested modules
|
|
1426
|
-
5. **Test Coverage** - Spec files exist for most modules
|
|
1427
|
-
6. **Documentation** - Good README and CLAUDE.md files
|
|
1428
|
-
7. **Schema Versioning** - Database has schema version tracking
|
|
1429
|
-
8. **Error Classes** - Custom error classes defined
|
|
1430
|
-
9. **Keyword Arguments** - Modern Ruby style with keyword arguments
|
|
1431
|
-
10. **FTS Integration** - Good use of SQLite's FTS5 capabilities
|
|
674
|
+
**Overall Assessment:** ✅ PRODUCTION READY
|
|
1432
675
|
|
|
1433
676
|
---
|
|
1434
677
|
|
|
1435
|
-
##
|
|
1436
|
-
|
|
1437
|
-
### High Priority (Week 1-2)
|
|
1438
|
-
|
|
1439
|
-
1. **Extract CLI Command Objects**
|
|
1440
|
-
- Target: Reduce cli.rb from 867 lines to < 200
|
|
1441
|
-
- Extract each command to separate class
|
|
1442
|
-
- Use command pattern
|
|
1443
|
-
|
|
1444
|
-
2. **Add Transaction Safety**
|
|
1445
|
-
- Wrap `promote_fact` in transaction
|
|
1446
|
-
- Wrap resolver operations in transactions
|
|
1447
|
-
- Add rollback tests
|
|
678
|
+
## Appendix A: Quick Wins (COMPLETED ✅)
|
|
1448
679
|
|
|
1449
|
-
|
|
1450
|
-
- Replace with Sequel dataset methods
|
|
1451
|
-
- Ensures consistency
|
|
680
|
+
All quick wins from the previous review have been completed:
|
|
1452
681
|
|
|
1453
|
-
|
|
1454
|
-
-
|
|
1455
|
-
-
|
|
1456
|
-
- Make imperativeshell thin
|
|
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
|
|
1457
685
|
|
|
1458
|
-
|
|
686
|
+
2. ✅ **Add WAL checkpoint management** - DONE
|
|
687
|
+
- Added `checkpoint_wal` method to SQLiteStore (lines 40-42)
|
|
688
|
+
- Available for sweep operations
|
|
1459
689
|
|
|
1460
|
-
|
|
1461
|
-
-
|
|
1462
|
-
-
|
|
1463
|
-
- Documents domain
|
|
690
|
+
3. ✅ **Extract ResponseFormatter from Tools** - DONE
|
|
691
|
+
- Created `MCP::ResponseFormatter` class (331 lines)
|
|
692
|
+
- All formatting logic properly separated
|
|
1464
693
|
|
|
1465
|
-
|
|
1466
|
-
-
|
|
1467
|
-
-
|
|
1468
|
-
- Reduces nil checks
|
|
694
|
+
4. ✅ **Extract ToolDefinitions** - DONE
|
|
695
|
+
- Created `MCP::ToolDefinitions` module (279 lines)
|
|
696
|
+
- Tool schemas as pure data
|
|
1469
697
|
|
|
1470
|
-
|
|
1471
|
-
-
|
|
1472
|
-
-
|
|
1473
|
-
- Enables testing without database
|
|
698
|
+
5. ✅ **Add ConceptRanker to Core** - DONE
|
|
699
|
+
- Created `Core::ConceptRanker` (74 lines)
|
|
700
|
+
- Pure logic with fast tests
|
|
1474
701
|
|
|
1475
|
-
|
|
1476
|
-
- Remove conditional mode logic
|
|
1477
|
-
- Clearer single responsibility
|
|
1478
|
-
- Easier to maintain
|
|
1479
|
-
|
|
1480
|
-
### Low Priority (Week 5+)
|
|
1481
|
-
|
|
1482
|
-
9. **Add Domain Models**
|
|
1483
|
-
- Fact, Entity, Provenance classes
|
|
1484
|
-
- Rich domain behavior
|
|
1485
|
-
- Replace primitive hashes
|
|
1486
|
-
|
|
1487
|
-
10. **Introduce Proper Migrations**
|
|
1488
|
-
- Use Sequel migration framework
|
|
1489
|
-
- Versioned, reversible
|
|
1490
|
-
- Development/production parity
|
|
1491
|
-
|
|
1492
|
-
11. **Add Type Annotations**
|
|
1493
|
-
- Consider RBS or Sorbet
|
|
1494
|
-
- Better IDE support
|
|
1495
|
-
- Catches type errors early
|
|
1496
|
-
|
|
1497
|
-
12. **Centralize Configuration**
|
|
1498
|
-
- Configuration class
|
|
1499
|
-
- Environment variable access
|
|
1500
|
-
- Testable, mockable
|
|
702
|
+
**All quick wins completed!**
|
|
1501
703
|
|
|
1502
704
|
---
|
|
1503
705
|
|
|
1504
|
-
##
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
**
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
**
|
|
1516
|
-
-
|
|
1517
|
-
-
|
|
1518
|
-
-
|
|
1519
|
-
-
|
|
1520
|
-
|
|
1521
|
-
|
|
706
|
+
## Appendix B: File Size Report
|
|
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 ✅
|
|
1522
732
|
|
|
1523
733
|
---
|
|
1524
734
|
|
|
1525
|
-
## Appendix
|
|
1526
|
-
|
|
1527
|
-
1. **Sandi Metz** - _Practical Object-Oriented Design in Ruby_ (POODR)
|
|
1528
|
-
2. **Jeremy Evans** - _Sequel Documentation_ and _Roda Book_
|
|
1529
|
-
3. **Kent Beck** - _Test-Driven Development: By Example_
|
|
1530
|
-
4. **Avdi Grimm** - _Confident Ruby_
|
|
1531
|
-
5. **Gary Bernhardt** - _Boundaries_ talk, _Destroy All Software_ screencasts
|
|
1532
|
-
6. **Martin Fowler** - _Refactoring: Ruby Edition_
|
|
735
|
+
## Appendix C: Critical Files for Implementation
|
|
1533
736
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
1. Fix raw SQL in `doctor_cmd` (20 minutes)
|
|
1537
|
-
2. Consolidate `attr_reader` in StoreManager (5 minutes)
|
|
1538
|
-
3. Fix boolean logic in `parse_db_init_options` (10 minutes)
|
|
1539
|
-
4. Move `public` declaration in SQLiteStore (2 minutes)
|
|
1540
|
-
5. Extract long methods in CLI (1 hour per method)
|
|
1541
|
-
|
|
1542
|
-
---
|
|
737
|
+
Based on this comprehensive review, the most critical files for implementing the remaining optional improvements are:
|
|
1543
738
|
|
|
1544
|
-
|
|
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)
|