Skip to content

Commit 364043d

Browse files
haasonsaasclaude
andcommitted
feat: add default exclude patterns, env var fallbacks, and richer metrics
- Default exclude patterns for lock files, minified assets, generated code, and vendor directories so reviews focus on meaningful changes - Env var fallbacks for GITHUB_TOKEN and DIFFSCOPE_WEBHOOK_SECRET in config - Token usage metrics (total, prompt, completion) for cost tracking - Labeled metrics for comment severity and category breakdowns Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 21a5b4e commit 364043d

2 files changed

Lines changed: 126 additions & 3 deletions

File tree

src/config.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ pub struct Config {
180180
#[serde(default)]
181181
pub plugins: PluginConfig,
182182

183-
#[serde(default)]
183+
#[serde(default = "default_exclude_patterns")]
184184
pub exclude_patterns: Vec<String>,
185185

186186
#[serde(default)]
@@ -349,7 +349,7 @@ impl Default for Config {
349349
vault_mount: None,
350350
vault_namespace: None,
351351
plugins: PluginConfig::default(),
352-
exclude_patterns: Vec::new(),
352+
exclude_patterns: default_exclude_patterns(),
353353
paths: HashMap::new(),
354354
custom_context: Vec::new(),
355355
pattern_repositories: Vec::new(),
@@ -500,6 +500,18 @@ impl Config {
500500
.filter(|s| !s.trim().is_empty());
501501
}
502502

503+
// Env var fallbacks for GitHub integration
504+
if self.github_token.is_none() {
505+
self.github_token = std::env::var("GITHUB_TOKEN")
506+
.ok()
507+
.filter(|s| !s.trim().is_empty());
508+
}
509+
if self.github_webhook_secret.is_none() {
510+
self.github_webhook_secret = std::env::var("DIFFSCOPE_WEBHOOK_SECRET")
511+
.ok()
512+
.filter(|s| !s.trim().is_empty());
513+
}
514+
503515
// Validate base_url: must be a valid http/https URL with a host
504516
if let Some(ref raw_url) = self.base_url {
505517
match url::Url::parse(raw_url) {
@@ -999,6 +1011,34 @@ fn default_max_diff_chars() -> usize {
9991011
40000
10001012
}
10011013

1014+
fn default_exclude_patterns() -> Vec<String> {
1015+
[
1016+
"*.lock",
1017+
"Cargo.lock",
1018+
"package-lock.json",
1019+
"yarn.lock",
1020+
"pnpm-lock.yaml",
1021+
"go.sum",
1022+
"Gemfile.lock",
1023+
"Pipfile.lock",
1024+
"poetry.lock",
1025+
"composer.lock",
1026+
"*.min.js",
1027+
"*.min.css",
1028+
"*.map",
1029+
"*.generated.*",
1030+
"*.pb.go",
1031+
"*.pb.rs",
1032+
"*_generated.go",
1033+
"vendor/**",
1034+
"node_modules/**",
1035+
".git/**",
1036+
]
1037+
.iter()
1038+
.map(|s| s.to_string())
1039+
.collect()
1040+
}
1041+
10021042
fn default_context_max_chunks() -> usize {
10031043
24
10041044
}

src/server/metrics.rs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use axum::extract::State;
22
use axum::response::IntoResponse;
3+
use std::collections::HashMap;
34
use std::fmt::Write;
45
use std::sync::Arc;
56

@@ -19,6 +20,11 @@ pub async fn get_metrics(State(state): State<Arc<AppState>>) -> impl IntoRespons
1920
let mut total_files_reviewed: u64 = 0;
2021
let mut total_diff_bytes: u64 = 0;
2122
let mut github_posted: u64 = 0;
23+
let mut total_tokens: u64 = 0;
24+
let mut total_prompt_tokens: u64 = 0;
25+
let mut total_completion_tokens: u64 = 0;
26+
let mut severity_counts: HashMap<String, u64> = HashMap::new();
27+
let mut category_counts: HashMap<String, u64> = HashMap::new();
2228

2329
for session in reviews.values() {
2430
total += 1;
@@ -31,6 +37,15 @@ pub async fn get_metrics(State(state): State<Arc<AppState>>) -> impl IntoRespons
3137
total_comments += session.comments.len() as u64;
3238
total_files_reviewed += session.files_reviewed as u64;
3339

40+
for comment in &session.comments {
41+
*severity_counts
42+
.entry(comment.severity.to_string())
43+
.or_default() += 1;
44+
*category_counts
45+
.entry(comment.category.to_string())
46+
.or_default() += 1;
47+
}
48+
3449
if let Some(event) = &session.event {
3550
if event.duration_ms > 0 {
3651
total_duration_ms += event.duration_ms;
@@ -40,12 +55,21 @@ pub async fn get_metrics(State(state): State<Arc<AppState>>) -> impl IntoRespons
4055
if event.github_posted {
4156
github_posted += 1;
4257
}
58+
if let Some(t) = event.tokens_total {
59+
total_tokens += t as u64;
60+
}
61+
if let Some(t) = event.tokens_prompt {
62+
total_prompt_tokens += t as u64;
63+
}
64+
if let Some(t) = event.tokens_completion {
65+
total_completion_tokens += t as u64;
66+
}
4367
}
4468
}
4569

4670
drop(reviews);
4771

48-
let mut buf = String::with_capacity(2048);
72+
let mut buf = String::with_capacity(4096);
4973

5074
write_metric(
5175
&mut buf,
@@ -124,6 +148,65 @@ pub async fn get_metrics(State(state): State<Arc<AppState>>) -> impl IntoRespons
124148
"counter",
125149
github_posted,
126150
);
151+
write_metric(
152+
&mut buf,
153+
"diffscope_tokens_total",
154+
"Total LLM tokens consumed",
155+
"counter",
156+
total_tokens,
157+
);
158+
write_metric(
159+
&mut buf,
160+
"diffscope_tokens_prompt_total",
161+
"Total LLM prompt tokens consumed",
162+
"counter",
163+
total_prompt_tokens,
164+
);
165+
write_metric(
166+
&mut buf,
167+
"diffscope_tokens_completion_total",
168+
"Total LLM completion tokens consumed",
169+
"counter",
170+
total_completion_tokens,
171+
);
172+
173+
// Per-severity comment counts
174+
let _ = writeln!(
175+
buf,
176+
"# HELP diffscope_comments_by_severity Comments by severity level"
177+
);
178+
let _ = writeln!(buf, "# TYPE diffscope_comments_by_severity counter");
179+
for severity in &["Error", "Warning", "Info", "Suggestion"] {
180+
let count = severity_counts.get(*severity).copied().unwrap_or(0);
181+
let _ = writeln!(
182+
buf,
183+
"diffscope_comments_by_severity{{severity=\"{severity}\"}} {count}"
184+
);
185+
}
186+
187+
// Per-category comment counts
188+
let _ = writeln!(
189+
buf,
190+
"# HELP diffscope_comments_by_category Comments by category"
191+
);
192+
let _ = writeln!(buf, "# TYPE diffscope_comments_by_category counter");
193+
for category in &[
194+
"Bug",
195+
"Security",
196+
"Performance",
197+
"Style",
198+
"Documentation",
199+
"BestPractice",
200+
"Maintainability",
201+
"Testing",
202+
"Architecture",
203+
] {
204+
let count = category_counts.get(*category).copied().unwrap_or(0);
205+
let _ = writeln!(
206+
buf,
207+
"diffscope_comments_by_category{{category=\"{category}\"}} {count}"
208+
);
209+
}
127210

128211
(
129212
[(

0 commit comments

Comments
 (0)