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.
- package/.claude/agents/claude-self-reflect-test.md +110 -66
- package/README.md +1 -1
- package/installer/setup-wizard.js +4 -2
- package/mcp-server/pyproject.toml +1 -0
- package/mcp-server/src/server.py +84 -0
- package/package.json +2 -1
- package/scripts/import-conversations-unified.py +225 -44
- package/scripts/importer/__init__.py +25 -0
- package/scripts/importer/__main__.py +14 -0
- package/scripts/importer/core/__init__.py +25 -0
- package/scripts/importer/core/config.py +120 -0
- package/scripts/importer/core/exceptions.py +52 -0
- package/scripts/importer/core/models.py +184 -0
- package/scripts/importer/embeddings/__init__.py +22 -0
- package/scripts/importer/embeddings/base.py +141 -0
- package/scripts/importer/embeddings/fastembed_provider.py +164 -0
- package/scripts/importer/embeddings/validator.py +136 -0
- package/scripts/importer/embeddings/voyage_provider.py +251 -0
- package/scripts/importer/main.py +393 -0
- package/scripts/importer/processors/__init__.py +15 -0
- package/scripts/importer/processors/ast_extractor.py +197 -0
- package/scripts/importer/processors/chunker.py +157 -0
- package/scripts/importer/processors/concept_extractor.py +109 -0
- package/scripts/importer/processors/conversation_parser.py +181 -0
- package/scripts/importer/processors/tool_extractor.py +165 -0
- package/scripts/importer/state/__init__.py +5 -0
- package/scripts/importer/state/state_manager.py +190 -0
- package/scripts/importer/storage/__init__.py +5 -0
- package/scripts/importer/storage/qdrant_storage.py +250 -0
- package/scripts/importer/utils/__init__.py +9 -0
- package/scripts/importer/utils/logger.py +87 -0
- 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
|