use std::path::PathBuf;
use crate::adapters::{CliDispatchContext, adapter_for};
use crate::cli::command_target_args;
use crate::core::{Eval, Mode, RunContext};
use super::RunError;
use super::util::mode_str;
mod build;
mod envs;
mod resolve;
mod stage;
#[derive(Debug, Clone, Default)]
pub struct RunOptions<'a> {
pub mode: Option<&'a str>,
pub baseline: Option<&'a str>,
pub only: Option<&'a [String]>,
pub skip: Option<&'a [String]>,
pub iteration: Option<u32>,
pub dry_run: bool,
pub no_stage: bool,
pub guard: Option<bool>,
pub stage_name: Option<&'a str>,
pub plan_mode: bool,
pub runs: u32,
pub agent_model: Option<&'a str>,
pub judge_model: Option<&'a str>,
pub label: Option<&'a str>,
}
impl RunOptions<'_> {
pub(crate) fn guard_armed(&self) -> bool {
self.guard == Some(true)
}
}
struct Resolved {
mode: Mode,
baseline: Option<String>,
skill_md_path: PathBuf,
iteration: u32,
iteration_dir: PathBuf,
run_nonce: String,
run_tag: String,
cond_a: &'static str,
cond_b: &'static str,
skill_path_a: Option<String>,
skill_path_b: Option<String>,
selected_evals: Vec<Eval>,
total_evals: usize,
groups: Vec<super::grouping::Group>,
}
struct Staged {
cond_a_slug: Option<String>,
cond_b_slug: Option<String>,
sibling_meta: Vec<(String, String)>,
bootstrap_content: Option<String>,
plan_mode_content: Option<String>,
}
pub fn command_run(ctx: &RunContext, opts: &RunOptions) -> Result<(), RunError> {
let resolved = resolve::resolve_request(ctx, opts)?;
let preflight = super::util::harness_run_preflight(
opts,
ctx,
super::util::evals_use_transcript_check(&resolved.selected_evals),
)?;
for warning in &preflight.warnings {
eprintln!("⚠ {warning}");
}
let opts = &preflight.opts;
let mut owned_ctx = ctx.clone();
owned_ctx.stage_root = resolved.iteration_dir.join("env");
let ctx = &owned_ctx;
print_run_plan(ctx, opts, &resolved);
let staged = stage::stage_conditions(ctx, opts, &resolved)?;
let num_tasks = build::write_dispatch(ctx, opts, &resolved, &staged)?;
build::post_build(ctx, opts, &resolved)?;
print_next_steps(ctx, opts, &resolved, num_tasks);
Ok(())
}
fn print_run_plan(ctx: &RunContext, opts: &RunOptions, r: &Resolved) {
println!(
"Preparing {} iteration-{} ({})",
ctx.skill_name,
r.iteration,
mode_str(r.mode)
);
println!(
" {}: {}",
r.cond_a,
r.skill_path_a.as_deref().unwrap_or("(no skill)")
);
println!(
" {}: {}",
r.cond_b,
r.skill_path_b.as_deref().unwrap_or("(no skill)")
);
if r.selected_evals.len() != r.total_evals {
let (flag, ids) = match (opts.only, opts.skip) {
(Some(ids), _) => ("--only", ids),
(_, skip) => ("--skip", skip.unwrap_or(&[])),
};
println!(
" selection: {} of {} evals ({flag} {})",
r.selected_evals.len(),
r.total_evals,
ids.join(", ")
);
}
if opts.no_stage {
println!(
" staging: disabled (--no-stage) — skills will be inlined into dispatch_prompt for harnesses without project-local skill discovery"
);
}
if opts.guard_armed() {
println!(
" guard: armed — the write guard blocks out-of-env writes during dispatch (--no-guard to opt out)"
);
}
}
fn print_next_steps(ctx: &RunContext, opts: &RunOptions, r: &Resolved, num_tasks: usize) {
let iteration = r.iteration;
println!("\nWorkspace prepared: {}", r.iteration_dir.display());
println!(
"Dispatch manifest: {}",
r.iteration_dir.join("dispatch-manifest.md").display()
);
println!(
"Dispatch tasks: {}",
r.iteration_dir.join("dispatch.json").display()
);
println!(
"Runbook: {} — a human-followed copy of the steps below.",
r.iteration_dir.join("RUNBOOK.md").display()
);
let run_counts: Vec<u32> = r
.selected_evals
.iter()
.map(|e| e.runs.unwrap_or(opts.runs))
.collect();
let uniform_runs = run_counts
.first()
.filter(|&&n| run_counts.iter().all(|&m| m == n));
match uniform_runs {
Some(1) => println!(
"\n{} dispatches required ({} evals × 2 conditions).",
num_tasks,
r.selected_evals.len()
),
Some(n) => println!(
"\n{} dispatches required ({} evals × 2 conditions × {n} runs).",
num_tasks,
r.selected_evals.len()
),
None => println!(
"\n{} dispatches required ({} evals × 2 conditions, per-eval run counts).",
num_tasks,
r.selected_evals.len()
),
}
if opts.dry_run {
println!("\n--dry-run: stopping after workspace prep.");
return;
}
let target_args = command_target_args(ctx);
println!(
"{}",
adapter_for(ctx.harness).cli_next_steps(CliDispatchContext {
guard: opts.guard_armed(),
target_args: &target_args,
iteration,
agent_model: opts.agent_model,
})
);
}