use clap::Subcommand;
use crate::daemon::client::DaemonClient;
use crate::daemon::protocol::Method;
#[derive(Subcommand, Debug)]
pub enum SatCommands {
Run {
#[arg(conflicts_with = "target")]
run_id: Option<String>,
#[arg(short, long, default_value = ".")]
workspace: String,
#[arg(long)]
target: Option<String>,
},
}
pub async fn handle(command: SatCommands, client: &mut DaemonClient) -> anyhow::Result<()> {
match command {
SatCommands::Run {
run_id,
workspace,
target,
} => {
if let Some(ref target_path) = target {
let ws = if workspace == "." {
target_path.as_str()
} else {
&workspace
};
return handle_standalone_sat(target_path, ws).await;
}
let run_id =
run_id.ok_or_else(|| anyhow::anyhow!("either --run-id or --target is required"))?;
println!("Running SAT for run {}...", &run_id[..8.min(run_id.len())]);
let params = serde_json::json!({
"kind": "sat_verify",
"issue": 0,
"workspace": workspace,
"run_id": run_id,
});
let response = client.send(Method::WorkflowStart, params).await?;
if let Some(err) = DaemonClient::extract_error(&response) {
eprintln!("SAT failed: {err}");
std::process::exit(1);
}
let result = DaemonClient::extract_result(&response)
.ok_or_else(|| anyhow::anyhow!("no result"))?;
println!();
println!("SAT Result:");
println!("{}", serde_json::to_string_pretty(result)?);
Ok(())
}
}
}
async fn handle_standalone_sat(repo: &str, workspace: &str) -> anyhow::Result<()> {
use darq_core::config::{load_or_default, load_sat_config};
use darq_core::sat::{SatContext, execute_sat};
use std::path::PathBuf;
let workspace_dir = PathBuf::from(workspace);
let sat_config = load_sat_config(&workspace_dir).map_err(|e| anyhow::anyhow!(e))?;
let config = load_or_default();
let agent_settings = config.agent.clone();
let test_command = config.pipeline.test_command.clone();
let code_diff = {
use std::process::Command;
let output = Command::new("git")
.args(["diff", "HEAD"])
.current_dir(&workspace_dir)
.output();
match output {
Ok(o) if o.status.success() => {
let diff = String::from_utf8_lossy(&o.stdout).to_string();
if diff.is_empty() {
let output2 = Command::new("git")
.args(["diff", "HEAD~1"])
.current_dir(&workspace_dir)
.output();
match output2 {
Ok(o2) if o2.status.success() => {
let d = String::from_utf8_lossy(&o2.stdout).to_string();
if d.is_empty() { None } else { Some(d) }
}
_ => None,
}
} else {
Some(diff)
}
}
_ => None,
}
};
let ctx = SatContext {
workspace_dir: workspace_dir.clone(),
project_dir: workspace_dir.clone(),
parent_run_id: format!("sat-target-{}", repo),
config: sat_config,
test_command,
code_diff,
agent_settings,
};
println!("Running SAT against: {}", repo);
println!(" Workspace: {}", workspace);
println!(" Test command: {}", ctx.test_command);
println!(" Personas: {}", ctx.config.personas.len());
println!(
" Multi-judge: {}",
if ctx
.config
.judges
.as_ref()
.map(|j| j.enabled)
.unwrap_or(false)
{
"enabled"
} else {
"disabled"
}
);
let result = execute_sat(&ctx).await?;
println!();
println!("SAT Result:");
println!("{}", serde_json::to_string_pretty(&result.result)?);
Ok(())
}