claude-self-reflect 3.0.0 → 3.0.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.
Files changed (32) hide show
  1. package/.claude/agents/claude-self-reflect-test.md +110 -66
  2. package/README.md +1 -1
  3. package/installer/setup-wizard.js +4 -2
  4. package/mcp-server/pyproject.toml +1 -0
  5. package/mcp-server/src/server.py +84 -0
  6. package/package.json +2 -1
  7. package/scripts/import-conversations-unified.py +225 -44
  8. package/scripts/importer/__init__.py +25 -0
  9. package/scripts/importer/__main__.py +14 -0
  10. package/scripts/importer/core/__init__.py +25 -0
  11. package/scripts/importer/core/config.py +120 -0
  12. package/scripts/importer/core/exceptions.py +52 -0
  13. package/scripts/importer/core/models.py +184 -0
  14. package/scripts/importer/embeddings/__init__.py +22 -0
  15. package/scripts/importer/embeddings/base.py +141 -0
  16. package/scripts/importer/embeddings/fastembed_provider.py +164 -0
  17. package/scripts/importer/embeddings/validator.py +136 -0
  18. package/scripts/importer/embeddings/voyage_provider.py +251 -0
  19. package/scripts/importer/main.py +393 -0
  20. package/scripts/importer/processors/__init__.py +15 -0
  21. package/scripts/importer/processors/ast_extractor.py +197 -0
  22. package/scripts/importer/processors/chunker.py +157 -0
  23. package/scripts/importer/processors/concept_extractor.py +109 -0
  24. package/scripts/importer/processors/conversation_parser.py +181 -0
  25. package/scripts/importer/processors/tool_extractor.py +165 -0
  26. package/scripts/importer/state/__init__.py +5 -0
  27. package/scripts/importer/state/state_manager.py +190 -0
  28. package/scripts/importer/storage/__init__.py +5 -0
  29. package/scripts/importer/storage/qdrant_storage.py +250 -0
  30. package/scripts/importer/utils/__init__.py +9 -0
  31. package/scripts/importer/utils/logger.py +87 -0
  32. package/scripts/importer/utils/project_normalizer.py +120 -0
@@ -0,0 +1,87 @@
1
+ """Centralized logging configuration."""
2
+
3
+ import logging
4
+ import sys
5
+ from pathlib import Path
6
+ from datetime import datetime
7
+ from typing import Optional
8
+
9
+
10
+ def setup_logging(
11
+ level: str = "INFO",
12
+ log_file: Optional[str] = None,
13
+ format_string: Optional[str] = None
14
+ ) -> logging.Logger:
15
+ """
16
+ Set up logging configuration.
17
+
18
+ Args:
19
+ level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
20
+ log_file: Optional log file path
21
+ format_string: Optional custom format string
22
+
23
+ Returns:
24
+ Configured root logger
25
+ """
26
+ # Default format
27
+ if not format_string:
28
+ format_string = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
29
+
30
+ # Configure root logger
31
+ logging.basicConfig(
32
+ level=getattr(logging, level.upper()),
33
+ format=format_string,
34
+ handlers=[] # Clear default handlers
35
+ )
36
+
37
+ logger = logging.getLogger()
38
+
39
+ # Console handler
40
+ console_handler = logging.StreamHandler(sys.stdout)
41
+ console_handler.setFormatter(logging.Formatter(format_string))
42
+ logger.addHandler(console_handler)
43
+
44
+ # File handler if specified
45
+ if log_file:
46
+ log_path = Path(log_file)
47
+ log_path.parent.mkdir(parents=True, exist_ok=True)
48
+
49
+ file_handler = logging.FileHandler(log_path)
50
+ file_handler.setFormatter(logging.Formatter(format_string))
51
+ logger.addHandler(file_handler)
52
+
53
+ return logger
54
+
55
+
56
+ class ProgressLogger:
57
+ """Logger for tracking import progress."""
58
+
59
+ def __init__(self, total: int, logger: Optional[logging.Logger] = None):
60
+ self.total = total
61
+ self.current = 0
62
+ self.logger = logger or logging.getLogger(__name__)
63
+ self.start_time = datetime.now()
64
+
65
+ def update(self, increment: int = 1, message: Optional[str] = None) -> None:
66
+ """Update progress."""
67
+ self.current += increment
68
+ percentage = (self.current / self.total * 100) if self.total > 0 else 0
69
+
70
+ elapsed = (datetime.now() - self.start_time).total_seconds()
71
+ rate = self.current / elapsed if elapsed > 0 else 0
72
+ eta = (self.total - self.current) / rate if rate > 0 else 0
73
+
74
+ log_message = f"Progress: {self.current}/{self.total} ({percentage:.1f}%)"
75
+ if message:
76
+ log_message += f" - {message}"
77
+ log_message += f" - Rate: {rate:.1f}/s - ETA: {eta:.0f}s"
78
+
79
+ self.logger.info(log_message)
80
+
81
+ def complete(self) -> None:
82
+ """Mark as complete."""
83
+ elapsed = (datetime.now() - self.start_time).total_seconds()
84
+ self.logger.info(
85
+ f"Completed {self.total} items in {elapsed:.1f}s "
86
+ f"({self.total/elapsed:.1f} items/s)"
87
+ )
@@ -0,0 +1,120 @@
1
+ """Project name normalization utilities."""
2
+
3
+ import hashlib
4
+ import logging
5
+ from pathlib import Path
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class ProjectNormalizer:
11
+ """
12
+ Normalize project names and generate collection names.
13
+
14
+ This is the CRITICAL component that was broken before.
15
+ Must correctly extract project names from Claude's dash-separated format.
16
+ """
17
+
18
+ @staticmethod
19
+ def normalize_project_name(project_path: str) -> str:
20
+ """
21
+ Normalize a project path to a consistent project name.
22
+
23
+ CRITICAL: This must match the implementation in utils.py
24
+
25
+ Examples:
26
+ - "-Users-name-projects-claude-self-reflect" -> "claude-self-reflect"
27
+ - "claude-self-reflect" -> "claude-self-reflect"
28
+ - "/path/to/-Users-name-projects-myapp" -> "myapp"
29
+ """
30
+ # Get the final component of the path
31
+ if '/' in project_path:
32
+ final_component = project_path.split('/')[-1]
33
+ else:
34
+ final_component = project_path
35
+
36
+ # Handle Claude's dash-separated format
37
+ if final_component.startswith('-') and 'projects' in final_component:
38
+ # Find the last occurrence of 'projects-'
39
+ idx = final_component.rfind('projects-')
40
+ if idx != -1:
41
+ # Extract everything after 'projects-'
42
+ project_name = final_component[idx + len('projects-'):]
43
+ logger.debug(f"Normalized '{project_path}' to '{project_name}'")
44
+ return project_name
45
+
46
+ # Already normalized or different format
47
+ logger.debug(f"Project path '{project_path}' already normalized")
48
+ return final_component
49
+
50
+ def get_project_name(self, file_path: Path) -> str:
51
+ """
52
+ Extract project name from a file path.
53
+
54
+ Args:
55
+ file_path: Path to a conversation file
56
+
57
+ Returns:
58
+ Normalized project name
59
+ """
60
+ # Get the parent directory name
61
+ parent_name = file_path.parent.name
62
+
63
+ # Normalize it
64
+ return self.normalize_project_name(parent_name)
65
+
66
+ def get_collection_name(self, file_path: Path) -> str:
67
+ """
68
+ Generate collection name for a file.
69
+
70
+ Format: conv_HASH_local
71
+ Where HASH is first 8 chars of MD5 hash of normalized project name.
72
+ """
73
+ project_name = self.get_project_name(file_path)
74
+
75
+ # Generate hash
76
+ project_hash = hashlib.md5(project_name.encode()).hexdigest()[:8]
77
+
78
+ # Generate collection name
79
+ collection_name = f"conv_{project_hash}_local"
80
+
81
+ logger.debug(f"Collection for project '{project_name}': {collection_name}")
82
+ return collection_name
83
+
84
+ @staticmethod
85
+ def validate_normalization() -> bool:
86
+ """
87
+ Self-test to ensure normalization is working correctly.
88
+
89
+ Returns:
90
+ True if all tests pass
91
+ """
92
+ test_cases = [
93
+ ("-Users-name-projects-claude-self-reflect", "claude-self-reflect", "7f6df0fc"),
94
+ ("claude-self-reflect", "claude-self-reflect", "7f6df0fc"),
95
+ ("/Users/name/.claude/projects/-Users-name-projects-myapp", "myapp", None),
96
+ ("-Users-name-projects-procsolve-website", "procsolve-website", "9f2f312b")
97
+ ]
98
+
99
+ normalizer = ProjectNormalizer()
100
+ all_passed = True
101
+
102
+ for input_path, expected_name, expected_hash in test_cases:
103
+ normalized = normalizer.normalize_project_name(input_path)
104
+ if normalized != expected_name:
105
+ logger.error(
106
+ f"Normalization failed: '{input_path}' -> '{normalized}' "
107
+ f"(expected '{expected_name}')"
108
+ )
109
+ all_passed = False
110
+
111
+ if expected_hash:
112
+ actual_hash = hashlib.md5(normalized.encode()).hexdigest()[:8]
113
+ if actual_hash != expected_hash:
114
+ logger.error(
115
+ f"Hash mismatch for '{normalized}': "
116
+ f"{actual_hash} != {expected_hash}"
117
+ )
118
+ all_passed = False
119
+
120
+ return all_passed