Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions robot-repo-automaton/src/hypatia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,13 +619,15 @@ fn recipe_to_rule(recipe: &serde_json::Value) -> Option<Rule> {
// Build pattern from recipe detection info
let pattern = if let Some(glob) = recipe.get("file_glob").and_then(|v| v.as_str()) {
RulePattern::FileGlob { glob: glob.to_string() }
} else if let Some(regex) = recipe.get("pattern").and_then(|v| v.as_str()) {
} else {
let regex = recipe.get("pattern").and_then(|v| v.as_str())?;
RulePattern::ContentRegex {
regex: regex.to_string(),
file_glob: recipe.get("applies_to").and_then(|v| v.as_str()).map(|s| s.to_string()),
file_glob: recipe
.get("applies_to")
.and_then(|v| v.as_str())
.map(|s| s.to_string()),
}
} else {
return None;
};

// Build fix from recipe
Expand Down
21 changes: 17 additions & 4 deletions shared-context/benches/fleet_benchmarks.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
//! Performance benchmarks for gitbot-fleet operations

use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use gitbot_shared_context::{BotId, Context, Finding, Severity};
use std::hint::black_box;
use std::path::PathBuf;
Expand Down Expand Up @@ -102,7 +102,12 @@ fn bench_finding_queries(c: &mut Criterion) {
2 => Severity::Info,
_ => Severity::Suggestion,
};
ctx.add_finding(Finding::new(bot, &format!("TEST-{:03}", i), severity, "Test"));
ctx.add_finding(Finding::new(
bot,
&format!("TEST-{:03}", i),
severity,
"Test",
));
}

group.bench_function("query_by_bot", |b| {
Expand Down Expand Up @@ -191,7 +196,11 @@ fn bench_health_check(c: &mut Criterion) {
ctx.add_finding(Finding::new(
BotId::Rhodibot,
&format!("TEST-{:03}", i),
if i < 5 { Severity::Error } else { Severity::Warning },
if i < 5 {
Severity::Error
} else {
Severity::Warning
},
"Test finding",
));
}
Expand Down Expand Up @@ -227,7 +236,11 @@ fn bench_report_generation(c: &mut Criterion) {
ctx.add_finding(Finding::new(
BotId::Rhodibot,
&format!("TEST-{:03}", i),
if i % 4 == 0 { Severity::Error } else { Severity::Warning },
if i % 4 == 0 {
Severity::Error
} else {
Severity::Warning
},
"Test finding with some detail",
));
}
Expand Down
16 changes: 11 additions & 5 deletions shared-context/src/bot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,14 @@ impl BotId {
/// Get the tier this bot belongs to
pub fn tier(&self) -> Tier {
match self {
BotId::Rhodibot | BotId::Echidnabot | BotId::Sustainabot | BotId::Oikosbot | BotId::Panicbot => Tier::Verifier,
BotId::Glambot | BotId::Seambot | BotId::Finishbot | BotId::Accessibilitybot => Tier::Finisher,
BotId::Rhodibot
| BotId::Echidnabot
| BotId::Sustainabot
| BotId::Oikosbot
| BotId::Panicbot => Tier::Verifier,
BotId::Glambot | BotId::Seambot | BotId::Finishbot | BotId::Accessibilitybot => {
Tier::Finisher
}
BotId::Cipherbot => Tier::Specialist,
BotId::RobotRepoAutomaton => Tier::Executor,
BotId::Hypatia => Tier::Engine,
Expand Down Expand Up @@ -134,11 +140,11 @@ impl Tier {
/// Get execution order (lower = earlier)
pub fn execution_order(&self) -> u8 {
match self {
Tier::Engine => 0, // Engine coordinates, runs first
Tier::Engine => 0, // Engine coordinates, runs first
Tier::Verifier => 1,
Tier::Finisher => 2,
Tier::Specialist => 3, // Specialist runs after verifiers/finishers
Tier::Executor => 4, // Executor runs after all analysis
Tier::Specialist => 3, // Specialist runs after verifiers/finishers
Tier::Executor => 4, // Executor runs after all analysis
Tier::Custom => 5,
}
}
Expand Down
7 changes: 2 additions & 5 deletions shared-context/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: MPL-2.0
//! Shared context for coordinating bot executions

use crate::Result;
use crate::bot::{BotExecution, BotId, BotStatus, Tier};
use crate::finding::{Finding, FindingSet};
use crate::Result;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
Expand Down Expand Up @@ -191,10 +191,7 @@ impl Context {
}

let info = BotInfo::standard(*bot);
let deps_satisfied = info
.depends_on
.iter()
.all(|dep| self.bot_completed(*dep));
let deps_satisfied = info.depends_on.iter().all(|dep| self.bot_completed(*dep));

if deps_satisfied {
ready.push(*bot);
Expand Down
57 changes: 37 additions & 20 deletions shared-context/src/exclusion_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,7 @@ impl FromStr for ExclusionRegistry {
.into_iter()
.map(|v| {
let p = Pattern::new(&v.pattern).map_err(|e| {
ExclusionError::Parse(format!(
"invalid vendored pattern {:?}: {e}",
v.pattern
))
ExclusionError::Parse(format!("invalid vendored pattern {:?}: {e}", v.pattern))
})?;
Ok(CompiledPattern {
pattern: p,
Expand Down Expand Up @@ -262,8 +259,12 @@ impl ExclusionRegistry {
/// file wins. Covers the common layouts on this machine.
fn conventional_paths() -> Vec<PathBuf> {
vec![
PathBuf::from("/var/mnt/eclipse/repos/developer-ecosystem/standards/.machine_readable/bot_exclusion_registry.a2ml"),
PathBuf::from("/var/mnt/eclipse/repos/standards/.machine_readable/bot_exclusion_registry.a2ml"),
PathBuf::from(
"/var/mnt/eclipse/repos/developer-ecosystem/standards/.machine_readable/bot_exclusion_registry.a2ml",
),
PathBuf::from(
"/var/mnt/eclipse/repos/standards/.machine_readable/bot_exclusion_registry.a2ml",
),
PathBuf::from("./standards/.machine_readable/bot_exclusion_registry.a2ml"),
PathBuf::from("../standards/.machine_readable/bot_exclusion_registry.a2ml"),
PathBuf::from("../../standards/.machine_readable/bot_exclusion_registry.a2ml"),
Expand Down Expand Up @@ -300,10 +301,7 @@ impl ExclusionRegistry {
if matches!(k.as_str(), "off" | "disabled" | "0" | "false" | "halt") {
return Decision::Deny {
axis: DenyAxis::KillSwitch,
reason: format!(
"HYPATIA_AUTOMATION={} — global kill switch engaged",
kill
),
reason: format!("HYPATIA_AUTOMATION={} — global kill switch engaged", kill),
};
}
}
Expand Down Expand Up @@ -664,48 +662,67 @@ mod real_registry_smoke {
#[test]
fn real_registry_file_parses_and_has_expected_axes() {
let path = std::path::Path::new(
"/var/mnt/eclipse/repos/developer-ecosystem/standards/.machine_readable/bot_exclusion_registry.a2ml"
"/var/mnt/eclipse/repos/developer-ecosystem/standards/.machine_readable/bot_exclusion_registry.a2ml",
);
if !path.exists() {
eprintln!("skipping: real registry not at {:?}", path);
return;
}
let r = ExclusionRegistry::load(path).expect("parse real registry");
// Smoke: at least one of each axis.
assert!(!r.external_repos.is_empty(), "external_repos axis populated");
assert!(!r.vendored_patterns.is_empty(), "vendored_patterns axis populated");
assert!(!r.remote_origin_patterns.is_empty(), "remote_origin_patterns axis populated");
assert!(
!r.external_repos.is_empty(),
"external_repos axis populated"
);
assert!(
!r.vendored_patterns.is_empty(),
"vendored_patterns axis populated"
);
assert!(
!r.remote_origin_patterns.is_empty(),
"remote_origin_patterns axis populated"
);
}

#[test]
fn real_registry_blocks_joshuajewell() {
let path = std::path::Path::new(
"/var/mnt/eclipse/repos/developer-ecosystem/standards/.machine_readable/bot_exclusion_registry.a2ml"
"/var/mnt/eclipse/repos/developer-ecosystem/standards/.machine_readable/bot_exclusion_registry.a2ml",
);
if !path.exists() { return; }
if !path.exists() {
return;
}
let r = ExclusionRegistry::load(path).unwrap();
let d = r.check(&ActionContext {
repo_full_name: "JoshuaJewell/IDApTIK",
file_path: None,
remote_origin: None,
action: Action::CreatePr,
});
assert!(!d.is_allow(), "real registry must deny JoshuaJewell/IDApTIK writes");
assert!(
!d.is_allow(),
"real registry must deny JoshuaJewell/IDApTIK writes"
);
}

#[test]
fn real_registry_blocks_rust_lang_origin() {
let path = std::path::Path::new(
"/var/mnt/eclipse/repos/developer-ecosystem/standards/.machine_readable/bot_exclusion_registry.a2ml"
"/var/mnt/eclipse/repos/developer-ecosystem/standards/.machine_readable/bot_exclusion_registry.a2ml",
);
if !path.exists() { return; }
if !path.exists() {
return;
}
let r = ExclusionRegistry::load(path).unwrap();
let d = r.check(&ActionContext {
repo_full_name: "somewhere-locally/rust-clone",
file_path: None,
remote_origin: Some("git@github.com:rust-lang/rust.git"),
action: Action::CreatePr,
});
assert!(!d.is_allow(), "real registry must deny rust-lang origin writes");
assert!(
!d.is_allow(),
"real registry must deny rust-lang origin writes"
);
}
}
20 changes: 12 additions & 8 deletions shared-context/src/finding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,10 @@ impl Finding {

/// Get location string for display
pub fn location_string(&self) -> Option<String> {
self.file.as_ref().map(|f| {
match (self.line, self.column) {
(Some(l), Some(c)) => format!("{}:{}:{}", f.display(), l, c),
(Some(l), None) => format!("{}:{}", f.display(), l),
_ => f.display().to_string(),
}
self.file.as_ref().map(|f| match (self.line, self.column) {
(Some(l), Some(c)) => format!("{}:{}:{}", f.display(), l, c),
(Some(l), None) => format!("{}:{}", f.display(), l),
_ => f.display().to_string(),
})
}
}
Expand Down Expand Up @@ -231,7 +229,10 @@ impl FindingSet {

/// Get findings by source bot
pub fn by_source(&self, source: BotId) -> Vec<&Finding> {
self.findings.iter().filter(|f| f.source == source).collect()
self.findings
.iter()
.filter(|f| f.source == source)
.collect()
}

/// Get findings by severity
Expand Down Expand Up @@ -272,7 +273,10 @@ impl FindingSet {

/// Get fixable findings
pub fn fixable(&self) -> Vec<&Finding> {
self.findings.iter().filter(|f| f.fixable && !f.fixed).collect()
self.findings
.iter()
.filter(|f| f.fixable && !f.fixed)
.collect()
}

/// Get unfixed findings
Expand Down
53 changes: 27 additions & 26 deletions shared-context/src/health.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,12 @@ impl Context {
let anomalies = self.detect_bot_anomalies(*bot_id, execution);
let bot_status = determine_bot_health_status(execution, &anomalies);

let duration_ms = if let (Some(start), Some(end)) =
(execution.started_at, execution.completed_at)
{
Some((end - start).num_milliseconds() as u64)
} else {
None
};
let duration_ms =
if let (Some(start), Some(end)) = (execution.started_at, execution.completed_at) {
Some((end - start).num_milliseconds() as u64)
} else {
None
};

health.insert(
format!("{:?}", bot_id),
Expand All @@ -202,12 +201,7 @@ impl Context {
/// Check health of tiers
fn check_tier_health(&self) -> HashMap<String, TierHealth> {
let mut tier_health = HashMap::new();
let tiers = [
Tier::Engine,
Tier::Verifier,
Tier::Finisher,
Tier::Executor,
];
let tiers = [Tier::Engine, Tier::Verifier, Tier::Finisher, Tier::Executor];

for tier in tiers {
let tier_bots: Vec<_> = self
Expand Down Expand Up @@ -473,9 +467,7 @@ impl Context {

/// Determine overall health status from score and alerts
fn determine_overall_status(score: f64, alerts: &[HealthAlert]) -> HealthStatus {
let has_critical = alerts
.iter()
.any(|a| a.severity == AlertSeverity::Critical);
let has_critical = alerts.iter().any(|a| a.severity == AlertSeverity::Critical);
let has_errors = alerts.iter().any(|a| a.severity == AlertSeverity::Error);

if has_critical || score < 30.0 {
Expand Down Expand Up @@ -525,9 +517,7 @@ impl FleetHealth {
println!("╠════════════════════════════════════════════════════════════════╣");
println!(
"║ Status: {} {:?} (Score: {:.1}/100) ║",
status_symbol,
self.status,
self.health_score
status_symbol, self.status, self.health_score
);
println!(
"║ Checked: {} ║",
Expand All @@ -553,7 +543,10 @@ impl FleetHealth {

// Alerts
if !self.alerts.is_empty() {
println!("║ Active Alerts: {} ║", self.alerts.len());
println!(
"║ Active Alerts: {} ║",
self.alerts.len()
);
for alert in self.alerts.iter().take(5) {
let severity_str = match alert.severity {
AlertSeverity::Info => "ℹ️ ",
Expand All @@ -566,10 +559,16 @@ impl FleetHealth {
} else {
alert.message.clone()
};
println!("║ {} {} ║", severity_str, msg);
println!(
"║ {} {} ║",
severity_str, msg
);
}
if self.alerts.len() > 5 {
println!("║ ... and {} more alerts ║", self.alerts.len() - 5);
println!(
"║ ... and {} more alerts ║",
self.alerts.len() - 5
);
}
println!("╠════════════════════════════════════════════════════════════════╣");
}
Expand Down Expand Up @@ -652,9 +651,11 @@ mod tests {
let bot_health = health.bot_health.get("Rhodibot").unwrap();

assert!(!bot_health.anomalies.is_empty());
assert!(bot_health
.anomalies
.iter()
.any(|a| a.contains("High error rate")));
assert!(
bot_health
.anomalies
.iter()
.any(|a| a.contains("High error rate"))
);
}
}
Loading
Loading