( states: AttributionState[], stagedFiles: string[], )
| 487 | * Compares session baseline to committed state. |
| 488 | */ |
| 489 | export async function calculateCommitAttribution( |
| 490 | states: AttributionState[], |
| 491 | stagedFiles: string[], |
| 492 | ): Promise<AttributionData> { |
| 493 | const cwd = getAttributionRepoRoot() |
| 494 | const sessionId = getSessionId() |
| 495 | |
| 496 | const files: Record<string, FileAttribution> = {} |
| 497 | const excludedGenerated: string[] = [] |
| 498 | const surfaces = new Set<string>() |
| 499 | const surfaceCounts: Record<string, number> = {} |
| 500 | |
| 501 | let totalNCodeChars = 0 |
| 502 | let totalHumanChars = 0 |
| 503 | |
| 504 | // Merge file states from all sessions |
| 505 | const mergedFileStates = new Map<string, FileAttributionState>() |
| 506 | const mergedBaselines = new Map< |
| 507 | string, |
| 508 | { contentHash: string; mtime: number } |
| 509 | >() |
| 510 | |
| 511 | for (const state of states) { |
| 512 | surfaces.add(state.surface) |
| 513 | |
| 514 | // Merge baselines (earliest baseline wins) |
| 515 | // Handle both Map and plain object (in case of serialization) |
| 516 | const baselines = |
| 517 | state.sessionBaselines instanceof Map |
| 518 | ? state.sessionBaselines |
| 519 | : new Map( |
| 520 | Object.entries( |
| 521 | (state.sessionBaselines ?? {}) as Record< |
| 522 | string, |
| 523 | { contentHash: string; mtime: number } |
| 524 | >, |
| 525 | ), |
| 526 | ) |
| 527 | for (const [path, baseline] of baselines) { |
| 528 | if (!mergedBaselines.has(path)) { |
| 529 | mergedBaselines.set(path, baseline) |
| 530 | } |
| 531 | } |
| 532 | |
| 533 | // Merge file states (accumulate contributions) |
| 534 | // Handle both Map and plain object (in case of serialization) |
| 535 | const fileStates = |
| 536 | state.fileStates instanceof Map |
| 537 | ? state.fileStates |
| 538 | : new Map( |
| 539 | Object.entries( |
| 540 | (state.fileStates ?? {}) as Record<string, FileAttributionState>, |
| 541 | ), |
| 542 | ) |
| 543 | for (const [path, fileState] of fileStates) { |
| 544 | const existing = mergedFileStates.get(path) |
| 545 | if (existing) { |
| 546 | mergedFileStates.set(path, { |
no test coverage detected