use super::{apply_shared_config_defaults, apply_workspace_config_defaults, global_flag_from_command_line,
parse_cli_with_config_defaults};
use crate::cli::{Cli, SharedOpts};
use crate::malvin_config_file::AgentConfig;
use clap::{CommandFactory, FromArgMatches};
pub(super) fn write_agent_config(work_dir: &std::path::Path) {
let path = crate::malvin_config_path(work_dir);
let mut text = std::fs::read_to_string(&path).expect("read");
if text.contains("[agent]") {
text = text
.lines()
.filter(|line| {
!line.starts_with("model =")
&& !line.starts_with("max_loops =")
&& !line.starts_with("max_loops_code =")
&& !line.starts_with("max_acp_retries =")
&& *line != "[agent]"
})
.collect::<Vec<_>>()
.join("\n");
}
text.push_str(
"\n[agent]\nmodel = \"cursor:cfg-model\"\nmax_loops = 9\nmax_loops_code = 7\nmax_acp_retries = 8\n",
);
std::fs::write(&path, text).expect("write");
}
pub(super) fn with_seeded_agent_config(f: impl FnOnce()) {
crate::test_utils::with_isolated_home(|work| {
let cwd = std::env::current_dir().expect("cwd");
std::env::set_current_dir(work).expect("chdir");
crate::malvin_config_file::open_malvin_config(work).expect("seed");
write_agent_config(work);
f();
std::env::set_current_dir(cwd).expect("restore cwd");
});
}
#[test]
fn write_agent_config_adds_agent_section_to_partial_file() {
crate::test_utils::with_isolated_home(|work| {
let path = crate::malvin_config_path(work);
std::fs::create_dir_all(path.parent().expect("parent")).expect("mkdir");
std::fs::write(&path, "mem_limit_gb = 2\n").expect("write");
write_agent_config(work);
let text = std::fs::read_to_string(&path).expect("read");
assert!(text.contains("[agent]"));
assert!(text.contains("model = \"cursor:cfg-model\""));
});
}
#[test]
fn flag_and_shared_helpers_detect_and_apply_defaults() {
let matches = Cli::command().get_matches_from(["malvin", "admin", "models"]);
assert!(!global_flag_from_command_line(&matches, "model"));
let agent = AgentConfig {
model: crate::model_id::parse_model_id("cursor:cfg").expect("model"),
max_loops: 8,
max_hypotheses: crate::malvin_config_file::DEFAULT_MAX_HYPOTHESES,
max_loops_code: 6,
max_acp_retries: 6,
};
let mut shared = SharedOpts {
model: crate::model_id::parse_model_id("cursor:old").expect("model"),
verbose: false,
max_acp_retries: 1,
doc: false,
};
apply_shared_config_defaults(&matches, &mut shared, &agent);
assert_eq!(shared.model.canonical(), "cursor:cfg");
assert_eq!(shared.max_acp_retries, 6);
}
#[test]
fn apply_workspace_config_defaults_overrides_unset_flags_for_gates_only() {
with_seeded_agent_config(|| {
let matches = Cli::command().get_matches_from(["malvin", "-g"]);
let mut cli = Cli::from_arg_matches(&matches).expect("cli");
apply_workspace_config_defaults(&matches, &mut cli).expect("apply");
assert_eq!(cli.shared.model.canonical(), "cursor:cfg-model");
assert_eq!(cli.shared.max_acp_retries, 8);
assert!(cli.command.is_none());
assert_eq!(cli.router.max_loops, 7);
});
}
#[test]
fn apply_workspace_config_defaults_respects_explicit_cli_flags_for_gates_only() {
with_seeded_agent_config(|| {
let matches = Cli::command().get_matches_from([
"malvin",
"--model",
"cursor:cli-model",
"--max-acp-retries",
"2",
"-g",
"--max-loops",
"3",
]);
let mut cli = Cli::from_arg_matches(&matches).expect("cli");
apply_workspace_config_defaults(&matches, &mut cli).expect("apply");
assert_eq!(cli.shared.model.canonical(), "cursor:cli-model");
assert_eq!(cli.shared.max_acp_retries, 2);
assert_eq!(cli.router.max_loops, 3);
});
}
#[test]
fn apply_workspace_config_defaults_for_admin_command() {
with_seeded_agent_config(|| {
let matches = Cli::command().get_matches_from(["malvin", "admin", "models"]);
let mut cli = Cli::from_arg_matches(&matches).expect("cli");
apply_workspace_config_defaults(&matches, &mut cli).expect("apply");
assert_eq!(cli.shared.model.canonical(), "cursor:cfg-model");
assert!(matches!(cli.command, Some(crate::cli::Commands::Admin(_))));
});
}
#[test]
fn apply_workspace_config_defaults_skips_do() {
crate::test_utils::with_isolated_home(|work| {
let cwd = std::env::current_dir().expect("cwd");
std::env::set_current_dir(work).expect("chdir");
let config_path = crate::malvin_config_path(work);
assert!(!config_path.exists());
let do_matches = Cli::command().get_matches_from(["malvin", "--do", "hello"]);
let mut do_cli = Cli::from_arg_matches(&do_matches).expect("cli");
apply_workspace_config_defaults(&do_matches, &mut do_cli).expect("apply");
assert!(!config_path.exists());
std::env::set_current_dir(cwd).expect("restore cwd");
});
}
#[test]
fn parse_cli_with_config_defaults_gates_only() {
crate::test_utils::with_isolated_home(|work| {
let cwd = std::env::current_dir().expect("cwd");
std::env::set_current_dir(work).expect("chdir");
let (cli, _) = parse_cli_with_config_defaults(["malvin", "-g"]).expect("parse");
assert!(cli.command.is_none());
assert!(cli.request.is_none());
assert!(cli.router.gates);
assert!(cli.router.max_loops >= 1);
std::env::set_current_dir(cwd).expect("restore cwd");
});
}