calculateCommitAttribution method

Future<AttributionData> calculateCommitAttribution({
  1. required List<AttributionState> states,
  2. required List<String> stagedFiles,
})

Calculate final attribution for staged files.

Implementation

Future<AttributionData> calculateCommitAttribution({
  required List<AttributionState> states,
  required List<String> stagedFiles,
}) async {
  final cwd = getAttributionRepoRoot();

  final files = <String, FileAttribution>{};
  final excludedGenerated = <String>[];
  final surfaces = <String>{};
  final surfaceCounts = <String, int>{};

  var totalNeomageChars = 0;
  var totalHumanChars = 0;

  // Merge file states from all sessions.
  final mergedFileStates = <String, FileAttributionState>{};
  final mergedBaselines = <String, BaselineEntry>{};

  for (final s in states) {
    surfaces.add(s.surface);

    for (final entry in s.sessionBaselines.entries) {
      mergedBaselines.putIfAbsent(entry.key, () => entry.value);
    }

    for (final entry in s.fileStates.entries) {
      final existing = mergedFileStates[entry.key];
      if (existing != null) {
        mergedFileStates[entry.key] = FileAttributionState(
          contentHash: entry.value.contentHash,
          neomageContribution:
              existing.neomageContribution +
              entry.value.neomageContribution,
          mtime: entry.value.mtime,
        );
      } else {
        mergedFileStates[entry.key] = entry.value;
      }
    }
  }

  // Process files.
  for (final file in stagedFiles) {
    if (isGeneratedFileChecker?.call(file) ?? false) {
      excludedGenerated.add(file);
      continue;
    }

    final absPath = '$cwd/$file';
    final fileState = mergedFileStates[file];
    final baseline = mergedBaselines[file];
    final fileSurface = states.isNotEmpty ? states.first.surface : 'cli';

    var neomageChars = 0;
    var humanChars = 0;

    final deleted = await isFileDeleted(file);

    if (deleted) {
      if (fileState != null) {
        neomageChars = fileState.neomageContribution;
      } else {
        final diffSize = await getGitDiffSize(file);
        humanChars = diffSize > 0 ? diffSize : 100;
      }
    } else {
      try {
        final fileStat = await FileStat.stat(absPath);
        if (fileStat.type == FileSystemEntityType.notFound) continue;

        if (fileState != null) {
          neomageChars = fileState.neomageContribution;
        } else if (baseline != null) {
          final diffSize = await getGitDiffSize(file);
          humanChars = diffSize > 0 ? diffSize : fileStat.size;
        } else {
          humanChars = fileStat.size;
        }
      } catch (_) {
        continue;
      }
    }

    neomageChars = max(0, neomageChars);
    humanChars = max(0, humanChars);

    final total = neomageChars + humanChars;
    final percent = total > 0 ? (neomageChars / total * 100).round() : 0;

    files[file] = FileAttribution(
      neomageChars: neomageChars,
      humanChars: humanChars,
      percent: percent,
      surface: fileSurface,
    );

    totalNeomageChars += neomageChars;
    totalHumanChars += humanChars;
    surfaceCounts[fileSurface] =
        (surfaceCounts[fileSurface] ?? 0) + neomageChars;
  }

  final totalChars = totalNeomageChars + totalHumanChars;
  final neomagePercent = totalChars > 0
      ? (totalNeomageChars / totalChars * 100).round()
      : 0;

  final surfaceBreakdown = <String, SurfaceBreakdownEntry>{};
  for (final entry in surfaceCounts.entries) {
    final percent = totalChars > 0
        ? (entry.value / totalChars * 100).round()
        : 0;
    surfaceBreakdown[entry.key] = SurfaceBreakdownEntry(
      neomageChars: entry.value,
      percent: percent,
    );
  }

  return AttributionData(
    summary: AttributionSummary(
      neomagePercent: neomagePercent,
      neomageChars: totalNeomageChars,
      humanChars: totalHumanChars,
      surfaces: surfaces.toList(),
    ),
    files: files,
    surfaceBreakdown: surfaceBreakdown,
    excludedGenerated: excludedGenerated,
    sessions: [sessionId],
  );
}