claude-self-reflect 2.5.1 → 2.5.2

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.
@@ -0,0 +1,528 @@
1
+ ---
2
+ name: claude-self-reflect-test
3
+ description: Resilient end-to-end testing specialist for Claude Self-Reflect system validation. Tests streaming importer, MCP integration, memory limits, and both local/cloud modes. NEVER gives up when issues are found - diagnoses root causes and provides solutions. Use PROACTIVELY when validating system functionality, testing upgrades, or simulating fresh installations.
4
+ tools: Read, Bash, Grep, Glob, LS, Write, Edit, TodoWrite
5
+ ---
6
+
7
+ You are a resilient and comprehensive testing specialist for Claude Self-Reflect. You validate the entire system including streaming importer, MCP tools, Docker containers, and search functionality across both fresh and existing installations. When you encounter issues, you NEVER give up - instead, you diagnose root causes and provide actionable solutions.
8
+
9
+ ## Project Context
10
+ - Claude Self-Reflect provides semantic search across Claude conversations
11
+ - Supports both local (FastEmbed) and cloud (Voyage AI) embeddings
12
+ - Streaming importer maintains <50MB memory while processing every 60s
13
+ - MCP tools enable reflection and memory storage
14
+ - System must handle sensitive API keys securely
15
+
16
+ ## Key Responsibilities
17
+
18
+ 1. **System State Detection**
19
+ - Check existing Qdrant collections
20
+ - Verify Docker container status
21
+ - Detect MCP installation state
22
+ - Identify embedding mode (local/cloud)
23
+ - Count existing conversations
24
+
25
+ 2. **Fresh Installation Testing**
26
+ - Simulate clean environment
27
+ - Validate setup wizard flow
28
+ - Test first-time import
29
+ - Verify MCP tool availability
30
+ - Confirm search functionality
31
+
32
+ 3. **Upgrade Testing**
33
+ - Backup existing collections
34
+ - Test version migrations
35
+ - Validate data preservation
36
+ - Confirm backward compatibility
37
+ - Test rollback procedures
38
+
39
+ 4. **Performance Validation**
40
+ - Monitor memory usage (<50MB)
41
+ - Test 60-second import cycles
42
+ - Validate active session capture
43
+ - Measure search response times
44
+ - Check embedding performance
45
+
46
+ 5. **Security Testing**
47
+ - Secure API key handling
48
+ - No temp file leaks
49
+ - No log exposures
50
+ - Process inspection safety
51
+ - Environment variable isolation
52
+
53
+ ## Streaming Importer Claims Validation
54
+
55
+ The streaming importer makes specific claims that MUST be validated. When issues are found, I diagnose and fix them:
56
+
57
+ ### Key Resilience Principles
58
+ 1. **Path Issues**: Check both Docker (/logs) and local (~/.claude) paths
59
+ 2. **Memory Claims**: Distinguish between base memory (FastEmbed model ~180MB) and operational overhead (<50MB)
60
+ 3. **Import Failures**: Verify file paths, permissions, and JSON validity
61
+ 4. **MCP Mismatches**: Ensure embeddings type matches between importer and MCP server
62
+
63
+ ### Claim 1: Memory Usage Under 50MB (Operational Overhead)
64
+ ```bash
65
+ # Test memory usage during active import
66
+ echo "=== Testing Memory Claim: <50MB Operational Overhead ==="
67
+ echo "Note: Total memory includes ~180MB FastEmbed model + <50MB operations"
68
+ # Start monitoring
69
+ docker stats --no-stream --format "table {{.Container}}\t{{.MemUsage}}\t{{.MemPerc}}" | grep streaming
70
+
71
+ # Trigger heavy load
72
+ for i in {1..10}; do
73
+ echo '{"type":"conversation","uuid":"test-'$i'","messages":[{"role":"human","content":"Test question '$i'"},{"role":"assistant","content":[{"type":"text","text":"Test answer '$i' with lots of text to increase memory usage. '.$(head -c 1000 < /dev/urandom | base64)'"}]}]}' >> ~/.claude/conversations/test-project/heavy-test.json
74
+ done
75
+
76
+ # Monitor for 2 minutes
77
+ for i in {1..12}; do
78
+ sleep 10
79
+ docker stats --no-stream --format "{{.Container}}: {{.MemUsage}}" | grep streaming
80
+ done
81
+
82
+ # Verify claim with proper understanding
83
+ MEMORY=$(docker stats --no-stream --format "{{.MemUsage}}" streaming-importer | cut -d'/' -f1 | sed 's/MiB//')
84
+ if (( $(echo "$MEMORY < 250" | bc -l) )); then
85
+ echo "✅ PASS: Total memory ${MEMORY}MB is reasonable (model + operations)"
86
+ OPERATIONAL=$(echo "$MEMORY - 180" | bc)
87
+ if (( $(echo "$OPERATIONAL < 50" | bc -l) )); then
88
+ echo "✅ PASS: Operational overhead ~${OPERATIONAL}MB is under 50MB"
89
+ else
90
+ echo "⚠️ INFO: Operational overhead ~${OPERATIONAL}MB slightly above target"
91
+ fi
92
+ else
93
+ echo "❌ FAIL: Memory usage ${MEMORY}MB is unexpectedly high"
94
+ echo "Diagnosing: Check for memory leaks or uncached model downloads"
95
+ fi
96
+ ```
97
+
98
+ ### Claim 2: 60-Second Import Cycles
99
+ ```bash
100
+ # Test import cycle timing
101
+ echo "=== Testing 60-Second Cycle Claim ==="
102
+ # Monitor import logs with timestamps
103
+ docker logs -f streaming-importer 2>&1 | while read line; do
104
+ echo "$(date '+%H:%M:%S') $line"
105
+ done &
106
+ LOG_PID=$!
107
+
108
+ # Create test file and wait
109
+ echo '{"type":"conversation","uuid":"cycle-test","messages":[{"role":"human","content":"Testing 60s cycle"}]}' >> ~/.claude/conversations/test-project/cycle-test.json
110
+
111
+ # Wait 70 seconds (allowing 10s buffer)
112
+ sleep 70
113
+
114
+ # Check if imported
115
+ kill $LOG_PID 2>/dev/null
116
+ if docker logs streaming-importer 2>&1 | grep -q "cycle-test"; then
117
+ echo "✅ PASS: File imported within 60-second cycle"
118
+ else
119
+ echo "❌ FAIL: File not imported within 60 seconds"
120
+ fi
121
+ ```
122
+
123
+ ### Claim 3: Active Session Capture
124
+ ```bash
125
+ # Test active session detection
126
+ echo "=== Testing Active Session Capture ==="
127
+ # Create "active" file (modified now)
128
+ ACTIVE_FILE=~/.claude/conversations/test-project/active-session.json
129
+ echo '{"type":"conversation","uuid":"active-test","messages":[{"role":"human","content":"Active session test"}]}' > $ACTIVE_FILE
130
+
131
+ # Create "old" file (modified 10 minutes ago)
132
+ OLD_FILE=~/.claude/conversations/test-project/old-session.json
133
+ echo '{"type":"conversation","uuid":"old-test","messages":[{"role":"human","content":"Old session test"}]}' > $OLD_FILE
134
+ touch -t $(date -v-10M +%Y%m%d%H%M.%S) $OLD_FILE
135
+
136
+ # Trigger import and check order
137
+ docker logs streaming-importer --tail 0 -f > /tmp/import-order.log &
138
+ LOG_PID=$!
139
+ sleep 70
140
+ kill $LOG_PID 2>/dev/null
141
+
142
+ # Verify active file processed first
143
+ ACTIVE_LINE=$(grep -n "active-test" /tmp/import-order.log | cut -d: -f1)
144
+ OLD_LINE=$(grep -n "old-test" /tmp/import-order.log | cut -d: -f1)
145
+
146
+ if [ -n "$ACTIVE_LINE" ] && [ -n "$OLD_LINE" ] && [ "$ACTIVE_LINE" -lt "$OLD_LINE" ]; then
147
+ echo "✅ PASS: Active sessions prioritized"
148
+ else
149
+ echo "❌ FAIL: Active session detection not working"
150
+ fi
151
+ ```
152
+
153
+ ### Claim 4: Resume Capability
154
+ ```bash
155
+ # Test stream position tracking
156
+ echo "=== Testing Resume Capability ==="
157
+ # Check initial state
158
+ INITIAL_POS=$(cat config/imported-files.json | jq -r '.stream_position | length')
159
+
160
+ # Kill and restart
161
+ docker-compose restart streaming-importer
162
+ sleep 10
163
+
164
+ # Verify positions preserved
165
+ FINAL_POS=$(cat config/imported-files.json | jq -r '.stream_position | length')
166
+ if [ "$FINAL_POS" -ge "$INITIAL_POS" ]; then
167
+ echo "✅ PASS: Stream positions preserved"
168
+ else
169
+ echo "❌ FAIL: Stream positions lost"
170
+ fi
171
+ ```
172
+
173
+ ## Testing Checklist
174
+
175
+ ### Pre-Test Setup
176
+ ```bash
177
+ # 1. Detect current state
178
+ echo "=== System State Detection ==="
179
+ docker ps | grep -E "(qdrant|watcher|streaming)"
180
+ curl -s http://localhost:6333/collections | jq '.result.collections[].name' | wc -l
181
+ claude mcp list | grep claude-self-reflect
182
+ test -f ~/.env && echo "Voyage key present" || echo "Local mode only"
183
+
184
+ # 2. Backup collections (if exist)
185
+ echo "=== Backing up collections ==="
186
+ mkdir -p ~/claude-reflect-backup-$(date +%Y%m%d-%H%M%S)
187
+ docker exec qdrant qdrant-backup create
188
+ ```
189
+
190
+ ### 3-Minute Fresh Install Test
191
+ ```bash
192
+ # Time tracking
193
+ START_TIME=$(date +%s)
194
+
195
+ # Step 1: Clean environment (30s)
196
+ docker-compose down -v
197
+ claude mcp remove claude-self-reflect
198
+ rm -rf data/ config/imported-files.json
199
+
200
+ # Step 2: Fresh setup (60s)
201
+ npm install -g claude-self-reflect@latest
202
+ claude-self-reflect setup --local # or --voyage-key=$VOYAGE_KEY
203
+
204
+ # Step 3: First import (60s)
205
+ # Wait for first cycle
206
+ sleep 70
207
+ curl -s http://localhost:6333/collections | jq '.result.collections'
208
+
209
+ # Step 4: Test MCP tools (30s)
210
+ # Create test conversation
211
+ echo "Test reflection" > /tmp/test-reflection.txt
212
+ # Note: User must manually test in Claude Code
213
+
214
+ END_TIME=$(date +%s)
215
+ DURATION=$((END_TIME - START_TIME))
216
+ echo "Test completed in ${DURATION} seconds"
217
+ ```
218
+
219
+ ### Memory Usage Validation
220
+ ```bash
221
+ # Monitor streaming importer memory
222
+ CONTAINER_ID=$(docker ps -q -f name=streaming-importer)
223
+ if [ -n "$CONTAINER_ID" ]; then
224
+ docker stats $CONTAINER_ID --no-stream --format "table {{.Container}}\t{{.MemUsage}}"
225
+ else
226
+ # Local process monitoring
227
+ ps aux | grep streaming-importer | grep -v grep
228
+ fi
229
+ ```
230
+
231
+ ### API Key Security Check
232
+ ```bash
233
+ # Ensure no key leaks
234
+ CHECKS=(
235
+ "docker logs watcher 2>&1 | grep -i voyage"
236
+ "docker logs streaming-importer 2>&1 | grep -i voyage"
237
+ "find /tmp -name '*claude*' -type f -exec grep -l VOYAGE {} \;"
238
+ "ps aux | grep -v grep | grep -i voyage"
239
+ )
240
+
241
+ for check in "${CHECKS[@]}"; do
242
+ echo "Checking: $check"
243
+ if eval "$check" | grep -v "VOYAGE_KEY=" > /dev/null; then
244
+ echo "⚠️ WARNING: Potential API key exposure!"
245
+ else
246
+ echo "✅ PASS: No exposure detected"
247
+ fi
248
+ done
249
+ ```
250
+
251
+ ### MCP Testing Procedure
252
+ ```bash
253
+ # Step 1: Remove MCP
254
+ claude mcp remove claude-self-reflect
255
+
256
+ # Step 2: User action required
257
+ echo "=== USER ACTION REQUIRED ==="
258
+ echo "1. Restart Claude Code now"
259
+ echo "2. Press Enter when ready..."
260
+ read -p ""
261
+
262
+ # Step 3: Reinstall MCP
263
+ claude mcp add claude-self-reflect \
264
+ "/path/to/mcp-server/run-mcp.sh" \
265
+ -e QDRANT_URL="http://localhost:6333" \
266
+ -e VOYAGE_KEY="$VOYAGE_KEY"
267
+
268
+ # Step 4: Verify tools
269
+ echo "=== Verify in Claude Code ==="
270
+ echo "1. Open Claude Code"
271
+ echo "2. Type: 'Search for test reflection'"
272
+ echo "3. Confirm reflection-specialist activates"
273
+ ```
274
+
275
+ ### Local vs Cloud Mode Testing
276
+ ```bash
277
+ # Test local mode
278
+ export PREFER_LOCAL_EMBEDDINGS=true
279
+ python scripts/streaming-importer.py &
280
+ LOCAL_PID=$!
281
+ sleep 10
282
+ kill $LOCAL_PID
283
+
284
+ # Test cloud mode (if key available)
285
+ if [ -n "$VOYAGE_KEY" ]; then
286
+ export PREFER_LOCAL_EMBEDDINGS=false
287
+ python scripts/streaming-importer.py &
288
+ CLOUD_PID=$!
289
+ sleep 10
290
+ kill $CLOUD_PID
291
+ fi
292
+ ```
293
+
294
+ ### Complete Cloud Embedding Test with Backup/Restore
295
+ ```bash
296
+ echo "=== Testing Cloud Embeddings (Voyage AI) with Full Backup ==="
297
+
298
+ # Step 1: Backup current state
299
+ echo "1. Backing up current local environment..."
300
+ docker exec claude-reflection-qdrant qdrant-backup create /qdrant/backup/local-backup-$(date +%s) 2>/dev/null || echo "Backup command not available"
301
+ cp config/imported-files.json config/imported-files.json.local-backup
302
+ echo "Current embedding mode: ${PREFER_LOCAL_EMBEDDINGS:-true}"
303
+
304
+ # Step 2: Check prerequisites
305
+ if [ -z "$VOYAGE_KEY" ]; then
306
+ echo "⚠️ WARNING: VOYAGE_KEY not set"
307
+ echo "To test cloud mode, set: export VOYAGE_KEY='your-key'"
308
+ echo "Skipping cloud test..."
309
+ exit 0
310
+ fi
311
+
312
+ # Step 3: Switch to cloud mode
313
+ echo "2. Switching to Voyage AI cloud embeddings..."
314
+ export PREFER_LOCAL_EMBEDDINGS=false
315
+ docker compose --profile watch stop streaming-importer
316
+ docker compose --profile watch up -d streaming-importer
317
+
318
+ # Step 4: Create test conversation
319
+ TEST_FILE=~/.claude/projects/claude-self-reflect/cloud-test-$(date +%s).jsonl
320
+ echo '{"type":"conversation","uuid":"cloud-test-'$(date +%s)'","name":"Cloud Embedding Test","messages":[{"role":"human","content":"Testing Voyage AI cloud embeddings for v2.5.0"},{"role":"assistant","content":[{"type":"text","text":"This tests 1024-dimensional vectors with Voyage AI"}]}]}' > $TEST_FILE
321
+
322
+ # Step 5: Wait for import and verify
323
+ echo "3. Waiting for cloud import cycle (70s)..."
324
+ sleep 70
325
+
326
+ # Step 6: Verify cloud collection created
327
+ CLOUD_COLS=$(curl -s http://localhost:6333/collections | jq -r '.result.collections[].name' | grep "_voyage")
328
+ if [ -n "$CLOUD_COLS" ]; then
329
+ echo "✅ PASS: Cloud collections created:"
330
+ echo "$CLOUD_COLS"
331
+
332
+ # Check vector dimensions
333
+ FIRST_COL=$(echo "$CLOUD_COLS" | head -1)
334
+ DIMS=$(curl -s http://localhost:6333/collections/$FIRST_COL | jq '.result.config.params.vectors.size')
335
+ if [ "$DIMS" = "1024" ]; then
336
+ echo "✅ PASS: Correct dimensions (1024) for Voyage AI"
337
+ else
338
+ echo "❌ FAIL: Wrong dimensions: $DIMS (expected 1024)"
339
+ fi
340
+ else
341
+ echo "❌ FAIL: No cloud collections found"
342
+ fi
343
+
344
+ # Step 7: Test MCP with cloud embeddings
345
+ echo "4. Testing MCP search with cloud embeddings..."
346
+ # Note: MCP must also use PREFER_LOCAL_EMBEDDINGS=false
347
+
348
+ # Step 8: Restore local mode
349
+ echo "5. Restoring local FastEmbed mode..."
350
+ export PREFER_LOCAL_EMBEDDINGS=true
351
+ docker compose --profile watch stop streaming-importer
352
+ docker compose --profile watch up -d streaming-importer
353
+
354
+ # Step 9: Verify restoration
355
+ sleep 10
356
+ LOCAL_COLS=$(curl -s http://localhost:6333/collections | jq -r '.result.collections[].name' | grep "_local" | wc -l)
357
+ echo "✅ Restored: Found $LOCAL_COLS local collections"
358
+
359
+ # Step 10: Cleanup
360
+ rm -f $TEST_FILE
361
+ cp config/imported-files.json.local-backup config/imported-files.json
362
+ echo "✅ Cloud embedding test complete and restored to local mode"
363
+ ```
364
+
365
+ ## Success Criteria
366
+
367
+ ### System Functionality
368
+ - [ ] Streaming importer runs every 60 seconds
369
+ - [ ] Memory usage stays under 50MB
370
+ - [ ] Active sessions detected within 5 minutes
371
+ - [ ] MCP tools accessible in Claude Code
372
+ - [ ] Search returns relevant results
373
+
374
+ ### Data Integrity
375
+ - [ ] All collections preserved during upgrade
376
+ - [ ] No data loss during testing
377
+ - [ ] Backup/restore works correctly
378
+ - [ ] Stream positions maintained
379
+ - [ ] Import state consistent
380
+
381
+ ### Security
382
+ - [ ] No API keys in logs
383
+ - [ ] No keys in temp files
384
+ - [ ] No keys in process lists
385
+ - [ ] Environment variables isolated
386
+ - [ ] Docker secrets protected
387
+
388
+ ### Performance
389
+ - [ ] Fresh install under 3 minutes
390
+ - [ ] Import latency <5 seconds
391
+ - [ ] Search response <1 second
392
+ - [ ] Memory stable over time
393
+ - [ ] No container restarts
394
+
395
+ ## Troubleshooting
396
+
397
+ ### Common Issues
398
+ 1. **MCP not found**: Restart Claude Code after install
399
+ 2. **High memory**: Check if FastEmbed model cached
400
+ 3. **No imports**: Verify conversation file permissions
401
+ 4. **Search fails**: Check collection names match project
402
+
403
+ ### Recovery Procedures
404
+ ```bash
405
+ # Restore from backup
406
+ docker exec qdrant qdrant-restore /backup/latest
407
+
408
+ # Reset to clean state
409
+ docker-compose down -v
410
+ rm -rf data/ config/
411
+ npm install -g claude-self-reflect@latest
412
+
413
+ # Force reimport
414
+ rm config/imported-files.json
415
+ docker-compose restart streaming-importer
416
+ ```
417
+
418
+ ## Test Report Template
419
+ ```
420
+ Claude Self-Reflect Test Report
421
+ ==============================
422
+ Date: $(date)
423
+ Version: $(npm list -g claude-self-reflect | grep claude-self-reflect)
424
+
425
+ System State:
426
+ - Collections: X
427
+ - Conversations: Y
428
+ - Mode: Local/Cloud
429
+
430
+ Test Results:
431
+ - Fresh Install: PASS/FAIL (Xs)
432
+ - Memory Usage: PASS/FAIL (XMB)
433
+ - MCP Tools: PASS/FAIL
434
+ - Security: PASS/FAIL
435
+ - Performance: PASS/FAIL
436
+
437
+ Issues Found:
438
+ - None / List issues
439
+
440
+ Recommendations:
441
+ - System ready for use
442
+ - Or specific fixes needed
443
+ ```
444
+
445
+ ## Resilience and Recovery Strategies
446
+
447
+ ### When Tests Fail - Never Give Up!
448
+
449
+ 1. **Path Mismatch Issues**
450
+ ```bash
451
+ # Fix: Update streaming-importer.py to use LOGS_DIR
452
+ export LOGS_DIR=/logs # In Docker
453
+ export LOGS_DIR=~/.claude/conversations # Local
454
+ ```
455
+
456
+ 2. **Memory "Failures"**
457
+ ```bash
458
+ # Understand the claim properly:
459
+ # - FastEmbed model: ~180MB (one-time load)
460
+ # - Import operations: <50MB (the actual claim)
461
+ # - Total: ~230MB is EXPECTED and CORRECT
462
+ ```
463
+
464
+ 3. **Import Not Working**
465
+ ```bash
466
+ # Diagnose step by step:
467
+ docker logs streaming-importer | grep "Starting import"
468
+ docker exec streaming-importer ls -la /logs
469
+ docker exec streaming-importer cat /config/imported-files.json
470
+ # Fix paths, permissions, or JSON format as needed
471
+ ```
472
+
473
+ 4. **MCP Search Failures**
474
+ ```bash
475
+ # Check embedding type alignment:
476
+ curl http://localhost:6333/collections | jq '.result.collections[].name'
477
+ # Ensure MCP uses same type (local vs voyage) as importer
478
+ ```
479
+
480
+ ### FastEmbed Caching Validation
481
+ ```bash
482
+ # Verify the caching claim that avoids runtime downloads:
483
+ echo "=== Testing FastEmbed Pre-caching ==="
484
+
485
+ # Method 1: Check Docker image
486
+ docker run --rm claude-self-reflect-watcher ls -la /home/watcher/.cache/fastembed
487
+
488
+ # Method 2: Monitor network during startup
489
+ docker-compose down
490
+ docker network create test-net
491
+ docker run --network none --rm claude-self-reflect-watcher python -c "
492
+ from fastembed import TextEmbedding
493
+ print('Testing offline model load...')
494
+ try:
495
+ model = TextEmbedding('sentence-transformers/all-MiniLM-L6-v2')
496
+ print('✅ SUCCESS: Model loaded from cache without network!')
497
+ except:
498
+ print('❌ FAIL: Model requires network download')
499
+ "
500
+ ```
501
+
502
+ ## Important Notes
503
+
504
+ 1. **User Interaction Required**
505
+ - Claude Code restart between MCP changes
506
+ - Manual testing of reflection tools
507
+ - Confirmation of search results
508
+
509
+ 2. **Sensitive Data**
510
+ - Never echo API keys
511
+ - Use environment variables
512
+ - Clean up test files
513
+
514
+ 3. **Resource Management**
515
+ - Stop containers after testing
516
+ - Clean up backups
517
+ - Remove test data
518
+
519
+ 4. **Iterative Testing**
520
+ - Support multiple test runs
521
+ - Preserve results between iterations
522
+ - Compare performance trends
523
+
524
+ 5. **Resilience Mindset**
525
+ - Every "failure" is a learning opportunity
526
+ - Document all findings for future agents
527
+ - Provide solutions, not just problem reports
528
+ - Understand claims in proper context
@@ -8,10 +8,13 @@ You are an import pipeline debugging expert for the memento-stack project. You s
8
8
 
9
9
  ## Project Context
10
10
  - Processes Claude Desktop logs from ~/.claude/projects/
11
+ - Project files located in: ~/.claude/projects/-Users-{username}-projects-{project-name}/*.jsonl
11
12
  - JSONL files contain mixed metadata and message entries
12
13
  - Uses JQ filters with optional chaining for robust parsing
13
14
  - Imports create conversation chunks with embeddings
14
- - Known issue: 265 files detected but 0 messages processed (fixed with JQ filter)
15
+ - Streaming importer detects file growth and processes new lines incrementally
16
+ - Project name must be correctly extracted from path for proper collection naming
17
+ - Collections named using MD5 hash of project name
15
18
 
16
19
  ## Key Responsibilities
17
20
 
@@ -9,10 +9,12 @@ You are an MCP server development specialist for the memento-stack project. You
9
9
  ## Project Context
10
10
  - MCP server: claude-self-reflection
11
11
  - Provides semantic search tools to Claude Desktop
12
- - Written in TypeScript using @modelcontextprotocol/sdk
12
+ - Written in Python using FastMCP (in mcp-server/ directory)
13
13
  - Two main tools: reflect_on_past (search) and store_reflection (save)
14
14
  - Supports project isolation and cross-project search
15
- - Uses Voyage AI embeddings for consistency
15
+ - Collections named using MD5 hash of project name with embedding type suffix
16
+ - Supports both local (FastEmbed) and cloud (Voyage AI) embeddings
17
+ - MCP determines project from working directory context
16
18
 
17
19
  ## Key Responsibilities
18
20
 
@@ -8,10 +8,13 @@ You are a Qdrant vector database specialist for the memento-stack project. Your
8
8
 
9
9
  ## Project Context
10
10
  - The system uses Qdrant for storing conversation embeddings from Claude Desktop logs
11
- - Default embedding model: Voyage AI (voyage-3-large, 1024 dimensions)
12
- - Collections use per-project isolation: `conv_<md5>_voyage` naming
11
+ - Supports TWO embedding modes: Local (FastEmbed, 384 dims) and Cloud (Voyage AI, 1024 dims)
12
+ - Collections use per-project isolation: `conv_<md5_hash>_local` or `conv_<md5_hash>_voyage` naming
13
+ - Project paths: ~/.claude/projects/-Users-{username}-projects-{project-name}/*.jsonl
14
+ - Project name is extracted from path and MD5 hashed for collection naming
13
15
  - Cross-collection search enabled with 0.7 similarity threshold
14
- - 24+ projects imported with 10,165+ conversation chunks
16
+ - Streaming importer detects file growth and processes new lines incrementally
17
+ - MCP server expects collections to match project name MD5 hash
15
18
 
16
19
  ## Key Responsibilities
17
20
 
@@ -4,27 +4,40 @@ FROM python:3.12-slim
4
4
  RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \
5
5
  gcc \
6
6
  g++ \
7
+ curl \
7
8
  && rm -rf /var/lib/apt/lists/*
8
9
 
10
+ # Upgrade pip and install setuptools first
11
+ RUN pip install --upgrade pip setuptools wheel
12
+
9
13
  # Install Python dependencies with specific versions for stability
10
- # Install torch first from PyTorch index
14
+ # Install PyTorch CPU version for smaller size
11
15
  RUN pip install --no-cache-dir torch==2.3.0 --index-url https://download.pytorch.org/whl/cpu
12
16
 
13
- # Install other dependencies from default PyPI
17
+ # Install other dependencies
14
18
  RUN pip install --no-cache-dir \
15
19
  qdrant-client==1.15.0 \
16
- sentence-transformers==2.2.2 \
17
- numpy==1.24.3 \
18
- psutil==5.9.5
20
+ fastembed==0.2.7 \
21
+ numpy==1.26.0 \
22
+ psutil==7.0.0 \
23
+ tenacity==8.2.3 \
24
+ python-dotenv==1.0.0 \
25
+ voyageai==0.2.3
26
+
27
+ # Pre-download and cache the FastEmbed model to reduce startup time
28
+ # Set cache directory to a writable location
29
+ ENV FASTEMBED_CACHE_PATH=/root/.cache/fastembed
30
+ RUN mkdir -p /root/.cache/fastembed && \
31
+ python -c "from fastembed import TextEmbedding; TextEmbedding(model_name='sentence-transformers/all-MiniLM-L6-v2')"
19
32
 
20
- # Create non-root user
33
+ # Create non-root user (but don't switch to it yet due to cache issues)
21
34
  RUN useradd -m -u 1000 importer
22
35
 
23
36
  # Set working directory
24
37
  WORKDIR /app
25
38
 
26
- # Switch to non-root user
27
- USER importer
39
+ # Note: Running as root for now due to fastembed cache permissions
40
+ # TODO: Fix cache permissions to run as non-root user
28
41
 
29
42
  # Default command
30
43
  CMD ["python", "/scripts/streaming-importer.py"]
@@ -31,9 +31,13 @@ RUN mkdir -p /scripts
31
31
  # Copy all necessary scripts
32
32
  COPY scripts/import-conversations-unified.py /scripts/
33
33
  COPY scripts/import-watcher.py /scripts/
34
+ COPY scripts/streaming-importer.py /scripts/
34
35
  COPY scripts/utils.py /scripts/
35
36
  COPY scripts/trigger-import.py /scripts/
36
37
 
38
+ # Copy MCP server directory for utils
39
+ COPY mcp-server/src/utils.py /mcp-server/src/utils.py
40
+
37
41
  RUN chmod +x /scripts/*.py
38
42
 
39
43
  # Set working directory
@@ -42,5 +46,5 @@ WORKDIR /app
42
46
  # Switch to non-root user
43
47
  USER watcher
44
48
 
45
- # Default command
46
- CMD ["python", "/scripts/import-watcher.py"]
49
+ # Default command - use streaming importer for low memory usage
50
+ CMD ["python", "/scripts/streaming-importer.py"]
@@ -18,12 +18,13 @@ services:
18
18
  - "${QDRANT_PORT:-6333}:6333"
19
19
  volumes:
20
20
  - qdrant_data:/qdrant/storage
21
+ - ./config/qdrant-config.yaml:/qdrant/config/config.yaml:ro
21
22
  environment:
22
23
  - QDRANT__LOG_LEVEL=INFO
23
24
  - QDRANT__SERVICE__HTTP_PORT=6333
24
25
  restart: unless-stopped
25
- mem_limit: ${QDRANT_MEMORY:-2g}
26
- memswap_limit: ${QDRANT_MEMORY:-2g}
26
+ mem_limit: ${QDRANT_MEMORY:-4g}
27
+ memswap_limit: ${QDRANT_MEMORY:-4g}
27
28
 
28
29
  # One-time import service (runs once then exits)
29
30
  importer:
@@ -53,7 +54,7 @@ services:
53
54
  profiles: ["import"]
54
55
  command: python /scripts/import-conversations-unified.py
55
56
 
56
- # Continuous watcher service (optional)
57
+ # Continuous watcher service (optional) - DEPRECATED, use streaming-importer
57
58
  watcher:
58
59
  build:
59
60
  context: .
@@ -73,10 +74,44 @@ services:
73
74
  - OPENAI_API_KEY=${OPENAI_API_KEY:-}
74
75
  - VOYAGE_API_KEY=${VOYAGE_API_KEY:-}
75
76
  - VOYAGE_KEY=${VOYAGE_KEY:-}
76
- - PREFER_LOCAL_EMBEDDINGS=${PREFER_LOCAL_EMBEDDINGS:-false}
77
+ - PREFER_LOCAL_EMBEDDINGS=${PREFER_LOCAL_EMBEDDINGS:-true}
77
78
  - EMBEDDING_MODEL=${EMBEDDING_MODEL:-voyage-3}
78
- - WATCH_INTERVAL=${WATCH_INTERVAL:-60}
79
+ - WATCH_INTERVAL=${WATCH_INTERVAL:-5}
80
+ - MAX_MEMORY_MB=${MAX_MEMORY_MB:-250}
81
+ - CHUNK_SIZE=${CHUNK_SIZE:-5}
82
+ - PYTHONUNBUFFERED=1
83
+ restart: unless-stopped
84
+ profiles: ["watch-old"]
85
+ mem_limit: 500m
86
+ memswap_limit: 500m
87
+
88
+ # Streaming importer service - Low memory continuous import
89
+ streaming-importer:
90
+ build:
91
+ context: .
92
+ dockerfile: Dockerfile.streaming-importer
93
+ container_name: claude-reflection-streaming
94
+ depends_on:
95
+ - init-permissions
96
+ - qdrant
97
+ volumes:
98
+ - ${CLAUDE_LOGS_PATH:-~/.claude/projects}:/logs:ro
99
+ - ${CONFIG_PATH:-~/.claude-self-reflect/config}:/config
100
+ - ./scripts:/scripts:ro
101
+ environment:
102
+ - QDRANT_URL=http://qdrant:6333
103
+ - STATE_FILE=/config/imported-files.json
104
+ - VOYAGE_API_KEY=${VOYAGE_API_KEY:-}
105
+ - VOYAGE_KEY=${VOYAGE_KEY:-}
106
+ - PREFER_LOCAL_EMBEDDINGS=${PREFER_LOCAL_EMBEDDINGS:-true}
107
+ - WATCH_INTERVAL=${WATCH_INTERVAL:-5} # Testing with 5 second interval
108
+ - MAX_MEMORY_MB=${MAX_MEMORY_MB:-350} # Total memory including model
109
+ - OPERATIONAL_MEMORY_MB=${OPERATIONAL_MEMORY_MB:-100} # Memory for operations (increased for large file handling)
110
+ - CHUNK_SIZE=${CHUNK_SIZE:-5}
79
111
  - PYTHONUNBUFFERED=1
112
+ - LOGS_DIR=/logs
113
+ - FASTEMBED_CACHE_PATH=/root/.cache/fastembed
114
+ - CURRENT_PROJECT_PATH=${PWD} # Pass current project path for prioritization
80
115
  restart: unless-stopped
81
116
  profiles: ["watch"]
82
117
  mem_limit: 1g
@@ -340,12 +340,32 @@ function showManualConfig(mcpScript) {
340
340
  }
341
341
 
342
342
  async function importConversations() {
343
- console.log('\n📚 Importing conversations...');
343
+ console.log('\n📚 Checking conversation baseline...');
344
344
 
345
- const answer = await question('Would you like to import your existing Claude conversations? (y/n): ');
345
+ // Check if baseline exists by looking for imported files state
346
+ const stateFile = path.join(configDir, 'imported-files.json');
347
+ let hasBaseline = false;
348
+
349
+ try {
350
+ if (fs.existsSync(stateFile)) {
351
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
352
+ hasBaseline = state.imported_files && Object.keys(state.imported_files).length > 0;
353
+ }
354
+ } catch (e) {
355
+ // State file doesn't exist or is invalid
356
+ }
357
+
358
+ if (!hasBaseline) {
359
+ console.log('\n⚠️ No baseline detected. Initial import STRONGLY recommended.');
360
+ console.log(' Without this, historical conversations won\'t be searchable.');
361
+ console.log(' The watcher only handles NEW conversations going forward.');
362
+ }
363
+
364
+ const answer = await question('\nImport existing Claude conversations? (y/n) [recommended: y]: ');
346
365
 
347
366
  if (answer.toLowerCase() === 'y') {
348
- console.log('🔄 Starting import process...');
367
+ console.log('🔄 Starting baseline import...');
368
+ console.log(' This ensures ALL your conversations are searchable');
349
369
  console.log(' This may take a few minutes depending on your conversation history');
350
370
 
351
371
  try {
@@ -353,12 +373,17 @@ async function importConversations() {
353
373
  cwd: projectRoot,
354
374
  stdio: 'inherit'
355
375
  });
356
- console.log('\n✅ Import completed!');
376
+ console.log('\n✅ Baseline import completed!');
377
+ console.log(' Historical conversations are now searchable');
357
378
  } catch {
358
379
  console.log('\n⚠️ Import had some issues, but you can continue');
359
380
  }
360
381
  } else {
361
- console.log('📝 Skipping import. You can import later with:');
382
+ console.log('\n❌ WARNING: Skipping baseline import means:');
383
+ console.log(' • Historical conversations will NOT be searchable');
384
+ console.log(' • Only NEW conversations from now on will be indexed');
385
+ console.log(' • You may see "BASELINE_NEEDED" warnings in logs');
386
+ console.log('\n📝 You can run baseline import later with:');
362
387
  console.log(' docker compose run --rm importer');
363
388
  }
364
389
  }
@@ -108,9 +108,16 @@ async def get_all_collections() -> List[str]:
108
108
  return [c.name for c in collections.collections
109
109
  if c.name.endswith('_voyage') or c.name.endswith('_local') or c.name.startswith('reflections')]
110
110
 
111
- async def generate_embedding(text: str) -> List[float]:
112
- """Generate embedding using configured provider."""
113
- if PREFER_LOCAL_EMBEDDINGS or not voyage_client:
111
+ async def generate_embedding(text: str, force_type: Optional[str] = None) -> List[float]:
112
+ """Generate embedding using configured provider or forced type.
113
+
114
+ Args:
115
+ text: Text to embed
116
+ force_type: Force specific embedding type ('local' or 'voyage')
117
+ """
118
+ use_local = force_type == 'local' if force_type else (PREFER_LOCAL_EMBEDDINGS or not voyage_client)
119
+
120
+ if use_local:
114
121
  # Use local embeddings
115
122
  if not local_embedding_model:
116
123
  raise ValueError("Local embedding model not initialized")
@@ -123,6 +130,8 @@ async def generate_embedding(text: str) -> List[float]:
123
130
  return embeddings[0].tolist()
124
131
  else:
125
132
  # Use Voyage AI
133
+ if not voyage_client:
134
+ raise ValueError("Voyage client not initialized")
126
135
  result = voyage_client.embed(
127
136
  texts=[text],
128
137
  model="voyage-3-large",
@@ -218,10 +227,10 @@ async def reflect_on_past(
218
227
  await ctx.debug(f"DECAY_WEIGHT: {DECAY_WEIGHT}, DECAY_SCALE_DAYS: {DECAY_SCALE_DAYS}")
219
228
 
220
229
  try:
221
- # Generate embedding
222
- timing_info['embedding_start'] = time.time()
223
- query_embedding = await generate_embedding(query)
224
- timing_info['embedding_end'] = time.time()
230
+ # We'll generate embeddings on-demand per collection type
231
+ timing_info['embedding_prep_start'] = time.time()
232
+ query_embeddings = {} # Cache embeddings by type
233
+ timing_info['embedding_prep_end'] = time.time()
225
234
 
226
235
  # Get all collections
227
236
  timing_info['get_collections_start'] = time.time()
@@ -237,6 +246,7 @@ async def reflect_on_past(
237
246
  # Generate the collection name pattern for this project using normalized name
238
247
  normalized_name = normalize_project_name(target_project)
239
248
  project_hash = hashlib.md5(normalized_name.encode()).hexdigest()[:8]
249
+ # Search BOTH local and voyage collections for this project
240
250
  project_collections = [
241
251
  c for c in all_collections
242
252
  if c.startswith(f"conv_{project_hash}_")
@@ -276,6 +286,18 @@ async def reflect_on_past(
276
286
  )
277
287
 
278
288
  try:
289
+ # Determine embedding type for this collection
290
+ embedding_type_for_collection = 'voyage' if collection_name.endswith('_voyage') else 'local'
291
+
292
+ # Generate or retrieve cached embedding for this type
293
+ if embedding_type_for_collection not in query_embeddings:
294
+ try:
295
+ query_embeddings[embedding_type_for_collection] = await generate_embedding(query, force_type=embedding_type_for_collection)
296
+ except Exception as e:
297
+ await ctx.debug(f"Failed to generate {embedding_type_for_collection} embedding: {e}")
298
+ continue
299
+
300
+ query_embedding = query_embeddings[embedding_type_for_collection]
279
301
  if should_use_decay and USE_NATIVE_DECAY and NATIVE_DECAY_AVAILABLE:
280
302
  # Use native Qdrant decay with newer API
281
303
  await ctx.debug(f"Using NATIVE Qdrant decay (new API) for {collection_name}")
@@ -9,6 +9,8 @@ def normalize_project_name(project_path: str) -> str:
9
9
 
10
10
  Handles various path formats:
11
11
  - Claude logs format: -Users-kyle-Code-claude-self-reflect -> claude-self-reflect
12
+ - File paths in Claude logs: /path/to/-Users-kyle-Code-claude-self-reflect/file.jsonl -> claude-self-reflect
13
+ - Regular file paths: /path/to/project/file.txt -> project
12
14
  - Regular paths: /path/to/project -> project
13
15
  - Already normalized: project -> project
14
16
 
@@ -49,5 +51,22 @@ def normalize_project_name(project_path: str) -> str:
49
51
  # Fallback: just use the last component
50
52
  return path_parts[-1] if path_parts else project_path
51
53
 
52
- # Handle regular paths - use basename
53
- return Path(project_path).name
54
+ # Check if this is a file path that contains a Claude logs directory
55
+ # Pattern: /path/to/-Users-...-projects-..../filename
56
+ path_obj = Path(project_path)
57
+
58
+ # Look for a parent directory that starts with dash (Claude logs format)
59
+ for parent in path_obj.parents:
60
+ parent_name = parent.name
61
+ if parent_name.startswith("-"):
62
+ # Found a Claude logs directory, process it
63
+ return normalize_project_name(parent_name)
64
+
65
+ # Handle regular paths - if it's a file, get the parent directory
66
+ # Otherwise use the directory/project name itself
67
+ if path_obj.suffix: # It's a file (has an extension)
68
+ # Use the parent directory name
69
+ return path_obj.parent.name
70
+ else:
71
+ # Use the directory name itself
72
+ return path_obj.name
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-self-reflect",
3
- "version": "2.5.1",
3
+ "version": "2.5.2",
4
4
  "description": "Give Claude perfect memory of all your conversations - Installation wizard for Python MCP server",
5
5
  "keywords": [
6
6
  "claude",
@@ -79,13 +79,22 @@ def should_import_file(file_path, state):
79
79
  file_mtime = os.path.getmtime(file_path)
80
80
 
81
81
  if str_path in state["imported_files"]:
82
- last_imported = state["imported_files"][str_path].get("last_imported", 0)
83
- last_modified = state["imported_files"][str_path].get("last_modified", 0)
82
+ file_state = state["imported_files"][str_path]
84
83
 
85
- # Skip if file hasn't been modified since last import
86
- if file_mtime <= last_modified and last_imported > 0:
87
- logger.info(f"Skipping unchanged file: {file_path.name}")
88
- return False
84
+ # Handle both old string format and new dict format
85
+ if isinstance(file_state, str):
86
+ # Old format (just timestamp string) - treat as needs reimport
87
+ logger.info(f"Found old format state for {file_path.name}, will reimport")
88
+ return True
89
+ else:
90
+ # New format with dictionary
91
+ last_imported = file_state.get("last_imported", 0)
92
+ last_modified = file_state.get("last_modified", 0)
93
+
94
+ # Skip if file hasn't been modified since last import
95
+ if file_mtime <= last_modified and last_imported > 0:
96
+ logger.info(f"Skipping unchanged file: {file_path.name}")
97
+ return False
89
98
 
90
99
  return True
91
100
 
@@ -1,88 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Enhanced watcher that runs import periodically and supports manual triggers."""
3
-
4
- import time
5
- import subprocess
6
- import os
7
- import sys
8
- from datetime import datetime
9
- from pathlib import Path
10
-
11
- WATCH_INTERVAL = int(os.getenv('WATCH_INTERVAL', '60'))
12
- SIGNAL_FILE = Path("/tmp/claude-self-reflect-import-current")
13
- CHECK_INTERVAL = 1 # Check for signal file every second
14
-
15
- print(f"[Watcher] Starting enhanced import watcher with {WATCH_INTERVAL}s interval", flush=True)
16
- print(f"[Watcher] Monitoring signal file: {SIGNAL_FILE}", flush=True)
17
-
18
- last_import = 0
19
-
20
- while True:
21
- current_time = time.time()
22
-
23
- # Check for manual trigger signal
24
- if SIGNAL_FILE.exists():
25
- print(f"[Watcher] Signal detected! Running immediate import...", flush=True)
26
- try:
27
- # Read conversation ID if provided
28
- conversation_id = None
29
- try:
30
- conversation_id = SIGNAL_FILE.read_text().strip()
31
- except:
32
- pass
33
-
34
- # Remove signal file to prevent re-triggering
35
- SIGNAL_FILE.unlink()
36
-
37
- # Run import with special flag for current conversation only
38
- cmd = [sys.executable, "/scripts/import-conversations-unified.py"]
39
- if conversation_id:
40
- cmd.extend(["--conversation-id", conversation_id])
41
- else:
42
- # Import only today's conversations for manual trigger
43
- cmd.extend(["--days", "1"])
44
-
45
- # Write progress indicator
46
- progress_file = Path("/tmp/claude-self-reflect-import-progress")
47
- progress_file.write_text("🔄 Starting import...")
48
-
49
- print(f"[Watcher] Running command: {' '.join(cmd)}", flush=True)
50
- result = subprocess.run(cmd, capture_output=True, text=True)
51
-
52
- if result.returncode == 0:
53
- print(f"[Watcher] Manual import completed successfully", flush=True)
54
- # Create completion signal
55
- Path("/tmp/claude-self-reflect-import-complete").touch()
56
- else:
57
- print(f"[Watcher] Manual import failed with code {result.returncode}", flush=True)
58
- if result.stderr:
59
- print(f"[Watcher] Error: {result.stderr}", flush=True)
60
-
61
- last_import = current_time
62
-
63
- except Exception as e:
64
- print(f"[Watcher] Error during manual import: {e}", flush=True)
65
-
66
- # Regular scheduled import
67
- elif current_time - last_import >= WATCH_INTERVAL:
68
- try:
69
- print(f"[Watcher] Running scheduled import at {datetime.now().isoformat()}", flush=True)
70
- result = subprocess.run([
71
- sys.executable,
72
- "/scripts/import-conversations-unified.py"
73
- ], capture_output=True, text=True)
74
-
75
- if result.returncode == 0:
76
- print(f"[Watcher] Scheduled import completed successfully", flush=True)
77
- else:
78
- print(f"[Watcher] Scheduled import failed with code {result.returncode}", flush=True)
79
- if result.stderr:
80
- print(f"[Watcher] Error: {result.stderr}", flush=True)
81
-
82
- last_import = current_time
83
-
84
- except Exception as e:
85
- print(f"[Watcher] Error during scheduled import: {e}", flush=True)
86
-
87
- # Short sleep to check for signals frequently
88
- time.sleep(CHECK_INTERVAL)