( blocks: ContentBlock[] | undefined, )
| 538 | * Groups by file path and sums up the stats. |
| 539 | */ |
| 540 | export function getFileStatsFromBlocks( |
| 541 | blocks: ContentBlock[] | undefined, |
| 542 | ): FileStats[] { |
| 543 | if (!blocks || blocks.length === 0) return [] |
| 544 | |
| 545 | const fileMap = new Map<string, FileStats>() |
| 546 | |
| 547 | for (const block of blocks) { |
| 548 | if ( |
| 549 | block.type === 'tool' && |
| 550 | ALL_EDIT_TOOL_NAMES.includes( |
| 551 | block.toolName as (typeof ALL_EDIT_TOOL_NAMES)[number], |
| 552 | ) |
| 553 | ) { |
| 554 | if (isFailedEditToolBlock(block)) continue |
| 555 | |
| 556 | const filePath = extractFilePath(block) |
| 557 | if (!filePath) continue |
| 558 | |
| 559 | const diff = extractDiff(block) |
| 560 | const stats = parseDiffStats(diff ?? undefined) |
| 561 | const changeType = getFileChangeType(block) |
| 562 | |
| 563 | const existing = fileMap.get(filePath) |
| 564 | if (existing) { |
| 565 | // Aggregate stats for same file |
| 566 | existing.stats.linesAdded += stats.linesAdded |
| 567 | existing.stats.linesRemoved += stats.linesRemoved |
| 568 | existing.stats.hunks += stats.hunks |
| 569 | } else { |
| 570 | fileMap.set(filePath, { |
| 571 | path: filePath, |
| 572 | changeType, |
| 573 | stats, |
| 574 | }) |
| 575 | } |
| 576 | } |
| 577 | } |
| 578 | |
| 579 | return Array.from(fileMap.values()) |
| 580 | } |
| 581 | |
| 582 | /** |
| 583 | * Build an activity timeline from agent blocks. |
no test coverage detected