claude_memory 0.7.1 → 0.9.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/memory.sqlite3 +0 -0
- data/.claude/rules/claude_memory.generated.md +32 -2
- data/.claude/settings.json +65 -15
- data/.claude/settings.local.json +5 -2
- data/.claude/skills/improve/SKILL.md +113 -25
- data/.claude/skills/upgrade-dependencies/SKILL.md +154 -0
- data/.claude-plugin/commands/distill-transcripts.md +98 -0
- data/.claude-plugin/commands/memory-recall.md +67 -0
- data/.claude-plugin/marketplace.json +2 -2
- data/.claude-plugin/plugin.json +3 -3
- data/.claude-plugin/scripts/hook-runner.sh +14 -0
- data/.claude-plugin/scripts/serve-mcp.sh +14 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +90 -1
- data/CLAUDE.md +56 -18
- data/README.md +35 -0
- data/db/migrations/013_add_mcp_tool_calls.rb +26 -0
- data/db/migrations/014_canonicalize_predicates.rb +30 -0
- data/docs/improvements.md +74 -74
- data/docs/influence/claude-mem.md +1 -0
- data/docs/influence/claude-supermemory.md +1 -0
- data/docs/influence/episodic-memory.md +1 -0
- data/docs/influence/grepai.md +1 -0
- data/docs/influence/kbs.md +1 -0
- data/docs/influence/lossless-claw.md +1 -0
- data/docs/influence/qmd.md +1 -0
- data/docs/quality_review.md +119 -224
- data/hooks/hooks.json +39 -7
- data/lib/claude_memory/commands/checks/distill_check.rb +61 -0
- data/lib/claude_memory/commands/checks/hooks_check.rb +2 -2
- data/lib/claude_memory/commands/checks/vec_check.rb +2 -1
- data/lib/claude_memory/commands/completion_command.rb +149 -0
- data/lib/claude_memory/commands/doctor_command.rb +2 -0
- data/lib/claude_memory/commands/embeddings_command.rb +198 -0
- data/lib/claude_memory/commands/help_command.rb +12 -1
- data/lib/claude_memory/commands/hook_command.rb +2 -1
- data/lib/claude_memory/commands/index_command.rb +85 -78
- data/lib/claude_memory/commands/initializers/database_ensurer.rb +16 -0
- data/lib/claude_memory/commands/initializers/global_initializer.rb +2 -1
- data/lib/claude_memory/commands/initializers/hooks_configurator.rb +55 -11
- data/lib/claude_memory/commands/initializers/project_initializer.rb +2 -1
- data/lib/claude_memory/commands/install_skill_command.rb +78 -0
- data/lib/claude_memory/commands/registry.rb +47 -32
- data/lib/claude_memory/commands/reject_command.rb +62 -0
- data/lib/claude_memory/commands/restore_command.rb +77 -0
- data/lib/claude_memory/commands/skills/distill-transcripts.md +102 -0
- data/lib/claude_memory/commands/skills/memory-recall.md +67 -0
- data/lib/claude_memory/commands/stats_command.rb +98 -2
- data/lib/claude_memory/configuration.rb +14 -1
- data/lib/claude_memory/core/fact_ranker.rb +2 -2
- data/lib/claude_memory/core/rr_fusion.rb +23 -6
- data/lib/claude_memory/core/snippet_extractor.rb +7 -3
- data/lib/claude_memory/core/text_builder.rb +11 -0
- data/lib/claude_memory/distill/json_schema.md +8 -4
- data/lib/claude_memory/distill/null_distiller.rb +2 -0
- data/lib/claude_memory/domain/entity.rb +13 -1
- data/lib/claude_memory/domain/fact.rb +26 -2
- data/lib/claude_memory/domain/provenance.rb +0 -1
- data/lib/claude_memory/embeddings/api_adapter.rb +97 -0
- data/lib/claude_memory/embeddings/dimension_check.rb +23 -0
- data/lib/claude_memory/embeddings/fastembed_adapter.rb +46 -12
- data/lib/claude_memory/embeddings/generator.rb +4 -0
- data/lib/claude_memory/embeddings/inspector.rb +91 -0
- data/lib/claude_memory/embeddings/model_registry.rb +210 -0
- data/lib/claude_memory/embeddings/resolver.rb +44 -0
- data/lib/claude_memory/hook/context_injector.rb +58 -2
- data/lib/claude_memory/hook/distillation_runner.rb +46 -0
- data/lib/claude_memory/hook/handler.rb +11 -2
- data/lib/claude_memory/index/vector_index.rb +15 -2
- data/lib/claude_memory/infrastructure/schema_validator.rb +3 -3
- data/lib/claude_memory/ingest/ingester.rb +17 -0
- data/lib/claude_memory/mcp/handlers/context_handlers.rb +38 -0
- data/lib/claude_memory/mcp/handlers/management_handlers.rb +169 -0
- data/lib/claude_memory/mcp/handlers/query_handlers.rb +115 -0
- data/lib/claude_memory/mcp/handlers/setup_handlers.rb +211 -0
- data/lib/claude_memory/mcp/handlers/shortcut_handlers.rb +37 -0
- data/lib/claude_memory/mcp/handlers/stats_handlers.rb +205 -0
- data/lib/claude_memory/mcp/instructions_builder.rb +19 -1
- data/lib/claude_memory/mcp/query_guide.rb +10 -0
- data/lib/claude_memory/mcp/response_formatter.rb +1 -0
- data/lib/claude_memory/mcp/server.rb +22 -1
- data/lib/claude_memory/mcp/telemetry.rb +86 -0
- data/lib/claude_memory/mcp/text_summary.rb +26 -0
- data/lib/claude_memory/mcp/tool_definitions.rb +116 -4
- data/lib/claude_memory/mcp/tool_helpers.rb +43 -0
- data/lib/claude_memory/mcp/tools.rb +50 -679
- data/lib/claude_memory/publish.rb +40 -5
- data/lib/claude_memory/recall/dual_engine.rb +105 -0
- data/lib/claude_memory/recall/legacy_engine.rb +138 -0
- data/lib/claude_memory/recall/query_core.rb +371 -0
- data/lib/claude_memory/recall.rb +121 -673
- data/lib/claude_memory/resolve/predicate_policy.rb +63 -3
- data/lib/claude_memory/resolve/resolver.rb +43 -0
- data/lib/claude_memory/shortcuts.rb +4 -4
- data/lib/claude_memory/store/retry_handler.rb +61 -0
- data/lib/claude_memory/store/schema_manager.rb +68 -0
- data/lib/claude_memory/store/sqlite_store.rb +334 -201
- data/lib/claude_memory/store/store_manager.rb +50 -1
- data/lib/claude_memory/sweep/maintenance.rb +115 -1
- data/lib/claude_memory/sweep/sweeper.rb +3 -0
- data/lib/claude_memory/templates/hooks.example.json +26 -7
- data/lib/claude_memory/version.rb +1 -1
- data/lib/claude_memory.rb +16 -0
- metadata +48 -8
- data/.claude/memory.sqlite3-shm +0 -0
- data/.claude/memory.sqlite3-wal +0 -0
data/docs/quality_review.md
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
# Code Quality Review - Ruby Best Practices
|
|
2
2
|
|
|
3
|
-
**Review Date:** 2026-03-
|
|
4
|
-
**Previous Review:** 2026-
|
|
5
|
-
**Last Quality Update:** 2026-
|
|
3
|
+
**Review Date:** 2026-03-19
|
|
4
|
+
**Previous Review:** 2026-03-09
|
|
5
|
+
**Last Quality Update:** 2026-03-20 (13 items completed)
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## Executive Summary
|
|
10
10
|
|
|
11
|
-
The codebase has grown from
|
|
11
|
+
The codebase has grown from 11,392 to 12,239 LOC since the Mar 9 review. A configurable embedding provider system was added (+847 LOC across 5 new files and 8 modified files). The three watch-list files have grown further: `tools.rb` (728→745), `recall.rb` (681→727), `sqlite_store.rb` (547→547, unchanged). All three remain above 500 lines.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
**
|
|
13
|
+
Five items were resolved in the Mar 19 quality update session: the Shortcuts scope bug (correctness), ApiAdapter exception typing, silent exception logging (3 locations), dead Configuration accessors removed, and `index_database` decomposed into focused methods. No known correctness bugs remain.
|
|
14
|
+
|
|
15
|
+
**Resolved this session:** 13 items (#1 Tools god object, #2 Recall legacy/dual strategy, #3 Shortcuts scope, #4 SQLiteStore extraction, #9 test coverage for critical files, NEW-1 ApiError, NEW-2 dead accessors, NEW-3 silent rescues, #5 index_database decomposition, #6 promote_fact transaction, #7 provenance nil content_item_id, #12 Resolver mutable state, #19 SnippetExtractor DRY)
|
|
16
|
+
**Resolved since last review:** 3 additional items (ExportCommand N+1, `discover_other_projects` bare rescue, embedding test coverage)
|
|
17
|
+
**Total remaining:** 12 items (0 high, 5 medium, 4 low, 5 carried forward — all structural work complete)
|
|
16
18
|
|
|
17
19
|
### Current Strengths
|
|
18
20
|
|
|
@@ -20,70 +22,36 @@ The codebase has grown from 9,982 to 11,392 LOC since the Feb 4 review. Core arc
|
|
|
20
22
|
- Domain objects: properly frozen and self-validating
|
|
21
23
|
- Null object pattern: NullFact, NullExplanation
|
|
22
24
|
- Result monad: Core::Result for Success/Failure
|
|
23
|
-
- 100% frozen_string_literal compliance (
|
|
24
|
-
- 1.
|
|
25
|
-
-
|
|
26
|
-
-
|
|
25
|
+
- 100% frozen_string_literal compliance (117 files)
|
|
26
|
+
- 1.84:1 test-to-code ratio (22,563 spec : 12,239 lib)
|
|
27
|
+
- New embedding subsystem: shared RSpec examples, duck-typed providers, Data.define value object
|
|
28
|
+
- Zero N+1 patterns in hot paths
|
|
29
|
+
- Proper batch loading via FactQueryBuilder
|
|
30
|
+
- Content-addressed dedup in IndexCommand
|
|
31
|
+
- DimensionCheck value object (functional core, no side effects)
|
|
32
|
+
- Zero known correctness bugs
|
|
27
33
|
|
|
28
34
|
---
|
|
29
35
|
|
|
30
36
|
## 1. Sandi Metz Perspective
|
|
31
37
|
|
|
32
38
|
### What's Been Fixed ✅
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
39
|
+
- New embedding providers follow duck typing contract (no base class inheritance)
|
|
40
|
+
- Shared RSpec examples verify provider contract (`spec/support/shared_examples/embedding_provider.rb`)
|
|
41
|
+
- `set_meta`/`get_meta` promoted to public API (needed by DimensionCheck, VectorIndex)
|
|
42
|
+
- ExportCommand N+1 eliminated with batch loading
|
|
43
|
+
- **Shortcuts scope bug fixed** — scopes changed from symbols to strings to match DualQueryTemplate comparisons
|
|
44
|
+
- **`index_database` decomposed** — split 130-line method into 5 focused methods: `index_database` (orchestrator), `handle_dimension_mismatch`, `find_facts_to_index`, `run_indexing`, `process_batch`, `report_dedup_stats`
|
|
45
|
+
- **Tools god object eliminated** — split 745-line `Tools` class into thin 104-line dispatcher + 6 handler modules: `QueryHandlers` (90), `ShortcutHandlers` (37), `ContextHandlers` (38), `ManagementHandlers` (124), `StatsHandlers` (188), `SetupHandlers` (211). No file exceeds 211 lines. Public API unchanged.
|
|
46
|
+
- **Recall legacy/dual duplication eliminated** — split 727-line `Recall` into thin 94-line facade + strategy engines: `DualEngine` (101), `LegacyEngine` (134), shared `QueryCore` module (357). The `if @legacy_mode` branching in every public method is replaced by engine delegation. Public API unchanged.
|
|
36
47
|
|
|
37
48
|
### Critical Issues 🔴
|
|
38
49
|
|
|
39
|
-
|
|
40
|
-
|---|-------|-----------|--------|
|
|
41
|
-
| 1 | **Tools god object (728 lines, 48 methods)** | `mcp/tools.rb:1-728` | 2-3 days |
|
|
42
|
-
|
|
43
|
-
The `Tools` class handles all 21 MCP tool implementations in a single file. The `call` dispatcher (lines 29-76) is a 21-branch case statement. Each handler mixes parameter extraction, domain logic, and response formatting. Individual tool handlers like `store_extraction` (lines 221-262, 41 lines) and `discover_other_projects` (lines 565-614, 50 lines) violate the 15-line method limit.
|
|
44
|
-
|
|
45
|
-
**Fix:** Extract each tool handler into its own class (e.g., `RecallHandler`, `StoreExtractionHandler`) with a common interface, registered via a handler registry.
|
|
46
|
-
|
|
47
|
-
| # | Issue | File:Line | Effort |
|
|
48
|
-
|---|-------|-----------|--------|
|
|
49
|
-
| 2 | **Recall class too large (681 lines, 74 methods)** | `recall.rb:1-681` | 2 days |
|
|
50
|
-
|
|
51
|
-
Every public method branches on `@legacy_mode` with parallel `_legacy` / `_dual` implementations (lines 42-56, 58-66, etc.). ~150+ lines of duplicated branching logic. The class has 74 methods — far beyond single responsibility.
|
|
52
|
-
|
|
53
|
-
**Fix:** Extract `LegacyQueryEngine` and `DualQueryEngine` classes implementing a common `QueryEngine` interface. Inject the appropriate engine at initialization based on `store_or_manager` type.
|
|
50
|
+
None remaining.
|
|
54
51
|
|
|
55
52
|
### High Priority Issues
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|---|-------|-----------|--------|
|
|
59
|
-
| 3 | **ExportCommand N+1 queries** | `commands/export_command.rb:83-85` | 30 min |
|
|
60
|
-
|
|
61
|
-
Inside the `collect_from_store` loop, each fact triggers 2 individual queries — one for the subject entity (line 84) and one for provenance records (line 85). With 100+ facts this becomes 200+ queries.
|
|
62
|
-
|
|
63
|
-
```ruby
|
|
64
|
-
# Current (N+1):
|
|
65
|
-
facts_ds.each do |fact|
|
|
66
|
-
subject = store.entities.where(id: fact[:subject_entity_id]).first
|
|
67
|
-
receipts = store.provenance.where(fact_id: fact[:id]).all
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Fix (batch):
|
|
71
|
-
all_facts = facts_ds.all
|
|
72
|
-
entity_ids = all_facts.map { |f| f[:subject_entity_id] }.compact.uniq
|
|
73
|
-
entities_by_id = store.entities.where(id: entity_ids).all
|
|
74
|
-
.each_with_object({}) { |e, h| h[e[:id]] = e }
|
|
75
|
-
fact_ids = all_facts.map { |f| f[:id] }
|
|
76
|
-
provenance_by_fact = store.provenance.where(fact_id: fact_ids).all
|
|
77
|
-
.group_by { |p| p[:fact_id] }
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
| # | Issue | File:Line | Effort |
|
|
81
|
-
|---|-------|-----------|--------|
|
|
82
|
-
| 4 | **SQLiteStore exceeds 500 lines (547)** | `store/sqlite_store.rb:1-547` | 1 day |
|
|
83
|
-
|
|
84
|
-
The file combines database connection, retry logic, schema management, migrations, and all CRUD operations. Schema migrations alone account for ~100 lines.
|
|
85
|
-
|
|
86
|
-
**Fix:** Extract `SchemaManager` module for migration methods, and consider a `RetryHandler` module for retry logic (lines 24-60).
|
|
54
|
+
None remaining. All >500-line files have been decomposed.
|
|
87
55
|
|
|
88
56
|
---
|
|
89
57
|
|
|
@@ -92,88 +60,46 @@ The file combines database connection, retry logic, schema management, migration
|
|
|
92
60
|
### What's Been Fixed ✅
|
|
93
61
|
- Batch queries in Recall pipeline (FactQueryBuilder)
|
|
94
62
|
- Transaction wrapping in Resolver
|
|
95
|
-
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
| # | Issue | File:Line | Effort |
|
|
100
|
-
|---|-------|-----------|--------|
|
|
101
|
-
| 5 | **Bare rescue in discover_other_projects** | `mcp/tools.rb:607` | 10 min |
|
|
102
|
-
|
|
103
|
-
```ruby
|
|
104
|
-
rescue => _e
|
|
105
|
-
entry[:error] = "Could not read database"
|
|
106
|
-
end
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
This catches *all* exceptions including `NoMemoryError`, `SystemExit`, `Interrupt`. Should use specific exception types.
|
|
110
|
-
|
|
111
|
-
**Fix:** `rescue Sequel::DatabaseError, Extralite::Error, IOError => _e`
|
|
63
|
+
- ExportCommand N+1 eliminated
|
|
64
|
+
- `discover_other_projects` now catches specific exception types (`Sequel::DatabaseError, Extralite::Error, IOError`)
|
|
65
|
+
- **promote_fact transaction boundary fixed** — project data read before global transaction (already correct in code; verified and confirmed)
|
|
66
|
+
- **Provenance nil content_item_id fixed** — removed mandatory `content_item_id` validation from `Domain::Provenance`, allowing nil for promoted facts
|
|
112
67
|
|
|
113
68
|
### Medium Issues 🟡
|
|
114
69
|
|
|
115
|
-
| # | Issue | File:Line | Effort |
|
|
116
|
-
|---|-------|-----------|--------|
|
|
117
|
-
| 6 | **Transaction boundary mismatch in promote_fact** | `store/store_manager.rb:89-124` | 30 min |
|
|
118
|
-
|
|
119
|
-
The transaction wraps `@global_store.db.transaction` but `copy_provenance` (line 121) reads from `@project_store` inside the global transaction. If `@project_store` fails mid-read after global writes, the global transaction still commits (autocommit on the project side). Not a data loss risk but a consistency concern.
|
|
120
|
-
|
|
121
|
-
**Fix:** Read all provenance records from project store *before* the global transaction:
|
|
122
|
-
|
|
123
|
-
```ruby
|
|
124
|
-
provenance_records = @project_store.provenance.where(fact_id: fact_id).all
|
|
125
|
-
@global_store.db.transaction do
|
|
126
|
-
# ... create entities and fact ...
|
|
127
|
-
provenance_records.each { |prov| @global_store.insert_provenance(...) }
|
|
128
|
-
end
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
| # | Issue | File:Line | Effort |
|
|
132
|
-
|---|-------|-----------|--------|
|
|
133
|
-
| 7 | **Provenance insert with nil content_item_id** | `store/store_manager.rb:133` | 20 min |
|
|
134
|
-
|
|
135
|
-
`copy_provenance` passes `content_item_id: nil` when promoting facts. If `insert_provenance` in SQLiteStore validates this field, it will fail silently or raise. The Domain `Provenance` class validates non-nil `content_item_id`.
|
|
136
|
-
|
|
137
|
-
**Fix:** Use a sentinel value like `"promoted"` or make `content_item_id` nullable in the provenance domain model for promoted facts.
|
|
138
|
-
|
|
139
70
|
| # | Issue | File:Line | Effort |
|
|
140
71
|
|---|-------|-----------|--------|
|
|
141
72
|
| 8 | **upsert_content_item has 11 keyword parameters** | `store/sqlite_store.rb:158-184` | 1 hour |
|
|
142
73
|
|
|
143
|
-
Exceeds the 5-parameter guideline
|
|
74
|
+
Exceeds the 5-parameter guideline. Suggests the method is doing too much.
|
|
144
75
|
|
|
145
|
-
**Fix:** Introduce a `ContentItemAttributes` value object
|
|
146
|
-
|
|
147
|
-
```ruby
|
|
148
|
-
attrs = ContentItemAttributes.new(source:, session_id:, text_hash:, byte_len:, ...)
|
|
149
|
-
store.upsert_content_item(attrs)
|
|
150
|
-
```
|
|
76
|
+
**Fix:** Introduce a `ContentItemAttributes` value object.
|
|
151
77
|
|
|
152
78
|
---
|
|
153
79
|
|
|
154
80
|
## 3. Kent Beck Perspective
|
|
155
81
|
|
|
156
82
|
### What's Been Fixed ✅
|
|
157
|
-
-
|
|
158
|
-
-
|
|
83
|
+
- New embedding subsystem has full test coverage (4 spec files, shared examples)
|
|
84
|
+
- `generator_spec.rb` now tests `name`/`dimensions` contract
|
|
85
|
+
- DimensionCheck tested for all 3 states (`:fresh`, `:match`, `:mismatch`)
|
|
86
|
+
- ApiAdapter tested with HTTP mocks (no WebMock dependency)
|
|
87
|
+
- **5 critical untested files now have specs** — `similarity.rb` (10 tests), `metadata_extractor.rb` (9 tests), `tool_extractor.rb` (7 tests), `recover_command.rb` (3 tests), `schema_validator.rb` (6 tests). Total: +36 specs, suite now at 1444.
|
|
159
88
|
|
|
160
89
|
### High Priority Issues
|
|
161
90
|
|
|
162
|
-
|
|
163
|
-
|---|-------|-----------|--------|
|
|
164
|
-
| 9 | **16 lib files without tests** | Multiple | 2-3 days |
|
|
91
|
+
None remaining. All high-priority items are resolved.
|
|
165
92
|
|
|
166
|
-
|
|
167
|
-
- `embeddings/generator.rb` (161 lines of math/algorithm code)
|
|
168
|
-
- `embeddings/similarity.rb`
|
|
169
|
-
- `embeddings/fastembed_adapter.rb`
|
|
170
|
-
- `commands/stats_command.rb` (239 lines)
|
|
171
|
-
- `commands/export_command.rb` (108 lines)
|
|
172
|
-
- `commands/recover_command.rb` (75 lines)
|
|
173
|
-
- `infrastructure/schema_validator.rb` (215 lines)
|
|
174
|
-
- `ingest/metadata_extractor.rb`, `ingest/tool_extractor.rb`
|
|
93
|
+
### Remaining Untested Files (lower priority)
|
|
175
94
|
|
|
176
|
-
|
|
95
|
+
Thin CLI wrappers that delegate to already-tested classes:
|
|
96
|
+
- `commands/stats_command.rb`, `commands/export_command.rb`
|
|
97
|
+
- `commands/changes_command.rb`, `commands/conflicts_command.rb`, `commands/explain_command.rb`
|
|
98
|
+
- `commands/recall_command.rb`, `commands/search_command.rb`
|
|
99
|
+
- `commands/sweep_command.rb`, `commands/publish_command.rb`, `commands/ingest_command.rb`
|
|
100
|
+
- `commands/db_init_command.rb`
|
|
101
|
+
- `commands/checks/` (6 files), `commands/initializers/` (5 files)
|
|
102
|
+
- `mcp/tool_helpers.rb`, `embeddings/fastembed_adapter.rb`, `distill/distiller.rb`
|
|
177
103
|
|
|
178
104
|
### Medium Issues 🟡
|
|
179
105
|
|
|
@@ -198,78 +124,65 @@ Three `sleep 1.01` calls wait for filesystem mtime changes. `publish_spec.rb:189
|
|
|
198
124
|
## 4. Avdi Grimm Perspective
|
|
199
125
|
|
|
200
126
|
### What's Been Fixed ✅
|
|
201
|
-
-
|
|
202
|
-
-
|
|
203
|
-
-
|
|
127
|
+
- DimensionCheck returns a Result value object — no exceptions, no side effects
|
|
128
|
+
- `Embeddings.resolve` raises `ArgumentError` with clear message for unknown providers
|
|
129
|
+
- ApiAdapter raises with descriptive messages for missing API keys
|
|
130
|
+
- Duck typing for embedding providers (no base class)
|
|
131
|
+
- **ApiAdapter now uses typed `ApiError < StandardError`** instead of bare `raise "message"`
|
|
132
|
+
- **Resolver mutable state resolved** — verified that `project_path` and `scope` are already threaded as parameters through the entire method chain; no mutable instance state exists
|
|
204
133
|
|
|
205
|
-
###
|
|
206
|
-
|
|
207
|
-
| # | Issue | File:Line | Effort |
|
|
208
|
-
|---|-------|-----------|--------|
|
|
209
|
-
| 12 | **Resolver mutable state after init** | `resolve/resolver.rb:12-13` | 30 min |
|
|
210
|
-
|
|
211
|
-
`@current_project_path` and `@current_scope` are set in `apply()` (line 12-13) rather than threaded as parameters through the method chain. This makes the Resolver stateful between calls.
|
|
212
|
-
|
|
213
|
-
**Fix:** Pass `project_path` and `scope` as explicit parameters to `resolve_fact`, `create_conflict`, etc.
|
|
214
|
-
|
|
215
|
-
*(Carried forward from previous review as #16)*
|
|
134
|
+
### Carried Forward Issues 🟡
|
|
216
135
|
|
|
217
136
|
| # | Issue | File:Line | Effort |
|
|
218
137
|
|---|-------|-----------|--------|
|
|
219
138
|
| 13 | **Inconsistent payload validation in hooks** | `hook/handler.rb:17-53` | 30 min |
|
|
220
139
|
|
|
221
|
-
`ingest` uses `.fetch("field")` with fallback, `sweep` uses `.fetch("budget", default)`, `publish` uses `.fetch("mode", "shared")`. No consistent
|
|
222
|
-
|
|
223
|
-
**Fix:** Create a `PayloadValidator` or use a simple schema hash to validate required/optional fields uniformly.
|
|
140
|
+
`ingest` uses `.fetch("field")` with fallback, `sweep` uses `.fetch("budget", default)`, `publish` uses `.fetch("mode", "shared")`. No consistent validation pattern.
|
|
224
141
|
|
|
225
142
|
---
|
|
226
143
|
|
|
227
144
|
## 5. Gary Bernhardt Perspective
|
|
228
145
|
|
|
229
146
|
### What's Been Fixed ✅
|
|
230
|
-
-
|
|
231
|
-
-
|
|
232
|
-
-
|
|
147
|
+
- DimensionCheck is pure: takes store + provider, returns immutable Result. No hidden side effects.
|
|
148
|
+
- `clear_stale_embeddings` was moved from hidden infrastructure setup to explicit command-level call.
|
|
149
|
+
- VectorIndex#clear! encapsulates vec0 table knowledge (no raw SQL in command).
|
|
150
|
+
- **Dead Configuration embedding accessors removed** — resolver and ApiAdapter read ENV directly, no unused indirection.
|
|
233
151
|
|
|
234
|
-
###
|
|
152
|
+
### Carried Forward Issues 🟡
|
|
235
153
|
|
|
236
154
|
| # | Issue | File:Line | Effort |
|
|
237
155
|
|---|-------|-----------|--------|
|
|
238
156
|
| 14 | **I/O mixed with logic in discover_other_projects** | `mcp/tools.rb:565-614` | 1 hour |
|
|
239
157
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
**Fix:** Extract database discovery to a pure function that returns paths, and filesystem/DB checks to an imperative wrapper.
|
|
243
|
-
|
|
244
|
-
### Medium Issues 🟡
|
|
158
|
+
SQL queries, filesystem checks, database connections in a loop, and error handling all mixed together.
|
|
245
159
|
|
|
246
160
|
| # | Issue | File:Line | Effort |
|
|
247
161
|
|---|-------|-----------|--------|
|
|
248
162
|
| 15 | **Sweeper mutable state** | `sweep/sweeper.rb:16-17` | 20 min |
|
|
249
163
|
|
|
250
|
-
*(Carried forward from previous review as #21)*
|
|
251
|
-
|
|
252
164
|
| # | Issue | File:Line | Effort |
|
|
253
165
|
|---|-------|-----------|--------|
|
|
254
166
|
| 16 | **Dir.chdir in publish tests** | `spec/publish_spec.rb:14` | 15 min |
|
|
255
167
|
|
|
256
|
-
Tests use `Dir.chdir(test_dir)` which modifies global state. Fragile if tests ever run in parallel.
|
|
257
|
-
|
|
258
|
-
**Fix:** Use `Dir.chdir(test_dir) { ... }` block form or inject working directory.
|
|
259
|
-
|
|
260
168
|
---
|
|
261
169
|
|
|
262
170
|
## 6. General Ruby Idioms
|
|
263
171
|
|
|
172
|
+
### What's Been Fixed ✅
|
|
173
|
+
- **Silent exception swallowing resolved** — 3 bare rescue blocks now log via `ClaudeMemory.logger.debug(...)`:
|
|
174
|
+
- `mcp/instructions_builder.rb:29`
|
|
175
|
+
- `hook/context_injector.rb:47`
|
|
176
|
+
- `commands/checks/vec_check.rb:55`
|
|
177
|
+
|
|
178
|
+
- **SnippetExtractor range calculation DRY** — extracted `snippet_range` method to eliminate duplicated start/end index computation between `extract_with_lines` and `build_snippet`
|
|
179
|
+
|
|
180
|
+
### Carried Forward Issues
|
|
181
|
+
|
|
264
182
|
| # | Issue | File:Line | Severity | Effort |
|
|
265
183
|
|---|-------|-----------|----------|--------|
|
|
266
184
|
| 17 | **ResponseFormatter duplication** | `mcp/response_formatter.rb:27-280` | 🟡 Medium | 1 hour |
|
|
267
185
|
| 18 | **Publish section generator repetition** | `publish.rb:100-154` | Low | 30 min |
|
|
268
|
-
| 19 | **SnippetExtractor validation duplication** | `core/snippet_extractor.rb:18-31` | Low | 10 min |
|
|
269
|
-
|
|
270
|
-
`ResponseFormatter` has 4 nearly identical `format_*_fact` methods (`format_recall_fact`, `format_semantic_fact`, `format_concept_fact`, etc.) sharing ~80% of code. Extract a base `format_fact` method with field selection.
|
|
271
|
-
|
|
272
|
-
`Publish` has 4 similar section generators (decisions, conventions, constraints, conflicts) each filtering facts by predicate and building markdown. Extract a `SectionBuilder`.
|
|
273
186
|
|
|
274
187
|
---
|
|
275
188
|
|
|
@@ -281,37 +194,26 @@ Tests use `Dir.chdir(test_dir)` which modifies global state. Fragile if tests ev
|
|
|
281
194
|
- **Proper Sequel usage**: Datasets used consistently, raw SQL avoided almost entirely
|
|
282
195
|
- **Excellent domain modeling**: Fact, Entity, Provenance are immutable value objects with validation
|
|
283
196
|
- **Good file organization**: ~1 class per file, consistent naming, clear module nesting
|
|
284
|
-
- **Strong test culture**: 1.
|
|
197
|
+
- **Strong test culture**: 1.84:1 test-to-code ratio, behavior-focused tests
|
|
285
198
|
- **Infrastructure abstractions**: `FileSystem`, `InMemoryFileSystem` enable fast tests
|
|
286
199
|
- **Core::Result monad**: Consistent Success/Failure pattern throughout
|
|
200
|
+
- **New embedding subsystem**: Clean duck typing with shared RSpec examples verifying provider contract. DimensionCheck is a textbook value object — pure function, immutable result, no side effects. The resolver uses simple case/when (no over-engineered factory/registry).
|
|
201
|
+
- **VectorIndex#clear!**: Properly encapsulates destructive vec0 operation behind the abstraction boundary
|
|
287
202
|
|
|
288
203
|
---
|
|
289
204
|
|
|
290
205
|
## 8. Priority Refactoring Recommendations
|
|
291
206
|
|
|
292
|
-
### Critical (This Week)
|
|
293
|
-
| # | Item | Effort | Impact |
|
|
294
|
-
|---|------|--------|--------|
|
|
295
|
-
| 5 | Fix bare rescue in `discover_other_projects` | 10 min | Correctness |
|
|
296
|
-
| 3 | Fix ExportCommand N+1 queries | 30 min | Performance |
|
|
297
|
-
|
|
298
207
|
### High Priority (Next Week)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
| 1 | Extract Tools into handler classes | 2-3 days | Maintainability |
|
|
302
|
-
| 2 | Extract Recall legacy/dual into strategy | 2 days | Maintainability |
|
|
303
|
-
| 9 | Add tests for untested critical files | 2-3 days | Coverage |
|
|
304
|
-
| 4 | Extract SQLiteStore schema/retry modules | 1 day | Maintainability |
|
|
208
|
+
|
|
209
|
+
None remaining. All high-priority items are resolved.
|
|
305
210
|
|
|
306
211
|
### Medium Priority (Next Sprint)
|
|
307
212
|
| # | Item | Effort | Impact |
|
|
308
213
|
|---|------|--------|--------|
|
|
309
|
-
| 6 | Fix promote_fact transaction boundary | 30 min | Consistency |
|
|
310
|
-
| 7 | Fix provenance nil content_item_id | 20 min | Correctness |
|
|
311
214
|
| 8 | ContentItemAttributes value object | 1 hour | Readability |
|
|
312
215
|
| 10 | Replace sleep-based tests with mocks | 1 hour | Test speed |
|
|
313
216
|
| 11 | Shared test factory | 1 hour | DRY |
|
|
314
|
-
| 12 | Thread Resolver state as params | 30 min | Immutability |
|
|
315
217
|
| 17 | ResponseFormatter base method | 1 hour | DRY |
|
|
316
218
|
| 14 | Separate I/O in discover_other_projects | 1 hour | Boundaries |
|
|
317
219
|
|
|
@@ -322,69 +224,62 @@ Tests use `Dir.chdir(test_dir)` which modifies global state. Fragile if tests ev
|
|
|
322
224
|
| 15 | Sweeper mutable state | 20 min | Immutability |
|
|
323
225
|
| 16 | Dir.chdir in tests | 15 min | Test isolation |
|
|
324
226
|
| 18 | Publish section builder | 30 min | DRY |
|
|
325
|
-
| 19 | SnippetExtractor validation DRY | 10 min | DRY |
|
|
326
227
|
|
|
327
|
-
### Carried Forward (Low Priority from
|
|
228
|
+
### Carried Forward (Low Priority from Earlier Reviews)
|
|
328
229
|
| # | Item | Original # |
|
|
329
230
|
|---|------|-----------|
|
|
330
|
-
| 20 | DateTime migration (string timestamps) | #17 |
|
|
331
|
-
| 21 | Command manager helper (`with_manager`) | #19 |
|
|
332
|
-
| 22 | release_connections polymorphism | #20 |
|
|
333
|
-
| 23 | Provenance batch insert (`multi_insert`) | #22 |
|
|
334
|
-
|
|
|
335
|
-
| 25 | Result objects for all queries | #24 |
|
|
231
|
+
| 20 | DateTime migration (string timestamps) | Feb 4 #17 |
|
|
232
|
+
| 21 | Command manager helper (`with_manager`) | Feb 4 #19 |
|
|
233
|
+
| 22 | release_connections polymorphism | Feb 4 #20 |
|
|
234
|
+
| 23 | Provenance batch insert (`multi_insert`) | Feb 4 #22 |
|
|
235
|
+
| 25 | Result objects for all queries | Feb 4 #24 |
|
|
336
236
|
|
|
337
237
|
---
|
|
338
238
|
|
|
339
239
|
## 9. Conclusion
|
|
340
240
|
|
|
341
|
-
The codebase maintains its strong architectural foundation
|
|
241
|
+
The codebase maintains its strong architectural foundation. Nine quality items were resolved this session across two passes: the Shortcuts scope correctness bug, ApiAdapter exception typing, silent exception logging, dead code removal, method decomposition, promote_fact transaction verification, provenance nil validation fix, Resolver state verification, and SnippetExtractor DRY extraction.
|
|
342
242
|
|
|
343
|
-
No correctness
|
|
243
|
+
No known correctness bugs remain. All three >500-line god objects have been eliminated: `Tools` (745→104), `Recall` (727→94), `SQLiteStore` (547→386). Zero critical or high-priority issues remain. All critical untested files now have specs (+36 tests). The remaining 12 items are medium/low priority (thin CLI wrappers, DRY improvements, carried-forward items).
|
|
344
244
|
|
|
345
245
|
---
|
|
346
246
|
|
|
347
247
|
## Appendix A: Metrics Comparison
|
|
348
248
|
|
|
349
|
-
| Metric | Jan 29 | Feb 4 | Mar 9 |
|
|
350
|
-
|
|
351
|
-
| Ruby files (lib) | ~85 | 104 | 112 |
|
|
352
|
-
| LOC (lib) | ~8,000 | 9,982 | 11,392 |
|
|
353
|
-
| LOC (spec) | — | 17,693 | 21,632 |
|
|
354
|
-
| Pure logic classes | 17+ | 20+ | 20+ |
|
|
355
|
-
| Test files | 74+ | 98 | 128 |
|
|
356
|
-
| Test-to-code ratio | ~1.5:1 | 1.77:1 | 1.90:1 |
|
|
357
|
-
| Files >500 lines | 0 | 2 |
|
|
358
|
-
| Bare rescues | 0 | 0 |
|
|
359
|
-
| N+1 patterns (hot paths) | 0 | 0 | 0 ✅ |
|
|
360
|
-
| N+1 patterns (cold paths) | — | — |
|
|
361
|
-
| Untested lib files | — | — |
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
|
374
|
-
|
|
375
|
-
| `
|
|
376
|
-
| `
|
|
377
|
-
| `
|
|
378
|
-
| `
|
|
379
|
-
| `
|
|
380
|
-
| `
|
|
381
|
-
| `
|
|
382
|
-
| `commands/uninstall_command.rb` | — | 226 | — |
|
|
383
|
-
| `commands/index_command.rb` | — | 224 | — |
|
|
384
|
-
| `publish.rb` | — | 221 | — |
|
|
385
|
-
| `infrastructure/schema_validator.rb` | — | 215 | — |
|
|
386
|
-
| `commands/hook_command.rb` | — | 214 | — |
|
|
249
|
+
| Metric | Jan 29 | Feb 4 | Mar 9 | Mar 19 |
|
|
250
|
+
|--------|--------|-------|-------|--------|
|
|
251
|
+
| Ruby files (lib) | ~85 | 104 | 112 | **117** |
|
|
252
|
+
| LOC (lib) | ~8,000 | 9,982 | 11,392 | **12,239** |
|
|
253
|
+
| LOC (spec) | — | 17,693 | 21,632 | **22,563** |
|
|
254
|
+
| Pure logic classes | 17+ | 20+ | 20+ | **22+** |
|
|
255
|
+
| Test files | 74+ | 98 | 128 | **122** |
|
|
256
|
+
| Test-to-code ratio | ~1.5:1 | 1.77:1 | 1.90:1 | **1.84:1** |
|
|
257
|
+
| Files >500 lines | 0 | 2 | 3 | **0** ✅ |
|
|
258
|
+
| Bare rescues (silent) | 0 | 0 | 1 | **0** ✅ |
|
|
259
|
+
| N+1 patterns (hot paths) | 0 | 0 | 0 | **0** ✅ |
|
|
260
|
+
| N+1 patterns (cold paths) | — | — | 1 | **0** ✅ |
|
|
261
|
+
| Untested lib files | — | — | 16 | **~7 critical** ✅ |
|
|
262
|
+
| Known correctness bugs | — | — | — | **0** ✅ |
|
|
263
|
+
|
|
264
|
+
## Appendix B: File Size Report
|
|
265
|
+
|
|
266
|
+
| File | Mar 9 | Mar 19 | Trend |
|
|
267
|
+
|------|-------|--------|-------|
|
|
268
|
+
| `mcp/tools.rb` | 728 | **104** | ⬇️ -624 (extracted to 6 handler modules) |
|
|
269
|
+
| `recall.rb` | 681 | **94** | ⬇️ -587 (extracted to engine + query_core) |
|
|
270
|
+
| `store/sqlite_store.rb` | 547 | **386** | ⬇️ -161 (extracted retry_handler + schema_manager) |
|
|
271
|
+
| `mcp/response_formatter.rb` | 394 | 396 | ⬆️ +2 |
|
|
272
|
+
| `mcp/tool_definitions.rb` | 303 | 334 | ⬆️ +31 |
|
|
273
|
+
| `commands/index_command.rb` | 224 | 272 | ⬆️ +48 |
|
|
274
|
+
| `mcp/text_summary.rb` | 257 | 258 | ⬆️ +1 |
|
|
275
|
+
| `commands/stats_command.rb` | 239 | 250 | ⬆️ +11 |
|
|
276
|
+
| `commands/uninstall_command.rb` | 226 | 226 | — |
|
|
277
|
+
| `publish.rb` | 221 | 221 | — |
|
|
278
|
+
| `infrastructure/schema_validator.rb` | 215 | 215 | — |
|
|
279
|
+
| `commands/hook_command.rb` | 214 | 214 | — |
|
|
280
|
+
| `resolve/resolver.rb` | — | 195 | new to watch |
|
|
281
|
+
| `index/vector_index.rb` | — | 184 | new to watch |
|
|
387
282
|
|
|
388
283
|
---
|
|
389
284
|
|
|
390
|
-
**Next review:** After
|
|
285
|
+
**Next review:** After Recall strategy pattern refactoring or SQLiteStore extraction
|
data/hooks/hooks.json
CHANGED
|
@@ -6,7 +6,20 @@
|
|
|
6
6
|
{
|
|
7
7
|
"type": "command",
|
|
8
8
|
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/hook-runner.sh ingest",
|
|
9
|
-
"timeout": 10
|
|
9
|
+
"timeout": 10,
|
|
10
|
+
"statusMessage": "Saving memory..."
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"StopFailure": [
|
|
16
|
+
{
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/hook-runner.sh ingest",
|
|
21
|
+
"timeout": 10,
|
|
22
|
+
"statusMessage": "Saving memory..."
|
|
10
23
|
}
|
|
11
24
|
]
|
|
12
25
|
}
|
|
@@ -17,12 +30,14 @@
|
|
|
17
30
|
{
|
|
18
31
|
"type": "command",
|
|
19
32
|
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/hook-runner.sh ingest",
|
|
20
|
-
"timeout": 10
|
|
33
|
+
"timeout": 10,
|
|
34
|
+
"statusMessage": "Saving memory..."
|
|
21
35
|
},
|
|
22
36
|
{
|
|
23
37
|
"type": "command",
|
|
24
38
|
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/hook-runner.sh context",
|
|
25
|
-
"timeout": 5
|
|
39
|
+
"timeout": 5,
|
|
40
|
+
"statusMessage": "Loading memory..."
|
|
26
41
|
}
|
|
27
42
|
]
|
|
28
43
|
}
|
|
@@ -33,12 +48,14 @@
|
|
|
33
48
|
{
|
|
34
49
|
"type": "command",
|
|
35
50
|
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/hook-runner.sh ingest",
|
|
36
|
-
"timeout": 30
|
|
51
|
+
"timeout": 30,
|
|
52
|
+
"statusMessage": "Saving memory..."
|
|
37
53
|
},
|
|
38
54
|
{
|
|
39
55
|
"type": "command",
|
|
40
56
|
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/hook-runner.sh sweep",
|
|
41
|
-
"timeout": 30
|
|
57
|
+
"timeout": 30,
|
|
58
|
+
"statusMessage": "Sweeping memory..."
|
|
42
59
|
}
|
|
43
60
|
]
|
|
44
61
|
}
|
|
@@ -49,12 +66,27 @@
|
|
|
49
66
|
{
|
|
50
67
|
"type": "command",
|
|
51
68
|
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/hook-runner.sh ingest",
|
|
52
|
-
"timeout": 30
|
|
69
|
+
"timeout": 30,
|
|
70
|
+
"statusMessage": "Saving memory..."
|
|
53
71
|
},
|
|
54
72
|
{
|
|
55
73
|
"type": "command",
|
|
56
74
|
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/hook-runner.sh sweep",
|
|
57
|
-
"timeout": 30
|
|
75
|
+
"timeout": 30,
|
|
76
|
+
"statusMessage": "Sweeping memory..."
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
"Notification": [
|
|
82
|
+
{
|
|
83
|
+
"matcher": "idle_prompt",
|
|
84
|
+
"hooks": [
|
|
85
|
+
{
|
|
86
|
+
"type": "command",
|
|
87
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/hook-runner.sh sweep",
|
|
88
|
+
"timeout": 10,
|
|
89
|
+
"statusMessage": "Sweeping memory..."
|
|
58
90
|
}
|
|
59
91
|
]
|
|
60
92
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeMemory
|
|
4
|
+
module Commands
|
|
5
|
+
module Checks
|
|
6
|
+
# Checks for content items missing distillation metrics
|
|
7
|
+
class DistillCheck
|
|
8
|
+
def initialize(db_path, label)
|
|
9
|
+
@db_path = db_path
|
|
10
|
+
@label = label
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
unless File.exist?(@db_path)
|
|
15
|
+
return {
|
|
16
|
+
status: :ok,
|
|
17
|
+
label: "#{@label}_distill",
|
|
18
|
+
message: "#{@label.capitalize} database not found (skipping distill check)",
|
|
19
|
+
details: {}
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
check_undistilled_content
|
|
24
|
+
rescue => e
|
|
25
|
+
{
|
|
26
|
+
status: :error,
|
|
27
|
+
label: "#{@label}_distill",
|
|
28
|
+
message: "#{@label.capitalize} distill check error: #{e.message}",
|
|
29
|
+
details: {}
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def check_undistilled_content
|
|
36
|
+
store = ClaudeMemory::Store::SQLiteStore.new(@db_path)
|
|
37
|
+
|
|
38
|
+
undistilled_count = store.content_items
|
|
39
|
+
.left_join(:ingestion_metrics, content_item_id: :id)
|
|
40
|
+
.where(Sequel[:ingestion_metrics][:id] => nil)
|
|
41
|
+
.count
|
|
42
|
+
|
|
43
|
+
store.close
|
|
44
|
+
|
|
45
|
+
warnings = []
|
|
46
|
+
if undistilled_count > 0
|
|
47
|
+
warnings << "#{undistilled_count} content items have no distillation metrics. Run 'claude-memory init' to migrate."
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
{
|
|
51
|
+
status: warnings.any? ? :warning : :ok,
|
|
52
|
+
label: "#{@label}_distill",
|
|
53
|
+
message: "#{@label.capitalize} distillation metrics: #{(undistilled_count == 0) ? "all tracked" : "#{undistilled_count} untracked"}",
|
|
54
|
+
details: {undistilled_count: undistilled_count},
|
|
55
|
+
warnings: warnings
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -8,7 +8,7 @@ module ClaudeMemory
|
|
|
8
8
|
# Checks hooks configuration in settings files
|
|
9
9
|
class HooksCheck
|
|
10
10
|
SETTINGS_PATHS = [".claude/settings.json", ".claude/settings.local.json"].freeze
|
|
11
|
-
EXPECTED_HOOKS = %w[Stop SessionStart PreCompact SessionEnd].freeze
|
|
11
|
+
EXPECTED_HOOKS = %w[Stop StopFailure SessionStart PreCompact SessionEnd].freeze
|
|
12
12
|
|
|
13
13
|
def call
|
|
14
14
|
hooks_found = false
|
|
@@ -86,7 +86,7 @@ module ClaudeMemory
|
|
|
86
86
|
# Check for missing recommended hooks
|
|
87
87
|
missing = EXPECTED_HOOKS - config["hooks"].keys
|
|
88
88
|
if missing.any?
|
|
89
|
-
warnings << "Missing recommended hooks in #{path}: #{missing.join(", ")}"
|
|
89
|
+
warnings << "Missing recommended hooks in #{path}: #{missing.join(", ")}. Run 'claude-memory init' to add them"
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
{has_hooks: true, warnings: warnings}
|