claude-self-reflect 7.1.10 → 7.1.11
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 +36 -78
- package/docs/design/GRADER_PROMPT.md +81 -0
- package/docs/design/batch_ground_truth_generator.py +496 -0
- package/docs/design/batch_import_all_projects.py +477 -0
- package/docs/design/batch_import_v3.py +278 -0
- package/docs/design/conversation-analyzer/SKILL.md +133 -0
- package/docs/design/conversation-analyzer/SKILL_V2.md +218 -0
- package/docs/design/conversation-analyzer/extract_structured.py +186 -0
- package/docs/design/extract_events_v3.py +533 -0
- package/docs/design/import_existing_batch.py +188 -0
- package/docs/design/recover_all_batches.py +297 -0
- package/docs/design/recover_batch_results.py +287 -0
- package/package.json +4 -1
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Smart Event Extraction V3 - Opus-Validated Approach
|
|
4
|
+
|
|
5
|
+
Based on Opus 4.1 analysis:
|
|
6
|
+
1. Single-vector approach: 500-token search index + 1000-token context cache
|
|
7
|
+
2. Fixed scoring: Requests=10, Edits=9, Blocking errors=9, Build=7, Test fails=6
|
|
8
|
+
3. Edit patterns instead of raw changes
|
|
9
|
+
4. Conversation signature metadata for filtering
|
|
10
|
+
5. Request-response pairing
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
from typing import Dict, List, Any, Tuple, Optional
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from collections import defaultdict
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_message_data(msg: Dict) -> Dict:
|
|
21
|
+
"""Extract message data handling nested structure."""
|
|
22
|
+
return msg.get("message", msg)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def calculate_importance_score_v3(msg: Dict, index: int, total: int) -> float:
|
|
26
|
+
"""
|
|
27
|
+
Score message importance (OPUS-VALIDATED WEIGHTS).
|
|
28
|
+
|
|
29
|
+
Priority hierarchy:
|
|
30
|
+
1. User requests (10pts) - The "what"
|
|
31
|
+
2. Successful edits (9pts) - The "how"
|
|
32
|
+
3. Blocking errors (9pts) - Critical learning moments
|
|
33
|
+
4. Build success (7pts) - Validation
|
|
34
|
+
5. Test failures (6pts) - Negative signals
|
|
35
|
+
6. Code reads (3pts) - Intermediate steps
|
|
36
|
+
"""
|
|
37
|
+
score = 0.0
|
|
38
|
+
msg_data = get_message_data(msg)
|
|
39
|
+
content = json.dumps(msg_data.get("content", "")).lower()
|
|
40
|
+
|
|
41
|
+
# User requests (the problem) - HIGHEST PRIORITY
|
|
42
|
+
if msg_data.get("role") == "user":
|
|
43
|
+
# Filter out tool_result noise
|
|
44
|
+
if not any(x in content for x in ["tool_result", "tool_use_id", "is_error"]):
|
|
45
|
+
if len(content) > 50:
|
|
46
|
+
score += 10.0
|
|
47
|
+
|
|
48
|
+
# Successful edits (the solution)
|
|
49
|
+
if isinstance(msg_data.get("content"), list):
|
|
50
|
+
for item in msg_data.get("content", []):
|
|
51
|
+
if isinstance(item, dict) and item.get("type") == "tool_use":
|
|
52
|
+
tool_name = str(item.get("name", "")).lower()
|
|
53
|
+
if "edit" in tool_name or "write" in tool_name:
|
|
54
|
+
if "todo" not in tool_name: # Exclude TodoWrite
|
|
55
|
+
score += 9.0
|
|
56
|
+
elif "bash" in tool_name:
|
|
57
|
+
score += 5.0
|
|
58
|
+
elif "read" in tool_name:
|
|
59
|
+
score += 3.0
|
|
60
|
+
|
|
61
|
+
# Blocking errors (critical learning moments)
|
|
62
|
+
error_keywords = ["error", "exception", "traceback", "failed", "failure", "err_"]
|
|
63
|
+
if any(kw in content for kw in error_keywords):
|
|
64
|
+
# Check if this is a blocking error (not resolved quickly)
|
|
65
|
+
score += 9.0
|
|
66
|
+
|
|
67
|
+
# Build/test success
|
|
68
|
+
if "compiled successfully" in content or "build" in content and "success" in content:
|
|
69
|
+
score += 7.0
|
|
70
|
+
|
|
71
|
+
# Test failures
|
|
72
|
+
if "test" in content and ("failed" in content or "error" in content):
|
|
73
|
+
score += 6.0
|
|
74
|
+
|
|
75
|
+
# Solution indicators
|
|
76
|
+
solution_keywords = ["fixed", "solved", "working", "success", "completed"]
|
|
77
|
+
if any(kw in content for kw in solution_keywords):
|
|
78
|
+
score += 8.0
|
|
79
|
+
|
|
80
|
+
# Position bias - beginnings and ends often important
|
|
81
|
+
relative_pos = index / max(total, 1)
|
|
82
|
+
if relative_pos < 0.1 or relative_pos > 0.8:
|
|
83
|
+
score *= 1.1 # Smaller boost (was 1.2)
|
|
84
|
+
|
|
85
|
+
return score
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def extract_edit_pattern(messages: List[Dict], edit_index: int) -> Dict:
|
|
89
|
+
"""
|
|
90
|
+
Extract edit as a reusable pattern, not just raw changes.
|
|
91
|
+
|
|
92
|
+
Opus recommendation: "Describe the reusable pattern, not specific implementation"
|
|
93
|
+
"""
|
|
94
|
+
msg_data = get_message_data(messages[edit_index])
|
|
95
|
+
|
|
96
|
+
pattern = {
|
|
97
|
+
"index": edit_index,
|
|
98
|
+
"file": "unknown",
|
|
99
|
+
"operation_type": "unknown",
|
|
100
|
+
"pattern_description": "",
|
|
101
|
+
"why": "Unknown"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Find the tool_use
|
|
105
|
+
content = msg_data.get("content", [])
|
|
106
|
+
if not isinstance(content, list):
|
|
107
|
+
return pattern
|
|
108
|
+
|
|
109
|
+
for item in content:
|
|
110
|
+
if not isinstance(item, dict) or item.get("type") != "tool_use":
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
tool_name = item.get("name", "")
|
|
114
|
+
if "edit" not in tool_name.lower() and "write" not in tool_name.lower():
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
if "todo" in tool_name.lower():
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
# Extract file
|
|
121
|
+
tool_input = item.get("input", {})
|
|
122
|
+
pattern["file"] = tool_input.get("file_path", "unknown")
|
|
123
|
+
|
|
124
|
+
# Determine operation type from edits
|
|
125
|
+
if tool_name == "MultiEdit":
|
|
126
|
+
edits = tool_input.get("edits", [])
|
|
127
|
+
edit_count = len(edits)
|
|
128
|
+
|
|
129
|
+
# Analyze edit patterns
|
|
130
|
+
if edit_count > 5:
|
|
131
|
+
pattern["operation_type"] = "cascade_updates"
|
|
132
|
+
pattern["pattern_description"] = f"Batch operation: {edit_count} coordinated changes"
|
|
133
|
+
elif any("remove" in str(e).lower() or "delete" in str(e).lower() for e in edits):
|
|
134
|
+
pattern["operation_type"] = "removal"
|
|
135
|
+
pattern["pattern_description"] = "Item removal with cascade cleanup"
|
|
136
|
+
else:
|
|
137
|
+
pattern["operation_type"] = "refactor"
|
|
138
|
+
pattern["pattern_description"] = f"Multi-point refactoring ({edit_count} changes)"
|
|
139
|
+
|
|
140
|
+
elif tool_name == "Edit":
|
|
141
|
+
old_str = tool_input.get("old_string", "")
|
|
142
|
+
new_str = tool_input.get("new_string", "")
|
|
143
|
+
|
|
144
|
+
if len(new_str) > len(old_str) * 2:
|
|
145
|
+
pattern["operation_type"] = "expansion"
|
|
146
|
+
pattern["pattern_description"] = "Code expansion/feature addition"
|
|
147
|
+
elif len(new_str) < len(old_str) * 0.5:
|
|
148
|
+
pattern["operation_type"] = "removal"
|
|
149
|
+
pattern["pattern_description"] = "Code removal/simplification"
|
|
150
|
+
else:
|
|
151
|
+
pattern["operation_type"] = "modification"
|
|
152
|
+
pattern["pattern_description"] = "In-place modification"
|
|
153
|
+
|
|
154
|
+
elif tool_name == "Write":
|
|
155
|
+
pattern["operation_type"] = "creation"
|
|
156
|
+
pattern["pattern_description"] = "New file creation"
|
|
157
|
+
|
|
158
|
+
# Find WHY (look at recent user messages)
|
|
159
|
+
for j in range(max(0, edit_index-5), edit_index):
|
|
160
|
+
check_data = get_message_data(messages[j])
|
|
161
|
+
if check_data.get("role") == "user":
|
|
162
|
+
content_str = str(check_data.get("content", ""))
|
|
163
|
+
if len(content_str) > 50 and "tool_result" not in content_str:
|
|
164
|
+
pattern["why"] = content_str[:150]
|
|
165
|
+
break
|
|
166
|
+
|
|
167
|
+
return pattern
|
|
168
|
+
|
|
169
|
+
return pattern
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def build_conversation_signature(messages: List[Dict], errors: List[Dict], patterns: List[Dict], metadata: Optional[Dict] = None) -> Dict:
|
|
173
|
+
"""
|
|
174
|
+
Create conversation signature for filtering/search.
|
|
175
|
+
|
|
176
|
+
Opus recommendation: Enable searching "all successful React fixes" vs "debugging sessions"
|
|
177
|
+
"""
|
|
178
|
+
msg_data_list = [get_message_data(m) for m in messages]
|
|
179
|
+
|
|
180
|
+
# Detect completion status
|
|
181
|
+
last_10 = msg_data_list[-10:]
|
|
182
|
+
has_build_success = any(
|
|
183
|
+
"compiled successfully" in str(m.get("content", "")).lower() or
|
|
184
|
+
("build" in str(m.get("content", "")).lower() and "success" in str(m.get("content", "")).lower())
|
|
185
|
+
for m in last_10
|
|
186
|
+
)
|
|
187
|
+
has_test_success = any("test" in str(m.get("content", "")).lower() and "pass" in str(m.get("content", "")).lower() for m in last_10)
|
|
188
|
+
has_deployment = any("deploy" in str(m.get("content", "")).lower() and "success" in str(m.get("content", "")).lower() for m in last_10)
|
|
189
|
+
|
|
190
|
+
# Check for explicit success confirmation in final messages
|
|
191
|
+
has_completion_confirmation = any(
|
|
192
|
+
"all tasks completed" in str(m.get("content", "")).lower() or
|
|
193
|
+
"successfully" in str(m.get("content", "")).lower() and ("deployment" in str(m.get("content", "")).lower() or "completed" in str(m.get("content", "")).lower())
|
|
194
|
+
for m in last_10
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Only count truly blocking unresolved errors (not TodoWrite noise or empty errors)
|
|
198
|
+
# AND only errors in the last 20% of conversation (earlier errors may have been worked around)
|
|
199
|
+
last_20_percent_index = int(len(messages) * 0.8)
|
|
200
|
+
blocking_errors = [
|
|
201
|
+
e for e in errors
|
|
202
|
+
if not e.get("resolved", True)
|
|
203
|
+
and "todowrite" not in e["error_text"].lower()
|
|
204
|
+
and len(e["error_text"].strip()) > 20
|
|
205
|
+
and e["index"] > last_20_percent_index # Only recent errors are blocking
|
|
206
|
+
and "vercel" not in e["error_text"].lower() # Deployment errors often get worked around
|
|
207
|
+
and not any(url in e["error_text"] for url in ["http://", "https://"]) # URLs aren't errors
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
if (has_build_success or has_test_success or has_deployment or has_completion_confirmation) and not blocking_errors:
|
|
211
|
+
completion_status = "success"
|
|
212
|
+
elif blocking_errors:
|
|
213
|
+
completion_status = "failed"
|
|
214
|
+
else:
|
|
215
|
+
completion_status = "partial"
|
|
216
|
+
|
|
217
|
+
# Detect frameworks/languages
|
|
218
|
+
all_content = " ".join(str(m.get("content", "")) for m in msg_data_list).lower()
|
|
219
|
+
|
|
220
|
+
frameworks = []
|
|
221
|
+
if "react" in all_content or "jsx" in all_content:
|
|
222
|
+
frameworks.append("react")
|
|
223
|
+
if "next" in all_content or "next.js" in all_content:
|
|
224
|
+
frameworks.append("nextjs")
|
|
225
|
+
if "typescript" in all_content or ".tsx" in all_content or ".ts" in all_content:
|
|
226
|
+
frameworks.append("typescript")
|
|
227
|
+
if "python" in all_content or ".py" in all_content:
|
|
228
|
+
frameworks.append("python")
|
|
229
|
+
|
|
230
|
+
# Pattern reusability
|
|
231
|
+
high_value_patterns = ["cascade_updates", "removal", "refactor"]
|
|
232
|
+
pattern_reusability = "high" if any(p["operation_type"] in high_value_patterns for p in patterns) else "medium"
|
|
233
|
+
|
|
234
|
+
# Error recovery
|
|
235
|
+
error_recovery = any(e.get("resolved", False) for e in errors)
|
|
236
|
+
|
|
237
|
+
signature = {
|
|
238
|
+
"completion_status": completion_status,
|
|
239
|
+
"frameworks": frameworks,
|
|
240
|
+
"pattern_reusability": pattern_reusability,
|
|
241
|
+
"error_recovery": error_recovery,
|
|
242
|
+
"total_edits": len(patterns),
|
|
243
|
+
"iteration_count": len([m for m in msg_data_list if m.get("role") == "user"])
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# Integrate metadata if provided
|
|
247
|
+
if metadata:
|
|
248
|
+
tool_usage = metadata.get('tool_usage', {})
|
|
249
|
+
signature.update({
|
|
250
|
+
"tools_used": list(tool_usage.get('tools_summary', {}).keys())[:10],
|
|
251
|
+
"files_modified": [f['path'] if isinstance(f, dict) else f for f in tool_usage.get('files_edited', [])][:10],
|
|
252
|
+
"concepts": list(metadata.get('concepts', []))[:10],
|
|
253
|
+
"analysis_only": len(tool_usage.get('files_edited', [])) == 0
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
return signature
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def build_search_index(messages: List[Dict], patterns: List[Dict], errors: List[Dict]) -> str:
|
|
260
|
+
"""
|
|
261
|
+
Build 500-token search index optimized for keyword matching.
|
|
262
|
+
|
|
263
|
+
Opus structure:
|
|
264
|
+
- User request (exact words)
|
|
265
|
+
- Solution type + tools used
|
|
266
|
+
- Files modified + operation types
|
|
267
|
+
- Primary keywords (3-5 specific terms)
|
|
268
|
+
"""
|
|
269
|
+
parts = []
|
|
270
|
+
|
|
271
|
+
# Extract user requests (exclude tool_result noise AND meta commands)
|
|
272
|
+
user_requests = []
|
|
273
|
+
for i, msg in enumerate(messages):
|
|
274
|
+
msg_data = get_message_data(msg)
|
|
275
|
+
if msg_data.get("role") == "user":
|
|
276
|
+
content = str(msg_data.get("content", ""))
|
|
277
|
+
# Skip tool results, meta commands, and system messages
|
|
278
|
+
if (len(content) > 50 and
|
|
279
|
+
"tool_result" not in content and
|
|
280
|
+
"tool_use_id" not in content and
|
|
281
|
+
"<command-name>" not in content and
|
|
282
|
+
"Caveat:" not in content and
|
|
283
|
+
"<local-command" not in content):
|
|
284
|
+
user_requests.append(content[:200])
|
|
285
|
+
if len(user_requests) >= 2: # Top 2 requests
|
|
286
|
+
break
|
|
287
|
+
|
|
288
|
+
if user_requests:
|
|
289
|
+
parts.append("## User Request")
|
|
290
|
+
for req in user_requests:
|
|
291
|
+
parts.append(req)
|
|
292
|
+
parts.append("")
|
|
293
|
+
|
|
294
|
+
# Edit patterns
|
|
295
|
+
if patterns:
|
|
296
|
+
parts.append("## Solution Pattern")
|
|
297
|
+
for p in patterns[:3]: # Top 3 patterns
|
|
298
|
+
file_short = p["file"].split("/")[-1] if "/" in p["file"] else p["file"]
|
|
299
|
+
parts.append(f"{p['operation_type']}: {file_short}")
|
|
300
|
+
parts.append(f" {p['pattern_description']}")
|
|
301
|
+
parts.append("")
|
|
302
|
+
|
|
303
|
+
# Unresolved errors only
|
|
304
|
+
unresolved = [e for e in errors if not e.get("resolved", True)]
|
|
305
|
+
if unresolved:
|
|
306
|
+
parts.append("## Active Issues")
|
|
307
|
+
for err in unresolved[:2]:
|
|
308
|
+
parts.append(err["error_text"][:100])
|
|
309
|
+
parts.append("")
|
|
310
|
+
|
|
311
|
+
return "\n".join(parts)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def build_context_cache(messages: List[Dict], patterns: List[Dict], errors: List[Dict]) -> str:
|
|
315
|
+
"""
|
|
316
|
+
Build 1000-token context cache with detailed implementation.
|
|
317
|
+
|
|
318
|
+
Opus structure:
|
|
319
|
+
- Full edit patterns with line numbers
|
|
320
|
+
- Error→recovery sequences
|
|
321
|
+
- Build/test output snippets
|
|
322
|
+
- Code snippets showing the pattern
|
|
323
|
+
"""
|
|
324
|
+
parts = []
|
|
325
|
+
|
|
326
|
+
# Detailed edit patterns
|
|
327
|
+
if patterns:
|
|
328
|
+
parts.append("## Implementation Details")
|
|
329
|
+
for p in patterns[:5]:
|
|
330
|
+
parts.append(f"[Msg {p['index']}] {p['operation_type']}")
|
|
331
|
+
parts.append(f" File: {p['file']}")
|
|
332
|
+
parts.append(f" Pattern: {p['pattern_description']}")
|
|
333
|
+
if p['why'] != "Unknown":
|
|
334
|
+
parts.append(f" Context: {p['why']}")
|
|
335
|
+
parts.append("")
|
|
336
|
+
|
|
337
|
+
# Error recovery sequences
|
|
338
|
+
resolved_errors = [e for e in errors if e.get("resolved", False)]
|
|
339
|
+
if resolved_errors:
|
|
340
|
+
parts.append("## Error Recovery")
|
|
341
|
+
for err in resolved_errors[:3]:
|
|
342
|
+
parts.append(f"[Msg {err['index']}] Error: {err['error_text'][:100]}")
|
|
343
|
+
if err.get("resolution"):
|
|
344
|
+
parts.append(f" Fix: {err['resolution'][:100]}")
|
|
345
|
+
parts.append("")
|
|
346
|
+
|
|
347
|
+
# Key validation moments
|
|
348
|
+
parts.append("## Validation")
|
|
349
|
+
for i, msg in enumerate(messages):
|
|
350
|
+
msg_data = get_message_data(msg)
|
|
351
|
+
content_str = str(msg_data.get("content", "")).lower()
|
|
352
|
+
|
|
353
|
+
if "compiled successfully" in content_str or ("build" in content_str and "success" in content_str):
|
|
354
|
+
parts.append(f"[Msg {i}] Build: Success")
|
|
355
|
+
elif "test" in content_str and "pass" in content_str:
|
|
356
|
+
parts.append(f"[Msg {i}] Tests: Passed")
|
|
357
|
+
|
|
358
|
+
return "\n".join(parts)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def extract_error_context(messages: List[Dict], error_index: int) -> Dict:
|
|
362
|
+
"""Extract error with resolution tracking."""
|
|
363
|
+
msg_data = get_message_data(messages[error_index])
|
|
364
|
+
content = msg_data.get("content", "")
|
|
365
|
+
|
|
366
|
+
# Extract clean error text
|
|
367
|
+
if isinstance(content, list):
|
|
368
|
+
error_parts = []
|
|
369
|
+
for item in content:
|
|
370
|
+
if isinstance(item, dict) and item.get("type") == "tool_result":
|
|
371
|
+
result_content = item.get("content", "")
|
|
372
|
+
if "error" in str(result_content).lower():
|
|
373
|
+
error_parts.append(str(result_content)[:300])
|
|
374
|
+
elif isinstance(item, str):
|
|
375
|
+
error_parts.append(item[:300])
|
|
376
|
+
error_text = " ".join(error_parts)
|
|
377
|
+
else:
|
|
378
|
+
error_text = str(content)[:300]
|
|
379
|
+
|
|
380
|
+
# Check if resolved (explicit or implicit)
|
|
381
|
+
resolved = False
|
|
382
|
+
resolution_text = None
|
|
383
|
+
for i in range(error_index+1, min(len(messages), error_index+15)):
|
|
384
|
+
check_data = get_message_data(messages[i])
|
|
385
|
+
check_msg = json.dumps(check_data.get("content", "")).lower()
|
|
386
|
+
|
|
387
|
+
# Explicit resolution
|
|
388
|
+
if any(word in check_msg for word in ["fixed", "solved", "working"]):
|
|
389
|
+
resolved = True
|
|
390
|
+
resolution_text = check_msg[:200]
|
|
391
|
+
break
|
|
392
|
+
|
|
393
|
+
# Implicit resolution: success after error
|
|
394
|
+
if "connection_refused" in error_text.lower():
|
|
395
|
+
# If server starts or page loads successfully after, it's resolved
|
|
396
|
+
if ("background" in check_msg and "running" in check_msg) or ("playwright" in check_msg and "success" not in check_msg and "error" not in check_msg):
|
|
397
|
+
resolved = True
|
|
398
|
+
resolution_text = "Server started / page loaded successfully"
|
|
399
|
+
break
|
|
400
|
+
|
|
401
|
+
# Build success after build error
|
|
402
|
+
if ("build" in error_text.lower() or "compil" in error_text.lower()) and "compiled successfully" in check_msg:
|
|
403
|
+
resolved = True
|
|
404
|
+
resolution_text = "Build succeeded"
|
|
405
|
+
break
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
"index": error_index,
|
|
409
|
+
"error_text": error_text,
|
|
410
|
+
"resolved": resolved,
|
|
411
|
+
"resolution": resolution_text
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def extract_events_v3(messages: List[Dict], metadata: Optional[Dict] = None) -> Dict[str, Any]:
|
|
416
|
+
"""
|
|
417
|
+
V3 extraction with Opus recommendations and optional metadata enrichment.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
messages: List of conversation messages
|
|
421
|
+
metadata: Optional dict with {
|
|
422
|
+
'tool_usage': {...}, # From extract_tool_usage_from_jsonl
|
|
423
|
+
'concepts': [...] # From extract_concepts
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
- search_index: 500 tokens for vector embedding
|
|
428
|
+
- context_cache: 1000 tokens for payload storage
|
|
429
|
+
- signature: Metadata for filtering (enriched with tool/concept data if provided)
|
|
430
|
+
- metadata: Original metadata passed through for use in narrative generation
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
# Score all messages with V3 weights
|
|
434
|
+
scores = [
|
|
435
|
+
(i, calculate_importance_score_v3(msg, i, len(messages)))
|
|
436
|
+
for i, msg in enumerate(messages)
|
|
437
|
+
]
|
|
438
|
+
scores.sort(key=lambda x: x[1], reverse=True)
|
|
439
|
+
top_indices = [idx for idx, score in scores[:20]]
|
|
440
|
+
|
|
441
|
+
# Extract edit patterns
|
|
442
|
+
patterns = []
|
|
443
|
+
for i in top_indices:
|
|
444
|
+
msg_data = get_message_data(messages[i])
|
|
445
|
+
if msg_data.get("role") == "assistant":
|
|
446
|
+
content = msg_data.get("content", [])
|
|
447
|
+
if isinstance(content, list):
|
|
448
|
+
for item in content:
|
|
449
|
+
if isinstance(item, dict) and item.get("type") == "tool_use":
|
|
450
|
+
tool_name = item.get("name", "")
|
|
451
|
+
if ("edit" in tool_name.lower() or "write" in tool_name.lower()) and "todo" not in tool_name.lower():
|
|
452
|
+
pattern = extract_edit_pattern(messages, i)
|
|
453
|
+
patterns.append(pattern)
|
|
454
|
+
|
|
455
|
+
# Extract errors
|
|
456
|
+
errors = []
|
|
457
|
+
for i, msg in enumerate(messages):
|
|
458
|
+
msg_data = get_message_data(msg)
|
|
459
|
+
content_str = json.dumps(msg_data.get("content", "")).lower()
|
|
460
|
+
if any(kw in content_str for kw in ["error", "exception", "failed"]):
|
|
461
|
+
errors.append(extract_error_context(messages, i))
|
|
462
|
+
|
|
463
|
+
# Build outputs (with metadata enrichment)
|
|
464
|
+
search_index = build_search_index(messages, patterns, errors)
|
|
465
|
+
context_cache = build_context_cache(messages, patterns, errors)
|
|
466
|
+
signature = build_conversation_signature(messages, errors, patterns, metadata)
|
|
467
|
+
|
|
468
|
+
result = {
|
|
469
|
+
"search_index": search_index,
|
|
470
|
+
"context_cache": context_cache,
|
|
471
|
+
"signature": signature,
|
|
472
|
+
"stats": {
|
|
473
|
+
"original_messages": len(messages),
|
|
474
|
+
"search_index_tokens": len(search_index) // 4,
|
|
475
|
+
"context_cache_tokens": len(context_cache) // 4,
|
|
476
|
+
"total_tokens": (len(search_index) + len(context_cache)) // 4,
|
|
477
|
+
"patterns_found": len(patterns),
|
|
478
|
+
"errors_found": len(errors)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
# Include metadata in result for narrative generation
|
|
483
|
+
if metadata:
|
|
484
|
+
result["metadata"] = metadata
|
|
485
|
+
|
|
486
|
+
return result
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
if __name__ == "__main__":
|
|
490
|
+
import sys
|
|
491
|
+
from pathlib import Path
|
|
492
|
+
|
|
493
|
+
if len(sys.argv) < 2:
|
|
494
|
+
print("Usage: python extract_events_v3.py <conversation.jsonl>")
|
|
495
|
+
sys.exit(1)
|
|
496
|
+
|
|
497
|
+
jsonl_path = Path(sys.argv[1])
|
|
498
|
+
|
|
499
|
+
# Read messages
|
|
500
|
+
messages = []
|
|
501
|
+
with open(jsonl_path) as f:
|
|
502
|
+
for line in f:
|
|
503
|
+
if line.strip():
|
|
504
|
+
messages.append(json.loads(line))
|
|
505
|
+
|
|
506
|
+
# Extract events V3
|
|
507
|
+
result = extract_events_v3(messages)
|
|
508
|
+
|
|
509
|
+
print(f"\n{'='*80}")
|
|
510
|
+
print(f"EVENT EXTRACTION V3 (OPUS-VALIDATED)")
|
|
511
|
+
print(f"{'='*80}\n")
|
|
512
|
+
|
|
513
|
+
print(f"Original messages: {result['stats']['original_messages']}")
|
|
514
|
+
print(f"Search index: {result['stats']['search_index_tokens']:,} tokens")
|
|
515
|
+
print(f"Context cache: {result['stats']['context_cache_tokens']:,} tokens")
|
|
516
|
+
print(f"Total: {result['stats']['total_tokens']:,} tokens")
|
|
517
|
+
print(f"Patterns found: {result['stats']['patterns_found']}")
|
|
518
|
+
print(f"Errors found: {result['stats']['errors_found']}")
|
|
519
|
+
|
|
520
|
+
print(f"\n{'='*80}")
|
|
521
|
+
print(f"CONVERSATION SIGNATURE")
|
|
522
|
+
print(f"{'='*80}\n")
|
|
523
|
+
print(json.dumps(result['signature'], indent=2))
|
|
524
|
+
|
|
525
|
+
print(f"\n{'='*80}")
|
|
526
|
+
print(f"SEARCH INDEX (for vector embedding)")
|
|
527
|
+
print(f"{'='*80}\n")
|
|
528
|
+
print(result['search_index'])
|
|
529
|
+
|
|
530
|
+
print(f"\n{'='*80}")
|
|
531
|
+
print(f"CONTEXT CACHE (for payload storage)")
|
|
532
|
+
print(f"{'='*80}\n")
|
|
533
|
+
print(result['context_cache'])
|