claude-self-reflect 2.2.1 → 2.3.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/README.md +55 -3
- package/installer/cli.js +24 -3
- package/installer/setup-wizard.js +1102 -41
- package/mcp-server/src/server_v2.py +16 -6
- package/package.json +1 -1
- package/scripts/import-conversations-voyage-streaming.py +18 -27
- package/scripts/import-conversations-voyage.py +4 -2
- package/scripts/import-current-conversation.py +3 -2
- package/scripts/import-live-conversation.py +5 -3
- package/scripts/import-watcher.py +2 -1
|
@@ -10,11 +10,21 @@ from fastmcp import FastMCP, Context
|
|
|
10
10
|
from pydantic import BaseModel, Field
|
|
11
11
|
from qdrant_client import AsyncQdrantClient, models
|
|
12
12
|
from qdrant_client.models import (
|
|
13
|
-
PointStruct, VectorParams, Distance
|
|
14
|
-
Query, Formula, Expression, MultExpression,
|
|
15
|
-
ExpDecayExpression, DecayParamsExpression,
|
|
16
|
-
SearchRequest, NamedQuery
|
|
13
|
+
PointStruct, VectorParams, Distance
|
|
17
14
|
)
|
|
15
|
+
try:
|
|
16
|
+
from qdrant_client.models import (
|
|
17
|
+
Query, Formula, Expression, MultExpression,
|
|
18
|
+
ExpDecayExpression, DecayParamsExpression,
|
|
19
|
+
SearchRequest, NamedQuery
|
|
20
|
+
)
|
|
21
|
+
NATIVE_DECAY_AVAILABLE = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
# Fallback for older qdrant-client versions
|
|
24
|
+
NATIVE_DECAY_AVAILABLE = False
|
|
25
|
+
Query = Formula = Expression = MultExpression = None
|
|
26
|
+
ExpDecayExpression = DecayParamsExpression = None
|
|
27
|
+
SearchRequest = NamedQuery = None
|
|
18
28
|
import voyageai
|
|
19
29
|
from dotenv import load_dotenv
|
|
20
30
|
|
|
@@ -83,7 +93,7 @@ async def reflect_on_past(
|
|
|
83
93
|
ctx: Context,
|
|
84
94
|
query: str = Field(description="The search query to find semantically similar conversations"),
|
|
85
95
|
limit: int = Field(default=5, description="Maximum number of results to return"),
|
|
86
|
-
min_score: float = Field(default=0.
|
|
96
|
+
min_score: float = Field(default=0.3, description="Minimum similarity score (0-1)"),
|
|
87
97
|
use_decay: Union[int, str] = Field(default=-1, description="Apply time-based decay: 1=enable, 0=disable, -1=use environment default (accepts int or str)")
|
|
88
98
|
) -> str:
|
|
89
99
|
"""Search for relevant past conversations using semantic search with optional time decay."""
|
|
@@ -122,7 +132,7 @@ async def reflect_on_past(
|
|
|
122
132
|
# Search each collection with native Qdrant decay
|
|
123
133
|
for collection_name in voyage_collections:
|
|
124
134
|
try:
|
|
125
|
-
if should_use_decay:
|
|
135
|
+
if should_use_decay and NATIVE_DECAY_AVAILABLE:
|
|
126
136
|
# Build the query with native Qdrant decay formula
|
|
127
137
|
query_obj = Query(
|
|
128
138
|
nearest=query_embedding,
|
package/package.json
CHANGED
|
@@ -25,13 +25,13 @@ logger = logging.getLogger(__name__)
|
|
|
25
25
|
# Configuration
|
|
26
26
|
QDRANT_URL = os.getenv("QDRANT_URL", "http://localhost:6333")
|
|
27
27
|
STATE_FILE = os.getenv("STATE_FILE", "./config-isolated/imported-files.json")
|
|
28
|
-
LOGS_DIR = os.getenv("LOGS_DIR", "
|
|
28
|
+
LOGS_DIR = os.getenv("LOGS_DIR", os.path.expanduser("~/.claude/projects"))
|
|
29
29
|
VOYAGE_API_KEY = os.getenv("VOYAGE_KEY_2") or os.getenv("VOYAGE_KEY")
|
|
30
30
|
BATCH_SIZE = int(os.getenv("BATCH_SIZE", "50"))
|
|
31
31
|
CHUNK_SIZE = int(os.getenv("CHUNK_SIZE", "10"))
|
|
32
32
|
STREAMING_BUFFER_SIZE = 100 # Process every 100 messages
|
|
33
33
|
RATE_LIMIT_DELAY = 0.1
|
|
34
|
-
EMBEDDING_MODEL = "voyage-3
|
|
34
|
+
EMBEDDING_MODEL = "voyage-3-large"
|
|
35
35
|
EMBEDDING_DIMENSIONS = 1024
|
|
36
36
|
VOYAGE_API_URL = "https://api.voyageai.com/v1/embeddings"
|
|
37
37
|
|
|
@@ -244,10 +244,14 @@ class StreamingVoyageImporter:
|
|
|
244
244
|
# Create points
|
|
245
245
|
points = []
|
|
246
246
|
for chunk, embedding in zip(batch, embeddings):
|
|
247
|
+
# Include both text and metadata in payload
|
|
248
|
+
payload = chunk['metadata'].copy()
|
|
249
|
+
payload['text'] = chunk['text']
|
|
250
|
+
|
|
247
251
|
points.append(PointStruct(
|
|
248
252
|
id=chunk['id'],
|
|
249
253
|
vector=embedding,
|
|
250
|
-
payload=
|
|
254
|
+
payload=payload
|
|
251
255
|
))
|
|
252
256
|
|
|
253
257
|
# Upsert to Qdrant
|
|
@@ -344,32 +348,19 @@ def main():
|
|
|
344
348
|
importer.import_large_file(str(file_path), project_name)
|
|
345
349
|
files_processed += 1
|
|
346
350
|
else:
|
|
347
|
-
#
|
|
351
|
+
# No specific project specified - scan for all projects
|
|
348
352
|
base_path = os.getenv("LOGS_PATH", "/logs")
|
|
349
353
|
if os.path.exists(base_path):
|
|
350
|
-
#
|
|
351
|
-
|
|
352
|
-
(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
("/Users/ramakrishnanannaswamy/.claude/projects/-Users-ramakrishnanannaswamy-memento-stack/2144c5bb-eab8-416b-89b9-5fffb2e1a543.jsonl",
|
|
361
|
-
"-Users-ramakrishnanannaswamy-memento-stack"),
|
|
362
|
-
("/Users/ramakrishnanannaswamy/.claude/projects/-Users-ramakrishnanannaswamy-memento-stack/40c86645-1bbf-446b-9b5b-ad31989f3655.jsonl",
|
|
363
|
-
"-Users-ramakrishnanannaswamy-memento-stack")
|
|
364
|
-
]
|
|
365
|
-
|
|
366
|
-
for file_path, project_name in large_files:
|
|
367
|
-
if os.path.exists(file_path):
|
|
368
|
-
file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
|
|
369
|
-
logger.info(f"Processing {os.path.basename(file_path)} ({file_size_mb:.1f} MB)")
|
|
370
|
-
importer.import_large_file(file_path, project_name)
|
|
371
|
-
else:
|
|
372
|
-
logger.warning(f"File not found: {file_path}")
|
|
354
|
+
# Scan for all project directories
|
|
355
|
+
for project_dir in Path(base_path).iterdir():
|
|
356
|
+
if project_dir.is_dir() and not project_dir.name.startswith('.'):
|
|
357
|
+
# Look for JSONL files in this project
|
|
358
|
+
jsonl_files = list(project_dir.glob("*.jsonl"))
|
|
359
|
+
if jsonl_files:
|
|
360
|
+
for jsonl_file in jsonl_files:
|
|
361
|
+
file_size_mb = jsonl_file.stat().st_size / (1024 * 1024)
|
|
362
|
+
logger.info(f"Processing {jsonl_file.name} ({file_size_mb:.1f} MB) from project {project_dir.name}")
|
|
363
|
+
importer.import_large_file(str(jsonl_file), project_dir.name)
|
|
373
364
|
|
|
374
365
|
logger.info(f"Streaming import complete! Total chunks: {importer.total_imported}")
|
|
375
366
|
|
|
@@ -19,8 +19,8 @@ import backoff
|
|
|
19
19
|
|
|
20
20
|
# Configuration
|
|
21
21
|
QDRANT_URL = os.getenv("QDRANT_URL", "http://localhost:6333")
|
|
22
|
-
LOGS_DIR = os.getenv("LOGS_DIR", "/
|
|
23
|
-
STATE_FILE = os.getenv("STATE_FILE", "/
|
|
22
|
+
LOGS_DIR = os.getenv("LOGS_DIR", os.path.expanduser("~/.claude/projects"))
|
|
23
|
+
STATE_FILE = os.getenv("STATE_FILE", os.path.expanduser("~/.claude-self-reflect/imported-files.json"))
|
|
24
24
|
VOYAGE_API_KEY = os.getenv("VOYAGE_KEY-2") or os.getenv("VOYAGE_KEY")
|
|
25
25
|
BATCH_SIZE = int(os.getenv("BATCH_SIZE", "50")) # Voyage supports batch embedding
|
|
26
26
|
CHUNK_SIZE = int(os.getenv("CHUNK_SIZE", "10")) # Can use larger chunks with 32k token limit
|
|
@@ -362,6 +362,8 @@ class VoyageConversationImporter:
|
|
|
362
362
|
|
|
363
363
|
if not os.path.exists(projects_dir):
|
|
364
364
|
logger.error(f"Claude projects directory not found: {projects_dir}")
|
|
365
|
+
logger.error("This usually means Claude Code hasn't created any projects yet.")
|
|
366
|
+
logger.error("Please open Claude Code and create a conversation first.")
|
|
365
367
|
return
|
|
366
368
|
|
|
367
369
|
# Get list of projects
|
|
@@ -16,8 +16,9 @@ def main():
|
|
|
16
16
|
# Import just the current conversation
|
|
17
17
|
importer = VoyageConversationImporter()
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
# Example: Update these to your project path and file
|
|
20
|
+
project_path = os.path.expanduser("~/.claude/projects/your-project-name")
|
|
21
|
+
target_file = "your-conversation-id.jsonl"
|
|
21
22
|
|
|
22
23
|
print(f"Importing current conversation: {target_file}")
|
|
23
24
|
|
|
@@ -13,7 +13,7 @@ import requests
|
|
|
13
13
|
|
|
14
14
|
# Configuration
|
|
15
15
|
QDRANT_URL = "http://localhost:6333"
|
|
16
|
-
VOYAGE_API_KEY = os.getenv("VOYAGE_KEY"
|
|
16
|
+
VOYAGE_API_KEY = os.getenv("VOYAGE_KEY")
|
|
17
17
|
VOYAGE_API_URL = "https://api.voyageai.com/v1/embeddings"
|
|
18
18
|
|
|
19
19
|
@backoff.on_exception(backoff.expo, Exception, max_tries=3)
|
|
@@ -43,7 +43,8 @@ def import_conversation(file_path):
|
|
|
43
43
|
client = QdrantClient(url=QDRANT_URL)
|
|
44
44
|
|
|
45
45
|
# Determine collection name
|
|
46
|
-
|
|
46
|
+
# Example: Update to your project path
|
|
47
|
+
project_path = os.path.expanduser("~/your-project-path")
|
|
47
48
|
project_hash = hashlib.md5(project_path.encode()).hexdigest()[:8]
|
|
48
49
|
collection_name = f"conv_{project_hash}_voyage"
|
|
49
50
|
|
|
@@ -138,7 +139,8 @@ def import_conversation(file_path):
|
|
|
138
139
|
print(f"\nCollection {collection_name} now has {info.points_count} points")
|
|
139
140
|
|
|
140
141
|
def main():
|
|
141
|
-
|
|
142
|
+
# Example: Update to your conversation file path
|
|
143
|
+
file_path = os.path.expanduser("~/.claude/projects/your-project/your-conversation-id.jsonl")
|
|
142
144
|
|
|
143
145
|
if not os.path.exists(file_path):
|
|
144
146
|
print(f"File not found: {file_path}")
|
|
@@ -80,8 +80,9 @@ class ImportWatcher:
|
|
|
80
80
|
logger.info(f"Starting import for project: {project_path.name}")
|
|
81
81
|
|
|
82
82
|
# Run the streaming importer
|
|
83
|
+
# Pass only the project directory name, not the full path
|
|
83
84
|
result = subprocess.run(
|
|
84
|
-
["python", IMPORTER_SCRIPT,
|
|
85
|
+
["python", IMPORTER_SCRIPT, "--project", project_path.name],
|
|
85
86
|
capture_output=True,
|
|
86
87
|
text=True,
|
|
87
88
|
timeout=300 # 5 minute timeout
|