this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix session parsing to capture all tool uses

- Use uuid instead of message.id for deduplication (streaming chunks share message.id)
- Store rawInput on ToolUse for file path extraction
- Lead transcript with FILES CREATED/EDITED summary
- This ensures implementation work is captured, not just exploration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

alice 1db0f5c3 dfdc0f6a

+67 -24
+66 -24
src/core/session-reader.ts
··· 52 52 const seen = new Set<string>(); 53 53 54 54 for await (const entry of parseJSONLStream(filePath)) { 55 - // Deduplication 56 - const hash = `${entry.message?.id || entry.uuid}:${entry.requestId || ''}`; 57 - if (seen.has(hash)) continue; 58 - seen.add(hash); 55 + // Deduplication - use uuid (unique per chunk) not message.id (same across streaming chunks) 56 + if (seen.has(entry.uuid)) continue; 57 + seen.add(entry.uuid); 59 58 60 59 // Extract metadata from first entry 61 60 if (!sessionId && entry.sessionId) { ··· 170 169 tools.push({ 171 170 name: item.name, 172 171 input: summarizeToolInput(item.name, item.input), 172 + rawInput: item.input, 173 173 }); 174 174 } 175 175 } ··· 211 211 212 212 /** 213 213 * Create a condensed transcript for LLM summarization 214 - * Focuses on user requests and key actions 214 + * Leads with action summary (files changed) to ensure implementation work is captured 215 215 */ 216 216 export function createCondensedTranscript(session: ParsedSession): string { 217 217 const parts: string[] = []; ··· 223 223 parts.push(`Duration: ${formatDuration(session.startTime, session.endTime)}`); 224 224 parts.push(''); 225 225 226 - // Extract user requests, assistant responses, and tool actions 226 + // LEAD with files changed - this is the most important signal of actual work 227 + const filesWritten: string[] = []; 228 + const filesEdited: string[] = []; 229 + const commandsRun: string[] = []; 230 + 227 231 for (const msg of session.messages) { 232 + if (msg.type === 'assistant') { 233 + for (const tool of msg.toolUses) { 234 + if (tool.name === 'Write') { 235 + const path = String((tool.rawInput as any)?.file_path || ''); 236 + if (path && !filesWritten.includes(path)) { 237 + filesWritten.push(path); 238 + } 239 + } else if (tool.name === 'Edit') { 240 + const path = String((tool.rawInput as any)?.file_path || ''); 241 + if (path && !filesEdited.includes(path)) { 242 + filesEdited.push(path); 243 + } 244 + } else if (tool.name === 'Bash') { 245 + const cmd = String((tool.rawInput as any)?.command || '').slice(0, 100); 246 + if (cmd && commandsRun.length < 10) { 247 + commandsRun.push(cmd); 248 + } 249 + } 250 + } 251 + } 252 + } 253 + 254 + // Show action summary at the TOP 255 + if (filesWritten.length > 0) { 256 + parts.push(`FILES CREATED (${filesWritten.length}):`); 257 + filesWritten.slice(0, 15).forEach(f => parts.push(` - ${f}`)); 258 + if (filesWritten.length > 15) parts.push(` ... and ${filesWritten.length - 15} more`); 259 + parts.push(''); 260 + } 261 + 262 + if (filesEdited.length > 0) { 263 + parts.push(`FILES EDITED (${filesEdited.length}):`); 264 + filesEdited.slice(0, 15).forEach(f => parts.push(` - ${f}`)); 265 + if (filesEdited.length > 15) parts.push(` ... and ${filesEdited.length - 15} more`); 266 + parts.push(''); 267 + } 268 + 269 + if (commandsRun.length > 0) { 270 + parts.push(`COMMANDS RUN (${commandsRun.length}):`); 271 + commandsRun.slice(0, 5).forEach(c => parts.push(` $ ${c}`)); 272 + parts.push(''); 273 + } 274 + 275 + // Then show conversation context (but less of it) 276 + parts.push('CONVERSATION:'); 277 + let messageCount = 0; 278 + for (const msg of session.messages) { 279 + if (messageCount > 20) break; // Limit to avoid overwhelming 280 + 228 281 if (msg.type === 'user' && msg.text) { 229 - // Include user prompts (truncated) 230 - const text = msg.text.slice(0, 500); 282 + const text = msg.text.slice(0, 300); 231 283 parts.push(`User: ${text}`); 232 - } else if (msg.type === 'assistant') { 233 - // Include assistant text responses 234 - if (msg.text) { 235 - const text = msg.text.slice(0, 400); 236 - parts.push(`Assistant: ${text}`); 237 - } 238 - // Also include tool usage 239 - if (msg.toolUses.length > 0) { 240 - const toolSummary = msg.toolUses 241 - .map((t) => `${t.name}: ${t.input}`) 242 - .join(', '); 243 - parts.push(`Tools: ${toolSummary.slice(0, 300)}`); 244 - } 284 + messageCount++; 285 + } else if (msg.type === 'assistant' && msg.text) { 286 + const text = msg.text.slice(0, 200); 287 + parts.push(`Assistant: ${text}`); 288 + messageCount++; 245 289 } 246 290 } 247 291 248 - // Add stats 292 + // Add stats at end 249 293 parts.push(''); 250 - parts.push(`Stats: ${session.stats.userMessages} user messages, ${session.stats.assistantMessages} assistant messages`); 251 - 252 294 const toolSummary = Object.entries(session.stats.toolCalls) 253 295 .sort((a, b) => b[1] - a[1]) 254 296 .slice(0, 10)
+1
src/types.ts
··· 56 56 export interface ToolUse { 57 57 name: string; 58 58 input: string; // Truncated/summarized 59 + rawInput?: Record<string, unknown>; // Full input for file path extraction 59 60 } 60 61 61 62 export interface SessionStats {