Skip to content

Commit 5928afb

Browse files
committed
Add smart review change walkthrough
1 parent dbe8113 commit 5928afb

File tree

2 files changed

+84
-13
lines changed

2 files changed

+84
-13
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,12 @@ Apache-2.0 License. See [LICENSE](LICENSE) for details.
274274
🚨 **Critical Issues:** 1
275275
📁 **Files Analyzed:** 3
276276
277+
## 🧭 Change Walkthrough
278+
279+
- `src/auth.py` (modified; +12, -3)
280+
- `src/models.py` (modified; +8, -1)
281+
- `src/routes.py` (new; +24, -0)
282+
277283
### 🎯 Priority Actions
278284
1. Address 1 security issue(s) immediately
279285
2. Consider performance optimization for database queries

src/main.rs

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,6 @@ async fn review_command(
257257

258258
let diffs = core::DiffParser::parse_unified_diff(&diff_content)?;
259259
info!("Parsed {} file diffs", diffs.len());
260-
261260
let model_config = adapters::llm::ModelConfig {
262261
model_name: config.model.clone(),
263262
api_key: config.api_key.clone(),
@@ -273,7 +272,7 @@ async fn review_command(
273272
base_prompt_config.max_diff_chars = config.max_diff_chars;
274273
let mut all_comments = Vec::new();
275274

276-
for diff in diffs {
275+
for diff in &diffs {
277276
// Check if file should be excluded
278277
if config.should_exclude(&diff.file_path) {
279278
info!("Skipping excluded file: {}", diff.file_path.display());
@@ -301,12 +300,12 @@ async fn review_command(
301300

302301
// Run pre-analyzers to get additional context
303302
let analyzer_chunks = plugin_manager
304-
.run_pre_analyzers(&diff, &repo_path_str)
303+
.run_pre_analyzers(diff, &repo_path_str)
305304
.await?;
306305
context_chunks.extend(analyzer_chunks);
307306

308307
// Extract symbols from diff and fetch their definitions
309-
let symbols = extract_symbols_from_diff(&diff);
308+
let symbols = extract_symbols_from_diff(diff);
310309
if !symbols.is_empty() {
311310
let definition_chunks = context_fetcher
312311
.fetch_related_definitions(&diff.file_path, &symbols)
@@ -799,7 +798,7 @@ async fn review_diff_content_raw(
799798
let repo_path_str = repo_path.to_string_lossy().to_string();
800799
let context_fetcher = core::ContextFetcher::new(repo_path.to_path_buf());
801800

802-
for diff in diffs {
801+
for diff in &diffs {
803802
// Check if file should be excluded
804803
if config.should_exclude(&diff.file_path) {
805804
info!("Skipping excluded file: {}", diff.file_path.display());
@@ -827,12 +826,12 @@ async fn review_diff_content_raw(
827826

828827
// Run pre-analyzers to get additional context
829828
let analyzer_chunks = plugin_manager
830-
.run_pre_analyzers(&diff, &repo_path_str)
829+
.run_pre_analyzers(diff, &repo_path_str)
831830
.await?;
832831
context_chunks.extend(analyzer_chunks);
833832

834833
// Extract symbols from diff and fetch their definitions
835-
let symbols = extract_symbols_from_diff(&diff);
834+
let symbols = extract_symbols_from_diff(diff);
836835
if !symbols.is_empty() {
837836
let definition_chunks = context_fetcher
838837
.fetch_related_definitions(&diff.file_path, &symbols)
@@ -1227,6 +1226,7 @@ async fn smart_review_command(
12271226

12281227
let diffs = core::DiffParser::parse_unified_diff(&diff_content)?;
12291228
info!("Parsed {} file diffs", diffs.len());
1229+
let walkthrough = build_change_walkthrough(&diffs);
12301230

12311231
let model_config = adapters::llm::ModelConfig {
12321232
model_name: config.model.clone(),
@@ -1240,7 +1240,7 @@ async fn smart_review_command(
12401240
let adapter = adapters::llm::create_adapter(&model_config)?;
12411241
let mut all_comments = Vec::new();
12421242

1243-
for diff in diffs {
1243+
for diff in &diffs {
12441244
// Check if file should be excluded
12451245
if config.should_exclude(&diff.file_path) {
12461246
info!("Skipping excluded file: {}", diff.file_path.display());
@@ -1268,7 +1268,7 @@ async fn smart_review_command(
12681268

12691269
// Run pre-analyzers to get additional context
12701270
let analyzer_chunks = plugin_manager
1271-
.run_pre_analyzers(&diff, &repo_path_str)
1271+
.run_pre_analyzers(diff, &repo_path_str)
12721272
.await?;
12731273
context_chunks.extend(analyzer_chunks);
12741274

@@ -1295,7 +1295,7 @@ async fn smart_review_command(
12951295
}
12961296

12971297
// Extract symbols and get definitions
1298-
let symbols = extract_symbols_from_diff(&diff);
1298+
let symbols = extract_symbols_from_diff(diff);
12991299
if !symbols.is_empty() {
13001300
let definition_chunks = context_fetcher
13011301
.fetch_related_definitions(&diff.file_path, &symbols)
@@ -1306,7 +1306,7 @@ async fn smart_review_command(
13061306
let guidance = build_review_guidance(&config, path_config);
13071307
let (system_prompt, user_prompt) =
13081308
core::SmartReviewPromptBuilder::build_enhanced_review_prompt(
1309-
&diff,
1309+
diff,
13101310
&context_chunks,
13111311
config.max_context_chars,
13121312
config.max_diff_chars,
@@ -1344,7 +1344,7 @@ async fn smart_review_command(
13441344
}
13451345
}
13461346

1347-
let comments = filter_comments_for_diff(&diff, comments);
1347+
let comments = filter_comments_for_diff(diff, comments);
13481348
all_comments.extend(comments);
13491349
}
13501350
}
@@ -1357,7 +1357,7 @@ async fn smart_review_command(
13571357

13581358
// Generate summary and output results
13591359
let summary = core::CommentSynthesizer::generate_summary(&processed_comments);
1360-
let output = format_smart_review_output(&processed_comments, &summary);
1360+
let output = format_smart_review_output(&processed_comments, &summary, &walkthrough);
13611361

13621362
if let Some(path) = output_path {
13631363
tokio::fs::write(path, output).await?;
@@ -1552,6 +1552,7 @@ fn parse_smart_tags(value: &str) -> Vec<String> {
15521552
fn format_smart_review_output(
15531553
comments: &[core::Comment],
15541554
summary: &core::comment::ReviewSummary,
1555+
walkthrough: &str,
15551556
) -> String {
15561557
let mut output = String::new();
15571558

@@ -1583,6 +1584,11 @@ fn format_smart_review_output(
15831584
summary.files_reviewed
15841585
));
15851586

1587+
if !walkthrough.trim().is_empty() {
1588+
output.push_str(walkthrough);
1589+
output.push('\n');
1590+
}
1591+
15861592
// Quick Stats
15871593
output.push_str("### 📈 Issue Breakdown\n\n");
15881594

@@ -1886,6 +1892,65 @@ fn build_review_guidance(
18861892
}
18871893
}
18881894

1895+
fn build_change_walkthrough(diffs: &[core::UnifiedDiff]) -> String {
1896+
let mut entries = Vec::new();
1897+
let mut truncated = false;
1898+
let max_entries = 50usize;
1899+
1900+
for diff in diffs {
1901+
if diff.is_binary {
1902+
continue;
1903+
}
1904+
1905+
let mut added = 0usize;
1906+
let mut removed = 0usize;
1907+
for hunk in &diff.hunks {
1908+
for change in &hunk.changes {
1909+
match change.change_type {
1910+
core::diff_parser::ChangeType::Added => added += 1,
1911+
core::diff_parser::ChangeType::Removed => removed += 1,
1912+
_ => {}
1913+
}
1914+
}
1915+
}
1916+
1917+
let status = if diff.is_deleted {
1918+
"deleted"
1919+
} else if diff.is_new {
1920+
"new"
1921+
} else {
1922+
"modified"
1923+
};
1924+
1925+
entries.push(format!(
1926+
"- `{}` ({}; +{}, -{})",
1927+
diff.file_path.display(),
1928+
status,
1929+
added,
1930+
removed
1931+
));
1932+
1933+
if entries.len() >= max_entries {
1934+
truncated = true;
1935+
break;
1936+
}
1937+
}
1938+
1939+
if entries.is_empty() {
1940+
return String::new();
1941+
}
1942+
1943+
let mut output = String::new();
1944+
output.push_str("## 🧭 Change Walkthrough\n\n");
1945+
output.push_str(&entries.join("\n"));
1946+
output.push('\n');
1947+
if truncated {
1948+
output.push_str("\n...truncated (too many files)\n");
1949+
}
1950+
1951+
output
1952+
}
1953+
18891954
fn apply_confidence_threshold(
18901955
comments: Vec<core::Comment>,
18911956
min_confidence: f32,

0 commit comments

Comments
 (0)