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
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# Remaining Improvements from Analysis
|
|
2
|
+
|
|
3
|
+
This document contains the improvements that have NOT yet been implemented from the episodic-memory and claude-mem analysis.
|
|
4
|
+
|
|
5
|
+
**Note:** The "index" command to generate embeddings for existing facts has been completed (2026-01-23).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Background Processing for Hooks
|
|
10
|
+
|
|
11
|
+
### What Episodic-Memory Does
|
|
12
|
+
|
|
13
|
+
**Background Sync**:
|
|
14
|
+
```bash
|
|
15
|
+
# SessionStart hook
|
|
16
|
+
episodic-memory sync --background
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Runs in background, user continues working immediately.
|
|
20
|
+
|
|
21
|
+
**File**: `src/sync.ts`
|
|
22
|
+
|
|
23
|
+
### What We Should Do
|
|
24
|
+
|
|
25
|
+
**Priority**: MEDIUM
|
|
26
|
+
|
|
27
|
+
**Implementation**:
|
|
28
|
+
|
|
29
|
+
1. **Background processing flag**:
|
|
30
|
+
```ruby
|
|
31
|
+
# lib/claude_memory/commands/hook_command.rb
|
|
32
|
+
def call(args)
|
|
33
|
+
opts = parse_options(args, { async: false })
|
|
34
|
+
|
|
35
|
+
if opts[:async]
|
|
36
|
+
# Fork and detach
|
|
37
|
+
pid = fork do
|
|
38
|
+
Process.setsid # Detach from terminal
|
|
39
|
+
execute_hook(subcommand, payload)
|
|
40
|
+
end
|
|
41
|
+
Process.detach(pid)
|
|
42
|
+
|
|
43
|
+
stdout.puts "Hook execution started in background (PID: #{pid})"
|
|
44
|
+
return Hook::ExitCodes::SUCCESS
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
execute_hook(subcommand, payload)
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
2. **Hook configuration**:
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"hooks": {
|
|
55
|
+
"SessionStart": [{
|
|
56
|
+
"matcher": "startup|resume",
|
|
57
|
+
"hooks": [{
|
|
58
|
+
"type": "command",
|
|
59
|
+
"command": "claude-memory hook ingest --async",
|
|
60
|
+
"async": true
|
|
61
|
+
}]
|
|
62
|
+
}]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Benefits**:
|
|
68
|
+
- Non-blocking hooks (user continues working immediately)
|
|
69
|
+
- Better user experience for long-running operations
|
|
70
|
+
- Doesn't delay session startup
|
|
71
|
+
|
|
72
|
+
**Trade-offs**:
|
|
73
|
+
- Background process management complexity
|
|
74
|
+
- Need logging for background execution
|
|
75
|
+
- Potential race conditions if multiple sessions start simultaneously
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 2. ROI Metrics and Token Economics
|
|
80
|
+
|
|
81
|
+
### What claude-mem Does
|
|
82
|
+
|
|
83
|
+
**Discovery Token Tracking**:
|
|
84
|
+
- `discovery_tokens` field on observations table
|
|
85
|
+
- Tracks tokens spent discovering each piece of knowledge
|
|
86
|
+
- Cumulative metrics in session summaries
|
|
87
|
+
- Footer displays ROI: "Access 10k tokens for 2,500t"
|
|
88
|
+
|
|
89
|
+
**File**: `src/services/sqlite/Database.ts`
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
observations: {
|
|
93
|
+
id: INTEGER PRIMARY KEY,
|
|
94
|
+
title: TEXT,
|
|
95
|
+
narrative: TEXT,
|
|
96
|
+
discovery_tokens: INTEGER, // ← Cost tracking
|
|
97
|
+
created_at_epoch: INTEGER
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
session_summaries: {
|
|
101
|
+
cumulative_discovery_tokens: INTEGER, // ← Running total
|
|
102
|
+
observation_count: INTEGER
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Context Footer Example**:
|
|
107
|
+
```markdown
|
|
108
|
+
💡 **Token Economics:**
|
|
109
|
+
- Context shown: 2,500 tokens
|
|
110
|
+
- Research captured: 10,000 tokens
|
|
111
|
+
- ROI: 4x compression
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### What We Should Do
|
|
115
|
+
|
|
116
|
+
**Priority**: MEDIUM
|
|
117
|
+
|
|
118
|
+
**Implementation**:
|
|
119
|
+
|
|
120
|
+
1. **Add metrics table**:
|
|
121
|
+
```ruby
|
|
122
|
+
create_table :ingestion_metrics do
|
|
123
|
+
primary_key :id
|
|
124
|
+
foreign_key :content_item_id, :content_items
|
|
125
|
+
Integer :input_tokens
|
|
126
|
+
Integer :output_tokens
|
|
127
|
+
Integer :facts_extracted
|
|
128
|
+
DateTime :created_at
|
|
129
|
+
end
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
2. **Track during distillation**:
|
|
133
|
+
```ruby
|
|
134
|
+
# lib/claude_memory/distill/distiller.rb
|
|
135
|
+
def distill(content)
|
|
136
|
+
response = api_call(content)
|
|
137
|
+
facts = extract_facts(response)
|
|
138
|
+
|
|
139
|
+
store_metrics(
|
|
140
|
+
input_tokens: response.usage.input_tokens,
|
|
141
|
+
output_tokens: response.usage.output_tokens,
|
|
142
|
+
facts_extracted: facts.size
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
facts
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
3. **Display in CLI**:
|
|
150
|
+
```ruby
|
|
151
|
+
# claude-memory stats
|
|
152
|
+
def stats_cmd
|
|
153
|
+
metrics = store.aggregate_metrics
|
|
154
|
+
puts "Token Economics:"
|
|
155
|
+
puts " Input: #{metrics[:input_tokens]} tokens"
|
|
156
|
+
puts " Output: #{metrics[:output_tokens]} tokens"
|
|
157
|
+
puts " Facts: #{metrics[:facts_extracted]}"
|
|
158
|
+
puts " Efficiency: #{metrics[:facts_extracted] / metrics[:input_tokens].to_f} facts/token"
|
|
159
|
+
end
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
4. **Add to published snapshot**:
|
|
163
|
+
```markdown
|
|
164
|
+
<!-- At bottom of .claude/rules/claude_memory.generated.md -->
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
*Memory stats: 145 facts from 12,500 ingested tokens (86 facts/1k tokens)*
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Benefits**:
|
|
172
|
+
- Visibility into memory system efficiency
|
|
173
|
+
- Justifies API costs (shows compression ratio)
|
|
174
|
+
- Helps tune distillation prompts for better extraction
|
|
175
|
+
|
|
176
|
+
**Trade-offs**:
|
|
177
|
+
- Requires API usage tracking
|
|
178
|
+
- Adds database complexity
|
|
179
|
+
- May not be meaningful for all distiller implementations
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 3. Structured Logging
|
|
184
|
+
|
|
185
|
+
### Implementation
|
|
186
|
+
|
|
187
|
+
**Priority**: LOW
|
|
188
|
+
|
|
189
|
+
1. **Add structured logger**:
|
|
190
|
+
```ruby
|
|
191
|
+
# lib/claude_memory/logging/logger.rb
|
|
192
|
+
module ClaudeMemory
|
|
193
|
+
module Logging
|
|
194
|
+
class Logger
|
|
195
|
+
def initialize(output = $stderr, level: :info)
|
|
196
|
+
@output = output
|
|
197
|
+
@level = level
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def info(message, metadata = {})
|
|
201
|
+
log(:info, message, metadata)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def error(message, exception: nil, metadata = {})
|
|
205
|
+
log(:error, message, metadata.merge(exception: exception&.message))
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
private
|
|
209
|
+
|
|
210
|
+
def log(level, message, metadata)
|
|
211
|
+
return if should_skip?(level)
|
|
212
|
+
|
|
213
|
+
log_entry = {
|
|
214
|
+
timestamp: Time.now.iso8601,
|
|
215
|
+
level: level,
|
|
216
|
+
message: message
|
|
217
|
+
}.merge(metadata)
|
|
218
|
+
|
|
219
|
+
@output.puts JSON.generate(log_entry)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
2. **Usage in Ingester**:
|
|
227
|
+
```ruby
|
|
228
|
+
def ingest(...)
|
|
229
|
+
logger.info("Starting ingestion",
|
|
230
|
+
session_id: session_id,
|
|
231
|
+
transcript_path: transcript_path
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
begin
|
|
235
|
+
# ... ingestion logic ...
|
|
236
|
+
|
|
237
|
+
logger.info("Ingestion complete",
|
|
238
|
+
content_items_created: 1,
|
|
239
|
+
facts_extracted: facts.size
|
|
240
|
+
)
|
|
241
|
+
rescue => e
|
|
242
|
+
logger.error("Ingestion failed",
|
|
243
|
+
exception: e,
|
|
244
|
+
session_id: session_id
|
|
245
|
+
)
|
|
246
|
+
raise
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Benefits**:
|
|
252
|
+
- Better debugging with structured data
|
|
253
|
+
- Easy log parsing and analysis
|
|
254
|
+
- Consistent log format
|
|
255
|
+
|
|
256
|
+
**Trade-offs**:
|
|
257
|
+
- Additional complexity
|
|
258
|
+
- Log output may be verbose
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
## Features to Avoid
|
|
264
|
+
|
|
265
|
+
### 1. Chroma Vector Database
|
|
266
|
+
|
|
267
|
+
**Their Approach**: Hybrid SQLite FTS5 + Chroma vector search.
|
|
268
|
+
|
|
269
|
+
**Our Take**: **Skip it.** Adds significant complexity:
|
|
270
|
+
|
|
271
|
+
- Python dependency
|
|
272
|
+
- ChromaDB server
|
|
273
|
+
- Embedding generation
|
|
274
|
+
- Sync overhead
|
|
275
|
+
|
|
276
|
+
**Alternative**: We've implemented lightweight TF-IDF embeddings without external dependencies.
|
|
277
|
+
|
|
278
|
+
### 2. Claude Agent SDK for Distillation
|
|
279
|
+
|
|
280
|
+
**Their Approach**: Use `@anthropic-ai/claude-agent-sdk` for observation compression.
|
|
281
|
+
|
|
282
|
+
**Our Take**: **Skip it.** We already have `Distill::Distiller` interface. SDK adds:
|
|
283
|
+
|
|
284
|
+
- Node.js dependency
|
|
285
|
+
- Subprocess management
|
|
286
|
+
- Complex event loop
|
|
287
|
+
|
|
288
|
+
**Alternative**: Direct API calls via `anthropic-rb` gem (if we implement distiller).
|
|
289
|
+
|
|
290
|
+
### 3. Worker Service Background Process
|
|
291
|
+
|
|
292
|
+
**Their Approach**: Long-running worker with HTTP API + MCP wrapper.
|
|
293
|
+
|
|
294
|
+
**Our Take**: **Skip it.** We use MCP server directly:
|
|
295
|
+
|
|
296
|
+
- No background process to manage
|
|
297
|
+
- No port conflicts
|
|
298
|
+
- No PID files
|
|
299
|
+
- Simpler deployment
|
|
300
|
+
|
|
301
|
+
**Alternative**: Keep stdio-based MCP server. Add HTTP transport only if needed.
|
|
302
|
+
|
|
303
|
+
### 4. Web Viewer UI
|
|
304
|
+
|
|
305
|
+
**Their Approach**: React-based web UI at `http://localhost:37777`.
|
|
306
|
+
|
|
307
|
+
**Our Take**: **Skip for MVP.** Significant effort for uncertain value:
|
|
308
|
+
|
|
309
|
+
- React + esbuild
|
|
310
|
+
- SSE implementation
|
|
311
|
+
- State management
|
|
312
|
+
- CSS/theming
|
|
313
|
+
|
|
314
|
+
**Alternative**: CLI output is sufficient. Add web UI if users request it.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Implementation Priorities
|
|
319
|
+
|
|
320
|
+
### Medium Priority
|
|
321
|
+
|
|
322
|
+
1. **Background Processing** - Non-blocking hooks for better UX
|
|
323
|
+
2. **ROI Metrics** - Track token economics for distillation
|
|
324
|
+
|
|
325
|
+
### Low Priority
|
|
326
|
+
|
|
327
|
+
3. **Structured Logging** - Better debugging with JSON logs
|
|
328
|
+
4. **Health Monitoring** - Only if we add background worker
|
|
329
|
+
5. **Web Viewer UI** - Only if users request visualization
|
|
330
|
+
6. **Configuration-Driven Context** - Only if users request snapshot customization
|
data/lefthook.yml
CHANGED
|
@@ -8,3 +8,16 @@ pre-commit:
|
|
|
8
8
|
stage_fixed: true
|
|
9
9
|
tests:
|
|
10
10
|
run: bundle exec rspec
|
|
11
|
+
quality-review:
|
|
12
|
+
run: |
|
|
13
|
+
staged_ruby=$(git diff --cached --name-only --diff-filter=ACM | grep '\.rb$' || true)
|
|
14
|
+
if [ -n "$staged_ruby" ]; then
|
|
15
|
+
echo "Running quality review on staged changes..."
|
|
16
|
+
claude -p "/review-commit" \
|
|
17
|
+
--no-session-persistence \
|
|
18
|
+
--model haiku \
|
|
19
|
+
--allowedTools "Bash Read Grep Glob" \
|
|
20
|
+
--no-chrome
|
|
21
|
+
else
|
|
22
|
+
echo "No Ruby files staged, skipping quality review"
|
|
23
|
+
fi
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeMemory
|
|
4
|
+
module Commands
|
|
5
|
+
module Checks
|
|
6
|
+
# Checks if CLAUDE.md exists and imports snapshot
|
|
7
|
+
class ClaudeMdCheck
|
|
8
|
+
CLAUDE_MD_PATH = ".claude/CLAUDE.md"
|
|
9
|
+
|
|
10
|
+
def call
|
|
11
|
+
unless File.exist?(CLAUDE_MD_PATH)
|
|
12
|
+
return {
|
|
13
|
+
status: :warning,
|
|
14
|
+
label: "claude_md",
|
|
15
|
+
message: "No .claude/CLAUDE.md found",
|
|
16
|
+
details: {path: CLAUDE_MD_PATH}
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
content = File.read(CLAUDE_MD_PATH)
|
|
21
|
+
|
|
22
|
+
if content.include?("claude_memory.generated.md")
|
|
23
|
+
{
|
|
24
|
+
status: :ok,
|
|
25
|
+
label: "claude_md",
|
|
26
|
+
message: "CLAUDE.md imports snapshot",
|
|
27
|
+
details: {path: CLAUDE_MD_PATH}
|
|
28
|
+
}
|
|
29
|
+
else
|
|
30
|
+
{
|
|
31
|
+
status: :warning,
|
|
32
|
+
label: "claude_md",
|
|
33
|
+
message: "CLAUDE.md does not import snapshot",
|
|
34
|
+
details: {path: CLAUDE_MD_PATH}
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeMemory
|
|
4
|
+
module Commands
|
|
5
|
+
module Checks
|
|
6
|
+
# Checks database existence, schema, and health
|
|
7
|
+
class DatabaseCheck
|
|
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 missing_database_result
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
check_database_health
|
|
19
|
+
rescue => e
|
|
20
|
+
{
|
|
21
|
+
status: :error,
|
|
22
|
+
label: @label,
|
|
23
|
+
message: "#{@label} database error: #{e.message}",
|
|
24
|
+
details: {}
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def missing_database_result
|
|
31
|
+
if @label == "global"
|
|
32
|
+
{
|
|
33
|
+
status: :error,
|
|
34
|
+
label: @label,
|
|
35
|
+
message: "Global database not found: #{@db_path}",
|
|
36
|
+
details: {}
|
|
37
|
+
}
|
|
38
|
+
else
|
|
39
|
+
{
|
|
40
|
+
status: :warning,
|
|
41
|
+
label: @label,
|
|
42
|
+
message: "Project database not found: #{@db_path} (run 'claude-memory init')",
|
|
43
|
+
details: {}
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def check_database_health
|
|
49
|
+
store = ClaudeMemory::Store::SQLiteStore.new(@db_path)
|
|
50
|
+
|
|
51
|
+
details = {
|
|
52
|
+
path: @db_path,
|
|
53
|
+
adapter: "extralite",
|
|
54
|
+
schema_version: store.schema_version,
|
|
55
|
+
fact_count: store.facts.count,
|
|
56
|
+
content_count: store.content_items.count,
|
|
57
|
+
conflict_count: store.conflicts.where(status: "open").count,
|
|
58
|
+
last_ingest: store.content_items.max(:ingested_at)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
warnings = []
|
|
62
|
+
errors = []
|
|
63
|
+
|
|
64
|
+
# Check for open conflicts
|
|
65
|
+
if details[:conflict_count] > 0
|
|
66
|
+
warnings << "#{details[:conflict_count]} open conflict(s) need resolution"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Check for missing ingests
|
|
70
|
+
if details[:last_ingest].nil? && @label == "project"
|
|
71
|
+
warnings << "No content has been ingested yet"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Check for stuck operations
|
|
75
|
+
tracker = ClaudeMemory::Infrastructure::OperationTracker.new(store)
|
|
76
|
+
stuck_ops = tracker.stuck_operations
|
|
77
|
+
if stuck_ops.any?
|
|
78
|
+
stuck_ops.each do |op|
|
|
79
|
+
warnings << "Stuck operation '#{op[:operation_type]}' (started #{op[:started_at]}). Run 'claude-memory recover' to reset."
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
details[:stuck_operations] = stuck_ops.size
|
|
83
|
+
|
|
84
|
+
# Run schema validation
|
|
85
|
+
validator = ClaudeMemory::Infrastructure::SchemaValidator.new(store)
|
|
86
|
+
validation = validator.validate
|
|
87
|
+
details[:schema_valid] = validation[:valid]
|
|
88
|
+
|
|
89
|
+
# Collect validation issues
|
|
90
|
+
if validation[:issues].any?
|
|
91
|
+
validation[:issues].each do |issue|
|
|
92
|
+
if issue[:severity] == "error"
|
|
93
|
+
errors << issue[:message]
|
|
94
|
+
else
|
|
95
|
+
warnings << issue[:message]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
store.close
|
|
101
|
+
|
|
102
|
+
status = if errors.any?
|
|
103
|
+
:error
|
|
104
|
+
else
|
|
105
|
+
(warnings.any? ? :warning : :ok)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
{
|
|
109
|
+
status: status,
|
|
110
|
+
label: @label,
|
|
111
|
+
message: "#{@label.capitalize} database exists: #{@db_path}",
|
|
112
|
+
details: details,
|
|
113
|
+
warnings: warnings,
|
|
114
|
+
errors: errors
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module ClaudeMemory
|
|
6
|
+
module Commands
|
|
7
|
+
module Checks
|
|
8
|
+
# Checks hooks configuration in settings files
|
|
9
|
+
class HooksCheck
|
|
10
|
+
SETTINGS_PATHS = [".claude/settings.json", ".claude/settings.local.json"].freeze
|
|
11
|
+
EXPECTED_HOOKS = %w[Stop SessionStart PreCompact SessionEnd].freeze
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
hooks_found = false
|
|
15
|
+
warnings = []
|
|
16
|
+
paths_checked = []
|
|
17
|
+
|
|
18
|
+
SETTINGS_PATHS.each do |path|
|
|
19
|
+
next unless File.exist?(path)
|
|
20
|
+
|
|
21
|
+
paths_checked << path
|
|
22
|
+
result = check_settings_file(path)
|
|
23
|
+
|
|
24
|
+
if result[:has_hooks]
|
|
25
|
+
hooks_found = true
|
|
26
|
+
warnings.concat(result[:warnings])
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Check for orphaned hooks
|
|
31
|
+
if hooks_found && !mcp_configured?
|
|
32
|
+
warnings << "Orphaned hooks detected: claude-memory hooks are configured but MCP server is not"
|
|
33
|
+
warnings << "Run 'claude-memory uninstall' to remove hooks, or 'claude-memory init' to reconfigure"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if hooks_found
|
|
37
|
+
{
|
|
38
|
+
status: warnings.any? ? :warning : :ok,
|
|
39
|
+
label: "hooks",
|
|
40
|
+
message: "Hooks configured in #{paths_checked.join(", ")}",
|
|
41
|
+
details: {
|
|
42
|
+
paths: paths_checked,
|
|
43
|
+
fallback_available: false
|
|
44
|
+
},
|
|
45
|
+
warnings: warnings
|
|
46
|
+
}
|
|
47
|
+
else
|
|
48
|
+
{
|
|
49
|
+
status: :warning,
|
|
50
|
+
label: "hooks",
|
|
51
|
+
message: "No hooks configured. Run 'claude-memory init' or configure manually.",
|
|
52
|
+
details: {
|
|
53
|
+
paths: SETTINGS_PATHS,
|
|
54
|
+
fallback_available: true,
|
|
55
|
+
fallback_commands: [
|
|
56
|
+
"claude-memory ingest --session-id <id> --transcript-path <path>",
|
|
57
|
+
"claude-memory sweep --budget 5",
|
|
58
|
+
"claude-memory publish"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
warnings: []
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def check_settings_file(path)
|
|
69
|
+
warnings = []
|
|
70
|
+
|
|
71
|
+
begin
|
|
72
|
+
config = JSON.parse(File.read(path))
|
|
73
|
+
|
|
74
|
+
unless config["hooks"]&.any?
|
|
75
|
+
return {has_hooks: false, warnings: []}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check if any hooks contain claude-memory commands
|
|
79
|
+
claude_memory_hooks = config["hooks"].values.flatten.any? do |hook_array|
|
|
80
|
+
next false unless hook_array.is_a?(Hash) && hook_array["hooks"].is_a?(Array)
|
|
81
|
+
hook_array["hooks"].any? { |h| h["command"]&.include?("claude-memory") }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
return {has_hooks: false, warnings: []} unless claude_memory_hooks
|
|
85
|
+
|
|
86
|
+
# Check for missing recommended hooks
|
|
87
|
+
missing = EXPECTED_HOOKS - config["hooks"].keys
|
|
88
|
+
if missing.any?
|
|
89
|
+
warnings << "Missing recommended hooks in #{path}: #{missing.join(", ")}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
{has_hooks: true, warnings: warnings}
|
|
93
|
+
rescue JSON::ParserError
|
|
94
|
+
{has_hooks: false, warnings: ["Invalid JSON in #{path}"]}
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def mcp_configured?
|
|
99
|
+
mcp_path = ".claude.json"
|
|
100
|
+
return false unless File.exist?(mcp_path)
|
|
101
|
+
|
|
102
|
+
begin
|
|
103
|
+
config = JSON.parse(File.read(mcp_path))
|
|
104
|
+
config["mcpServers"]&.key?("claude-memory")
|
|
105
|
+
rescue JSON::ParserError
|
|
106
|
+
false
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|