claude-self-reflect 5.0.4 → 5.0.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.
@@ -93,6 +93,49 @@ find . -type f \( -name "*test_*.py" -o -name "test_*.py" -o -name "*benchmark*.
93
93
  echo "=== Suggest archiving to: tests/throwaway/ ==="
94
94
  ```
95
95
 
96
+ ### 8. NPM Package Validation (Regression Check for #71)
97
+ ```bash
98
+ echo "=== NPM Package Contents Check ==="
99
+
100
+ # Quick check - verify critical refactored modules are packaged
101
+ npm pack --dry-run 2>&1 | tee /tmp/npm-pack-check.txt
102
+
103
+ CRITICAL_MODULES=(
104
+ "metadata_extractor.py"
105
+ "message_processors.py"
106
+ "import_strategies.py"
107
+ "embedding_service.py"
108
+ "doctor.py"
109
+ )
110
+
111
+ echo "Checking for critical modules..."
112
+ MISSING=0
113
+ for module in "${CRITICAL_MODULES[@]}"; do
114
+ if grep -q "$module" /tmp/npm-pack-check.txt; then
115
+ echo "✅ $module"
116
+ else
117
+ echo "❌ MISSING: $module"
118
+ MISSING=$((MISSING + 1))
119
+ fi
120
+ done
121
+
122
+ if [ $MISSING -eq 0 ]; then
123
+ echo "✅ All critical modules packaged"
124
+ else
125
+ echo "❌ $MISSING modules missing - update package.json!"
126
+ fi
127
+
128
+ # Also run the regression test if available
129
+ if [ -f tests/test_npm_package_contents.py ]; then
130
+ echo "Running regression test..."
131
+ python tests/test_npm_package_contents.py && echo "✅ Packaging test passed" || echo "❌ Packaging test failed"
132
+ else
133
+ echo "⚠️ Regression test not found (expected: tests/test_npm_package_contents.py)"
134
+ fi
135
+
136
+ rm -f /tmp/npm-pack-check.txt
137
+ ```
138
+
96
139
  ## Output Format
97
140
 
98
141
  ```
@@ -214,6 +214,83 @@ safety check -r mcp-server/requirements.txt
214
214
  # For Node: npm test
215
215
  ```
216
216
 
217
+ #### 4.1. NPM Packaging Validation (MANDATORY)
218
+
219
+ **CRITICAL**: Always verify npm package contents before release to prevent issues like #71.
220
+
221
+ ```bash
222
+ echo "=== NPM Packaging Validation ==="
223
+
224
+ # 1. Run packaging regression test
225
+ python tests/test_npm_package_contents.py
226
+ if [ $? -ne 0 ]; then
227
+ echo "❌ Packaging test failed! Fix before releasing."
228
+ exit 1
229
+ fi
230
+
231
+ # 2. Review npm pack output
232
+ echo "📦 Reviewing package contents..."
233
+ npm pack --dry-run 2>&1 | tee /tmp/npm-pack-output.txt
234
+
235
+ # 3. Verify critical modules present
236
+ echo "🔍 Checking critical Python modules..."
237
+ CRITICAL_MODULES=(
238
+ "scripts/metadata_extractor.py"
239
+ "scripts/message_processors.py"
240
+ "scripts/import_strategies.py"
241
+ "scripts/embedding_service.py"
242
+ "scripts/doctor.py"
243
+ "scripts/unified_state_manager.py"
244
+ "scripts/import-conversations-unified.py"
245
+ )
246
+
247
+ MISSING=0
248
+ for module in "${CRITICAL_MODULES[@]}"; do
249
+ if ! grep -q "$module" /tmp/npm-pack-output.txt; then
250
+ echo "❌ Missing: $module"
251
+ MISSING=$((MISSING + 1))
252
+ else
253
+ echo "✅ Found: $module"
254
+ fi
255
+ done
256
+
257
+ if [ $MISSING -gt 0 ]; then
258
+ echo "❌ $MISSING critical modules missing from package!"
259
+ echo "Add them to package.json 'files' array"
260
+ exit 1
261
+ fi
262
+
263
+ # 4. Check package size (should be 200-400KB)
264
+ npm pack
265
+ TARBALL=$(ls -1 claude-self-reflect-*.tgz | tail -1)
266
+ SIZE=$(du -h "$TARBALL" | cut -f1)
267
+ echo "📦 Package size: $SIZE"
268
+
269
+ # 5. Test local installation
270
+ echo "🧪 Testing local installation..."
271
+ npm install -g "./$TARBALL"
272
+ claude-self-reflect --version || {
273
+ echo "❌ CLI failed to load!"
274
+ exit 1
275
+ }
276
+
277
+ # 6. Smoke test setup command
278
+ claude-self-reflect setup --help || {
279
+ echo "❌ Setup command failed!"
280
+ exit 1
281
+ }
282
+
283
+ echo "✅ NPM packaging validation passed!"
284
+ echo "Package: $TARBALL ($SIZE)"
285
+
286
+ # Clean up test tarball
287
+ rm -f "$TARBALL"
288
+ ```
289
+
290
+ **Reference**: See `PACKAGING_CHECKLIST.md` for full guidelines.
291
+
292
+ **History**: Added after Issue #71 (v5.0.4 → v5.0.5) where refactored modules were missing from npm package.
293
+
217
294
  #### 4.5. Create Professional Release Notes
218
295
  ```bash
219
296
  # Create release notes file
@@ -28,6 +28,10 @@ RUN pip install --no-cache-dir \
28
28
  # This ensures scripts are available even without volume mounts
29
29
  COPY scripts/ /app/scripts/
30
30
 
31
+ # Create symlink for backwards compatibility with /scripts path
32
+ # This allows both /app/scripts and /scripts to work
33
+ RUN ln -s /app/scripts /scripts
34
+
31
35
  # Note: Volume mounts in docker-compose.yaml will override these files in local development
32
36
  # This provides compatibility for both local development and global npm installs
33
37
 
@@ -43,6 +43,10 @@ WORKDIR /app
43
43
  # Copy application scripts
44
44
  COPY scripts/ /app/scripts/
45
45
 
46
+ # Create symlink for backwards compatibility with /scripts path
47
+ # This allows both /app/scripts and /scripts to work
48
+ RUN ln -s /app/scripts /scripts
49
+
46
50
  # Make watcher-loop.sh executable
47
51
  RUN chmod +x /app/scripts/watcher-loop.sh
48
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-self-reflect",
3
- "version": "5.0.4",
3
+ "version": "5.0.6",
4
4
  "description": "Give Claude perfect memory of all your conversations - Installation wizard for Python MCP server",
5
5
  "keywords": [
6
6
  "claude",
@@ -60,7 +60,13 @@
60
60
  "scripts/delta-metadata-update-safe.py",
61
61
  "scripts/force-metadata-recovery.py",
62
62
  "scripts/precompact-hook.sh",
63
+ "scripts/watcher-loop.sh",
63
64
  "scripts/import-latest.py",
65
+ "scripts/metadata_extractor.py",
66
+ "scripts/message_processors.py",
67
+ "scripts/import_strategies.py",
68
+ "scripts/embedding_service.py",
69
+ "scripts/doctor.py",
64
70
  "shared/**/*.py",
65
71
  ".claude/agents/*.md",
66
72
  "config/qdrant-config.yaml",
@@ -0,0 +1,342 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Diagnostic script to check Claude Self-Reflect installation and identify issues.
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import json
9
+ import subprocess
10
+ from pathlib import Path
11
+ from typing import Dict, List, Tuple
12
+ import urllib.request
13
+ import urllib.error
14
+ from datetime import datetime
15
+
16
+ class Colors:
17
+ """Terminal colors for output"""
18
+ GREEN = '\033[92m'
19
+ YELLOW = '\033[93m'
20
+ RED = '\033[91m'
21
+ BLUE = '\033[94m'
22
+ ENDC = '\033[0m'
23
+ BOLD = '\033[1m'
24
+
25
+ def print_header(text: str):
26
+ """Print a section header"""
27
+ print(f"\n{Colors.BOLD}{Colors.BLUE}{'='*60}{Colors.ENDC}")
28
+ print(f"{Colors.BOLD}{Colors.BLUE}{text}{Colors.ENDC}")
29
+ print(f"{Colors.BOLD}{Colors.BLUE}{'='*60}{Colors.ENDC}")
30
+
31
+ def print_status(name: str, status: bool, message: str = ""):
32
+ """Print a status line with colored indicator"""
33
+ icon = f"{Colors.GREEN}✅{Colors.ENDC}" if status else f"{Colors.RED}❌{Colors.ENDC}"
34
+ status_text = f"{Colors.GREEN}OK{Colors.ENDC}" if status else f"{Colors.RED}FAILED{Colors.ENDC}"
35
+ print(f"{icon} {name}: {status_text}")
36
+ if message:
37
+ print(f" {Colors.YELLOW}{message}{Colors.ENDC}")
38
+
39
+ def check_docker() -> Tuple[bool, str]:
40
+ """Check if Docker is installed and running"""
41
+ try:
42
+ result = subprocess.run(['docker', 'info'], capture_output=True, text=True)
43
+ if result.returncode == 0:
44
+ # Check docker compose v2
45
+ compose_result = subprocess.run(['docker', 'compose', 'version'], capture_output=True, text=True)
46
+ if compose_result.returncode == 0:
47
+ return True, "Docker and Docker Compose v2 are running"
48
+ else:
49
+ return False, "Docker Compose v2 not found. Please update Docker Desktop"
50
+ else:
51
+ return False, "Docker is not running"
52
+ except FileNotFoundError:
53
+ return False, "Docker is not installed"
54
+
55
+ def check_qdrant() -> Tuple[bool, str]:
56
+ """Check if Qdrant is running and accessible"""
57
+ try:
58
+ req = urllib.request.Request('http://localhost:6333')
59
+ with urllib.request.urlopen(req, timeout=5) as response:
60
+ if response.status == 200:
61
+ data = json.loads(response.read().decode())
62
+ version = data.get('version', 'unknown')
63
+ return True, f"Qdrant {version} is running on port 6333"
64
+ except:
65
+ pass
66
+ return False, "Qdrant is not accessible on localhost:6333"
67
+
68
+ def check_collections() -> Tuple[bool, str, List[str]]:
69
+ """Check if Qdrant has any collections"""
70
+ try:
71
+ req = urllib.request.Request('http://localhost:6333/collections')
72
+ with urllib.request.urlopen(req, timeout=5) as response:
73
+ if response.status == 200:
74
+ data = json.loads(response.read().decode())
75
+ collections = data.get('result', {}).get('collections', [])
76
+ if collections:
77
+ collection_names = [c['name'] for c in collections]
78
+ return True, f"Found {len(collections)} collections", collection_names
79
+ else:
80
+ return False, "No collections found - import may not have run", []
81
+ except:
82
+ pass
83
+ return False, "Could not query Qdrant collections", []
84
+
85
+ def check_claude_projects() -> Tuple[bool, str, Dict]:
86
+ """Check Claude projects directory for JSONL files"""
87
+ claude_dir = Path.home() / '.claude' / 'projects'
88
+ stats = {
89
+ 'total_projects': 0,
90
+ 'total_files': 0,
91
+ 'total_size': 0,
92
+ 'sample_projects': []
93
+ }
94
+
95
+ if not claude_dir.exists():
96
+ return False, f"Claude projects directory not found: {claude_dir}", stats
97
+
98
+ try:
99
+ projects = list(claude_dir.iterdir())
100
+ for project in projects:
101
+ if project.is_dir():
102
+ jsonl_files = list(project.glob('*.jsonl'))
103
+ if jsonl_files:
104
+ stats['total_projects'] += 1
105
+ stats['total_files'] += len(jsonl_files)
106
+ for f in jsonl_files:
107
+ stats['total_size'] += f.stat().st_size
108
+ if len(stats['sample_projects']) < 3:
109
+ stats['sample_projects'].append(project.name)
110
+
111
+ if stats['total_files'] == 0:
112
+ return False, "No JSONL files found in Claude projects", stats
113
+
114
+ size_mb = stats['total_size'] / (1024 * 1024)
115
+ return True, f"Found {stats['total_files']} files across {stats['total_projects']} projects ({size_mb:.1f} MB)", stats
116
+ except Exception as e:
117
+ return False, f"Error scanning Claude projects: {e}", stats
118
+
119
+ def check_import_state() -> Tuple[bool, str, Dict]:
120
+ """Check the import state file"""
121
+ config_dir = Path.home() / '.claude-self-reflect' / 'config'
122
+ state_file = config_dir / 'imported-files.json'
123
+
124
+ stats = {
125
+ 'imported_count': 0,
126
+ 'last_import': None,
127
+ 'has_metadata': False
128
+ }
129
+
130
+ if not state_file.exists():
131
+ return False, "No import state file found - imports haven't run yet", stats
132
+
133
+ try:
134
+ with open(state_file) as f:
135
+ state = json.load(f)
136
+
137
+ imported = state.get('imported_files', {})
138
+ stats['imported_count'] = len(imported)
139
+
140
+ # Check for metadata (new format)
141
+ for file_path, data in imported.items():
142
+ if isinstance(data, dict):
143
+ stats['has_metadata'] = True
144
+ if data.get('imported_at'):
145
+ import_time = data['imported_at']
146
+ if not stats['last_import'] or import_time > stats['last_import']:
147
+ stats['last_import'] = import_time
148
+ elif isinstance(data, str):
149
+ # Old format
150
+ if not stats['last_import'] or data > stats['last_import']:
151
+ stats['last_import'] = data
152
+
153
+ if stats['imported_count'] == 0:
154
+ return False, "Import state exists but no files imported", stats
155
+
156
+ msg = f"Imported {stats['imported_count']} files"
157
+ if stats['last_import']:
158
+ msg += f" (last: {stats['last_import'][:19]})"
159
+ if not stats['has_metadata']:
160
+ msg += " - OLD FORMAT (consider re-importing for metadata)"
161
+
162
+ return True, msg, stats
163
+ except Exception as e:
164
+ return False, f"Error reading import state: {e}", stats
165
+
166
+ def check_env_file() -> Tuple[bool, str, Dict]:
167
+ """Check .env file configuration"""
168
+ env_file = Path('.env')
169
+ config = {
170
+ 'has_voyage_key': False,
171
+ 'prefer_local': True,
172
+ 'claude_logs_path': None,
173
+ 'config_path': None
174
+ }
175
+
176
+ if not env_file.exists():
177
+ return False, ".env file not found", config
178
+
179
+ try:
180
+ with open(env_file) as f:
181
+ content = f.read()
182
+
183
+ for line in content.split('\n'):
184
+ if '=' in line and not line.startswith('#'):
185
+ key, value = line.split('=', 1)
186
+ key = key.strip()
187
+ value = value.strip()
188
+
189
+ if key == 'VOYAGE_KEY' and value and not value.startswith('your-'):
190
+ config['has_voyage_key'] = True
191
+ elif key == 'PREFER_LOCAL_EMBEDDINGS':
192
+ config['prefer_local'] = value.lower() == 'true'
193
+ elif key == 'CLAUDE_LOGS_PATH':
194
+ config['claude_logs_path'] = value
195
+ elif key == 'CONFIG_PATH':
196
+ config['config_path'] = value
197
+
198
+ # Check critical paths
199
+ issues = []
200
+ if config['claude_logs_path'] and '~' in config['claude_logs_path']:
201
+ issues.append("CLAUDE_LOGS_PATH contains ~ which Docker won't expand")
202
+ if config['config_path'] and '~' in config['config_path']:
203
+ issues.append("CONFIG_PATH contains ~ which Docker won't expand")
204
+
205
+ if issues:
206
+ return False, "; ".join(issues), config
207
+
208
+ mode = "Local embeddings" if config['prefer_local'] else "Voyage AI embeddings"
209
+ return True, f"Configured for {mode}", config
210
+ except Exception as e:
211
+ return False, f"Error reading .env: {e}", config
212
+
213
+ def check_docker_containers() -> Tuple[bool, str, List[str]]:
214
+ """Check which Docker containers are running"""
215
+ try:
216
+ result = subprocess.run(
217
+ ['docker', 'compose', 'ps', '--format', 'json'],
218
+ capture_output=True, text=True, cwd='.'
219
+ )
220
+
221
+ if result.returncode != 0:
222
+ return False, "Could not query Docker containers", []
223
+
224
+ running = []
225
+ lines = result.stdout.strip().split('\n')
226
+ for line in lines:
227
+ if line:
228
+ try:
229
+ container = json.loads(line)
230
+ if container.get('State') == 'running':
231
+ running.append(container.get('Service', 'unknown'))
232
+ except:
233
+ pass
234
+
235
+ if not running:
236
+ return False, "No containers running", []
237
+
238
+ essential = ['qdrant']
239
+ missing = [s for s in essential if s not in running]
240
+
241
+ if missing:
242
+ return False, f"Essential services not running: {', '.join(missing)}", running
243
+
244
+ return True, f"Running: {', '.join(running)}", running
245
+ except Exception as e:
246
+ return False, f"Error checking containers: {e}", []
247
+
248
+ def main():
249
+ """Run all diagnostic checks"""
250
+ print(f"{Colors.BOLD}{Colors.BLUE}")
251
+ print("╔════════════════════════════════════════════════════════╗")
252
+ print("║ Claude Self-Reflect Diagnostic Tool v1.0 ║")
253
+ print("╚════════════════════════════════════════════════════════╝")
254
+ print(f"{Colors.ENDC}")
255
+
256
+ # Basic checks
257
+ print_header("1. Environment Checks")
258
+
259
+ docker_ok, docker_msg = check_docker()
260
+ print_status("Docker", docker_ok, docker_msg)
261
+
262
+ env_ok, env_msg, env_config = check_env_file()
263
+ print_status("Environment (.env)", env_ok, env_msg)
264
+
265
+ # Service checks
266
+ print_header("2. Service Status")
267
+
268
+ containers_ok, containers_msg, running_containers = check_docker_containers()
269
+ print_status("Docker Containers", containers_ok, containers_msg)
270
+
271
+ qdrant_ok, qdrant_msg = check_qdrant()
272
+ print_status("Qdrant Database", qdrant_ok, qdrant_msg)
273
+
274
+ # Data checks
275
+ print_header("3. Data & Import Status")
276
+
277
+ claude_ok, claude_msg, claude_stats = check_claude_projects()
278
+ print_status("Claude Projects", claude_ok, claude_msg)
279
+ if claude_stats['sample_projects']:
280
+ print(f" Sample projects: {', '.join(claude_stats['sample_projects'][:3])}")
281
+
282
+ import_ok, import_msg, import_stats = check_import_state()
283
+ print_status("Import State", import_ok, import_msg)
284
+
285
+ collections_ok, collections_msg, collection_list = check_collections()
286
+ print_status("Qdrant Collections", collections_ok, collections_msg)
287
+ if collection_list:
288
+ print(f" Collections: {', '.join(collection_list[:5])}")
289
+
290
+ # Summary and recommendations
291
+ print_header("4. Summary & Recommendations")
292
+
293
+ all_ok = all([docker_ok, env_ok, qdrant_ok, claude_ok])
294
+
295
+ if all_ok and collections_ok:
296
+ print(f"{Colors.GREEN}✅ System appears to be working correctly!{Colors.ENDC}")
297
+ else:
298
+ print(f"{Colors.YELLOW}⚠️ Issues detected:{Colors.ENDC}")
299
+
300
+ if not docker_ok:
301
+ print(f"\n{Colors.RED}Critical:{Colors.ENDC} Docker is required")
302
+ print(" → Install Docker Desktop from https://docker.com")
303
+
304
+ if env_ok and '~' in str(env_config.get('claude_logs_path', '')):
305
+ print(f"\n{Colors.RED}Critical:{Colors.ENDC} Path expansion issue in .env")
306
+ print(" → Run: claude-self-reflect setup")
307
+ print(" → Or manually fix paths in .env to use full paths")
308
+
309
+ if not qdrant_ok and docker_ok:
310
+ print(f"\n{Colors.YELLOW}Issue:{Colors.ENDC} Qdrant not running")
311
+ print(" → Run: docker compose --profile mcp up -d")
312
+
313
+ if claude_ok and not collections_ok:
314
+ print(f"\n{Colors.YELLOW}Issue:{Colors.ENDC} No collections found but JSONL files exist")
315
+ print(" → Run: docker compose run --rm importer")
316
+ print(" → This will import your conversation history")
317
+
318
+ if not claude_ok:
319
+ print(f"\n{Colors.YELLOW}Note:{Colors.ENDC} No Claude conversations found")
320
+ print(" → This is normal if you haven't used Claude Desktop yet")
321
+ print(" → The watcher will import new conversations automatically")
322
+
323
+ # Quick commands
324
+ print_header("5. Quick Commands")
325
+ print("• Start services: docker compose --profile mcp --profile watch up -d")
326
+ print("• Import conversations: docker compose run --rm importer")
327
+ print("• View logs: docker compose logs -f")
328
+ print("• Check status: claude-self-reflect status")
329
+ print("• Restart everything: docker compose down && docker compose --profile mcp --profile watch up -d")
330
+
331
+ print(f"\n{Colors.BLUE}Documentation: https://github.com/ramakay/claude-self-reflect{Colors.ENDC}")
332
+
333
+ # Return exit code based on critical issues
334
+ if not docker_ok:
335
+ sys.exit(1)
336
+ if all_ok:
337
+ sys.exit(0)
338
+ else:
339
+ sys.exit(2) # Non-critical issues
340
+
341
+ if __name__ == "__main__":
342
+ main()