use crate::agent::AgentOverrides;
use crate::config;
use crate::contracts::TaskStatus;
use crate::queue;
use crate::queue::RunnableSelectionOptions;
use crate::queue::operations::queue_runnability_report;
use anyhow::Result;
use super::selection::select_run_one_task_index;
pub fn dry_run_one(
resolved: &config::Resolved,
agent_overrides: &AgentOverrides,
target_task_id: Option<&str>,
) -> Result<()> {
let queue_file = queue::load_queue(&resolved.queue_path)?;
let done = queue::load_queue_or_default(&resolved.done_path)?;
let done_ref = if done.tasks.is_empty() && !resolved.done_path.exists() {
None
} else {
Some(&done)
};
let include_draft = agent_overrides.include_draft.unwrap_or(false);
let max_depth = resolved.config.queue.max_dependency_depth.unwrap_or(10);
let warnings = queue::validate_queue_set(
&queue_file,
done_ref,
&resolved.id_prefix,
resolved.id_width,
max_depth,
)?;
queue::log_warnings(&warnings);
let selected = select_run_one_task_index(&queue_file, done_ref, target_task_id, include_draft)?;
if let Some(idx) = selected {
let task = &queue_file.tasks[idx];
println!("Dry run: would run {} (status: {:?})", task.id, task.status);
if task.status == TaskStatus::Doing {
println!(" Note: Task is already in 'doing' status (resuming).");
}
return Ok(());
}
println!("Dry run: no task would be run.");
println!();
let candidates: Vec<_> = queue_file
.tasks
.iter()
.filter(|t| {
t.status == TaskStatus::Todo || (include_draft && t.status == TaskStatus::Draft)
})
.collect();
if candidates.is_empty() {
if include_draft {
println!("No todo or draft tasks found.");
} else {
println!("No todo tasks found.");
}
return Ok(());
}
let options = RunnableSelectionOptions::new(include_draft, true);
match queue_runnability_report(&queue_file, done_ref, options) {
Ok(report) => {
if report.summary.runnable_candidates > 0 {
println!(
"Warning: runnability report found {} runnable candidates but selection returned none.",
report.summary.runnable_candidates
);
} else {
println!("Blockers preventing task execution:");
if report.summary.blocked_by_dependencies > 0 {
println!(
" - {} task(s) blocked by unmet dependencies",
report.summary.blocked_by_dependencies
);
}
if report.summary.blocked_by_schedule > 0 {
println!(
" - {} task(s) blocked by future schedule",
report.summary.blocked_by_schedule
);
}
if report.summary.blocked_by_status_or_flags > 0 {
println!(
" - {} task(s) blocked by status/flags (e.g., draft excluded)",
report.summary.blocked_by_status_or_flags
);
}
println!();
for row in &report.tasks {
let is_candidate = row.status == TaskStatus::Todo
|| (include_draft && row.status == TaskStatus::Draft);
if !is_candidate || row.runnable || row.reasons.is_empty() {
continue;
}
println!("First blocking task: {} (status: {:?})", row.id, row.status);
for reason in &row.reasons {
match reason {
crate::queue::operations::NotRunnableReason::UnmetDependencies { dependencies } => {
if dependencies.len() == 1 {
match &dependencies[0] {
crate::queue::operations::DependencyIssue::Missing { id } => {
println!(" - Missing dependency: {}", id);
}
crate::queue::operations::DependencyIssue::NotComplete { id, status } => {
println!(" - Dependency {} not complete (status: {})", id, status);
}
}
} else {
println!(" - {} unmet dependencies", dependencies.len());
}
}
crate::queue::operations::NotRunnableReason::ScheduledStartInFuture { scheduled_start, seconds_until_runnable, .. } => {
let hours = seconds_until_runnable / 3600;
let minutes = (seconds_until_runnable % 3600) / 60;
if hours > 0 {
println!(" - Scheduled for: {} (in {}h {}m)",
scheduled_start, hours, minutes);
} else {
println!(" - Scheduled for: {} (in {}m)",
scheduled_start, minutes);
}
}
crate::queue::operations::NotRunnableReason::DraftExcluded => {
println!(" - Draft tasks excluded (use --include-draft)");
}
crate::queue::operations::NotRunnableReason::StatusNotRunnable { status } => {
println!(" - Status prevents running: {}", status);
}
}
}
break;
}
}
}
Err(e) => {
println!("Could not generate runnability report: {}", e);
}
}
println!();
println!("Run 'ralph queue explain' for a full report.");
Ok(())
}
pub fn dry_run_loop(resolved: &config::Resolved, agent_overrides: &AgentOverrides) -> Result<()> {
println!("Dry run: simulating run loop (reporting first selection only).");
println!("Note: Subsequent tasks depend on outcomes of earlier tasks.");
println!();
dry_run_one(resolved, agent_overrides, None)
}