Skip to content

Commit 9a7c044

Browse files
authored
Merge pull request #269 from acunniffe/fix/stats-tool-contract
tool contract for stats
2 parents 34e4785 + d18e1c4 commit 9a7c044

File tree

1 file changed

+47
-20
lines changed

1 file changed

+47
-20
lines changed

src/authorship/stats.rs

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -521,10 +521,6 @@ pub fn stats_from_authorship_log(
521521
prompt_record.agent_id.tool, prompt_record.agent_id.model
522522
);
523523
let tool_stats = commit_stats.tool_model_breakdown.entry(key).or_default();
524-
tool_stats.ai_additions += std::cmp::min(
525-
prompt_record.total_additions,
526-
prompt_record.overriden_lines + prompt_record.accepted_lines,
527-
);
528524
tool_stats.total_ai_additions += prompt_record.total_additions;
529525
tool_stats.total_ai_deletions += prompt_record.total_deletions;
530526
tool_stats.mixed_additions += prompt_record.overriden_lines;
@@ -544,6 +540,11 @@ pub fn stats_from_authorship_log(
544540
commit_stats.mixed_additions + commit_stats.ai_accepted,
545541
git_diff_added_lines,
546542
);
543+
544+
// Calculate ai_additions for each tool following the same contract: ai_additions = ai_accepted + mixed_additions
545+
for tool_stats in commit_stats.tool_model_breakdown.values_mut() {
546+
tool_stats.ai_additions = tool_stats.ai_accepted + tool_stats.mixed_additions;
547+
}
547548
}
548549

549550
// Human additions are the difference between total git diff and AI accepted lines (ensure non-negative)
@@ -564,7 +565,8 @@ pub fn stats_for_commit_stats(
564565
// Step 1: get the diff between this commit and its parent ON refname (if more than one parent)
565566
// If initial than everything is additions
566567
// We want the count here git shows +111 -55
567-
let (git_diff_added_lines, git_diff_deleted_lines) = get_git_diff_stats(repo, commit_sha, ignore_patterns)?;
568+
let (git_diff_added_lines, git_diff_deleted_lines) =
569+
get_git_diff_stats(repo, commit_sha, ignore_patterns)?;
568570

569571
// Step 2: get the authorship log for this commit
570572
let authorship_log = get_authorship(repo, &commit_sha);
@@ -578,7 +580,11 @@ pub fn stats_for_commit_stats(
578580
}
579581

580582
/// Get git diff statistics between commit and its parent
581-
pub fn get_git_diff_stats(repo: &Repository, commit_sha: &str, ignore_patterns: &[String]) -> Result<(u32, u32), GitAiError> {
583+
pub fn get_git_diff_stats(
584+
repo: &Repository,
585+
commit_sha: &str,
586+
ignore_patterns: &[String],
587+
) -> Result<(u32, u32), GitAiError> {
582588
// Use git show --numstat to get diff statistics
583589
let mut args = repo.global_args_for_exec();
584590
args.push("show".to_string());
@@ -993,7 +999,9 @@ mod tests {
993999
let tmp_repo = TmpRepo::new().unwrap();
9941000

9951001
// Initial commit
996-
tmp_repo.write_file("src/main.rs", "fn main() {}\n", true).unwrap();
1002+
tmp_repo
1003+
.write_file("src/main.rs", "fn main() {}\n", true)
1004+
.unwrap();
9971005
tmp_repo
9981006
.trigger_checkpoint_with_author("test_user")
9991007
.unwrap();
@@ -1014,12 +1022,14 @@ mod tests {
10141022
let head_sha = tmp_repo.get_head_commit_sha().unwrap();
10151023

10161024
// Test WITHOUT ignore - should count lockfile
1017-
let stats_with_lockfile = stats_for_commit_stats(&tmp_repo.gitai_repo(), &head_sha, &[]).unwrap();
1025+
let stats_with_lockfile =
1026+
stats_for_commit_stats(&tmp_repo.gitai_repo(), &head_sha, &[]).unwrap();
10181027
assert_eq!(stats_with_lockfile.git_diff_added_lines, 1001); // 1 source + 1000 lockfile
10191028

10201029
// Test WITH ignore - should exclude lockfile
10211030
let ignore_patterns = vec!["Cargo.lock".to_string()];
1022-
let stats_without_lockfile = stats_for_commit_stats(&tmp_repo.gitai_repo(), &head_sha, &ignore_patterns).unwrap();
1031+
let stats_without_lockfile =
1032+
stats_for_commit_stats(&tmp_repo.gitai_repo(), &head_sha, &ignore_patterns).unwrap();
10231033
assert_eq!(stats_without_lockfile.git_diff_added_lines, 1); // Only 1 source line
10241034
assert_eq!(stats_without_lockfile.ai_additions, 1);
10251035
}
@@ -1029,7 +1039,9 @@ mod tests {
10291039
let tmp_repo = TmpRepo::new().unwrap();
10301040

10311041
// Initial commit
1032-
tmp_repo.write_file("README.md", "# Project\n", true).unwrap();
1042+
tmp_repo
1043+
.write_file("README.md", "# Project\n", true)
1044+
.unwrap();
10331045
tmp_repo
10341046
.trigger_checkpoint_with_author("test_user")
10351047
.unwrap();
@@ -1065,7 +1077,8 @@ mod tests {
10651077
"package-lock.json".to_string(),
10661078
"yarn.lock".to_string(),
10671079
];
1068-
let stats_filtered = stats_for_commit_stats(&tmp_repo.gitai_repo(), &head_sha, &ignore_patterns).unwrap();
1080+
let stats_filtered =
1081+
stats_for_commit_stats(&tmp_repo.gitai_repo(), &head_sha, &ignore_patterns).unwrap();
10691082
assert_eq!(stats_filtered.git_diff_added_lines, 1);
10701083
assert_eq!(stats_filtered.human_additions, 1);
10711084
}
@@ -1075,7 +1088,9 @@ mod tests {
10751088
let tmp_repo = TmpRepo::new().unwrap();
10761089

10771090
// Initial commit
1078-
tmp_repo.write_file("src/lib.rs", "pub fn foo() {}\n", true).unwrap();
1091+
tmp_repo
1092+
.write_file("src/lib.rs", "pub fn foo() {}\n", true)
1093+
.unwrap();
10791094
tmp_repo
10801095
.trigger_checkpoint_with_author("test_user")
10811096
.unwrap();
@@ -1098,7 +1113,8 @@ mod tests {
10981113

10991114
// Test WITH ignore - shows 0 lines (lockfile-only commit)
11001115
let ignore_patterns = vec!["Cargo.lock".to_string()];
1101-
let stats_without = stats_for_commit_stats(&tmp_repo.gitai_repo(), &head_sha, &ignore_patterns).unwrap();
1116+
let stats_without =
1117+
stats_for_commit_stats(&tmp_repo.gitai_repo(), &head_sha, &ignore_patterns).unwrap();
11021118
assert_eq!(stats_without.git_diff_added_lines, 0);
11031119
assert_eq!(stats_without.ai_additions, 0);
11041120
assert_eq!(stats_without.human_additions, 0);
@@ -1137,7 +1153,9 @@ mod tests {
11371153
let tmp_repo = TmpRepo::new().unwrap();
11381154

11391155
// Initial commit
1140-
tmp_repo.write_file("src/lib.rs", "pub fn foo() {}\n", true).unwrap();
1156+
tmp_repo
1157+
.write_file("src/lib.rs", "pub fn foo() {}\n", true)
1158+
.unwrap();
11411159
tmp_repo
11421160
.trigger_checkpoint_with_author("test_user")
11431161
.unwrap();
@@ -1154,10 +1172,18 @@ mod tests {
11541172
.write_file("package-lock.json", "{}\n".repeat(500).as_str(), true)
11551173
.unwrap();
11561174
tmp_repo
1157-
.write_file("api.generated.ts", "// generated\n".repeat(300).as_str(), true)
1175+
.write_file(
1176+
"api.generated.ts",
1177+
"// generated\n".repeat(300).as_str(),
1178+
true,
1179+
)
11581180
.unwrap();
11591181
tmp_repo
1160-
.write_file("schema.generated.js", "// schema\n".repeat(200).as_str(), true)
1182+
.write_file(
1183+
"schema.generated.js",
1184+
"// schema\n".repeat(200).as_str(),
1185+
true,
1186+
)
11611187
.unwrap();
11621188
tmp_repo
11631189
.trigger_checkpoint_with_ai("Claude", Some("claude-3-sonnet"), Some("cursor"))
@@ -1172,11 +1198,12 @@ mod tests {
11721198

11731199
// Test WITH glob patterns - only source code (1 line)
11741200
let glob_patterns = vec![
1175-
"*.lock".to_string(), // Matches Cargo.lock
1176-
"*lock.json".to_string(), // Matches package-lock.json
1177-
"*.generated.*".to_string(), // Matches *.generated.ts, *.generated.js
1201+
"*.lock".to_string(), // Matches Cargo.lock
1202+
"*lock.json".to_string(), // Matches package-lock.json
1203+
"*.generated.*".to_string(), // Matches *.generated.ts, *.generated.js
11781204
];
1179-
let stats_filtered = stats_for_commit_stats(&tmp_repo.gitai_repo(), &head_sha, &glob_patterns).unwrap();
1205+
let stats_filtered =
1206+
stats_for_commit_stats(&tmp_repo.gitai_repo(), &head_sha, &glob_patterns).unwrap();
11801207
assert_eq!(stats_filtered.git_diff_added_lines, 1);
11811208
assert_eq!(stats_filtered.ai_additions, 1);
11821209
}

0 commit comments

Comments
 (0)