claude-self-reflect 2.5.19 → 2.7.1
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/.env.example +19 -18
- package/Dockerfile.importer +6 -2
- package/Dockerfile.safe-watcher +44 -0
- package/README.md +31 -1
- package/docker-compose.yaml +43 -11
- package/mcp-server/pyproject.toml +1 -1
- package/mcp-server/src/project_resolver.py +527 -0
- package/mcp-server/src/server.py +14 -10
- package/mcp-server/src/utils.py +20 -3
- package/package.json +7 -1
- package/scripts/import-conversations-unified.backup.py +374 -0
- package/scripts/import-conversations-unified.py +305 -560
- package/scripts/import-latest.py +124 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Quick import script for current project's latest conversations.
|
|
4
|
+
Designed for PreCompact hook integration - targets <10 second imports.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
import subprocess
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
# Configuration
|
|
16
|
+
LOGS_DIR = os.getenv("LOGS_DIR", os.path.expanduser("~/.claude/projects"))
|
|
17
|
+
STATE_FILE = os.getenv("STATE_FILE", os.path.expanduser("~/.claude-self-reflect-state.json"))
|
|
18
|
+
HOURS_BACK = int(os.getenv("IMPORT_HOURS_BACK", "2")) # Only import last 2 hours by default
|
|
19
|
+
|
|
20
|
+
# Set up logging
|
|
21
|
+
logging.basicConfig(
|
|
22
|
+
level=logging.INFO,
|
|
23
|
+
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
24
|
+
)
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
def load_state():
|
|
28
|
+
"""Load import state from file."""
|
|
29
|
+
if os.path.exists(STATE_FILE):
|
|
30
|
+
try:
|
|
31
|
+
with open(STATE_FILE, 'r') as f:
|
|
32
|
+
return json.load(f)
|
|
33
|
+
except:
|
|
34
|
+
return {}
|
|
35
|
+
return {}
|
|
36
|
+
|
|
37
|
+
def save_state(state):
|
|
38
|
+
"""Save import state to file."""
|
|
39
|
+
os.makedirs(os.path.dirname(STATE_FILE), exist_ok=True)
|
|
40
|
+
with open(STATE_FILE, 'w') as f:
|
|
41
|
+
json.dump(state, f, indent=2)
|
|
42
|
+
|
|
43
|
+
def get_project_from_cwd():
|
|
44
|
+
"""Detect project from current working directory."""
|
|
45
|
+
cwd = os.getcwd()
|
|
46
|
+
# Convert path to project name format used in logs
|
|
47
|
+
# Claude logs use format: -Users-username-path-to-project
|
|
48
|
+
project_name = cwd.replace('/', '-')
|
|
49
|
+
# Keep the leading dash as that's how Claude stores it
|
|
50
|
+
if not project_name.startswith('-'):
|
|
51
|
+
project_name = '-' + project_name
|
|
52
|
+
return project_name
|
|
53
|
+
|
|
54
|
+
def get_recent_files(project_path: Path, hours_back: int):
|
|
55
|
+
"""Get JSONL files modified in the last N hours."""
|
|
56
|
+
cutoff_time = datetime.now() - timedelta(hours=hours_back)
|
|
57
|
+
recent_files = []
|
|
58
|
+
|
|
59
|
+
for jsonl_file in project_path.glob("*.jsonl"):
|
|
60
|
+
mtime = datetime.fromtimestamp(jsonl_file.stat().st_mtime)
|
|
61
|
+
if mtime > cutoff_time:
|
|
62
|
+
recent_files.append(jsonl_file)
|
|
63
|
+
|
|
64
|
+
return sorted(recent_files, key=lambda f: f.stat().st_mtime, reverse=True)
|
|
65
|
+
|
|
66
|
+
def main():
|
|
67
|
+
"""Main quick import function."""
|
|
68
|
+
start_time = datetime.now()
|
|
69
|
+
|
|
70
|
+
# Detect current project
|
|
71
|
+
project_name = get_project_from_cwd()
|
|
72
|
+
project_path = Path(LOGS_DIR) / project_name
|
|
73
|
+
|
|
74
|
+
if not project_path.exists():
|
|
75
|
+
logger.warning(f"Project logs not found: {project_path}")
|
|
76
|
+
logger.info("Make sure you're in a project directory with Claude conversations.")
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
logger.info(f"Quick importing latest conversations for: {project_name}")
|
|
80
|
+
|
|
81
|
+
# Get recent files
|
|
82
|
+
recent_files = get_recent_files(project_path, HOURS_BACK)
|
|
83
|
+
logger.info(f"Found {len(recent_files)} files modified in last {HOURS_BACK} hours")
|
|
84
|
+
|
|
85
|
+
if not recent_files:
|
|
86
|
+
logger.info("No recent conversations to import")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
# For now, just call the unified importer with the specific project
|
|
90
|
+
# This is a temporary solution until we implement incremental imports
|
|
91
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
92
|
+
unified_script = os.path.join(script_dir, "import-conversations-unified.py")
|
|
93
|
+
|
|
94
|
+
# Set environment to only process this project
|
|
95
|
+
env = os.environ.copy()
|
|
96
|
+
env['LOGS_DIR'] = str(project_path.parent)
|
|
97
|
+
env['IMPORT_PROJECT'] = project_name
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
# Run the unified importer for just this project
|
|
101
|
+
result = subprocess.run(
|
|
102
|
+
[sys.executable, unified_script],
|
|
103
|
+
env=env,
|
|
104
|
+
capture_output=True,
|
|
105
|
+
text=True,
|
|
106
|
+
timeout=60 # 60 second timeout
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if result.returncode == 0:
|
|
110
|
+
logger.info("Quick import completed successfully")
|
|
111
|
+
else:
|
|
112
|
+
logger.error(f"Import failed: {result.stderr}")
|
|
113
|
+
|
|
114
|
+
except subprocess.TimeoutExpired:
|
|
115
|
+
logger.warning("Import timed out after 60 seconds")
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.error(f"Error during import: {e}")
|
|
118
|
+
|
|
119
|
+
# Report timing
|
|
120
|
+
elapsed = (datetime.now() - start_time).total_seconds()
|
|
121
|
+
logger.info(f"Quick import completed in {elapsed:.1f} seconds")
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
main()
|