claude-self-reflect 2.2.1 → 2.3.0

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.
@@ -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.7, description="Minimum similarity score (0-1)"),
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-self-reflect",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "Give Claude perfect memory of all your conversations - Installation wizard for Python MCP server",
5
5
  "keywords": [
6
6
  "claude",
@@ -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", "/Users/ramakrishnanannaswamy/.claude/projects")
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.5-lite"
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=chunk['metadata']
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
- # Fallback to default behavior with dynamic path detection
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
- # Use Docker paths
351
- large_files = [
352
- (f"{base_path}/-Users-ramakrishnanannaswamy-memento-stack/2144c5bb-eab8-416b-89b9-5fffb2e1a543.jsonl",
353
- "-Users-ramakrishnanannaswamy-memento-stack"),
354
- (f"{base_path}/-Users-ramakrishnanannaswamy-memento-stack/40c86645-1bbf-446b-9b5b-ad31989f3655.jsonl",
355
- "-Users-ramakrishnanannaswamy-memento-stack")
356
- ]
357
- else:
358
- # Use local paths
359
- large_files = [
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", "/logs")
23
- STATE_FILE = os.getenv("STATE_FILE", "/config/imported-files.json")
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
@@ -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, str(project_path)],
85
+ ["python", IMPORTER_SCRIPT, "--project", project_path.name],
85
86
  capture_output=True,
86
87
  text=True,
87
88
  timeout=300 # 5 minute timeout