use std::path::PathBuf;
use crate::adapters::{CliDispatchContext, adapter_for};
use crate::cli::command_target_args;
use crate::core::{AvailableSkill, DispatchMechanism, Eval, Mode, RunContext, mechanism_for};
use super::RunError;
use super::util::mode_str;
mod build;
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: 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>,
}
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,
}
struct Staged {
cond_a_slug: Option<String>,
cond_b_slug: Option<String>,
sibling_skills: Vec<AvailableSkill>,
bootstrap_content: Option<String>,
plan_mode_content: Option<String>,
skills_dir_preexisted: bool,
}
pub fn command_run(ctx: &RunContext, opts: &RunOptions) -> Result<(), RunError> {
let resolved = resolve::resolve_request(ctx, opts)?;
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, &staged)?;
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"
);
}
}
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()
);
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);
match mechanism_for(ctx.harness) {
DispatchMechanism::InSession => println!(
"\nNext: iterate the tasks[] array in dispatch.json and dispatch each task as a subagent, passing its `agent_description` verbatim as the subagent description (that string is the key that links each transcript back — without it tool calls, tokens, and duration come back empty). Then run:\n eval-magic ingest{target_args} --iteration {iteration}\n(ingest auto-resolves the subagents dir from CLAUDE_CODE_SESSION_ID; outside that session, add --session-id <id> or --subagents-dir <path>.)"
),
DispatchMechanism::Cli => println!(
"{}",
adapter_for(ctx.harness).cli_next_steps(CliDispatchContext {
guard: opts.guard,
target_args: &target_args,
iteration,
agent_model: opts.agent_model,
})
),
}
}