claude-self-reflect 2.8.4 → 2.8.6
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.
- package/Dockerfile.async-importer +8 -1
- package/Dockerfile.importer +7 -3
- package/Dockerfile.importer-isolated +8 -1
- package/Dockerfile.importer-isolated.alpine +1 -1
- package/Dockerfile.importer.alpine +1 -1
- package/Dockerfile.mcp-server +7 -2
- package/Dockerfile.mcp-server.alpine +1 -1
- package/Dockerfile.safe-watcher +8 -1
- package/Dockerfile.streaming-importer +10 -4
- package/Dockerfile.streaming-importer.alpine +1 -1
- package/Dockerfile.watcher +9 -5
- package/Dockerfile.watcher.alpine +1 -1
- package/README.md +112 -24
- package/mcp-server/src/health.py +190 -0
- package/mcp-server/src/project_resolver.py +45 -9
- package/package.json +1 -1
- package/scripts/import-conversations-unified.py +8 -5
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
FROM python:3.
|
|
1
|
+
FROM python:3.13-slim
|
|
2
|
+
|
|
3
|
+
# SECURITY: CVE-2025-58050 mitigation - PCRE2 heap buffer overflow
|
|
4
|
+
# TODO: Remove explicit PCRE2 upgrade when base image includes patched version
|
|
5
|
+
RUN apt-get update && \
|
|
6
|
+
(apt-get install -y --only-upgrade libpcre2-8-0 2>/dev/null || \
|
|
7
|
+
echo "Warning: PCRE2 10.46+ not yet available") && \
|
|
8
|
+
apt-get upgrade -y && rm -rf /var/lib/apt/lists/*
|
|
2
9
|
|
|
3
10
|
# Install system dependencies
|
|
4
11
|
RUN apt-get update && apt-get install -y \
|
package/Dockerfile.importer
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
FROM python:3.
|
|
1
|
+
FROM python:3.13-slim
|
|
2
2
|
|
|
3
3
|
WORKDIR /app
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
|
|
5
|
+
# SECURITY: CVE-2025-58050 mitigation - PCRE2 heap buffer overflow
|
|
6
|
+
# TODO: Remove explicit PCRE2 upgrade when base image includes patched version
|
|
7
|
+
RUN apt-get update && \
|
|
8
|
+
(apt-get install -y --only-upgrade libpcre2-8-0 2>/dev/null || \
|
|
9
|
+
echo "Warning: PCRE2 10.46+ not yet available") && \
|
|
10
|
+
apt-get upgrade -y && rm -rf /var/lib/apt/lists/*
|
|
7
11
|
|
|
8
12
|
# Install dependencies directly (avoids file path issues with global npm installs)
|
|
9
13
|
RUN pip install --no-cache-dir \
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
FROM python:3.
|
|
1
|
+
FROM python:3.13-slim
|
|
2
|
+
|
|
3
|
+
# SECURITY: CVE-2025-58050 mitigation - PCRE2 heap buffer overflow
|
|
4
|
+
# TODO: Remove explicit PCRE2 upgrade when base image includes patched version
|
|
5
|
+
RUN apt-get update && \
|
|
6
|
+
(apt-get install -y --only-upgrade libpcre2-8-0 2>/dev/null || \
|
|
7
|
+
echo "Warning: PCRE2 10.46+ not yet available") && \
|
|
8
|
+
apt-get upgrade -y && rm -rf /var/lib/apt/lists/*
|
|
2
9
|
|
|
3
10
|
# Update system packages for security and install curl
|
|
4
11
|
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \
|
package/Dockerfile.mcp-server
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
FROM python:3.
|
|
1
|
+
FROM python:3.13-slim
|
|
2
2
|
|
|
3
3
|
WORKDIR /app
|
|
4
4
|
|
|
5
|
+
# SECURITY: CVE-2025-58050 mitigation - PCRE2 heap buffer overflow
|
|
5
6
|
# Update system packages for security
|
|
6
|
-
|
|
7
|
+
# TODO: Remove explicit PCRE2 upgrade when base image includes patched version
|
|
8
|
+
RUN apt-get update && \
|
|
9
|
+
(apt-get install -y --only-upgrade libpcre2-8-0 2>/dev/null || \
|
|
10
|
+
echo "Warning: PCRE2 10.46+ not yet available") && \
|
|
11
|
+
apt-get upgrade -y && rm -rf /var/lib/apt/lists/*
|
|
7
12
|
|
|
8
13
|
# Copy the MCP server package files
|
|
9
14
|
COPY mcp-server/pyproject.toml ./
|
package/Dockerfile.safe-watcher
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
FROM python:3.
|
|
1
|
+
FROM python:3.13-slim
|
|
2
|
+
|
|
3
|
+
# SECURITY: CVE-2025-58050 mitigation - PCRE2 heap buffer overflow
|
|
4
|
+
# TODO: Remove explicit PCRE2 upgrade when base image includes patched version
|
|
5
|
+
RUN apt-get update && \
|
|
6
|
+
(apt-get install -y --only-upgrade libpcre2-8-0 2>/dev/null || \
|
|
7
|
+
echo "Warning: PCRE2 10.46+ not yet available") && \
|
|
8
|
+
apt-get upgrade -y && rm -rf /var/lib/apt/lists/*
|
|
2
9
|
|
|
3
10
|
# Install system dependencies
|
|
4
11
|
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
FROM python:3.
|
|
2
|
-
|
|
3
|
-
#
|
|
4
|
-
|
|
1
|
+
FROM python:3.13-slim
|
|
2
|
+
|
|
3
|
+
# SECURITY: CVE-2025-58050 mitigation - PCRE2 heap buffer overflow
|
|
4
|
+
# Attempting explicit upgrade of libpcre2-8-0 (vulnerable: 10.45-1, fixed: 10.46+)
|
|
5
|
+
# TODO: Remove explicit PCRE2 upgrade when base image includes patched version
|
|
6
|
+
RUN apt-get update && \
|
|
7
|
+
(apt-get install -y --only-upgrade libpcre2-8-0 2>/dev/null || \
|
|
8
|
+
echo "Warning: PCRE2 10.46+ not yet available, continuing with security updates") && \
|
|
9
|
+
apt-get upgrade -y && \
|
|
10
|
+
apt-get install -y --no-install-recommends \
|
|
5
11
|
gcc \
|
|
6
12
|
g++ \
|
|
7
13
|
curl \
|
package/Dockerfile.watcher
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
FROM python:3.
|
|
1
|
+
FROM python:3.13-slim
|
|
2
2
|
|
|
3
|
+
# SECURITY: CVE-2025-58050 mitigation - PCRE2 heap buffer overflow
|
|
3
4
|
# Update system packages for security and install build dependencies for psutil
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
# TODO: Remove explicit PCRE2 upgrade when base image includes patched version
|
|
6
|
+
RUN apt-get update && \
|
|
7
|
+
(apt-get install -y --only-upgrade libpcre2-8-0 2>/dev/null || \
|
|
8
|
+
echo "Warning: PCRE2 10.46+ not yet available") && \
|
|
9
|
+
apt-get upgrade -y && \
|
|
10
|
+
apt-get install -y gcc python3-dev && \
|
|
11
|
+
rm -rf /var/lib/apt/lists/*
|
|
8
12
|
|
|
9
13
|
# Install Python dependencies
|
|
10
14
|
RUN pip install --no-cache-dir \
|
package/README.md
CHANGED
|
@@ -26,6 +26,21 @@ Give Claude perfect memory of all your conversations. Search past discussions in
|
|
|
26
26
|
|
|
27
27
|
**🔒 100% Local by Default** • **⚡ Blazing Fast Search** • **🚀 Zero Configuration** • **🏭 Production Ready**
|
|
28
28
|
|
|
29
|
+
## 📑 Table of Contents
|
|
30
|
+
|
|
31
|
+
- [🚀 Quick Install](#-quick-install)
|
|
32
|
+
- [✨ The Magic](#-the-magic)
|
|
33
|
+
- [📊 Before & After](#-before--after)
|
|
34
|
+
- [💬 Real Examples](#-real-examples)
|
|
35
|
+
- [🆕 NEW: Real-time Indexing Status](#-new-real-time-indexing-status-in-your-terminal)
|
|
36
|
+
- [🎯 Key Features](#-key-features)
|
|
37
|
+
- [🏗️ Architecture](#️-architecture)
|
|
38
|
+
- [🛠️ Requirements](#️-requirements)
|
|
39
|
+
- [📖 Documentation](#-documentation)
|
|
40
|
+
- [📦 What's New](#-whats-new)
|
|
41
|
+
- [🔧 Troubleshooting](#-troubleshooting)
|
|
42
|
+
- [👥 Contributors](#-contributors)
|
|
43
|
+
|
|
29
44
|
## 🚀 Quick Install
|
|
30
45
|
|
|
31
46
|
```bash
|
|
@@ -97,7 +112,9 @@ Works with [Claude Code Statusline](https://github.com/sirmalloc/ccstatusline) -
|
|
|
97
112
|
|
|
98
113
|
## 🎯 Key Features
|
|
99
114
|
|
|
100
|
-
|
|
115
|
+
<details>
|
|
116
|
+
<summary><b>📊 Statusline Integration</b></summary>
|
|
117
|
+
|
|
101
118
|
See your indexing progress right in your terminal! Works with [Claude Code Statusline](https://github.com/sirmalloc/ccstatusline):
|
|
102
119
|
- **Progress Bar** - Visual indicator `[████████ ] 91%`
|
|
103
120
|
- **Indexing Lag** - Shows backlog `• 7h behind`
|
|
@@ -106,7 +123,11 @@ See your indexing progress right in your terminal! Works with [Claude Code Statu
|
|
|
106
123
|
|
|
107
124
|
[Learn more about statusline integration →](docs/statusline-integration.md)
|
|
108
125
|
|
|
109
|
-
|
|
126
|
+
</details>
|
|
127
|
+
|
|
128
|
+
<details>
|
|
129
|
+
<summary><b>🔍 Project-Scoped Search</b></summary>
|
|
130
|
+
|
|
110
131
|
Searches are **project-aware by default**. Claude automatically searches within your current project:
|
|
111
132
|
|
|
112
133
|
```
|
|
@@ -119,21 +140,37 @@ You: "Search all projects for WebSocket implementations"
|
|
|
119
140
|
Claude: [Searches across ALL your projects]
|
|
120
141
|
```
|
|
121
142
|
|
|
122
|
-
|
|
143
|
+
</details>
|
|
144
|
+
|
|
145
|
+
<details>
|
|
146
|
+
<summary><b>⏱️ Memory Decay</b></summary>
|
|
147
|
+
|
|
123
148
|
Recent conversations matter more. Old ones fade. Like your brain, but reliable.
|
|
149
|
+
- **90-day half-life**: Recent memories stay strong
|
|
150
|
+
- **Graceful aging**: Old information fades naturally
|
|
151
|
+
- **Configurable**: Adjust decay rate to your needs
|
|
152
|
+
|
|
153
|
+
</details>
|
|
154
|
+
|
|
155
|
+
<details>
|
|
156
|
+
<summary><b>⚡ Performance at Scale</b></summary>
|
|
124
157
|
|
|
125
|
-
### ⚡ Performance at Scale
|
|
126
158
|
- **Search**: <3ms average response time
|
|
127
159
|
- **Scale**: 600+ conversations across 24 projects
|
|
128
160
|
- **Reliability**: 100% indexing success rate
|
|
129
161
|
- **Memory**: 96% reduction from v2.5.15
|
|
162
|
+
- **Real-time**: HOT/WARM/COLD intelligent prioritization
|
|
163
|
+
|
|
164
|
+
</details>
|
|
130
165
|
|
|
131
166
|
## 🏗️ Architecture
|
|
132
167
|
|
|
168
|
+
<details>
|
|
169
|
+
<summary><b>View Architecture Diagram & Details</b></summary>
|
|
170
|
+
|
|
133
171
|

|
|
134
172
|
|
|
135
|
-
|
|
136
|
-
<summary>🔥 HOT/WARM/COLD Intelligent Prioritization</summary>
|
|
173
|
+
### 🔥 HOT/WARM/COLD Intelligent Prioritization
|
|
137
174
|
|
|
138
175
|
- **🔥 HOT** (< 5 minutes): 2-second intervals for near real-time import
|
|
139
176
|
- **🌡️ WARM** (< 24 hours): Normal priority with starvation prevention
|
|
@@ -141,13 +178,37 @@ Recent conversations matter more. Old ones fade. Like your brain, but reliable.
|
|
|
141
178
|
|
|
142
179
|
Files are categorized by age and processed with priority queuing to ensure newest content gets imported quickly while preventing older files from being starved.
|
|
143
180
|
|
|
181
|
+
### Components
|
|
182
|
+
- **Vector Database**: Qdrant for semantic search
|
|
183
|
+
- **MCP Server**: Python-based using FastMCP
|
|
184
|
+
- **Embeddings**: Local (FastEmbed) or Cloud (Voyage AI)
|
|
185
|
+
- **Import Pipeline**: Docker-based with automatic monitoring
|
|
186
|
+
|
|
144
187
|
</details>
|
|
145
188
|
|
|
146
189
|
## 🛠️ Requirements
|
|
147
190
|
|
|
191
|
+
<details>
|
|
192
|
+
<summary><b>System Requirements</b></summary>
|
|
193
|
+
|
|
194
|
+
### Minimum Requirements
|
|
148
195
|
- **Docker Desktop** (macOS/Windows) or **Docker Engine** (Linux)
|
|
149
196
|
- **Node.js** 16+ (for the setup wizard)
|
|
150
197
|
- **Claude Code** CLI
|
|
198
|
+
- **4GB RAM** available for Docker
|
|
199
|
+
- **2GB disk space** for vector database
|
|
200
|
+
|
|
201
|
+
### Recommended
|
|
202
|
+
- **8GB RAM** for optimal performance
|
|
203
|
+
- **SSD storage** for faster indexing
|
|
204
|
+
- **Docker Desktop 4.0+** for best compatibility
|
|
205
|
+
|
|
206
|
+
### Operating Systems
|
|
207
|
+
- ✅ macOS 11+ (Intel & Apple Silicon)
|
|
208
|
+
- ✅ Windows 10/11 with WSL2
|
|
209
|
+
- ✅ Linux (Ubuntu 20.04+, Debian 11+)
|
|
210
|
+
|
|
211
|
+
</details>
|
|
151
212
|
|
|
152
213
|
## 📖 Documentation
|
|
153
214
|
|
|
@@ -254,9 +315,10 @@ docker compose run --rm importer python /app/scripts/delta-metadata-update-safe.
|
|
|
254
315
|
|
|
255
316
|
## 🔧 Troubleshooting
|
|
256
317
|
|
|
257
|
-
|
|
318
|
+
<details>
|
|
319
|
+
<summary><b>Common Issues and Solutions</b></summary>
|
|
258
320
|
|
|
259
|
-
|
|
321
|
+
### 1. "No collections created" after import
|
|
260
322
|
**Symptom**: Import runs but Qdrant shows no collections
|
|
261
323
|
**Cause**: Docker can't access Claude projects directory
|
|
262
324
|
**Solution**:
|
|
@@ -272,12 +334,12 @@ cat .env | grep CLAUDE_LOGS_PATH
|
|
|
272
334
|
# Should show: CLAUDE_LOGS_PATH=/Users/YOUR_NAME/.claude/projects
|
|
273
335
|
```
|
|
274
336
|
|
|
275
|
-
|
|
337
|
+
### 2. MCP server shows "ERROR" but it's actually INFO
|
|
276
338
|
**Symptom**: `[ERROR] MCP server "claude-self-reflect" Server stderr: INFO Starting MCP server`
|
|
277
339
|
**Cause**: Claude Code displays all stderr output as errors
|
|
278
340
|
**Solution**: This is not an actual error - the MCP is working correctly. The INFO message confirms successful startup.
|
|
279
341
|
|
|
280
|
-
|
|
342
|
+
### 3. "No JSONL files found"
|
|
281
343
|
**Symptom**: Setup can't find any conversation files
|
|
282
344
|
**Cause**: Claude Code hasn't been used yet or stores files elsewhere
|
|
283
345
|
**Solution**:
|
|
@@ -289,7 +351,7 @@ ls ~/.claude/projects/
|
|
|
289
351
|
# The watcher will import them automatically
|
|
290
352
|
```
|
|
291
353
|
|
|
292
|
-
|
|
354
|
+
### 4. Docker volume mount issues
|
|
293
355
|
**Symptom**: Import fails with permission errors
|
|
294
356
|
**Cause**: Docker can't access home directory
|
|
295
357
|
**Solution**:
|
|
@@ -303,7 +365,7 @@ docker compose down
|
|
|
303
365
|
claude-self-reflect setup
|
|
304
366
|
```
|
|
305
367
|
|
|
306
|
-
|
|
368
|
+
### 5. Qdrant not accessible
|
|
307
369
|
**Symptom**: Can't connect to localhost:6333
|
|
308
370
|
**Solution**:
|
|
309
371
|
```bash
|
|
@@ -317,9 +379,12 @@ docker compose ps
|
|
|
317
379
|
docker compose logs qdrant
|
|
318
380
|
```
|
|
319
381
|
|
|
320
|
-
|
|
382
|
+
</details>
|
|
321
383
|
|
|
322
|
-
|
|
384
|
+
<details>
|
|
385
|
+
<summary><b>Diagnostic Tools</b></summary>
|
|
386
|
+
|
|
387
|
+
### Run Comprehensive Diagnostics
|
|
323
388
|
```bash
|
|
324
389
|
claude-self-reflect doctor
|
|
325
390
|
```
|
|
@@ -331,18 +396,41 @@ This checks:
|
|
|
331
396
|
- Import status and collections
|
|
332
397
|
- Service health
|
|
333
398
|
|
|
334
|
-
###
|
|
399
|
+
### Check Logs
|
|
400
|
+
```bash
|
|
401
|
+
# View all service logs
|
|
402
|
+
docker compose logs -f
|
|
403
|
+
|
|
404
|
+
# View specific service
|
|
405
|
+
docker compose logs qdrant
|
|
406
|
+
docker compose logs watcher
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Generate Diagnostic Report
|
|
410
|
+
```bash
|
|
411
|
+
# Create diagnostic file for issue reporting
|
|
412
|
+
claude-self-reflect doctor > diagnostic.txt
|
|
413
|
+
```
|
|
335
414
|
|
|
336
|
-
|
|
337
|
-
```bash
|
|
338
|
-
docker compose logs -f
|
|
339
|
-
```
|
|
415
|
+
</details>
|
|
340
416
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
417
|
+
<details>
|
|
418
|
+
<summary><b>Getting Help</b></summary>
|
|
419
|
+
|
|
420
|
+
1. **Check Documentation**
|
|
421
|
+
- [Troubleshooting Guide](docs/troubleshooting.md)
|
|
422
|
+
- [FAQ](docs/faq.md)
|
|
423
|
+
- [Windows Setup](docs/windows-setup.md)
|
|
424
|
+
|
|
425
|
+
2. **Community Support**
|
|
426
|
+
- [GitHub Discussions](https://github.com/ramakay/claude-self-reflect/discussions)
|
|
427
|
+
- [Discord Community](https://discord.gg/claude-self-reflect)
|
|
428
|
+
|
|
429
|
+
3. **Report Issues**
|
|
430
|
+
- [GitHub Issues](https://github.com/ramakay/claude-self-reflect/issues)
|
|
431
|
+
- Include diagnostic output when reporting
|
|
432
|
+
|
|
433
|
+
</details>
|
|
346
434
|
|
|
347
435
|
## 👥 Contributors
|
|
348
436
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Health check endpoint for Claude Self-Reflect system
|
|
4
|
+
|
|
5
|
+
Provides a simple way to monitor system health and import status.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from datetime import datetime, timedelta
|
|
13
|
+
from typing import Dict, Any
|
|
14
|
+
|
|
15
|
+
# No sys.path modification needed - using subprocess for imports
|
|
16
|
+
|
|
17
|
+
def check_qdrant_health() -> Dict[str, Any]:
|
|
18
|
+
"""Check if Qdrant is accessible and has data"""
|
|
19
|
+
try:
|
|
20
|
+
from qdrant_client import QdrantClient
|
|
21
|
+
from qdrant_client.http.exceptions import ResponseHandlingException
|
|
22
|
+
|
|
23
|
+
# Add timeout for network operations
|
|
24
|
+
client = QdrantClient(
|
|
25
|
+
'localhost',
|
|
26
|
+
port=6333,
|
|
27
|
+
timeout=5 # 5 second timeout
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
collections = client.get_collections().collections
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
'status': 'healthy',
|
|
34
|
+
'collections': len(collections),
|
|
35
|
+
'accessible': True
|
|
36
|
+
}
|
|
37
|
+
except (ResponseHandlingException, ConnectionError, TimeoutError) as e:
|
|
38
|
+
# Sanitize error messages to avoid information disclosure
|
|
39
|
+
return {
|
|
40
|
+
'status': 'unhealthy',
|
|
41
|
+
'error': 'Connection failed',
|
|
42
|
+
'accessible': False
|
|
43
|
+
}
|
|
44
|
+
except Exception as e:
|
|
45
|
+
return {
|
|
46
|
+
'status': 'unhealthy',
|
|
47
|
+
'error': 'Service unavailable',
|
|
48
|
+
'accessible': False
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def check_import_status() -> Dict[str, Any]:
|
|
52
|
+
"""Check import status from status.py"""
|
|
53
|
+
try:
|
|
54
|
+
# Run status.py and parse output
|
|
55
|
+
import subprocess
|
|
56
|
+
import json
|
|
57
|
+
|
|
58
|
+
result = subprocess.run(
|
|
59
|
+
[sys.executable, str(Path(__file__).parent / "status.py")],
|
|
60
|
+
capture_output=True,
|
|
61
|
+
text=True,
|
|
62
|
+
timeout=10
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if result.returncode == 0:
|
|
66
|
+
status = json.loads(result.stdout)
|
|
67
|
+
return {
|
|
68
|
+
'percentage': status['overall']['percentage'],
|
|
69
|
+
'indexed': status['overall']['indexed'],
|
|
70
|
+
'total': status['overall']['total'],
|
|
71
|
+
'backlog': status['overall']['backlog']
|
|
72
|
+
}
|
|
73
|
+
else:
|
|
74
|
+
return {
|
|
75
|
+
'error': result.stderr,
|
|
76
|
+
'percentage': 0
|
|
77
|
+
}
|
|
78
|
+
except Exception as e:
|
|
79
|
+
return {
|
|
80
|
+
'error': str(e),
|
|
81
|
+
'percentage': 0
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def check_watcher_status() -> Dict[str, Any]:
|
|
85
|
+
"""Check if Docker watcher is running"""
|
|
86
|
+
try:
|
|
87
|
+
import subprocess
|
|
88
|
+
result = subprocess.run(
|
|
89
|
+
["docker", "ps", "--filter", "name=claude-reflection-safe-watcher", "--format", "{{.Status}}"],
|
|
90
|
+
capture_output=True,
|
|
91
|
+
text=True,
|
|
92
|
+
timeout=5
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if result.stdout and "Up" in result.stdout:
|
|
96
|
+
return {
|
|
97
|
+
'status': 'running',
|
|
98
|
+
'details': result.stdout.strip()
|
|
99
|
+
}
|
|
100
|
+
else:
|
|
101
|
+
return {
|
|
102
|
+
'status': 'stopped',
|
|
103
|
+
'details': 'Container not running'
|
|
104
|
+
}
|
|
105
|
+
except Exception as e:
|
|
106
|
+
return {
|
|
107
|
+
'status': 'unknown',
|
|
108
|
+
'error': 'Docker check failed'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def check_recent_imports() -> Dict[str, Any]:
|
|
112
|
+
"""Check for recent import activity"""
|
|
113
|
+
try:
|
|
114
|
+
import_file = Path.home() / ".claude-self-reflect" / "config" / "imported-files.json"
|
|
115
|
+
if import_file.exists():
|
|
116
|
+
mtime = datetime.fromtimestamp(import_file.stat().st_mtime)
|
|
117
|
+
age = datetime.now() - mtime
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
'last_import': mtime.isoformat(),
|
|
121
|
+
'hours_ago': round(age.total_seconds() / 3600, 1),
|
|
122
|
+
'active': age < timedelta(hours=24)
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
'last_import': None,
|
|
126
|
+
'active': False
|
|
127
|
+
}
|
|
128
|
+
except Exception as e:
|
|
129
|
+
return {
|
|
130
|
+
'error': 'Import check failed',
|
|
131
|
+
'active': False
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
def get_health_status() -> Dict[str, Any]:
|
|
135
|
+
"""Get comprehensive health status"""
|
|
136
|
+
|
|
137
|
+
# Collect all health checks
|
|
138
|
+
qdrant = check_qdrant_health()
|
|
139
|
+
imports = check_import_status()
|
|
140
|
+
watcher = check_watcher_status()
|
|
141
|
+
recent = check_recent_imports()
|
|
142
|
+
|
|
143
|
+
# Determine overall health
|
|
144
|
+
is_healthy = (
|
|
145
|
+
qdrant.get('accessible', False) and
|
|
146
|
+
imports.get('percentage', 0) > 95 and
|
|
147
|
+
watcher.get('status') == 'running'
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
'timestamp': datetime.now().isoformat(),
|
|
152
|
+
'healthy': is_healthy,
|
|
153
|
+
'status': 'healthy' if is_healthy else 'degraded',
|
|
154
|
+
'components': {
|
|
155
|
+
'qdrant': qdrant,
|
|
156
|
+
'imports': imports,
|
|
157
|
+
'watcher': watcher,
|
|
158
|
+
'recent_activity': recent
|
|
159
|
+
},
|
|
160
|
+
'summary': {
|
|
161
|
+
'import_percentage': imports.get('percentage', 0),
|
|
162
|
+
'collections': qdrant.get('collections', 0),
|
|
163
|
+
'watcher_running': watcher.get('status') == 'running',
|
|
164
|
+
'recent_imports': recent.get('active', False)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
def main():
|
|
169
|
+
"""Main entry point for CLI usage"""
|
|
170
|
+
try:
|
|
171
|
+
health = get_health_status()
|
|
172
|
+
|
|
173
|
+
# Pretty print
|
|
174
|
+
print(json.dumps(health, indent=2))
|
|
175
|
+
|
|
176
|
+
# Exit code based on health
|
|
177
|
+
sys.exit(0 if health['healthy'] else 1)
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
error_response = {
|
|
181
|
+
'timestamp': datetime.now().isoformat(),
|
|
182
|
+
'healthy': False,
|
|
183
|
+
'status': 'error',
|
|
184
|
+
'error': 'Health check failed'
|
|
185
|
+
}
|
|
186
|
+
print(json.dumps(error_response, indent=2))
|
|
187
|
+
sys.exit(2)
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
main()
|
|
@@ -76,31 +76,64 @@ class ProjectResolver:
|
|
|
76
76
|
return []
|
|
77
77
|
|
|
78
78
|
# Strategy 1: Direct hash of input (handles full paths)
|
|
79
|
-
|
|
79
|
+
# Try both MD5 (used by streaming-watcher) and SHA256 (legacy)
|
|
80
|
+
direct_hash_md5 = hashlib.md5(user_project_name.encode()).hexdigest()[:8]
|
|
81
|
+
direct_hash_sha256 = hashlib.sha256(user_project_name.encode()).hexdigest()[:16]
|
|
82
|
+
|
|
80
83
|
# Match exact hash segment between underscores, not substring
|
|
81
84
|
direct_matches = [c for c in collection_names
|
|
82
|
-
if f"_{
|
|
85
|
+
if f"_{direct_hash_md5}_" in c or c.endswith(f"_{direct_hash_md5}") or
|
|
86
|
+
f"_{direct_hash_sha256}_" in c or c.endswith(f"_{direct_hash_sha256}")]
|
|
83
87
|
matching_collections.update(direct_matches)
|
|
84
88
|
|
|
85
89
|
# Strategy 2: Try normalized version
|
|
86
90
|
normalized = self._normalize_project_name(user_project_name)
|
|
87
91
|
if normalized != user_project_name:
|
|
88
|
-
|
|
92
|
+
norm_hash_md5 = hashlib.md5(normalized.encode()).hexdigest()[:8]
|
|
93
|
+
norm_hash_sha256 = hashlib.sha256(normalized.encode()).hexdigest()[:16]
|
|
94
|
+
|
|
89
95
|
# Match exact hash segment between underscores, not substring
|
|
90
96
|
norm_matches = [c for c in collection_names
|
|
91
|
-
if f"_{
|
|
97
|
+
if f"_{norm_hash_md5}_" in c or c.endswith(f"_{norm_hash_md5}") or
|
|
98
|
+
f"_{norm_hash_sha256}_" in c or c.endswith(f"_{norm_hash_sha256}")]
|
|
92
99
|
matching_collections.update(norm_matches)
|
|
93
100
|
|
|
94
101
|
# Strategy 3: Case-insensitive normalized version
|
|
95
102
|
lower_normalized = normalized.lower()
|
|
96
103
|
if lower_normalized != normalized:
|
|
97
|
-
|
|
104
|
+
lower_hash_md5 = hashlib.md5(lower_normalized.encode()).hexdigest()[:8]
|
|
105
|
+
lower_hash_sha256 = hashlib.sha256(lower_normalized.encode()).hexdigest()[:16]
|
|
106
|
+
|
|
98
107
|
# Match exact hash segment between underscores, not substring
|
|
99
108
|
lower_matches = [c for c in collection_names
|
|
100
|
-
if f"_{
|
|
109
|
+
if f"_{lower_hash_md5}_" in c or c.endswith(f"_{lower_hash_md5}") or
|
|
110
|
+
f"_{lower_hash_sha256}_" in c or c.endswith(f"_{lower_hash_sha256}")]
|
|
101
111
|
matching_collections.update(lower_matches)
|
|
102
112
|
|
|
103
|
-
# Strategy 4:
|
|
113
|
+
# Strategy 4: ALWAYS try mapping project name to full directory path in .claude/projects/
|
|
114
|
+
# This ensures we find all related collections, not just the first match
|
|
115
|
+
# This handles the case where streaming-watcher uses full path but MCP uses short name
|
|
116
|
+
if not user_project_name.startswith('-'):
|
|
117
|
+
# Check if there's a matching directory in .claude/projects/
|
|
118
|
+
projects_dir = Path.home() / ".claude" / "projects"
|
|
119
|
+
if projects_dir.exists():
|
|
120
|
+
for proj_dir in projects_dir.iterdir():
|
|
121
|
+
if proj_dir.is_dir():
|
|
122
|
+
# Check if the directory name contains the project name
|
|
123
|
+
# This handles both "claude-self-reflect" and "-Users-...-projects-claude-self-reflect"
|
|
124
|
+
if (proj_dir.name.endswith(f"-{user_project_name}") or
|
|
125
|
+
f"-{user_project_name}" in proj_dir.name or
|
|
126
|
+
proj_dir.name == user_project_name):
|
|
127
|
+
# Found a matching directory - hash its name
|
|
128
|
+
dir_name = proj_dir.name
|
|
129
|
+
dir_hash_md5 = hashlib.md5(dir_name.encode()).hexdigest()[:8]
|
|
130
|
+
|
|
131
|
+
# Find collections with this hash
|
|
132
|
+
dir_matches = [c for c in collection_names
|
|
133
|
+
if f"_{dir_hash_md5}_" in c or c.endswith(f"_{dir_hash_md5}")]
|
|
134
|
+
matching_collections.update(dir_matches)
|
|
135
|
+
|
|
136
|
+
# Strategy 5: Use segment-based discovery for complex paths
|
|
104
137
|
if not matching_collections:
|
|
105
138
|
# Extract segments from the input
|
|
106
139
|
segments = self._extract_project_segments(user_project_name)
|
|
@@ -111,10 +144,13 @@ class ProjectResolver:
|
|
|
111
144
|
|
|
112
145
|
# Try each candidate
|
|
113
146
|
for candidate in candidates:
|
|
114
|
-
|
|
147
|
+
candidate_hash_md5 = hashlib.md5(candidate.encode()).hexdigest()[:8]
|
|
148
|
+
candidate_hash_sha256 = hashlib.sha256(candidate.encode()).hexdigest()[:16]
|
|
149
|
+
|
|
115
150
|
# Match exact hash segment between underscores, not substring
|
|
116
151
|
candidate_matches = [c for c in collection_names
|
|
117
|
-
if f"_{
|
|
152
|
+
if f"_{candidate_hash_md5}_" in c or c.endswith(f"_{candidate_hash_md5}") or
|
|
153
|
+
f"_{candidate_hash_sha256}_" in c or c.endswith(f"_{candidate_hash_sha256}")]
|
|
118
154
|
matching_collections.update(candidate_matches)
|
|
119
155
|
|
|
120
156
|
# Stop if we found matches
|
package/package.json
CHANGED
|
@@ -30,13 +30,16 @@ logger = logging.getLogger(__name__)
|
|
|
30
30
|
|
|
31
31
|
# Environment variables
|
|
32
32
|
QDRANT_URL = os.getenv("QDRANT_URL", "http://localhost:6333")
|
|
33
|
-
STATE_FILE = os.getenv("STATE_FILE", "/config/imported-files.json")
|
|
33
|
+
STATE_FILE = os.getenv("STATE_FILE", os.path.expanduser("~/.claude-self-reflect/config/imported-files.json"))
|
|
34
34
|
PREFER_LOCAL_EMBEDDINGS = os.getenv("PREFER_LOCAL_EMBEDDINGS", "true").lower() == "true"
|
|
35
35
|
VOYAGE_API_KEY = os.getenv("VOYAGE_KEY")
|
|
36
36
|
MAX_CHUNK_SIZE = int(os.getenv("MAX_CHUNK_SIZE", "50")) # Messages per chunk
|
|
37
37
|
|
|
38
|
-
# Initialize Qdrant client
|
|
39
|
-
client = QdrantClient(
|
|
38
|
+
# Initialize Qdrant client with timeout
|
|
39
|
+
client = QdrantClient(
|
|
40
|
+
url=QDRANT_URL,
|
|
41
|
+
timeout=30 # 30 second timeout for network operations
|
|
42
|
+
)
|
|
40
43
|
|
|
41
44
|
# Initialize embedding provider
|
|
42
45
|
embedding_provider = None
|
|
@@ -138,11 +141,11 @@ def process_and_upload_chunk(messages: List[Dict[str, Any]], chunk_index: int,
|
|
|
138
141
|
payload=payload
|
|
139
142
|
)
|
|
140
143
|
|
|
141
|
-
# Upload immediately
|
|
144
|
+
# Upload immediately (no wait for better throughput)
|
|
142
145
|
client.upsert(
|
|
143
146
|
collection_name=collection_name,
|
|
144
147
|
points=[point],
|
|
145
|
-
wait=
|
|
148
|
+
wait=False # Don't wait for indexing to complete
|
|
146
149
|
)
|
|
147
150
|
|
|
148
151
|
return 1
|