use argonauts_core::TaskId;
use argonauts_core::events::task::TaskKind;
use argonauts_core::names::ModelRef;
use crate::state::RunState;
use crate::state::tasks::TaskEntry;
pub const NONE_SET: &str = "No reviewer is set for this task";
pub fn for_task(
state: &RunState,
task: &TaskId,
providers: &dyn Fn(&ModelRef) -> String,
other_provider: bool,
crew: &[ModelRef],
) -> Vec<ModelRef> {
let Some(record) = state.tasks.get(task) else {
return Vec::new();
};
if takes_the_crews(record) {
return without_authors(crew, record, providers, other_provider);
}
let worker = record.worker.as_ref().or(record.spec.worker.as_ref());
eligible(&record.spec.reviewers, worker, providers, other_provider)
}
pub fn none_set(state: &RunState, task: &TaskId, crew: &[ModelRef]) -> bool {
state.tasks.get(task).is_some_and(|record| {
record.spec.reviewers.is_empty() && (crew.is_empty() || !takes_the_crews(record))
})
}
fn takes_the_crews(record: &TaskEntry) -> bool {
record.spec.reviewers.is_empty() && record.spec.kind != TaskKind::Captain
}
fn without_authors(
crew: &[ModelRef],
record: &TaskEntry,
providers: &dyn Fn(&ModelRef) -> String,
other_provider: bool,
) -> Vec<ModelRef> {
let authors: Vec<&ModelRef> = record
.commits
.iter()
.filter(|commit| commit.sha.is_some())
.filter_map(|commit| commit.by.as_ref())
.collect();
let authored: Vec<String> = authors.iter().map(|author| providers(author)).collect();
let mut kept: Vec<ModelRef> = Vec::new();
for reviewer in crew {
let own = authors.contains(&reviewer);
let same_provider = other_provider && authored.contains(&providers(reviewer));
if !own && !same_provider && !kept.contains(reviewer) {
kept.push(reviewer.clone());
}
}
kept
}
pub fn eligible(
reviewers: &[ModelRef],
worker: Option<&ModelRef>,
providers: &dyn Fn(&ModelRef) -> String,
other_provider: bool,
) -> Vec<ModelRef> {
let worker_provider = worker.map(providers);
let mut kept: Vec<ModelRef> = Vec::new();
for reviewer in reviewers {
let own = Some(reviewer) == worker;
let same_provider =
other_provider && worker_provider.as_deref() == Some(providers(reviewer).as_str());
if !own && !same_provider && !kept.contains(reviewer) {
kept.push(reviewer.clone());
}
}
kept
}
#[cfg(test)]
mod tests {
use super::*;
fn provider(worker: &ModelRef) -> String {
match worker.agent.as_str() {
"claude-code" => "claude",
"codex" | "opencode" => "gpt",
_ => "gemini",
}
.to_owned()
}
fn workers(names: &[&str]) -> Vec<ModelRef> {
names.iter().map(|n| n.parse().unwrap()).collect()
}
#[test]
fn reviewers_of_the_workers_provider_are_skipped() {
let reviewers = workers(&["opencode", "antigravity-cli", "codex", "antigravity-cli"]);
let worker: ModelRef = "codex".parse().unwrap();
let kept = eligible(&reviewers, Some(&worker), &provider, true);
assert_eq!(kept, workers(&["antigravity-cli"]));
let kept = eligible(&reviewers, Some(&worker), &provider, false);
assert_eq!(kept, workers(&["opencode", "antigravity-cli"]));
}
#[test]
fn with_no_worker_every_distinct_reviewer_stays() {
let reviewers = workers(&["codex", "codex", "claude-code"]);
assert_eq!(
eligible(&reviewers, None, &provider, true),
workers(&["codex", "claude-code"])
);
}
#[test]
fn an_unknown_task_has_no_reviewers() {
let other = TaskId::new("task-009").unwrap();
let crew = workers(&["codex"]);
assert!(for_task(&RunState::default(), &other, &provider, true, &crew).is_empty());
}
#[test]
fn a_task_that_names_no_reviewers_takes_the_crews_without_its_authors() {
use argonauts_core::{CommitInfo, DiffStat};
let task = TaskId::new("task-001").unwrap();
let mut record = TaskEntry::new(crate::testutil::task_spec("task-001", &[], None));
let commit = |by: &str, sha: Option<&str>| CommitInfo {
branch: "argonauts/run/task-001".to_owned(),
sha: sha.map(str::to_owned),
message: String::new(),
stat: DiffStat::default(),
by: Some(by.parse().unwrap()),
};
record.commits = vec![
commit("claude-code", Some("bee47e62")),
commit("codex", None),
];
record.worker = Some("codex".parse().unwrap());
let mut state = RunState::default();
state.tasks.by_id.insert(task.clone(), record);
let crew = workers(&["claude-code", "codex", "antigravity-cli"]);
assert_eq!(
for_task(&state, &task, &provider, true, &crew),
workers(&["codex", "antigravity-cli"])
);
assert!(!none_set(&state, &task, &crew));
assert!(none_set(&state, &task, &[]));
}
}