claude-self-reflect 3.2.4 → 3.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.
- package/.claude/agents/claude-self-reflect-test.md +595 -528
- package/.claude/agents/reflection-specialist.md +59 -3
- package/README.md +14 -5
- package/mcp-server/run-mcp.sh +49 -5
- package/mcp-server/src/app_context.py +64 -0
- package/mcp-server/src/config.py +57 -0
- package/mcp-server/src/connection_pool.py +286 -0
- package/mcp-server/src/decay_manager.py +106 -0
- package/mcp-server/src/embedding_manager.py +64 -40
- package/mcp-server/src/embeddings_old.py +141 -0
- package/mcp-server/src/models.py +64 -0
- package/mcp-server/src/parallel_search.py +371 -0
- package/mcp-server/src/project_resolver.py +5 -0
- package/mcp-server/src/reflection_tools.py +206 -0
- package/mcp-server/src/rich_formatting.py +196 -0
- package/mcp-server/src/search_tools.py +826 -0
- package/mcp-server/src/server.py +127 -1720
- package/mcp-server/src/temporal_design.py +132 -0
- package/mcp-server/src/temporal_tools.py +597 -0
- package/mcp-server/src/temporal_utils.py +384 -0
- package/mcp-server/src/utils.py +150 -67
- package/package.json +10 -1
- package/scripts/add-timestamp-indexes.py +134 -0
- package/scripts/check-collections.py +29 -0
- package/scripts/debug-august-parsing.py +76 -0
- package/scripts/debug-import-single.py +91 -0
- package/scripts/debug-project-resolver.py +82 -0
- package/scripts/debug-temporal-tools.py +135 -0
- package/scripts/delta-metadata-update.py +547 -0
- package/scripts/import-conversations-unified.py +53 -2
- package/scripts/precompact-hook.sh +33 -0
- package/scripts/streaming-watcher.py +1443 -0
- package/scripts/utils.py +39 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Rich formatting for search results with emojis and enhanced display."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from typing import List, Dict, Any, Optional
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def format_search_results_rich(
|
|
13
|
+
results: List[Dict],
|
|
14
|
+
query: str,
|
|
15
|
+
target_project: str,
|
|
16
|
+
collections_searched: int,
|
|
17
|
+
timing_info: Dict[str, float],
|
|
18
|
+
start_time: float,
|
|
19
|
+
brief: bool = False,
|
|
20
|
+
include_raw: bool = False,
|
|
21
|
+
indexing_status: Optional[Dict] = None
|
|
22
|
+
) -> str:
|
|
23
|
+
"""Format search results with rich formatting including emojis and performance metrics."""
|
|
24
|
+
|
|
25
|
+
# Initialize upfront summary
|
|
26
|
+
upfront_summary = ""
|
|
27
|
+
|
|
28
|
+
# Show result summary with emojis
|
|
29
|
+
if results:
|
|
30
|
+
score_info = "high" if results[0]['score'] >= 0.85 else "good" if results[0]['score'] >= 0.75 else "partial"
|
|
31
|
+
upfront_summary += f"🎯 RESULTS: {len(results)} matches ({score_info} relevance, top score: {results[0]['score']:.3f})\n"
|
|
32
|
+
|
|
33
|
+
# Show performance metrics
|
|
34
|
+
total_time = time.time() - start_time
|
|
35
|
+
indexing_info = ""
|
|
36
|
+
if indexing_status and indexing_status.get("percentage", 100) < 100.0:
|
|
37
|
+
indexing_info = f" | 📊 {indexing_status['indexed_conversations']}/{indexing_status['total_conversations']} indexed"
|
|
38
|
+
upfront_summary += f"⚡ PERFORMANCE: {int(total_time * 1000)}ms ({collections_searched} collections searched{indexing_info})\n"
|
|
39
|
+
else:
|
|
40
|
+
upfront_summary += f"❌ NO RESULTS: No conversations found matching '{query}'\n"
|
|
41
|
+
|
|
42
|
+
# Start XML format with upfront summary
|
|
43
|
+
result_text = upfront_summary + "\n<search>\n"
|
|
44
|
+
|
|
45
|
+
# Add indexing status if not fully baselined
|
|
46
|
+
if indexing_status and indexing_status.get("percentage", 100) < 95.0:
|
|
47
|
+
result_text += f' <info status="indexing" progress="{indexing_status["percentage"]:.1f}%" backlog="{indexing_status.get("backlog_count", 0)}">\n'
|
|
48
|
+
result_text += f' <message>📊 Indexing: {indexing_status["indexed_conversations"]}/{indexing_status["total_conversations"]} conversations ({indexing_status["percentage"]:.1f}% complete)</message>\n'
|
|
49
|
+
result_text += f" </info>\n"
|
|
50
|
+
|
|
51
|
+
# Add high-level result summary
|
|
52
|
+
if results:
|
|
53
|
+
# Count time-based results
|
|
54
|
+
now = datetime.now(timezone.utc)
|
|
55
|
+
today_count = 0
|
|
56
|
+
yesterday_count = 0
|
|
57
|
+
week_count = 0
|
|
58
|
+
|
|
59
|
+
for result in results:
|
|
60
|
+
timestamp_str = result.get('timestamp', '')
|
|
61
|
+
if timestamp_str:
|
|
62
|
+
try:
|
|
63
|
+
# Clean timestamp
|
|
64
|
+
timestamp_clean = timestamp_str.replace('Z', '+00:00') if timestamp_str.endswith('Z') else timestamp_str
|
|
65
|
+
timestamp_dt = datetime.fromisoformat(timestamp_clean)
|
|
66
|
+
if timestamp_dt.tzinfo is None:
|
|
67
|
+
timestamp_dt = timestamp_dt.replace(tzinfo=timezone.utc)
|
|
68
|
+
|
|
69
|
+
days_ago = (now - timestamp_dt).days
|
|
70
|
+
if days_ago == 0:
|
|
71
|
+
today_count += 1
|
|
72
|
+
elif days_ago == 1:
|
|
73
|
+
yesterday_count += 1
|
|
74
|
+
if days_ago <= 7:
|
|
75
|
+
week_count += 1
|
|
76
|
+
except:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
# Compact summary with key info
|
|
80
|
+
time_info = ""
|
|
81
|
+
if today_count > 0:
|
|
82
|
+
time_info = f"{today_count} today"
|
|
83
|
+
elif yesterday_count > 0:
|
|
84
|
+
time_info = f"{yesterday_count} yesterday"
|
|
85
|
+
elif week_count > 0:
|
|
86
|
+
time_info = f"{week_count} this week"
|
|
87
|
+
else:
|
|
88
|
+
time_info = "older results"
|
|
89
|
+
|
|
90
|
+
score_info = "high" if results[0]['score'] >= 0.85 else "good" if results[0]['score'] >= 0.75 else "partial"
|
|
91
|
+
|
|
92
|
+
result_text += f' <summary count="{len(results)}" relevance="{score_info}" recency="{time_info}" top-score="{results[0]["score"]:.3f}">\n'
|
|
93
|
+
|
|
94
|
+
# Short preview of top result
|
|
95
|
+
top_excerpt = results[0].get('excerpt', results[0].get('content', ''))[:100].strip()
|
|
96
|
+
if '...' not in top_excerpt:
|
|
97
|
+
top_excerpt += "..."
|
|
98
|
+
result_text += f' <preview>{top_excerpt}</preview>\n'
|
|
99
|
+
result_text += f" </summary>\n"
|
|
100
|
+
else:
|
|
101
|
+
result_text += f" <result-summary>\n"
|
|
102
|
+
result_text += f" <headline>No matches found</headline>\n"
|
|
103
|
+
result_text += f" <relevance>No conversations matched your query</relevance>\n"
|
|
104
|
+
result_text += f" </result-summary>\n"
|
|
105
|
+
|
|
106
|
+
# Add metadata
|
|
107
|
+
result_text += f" <meta>\n"
|
|
108
|
+
result_text += f" <q>{query}</q>\n"
|
|
109
|
+
result_text += f" <scope>{target_project if target_project != 'all' else 'all'}</scope>\n"
|
|
110
|
+
result_text += f" <count>{len(results)}</count>\n"
|
|
111
|
+
if results:
|
|
112
|
+
result_text += f" <range>{results[-1]['score']:.3f}-{results[0]['score']:.3f}</range>\n"
|
|
113
|
+
|
|
114
|
+
# Add performance metadata
|
|
115
|
+
total_time = time.time() - start_time
|
|
116
|
+
result_text += f" <perf>\n"
|
|
117
|
+
result_text += f" <ttl>{int(total_time * 1000)}</ttl>\n"
|
|
118
|
+
result_text += f" <emb>{int((timing_info.get('embedding_end', 0) - timing_info.get('embedding_start', 0)) * 1000)}</emb>\n"
|
|
119
|
+
result_text += f" <srch>{int((timing_info.get('search_all_end', 0) - timing_info.get('search_all_start', 0)) * 1000)}</srch>\n"
|
|
120
|
+
result_text += f" <cols>{collections_searched}</cols>\n"
|
|
121
|
+
result_text += f" </perf>\n"
|
|
122
|
+
result_text += f" </meta>\n"
|
|
123
|
+
|
|
124
|
+
# Add individual results
|
|
125
|
+
result_text += " <results>\n"
|
|
126
|
+
for i, result in enumerate(results):
|
|
127
|
+
result_text += f' <r rank="{i+1}">\n'
|
|
128
|
+
result_text += f" <s>{result['score']:.3f}</s>\n"
|
|
129
|
+
result_text += f" <p>{result.get('project_name', 'unknown')}</p>\n"
|
|
130
|
+
|
|
131
|
+
# Calculate relative time
|
|
132
|
+
timestamp_str = result.get('timestamp', '')
|
|
133
|
+
if timestamp_str:
|
|
134
|
+
try:
|
|
135
|
+
timestamp_clean = timestamp_str.replace('Z', '+00:00') if timestamp_str.endswith('Z') else timestamp_str
|
|
136
|
+
timestamp_dt = datetime.fromisoformat(timestamp_clean)
|
|
137
|
+
if timestamp_dt.tzinfo is None:
|
|
138
|
+
timestamp_dt = timestamp_dt.replace(tzinfo=timezone.utc)
|
|
139
|
+
now = datetime.now(timezone.utc)
|
|
140
|
+
days_ago = (now - timestamp_dt).days
|
|
141
|
+
if days_ago == 0:
|
|
142
|
+
time_str = "today"
|
|
143
|
+
elif days_ago == 1:
|
|
144
|
+
time_str = "yesterday"
|
|
145
|
+
else:
|
|
146
|
+
time_str = f"{days_ago}d"
|
|
147
|
+
result_text += f" <t>{time_str}</t>\n"
|
|
148
|
+
except:
|
|
149
|
+
result_text += f" <t>unknown</t>\n"
|
|
150
|
+
|
|
151
|
+
# Get excerpt/content
|
|
152
|
+
excerpt = result.get('excerpt', result.get('content', ''))
|
|
153
|
+
|
|
154
|
+
if not brief and excerpt:
|
|
155
|
+
# Extract title from first line of excerpt
|
|
156
|
+
excerpt_lines = excerpt.split('\n')
|
|
157
|
+
title = excerpt_lines[0][:80] + "..." if len(excerpt_lines[0]) > 80 else excerpt_lines[0]
|
|
158
|
+
result_text += f" <title>{title}</title>\n"
|
|
159
|
+
|
|
160
|
+
# Key finding - summarize the main point
|
|
161
|
+
key_finding = excerpt[:100] + "..." if len(excerpt) > 100 else excerpt
|
|
162
|
+
result_text += f" <key-finding>{key_finding.strip()}</key-finding>\n"
|
|
163
|
+
|
|
164
|
+
# Always include excerpt
|
|
165
|
+
if brief:
|
|
166
|
+
brief_excerpt = excerpt[:100] + "..." if len(excerpt) > 100 else excerpt
|
|
167
|
+
result_text += f" <excerpt>{brief_excerpt.strip()}</excerpt>\n"
|
|
168
|
+
else:
|
|
169
|
+
result_text += f" <excerpt><![CDATA[{excerpt}]]></excerpt>\n"
|
|
170
|
+
|
|
171
|
+
# Add conversation ID if present
|
|
172
|
+
if result.get('conversation_id'):
|
|
173
|
+
result_text += f" <cid>{result['conversation_id']}</cid>\n"
|
|
174
|
+
|
|
175
|
+
# Include raw data if requested
|
|
176
|
+
if include_raw and result.get('raw_payload'):
|
|
177
|
+
result_text += " <raw>\n"
|
|
178
|
+
payload = result['raw_payload']
|
|
179
|
+
result_text += f" <txt><![CDATA[{payload.get('text', '')}]]></txt>\n"
|
|
180
|
+
result_text += f" <id>{result.get('id', '')}</id>\n"
|
|
181
|
+
result_text += " </raw>\n"
|
|
182
|
+
|
|
183
|
+
# Add metadata fields if present
|
|
184
|
+
if result.get('files_analyzed'):
|
|
185
|
+
result_text += f" <files>{', '.join(result['files_analyzed'][:5])}</files>\n"
|
|
186
|
+
if result.get('tools_used'):
|
|
187
|
+
result_text += f" <tools>{', '.join(result['tools_used'][:5])}</tools>\n"
|
|
188
|
+
if result.get('concepts'):
|
|
189
|
+
result_text += f" <concepts>{', '.join(result['concepts'][:5])}</concepts>\n"
|
|
190
|
+
|
|
191
|
+
result_text += " </r>\n"
|
|
192
|
+
|
|
193
|
+
result_text += " </results>\n"
|
|
194
|
+
result_text += "</search>\n"
|
|
195
|
+
|
|
196
|
+
return result_text
|