use anyhow::Result;
use std::path::PathBuf;
use crate::models::task::TaskStatus;
use crate::storage::Storage;
use crate::sync::claude_tasks::{claude_tasks_dir, ClaudeTaskList};
pub fn run(project_root: Option<PathBuf>) -> Result<()> {
let project_root = project_root.unwrap_or_else(|| std::env::current_dir().unwrap());
let storage = Storage::new(Some(project_root.clone()));
if !storage.is_initialized() {
return Ok(());
}
let tasks_dir = claude_tasks_dir();
if !tasks_dir.exists() {
return Ok(());
}
let entries = match std::fs::read_dir(&tasks_dir) {
Ok(e) => e,
Err(_) => return Ok(()),
};
for entry in entries.flatten() {
let path = entry.path();
let file_name = match path.file_stem().and_then(|s| s.to_str()) {
Some(name) if name.starts_with("scud-") => name,
_ => continue,
};
let tag = match file_name.strip_prefix("scud-") {
Some(t) => t,
None => continue,
};
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
let claude_list: ClaudeTaskList = match serde_json::from_str(&content) {
Ok(l) => l,
Err(_) => continue,
};
let mut phase = match storage.load_group(tag) {
Ok(p) => p,
Err(_) => continue,
};
let mut changed = false;
for claude_task in &claude_list.tasks {
let task_id = claude_task
.id
.strip_prefix(&format!("{}:", tag))
.unwrap_or(&claude_task.id);
if let Some(scud_task) = phase.get_task_mut(task_id) {
let new_status = match claude_task.status.as_str() {
"pending" => TaskStatus::Pending,
"in_progress" => TaskStatus::InProgress,
"completed" => TaskStatus::Done,
_ => continue,
};
if scud_task.status != new_status {
scud_task.set_status(new_status);
changed = true;
}
}
}
if changed {
if let Err(e) = storage.update_group(tag, &phase) {
eprintln!("Warning: Failed to sync changes for tag '{}': {}", tag, e);
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::phase::Phase;
use crate::models::task::Task;
use crate::sync::claude_tasks::sync_phase;
use tempfile::TempDir;
#[test]
fn test_sync_status_change() {
let tag = format!("sync-test-change-{}", std::process::id());
let tmp = TempDir::new().unwrap();
let storage = Storage::new(Some(tmp.path().to_path_buf()));
storage.initialize().unwrap();
let mut phase = Phase::new(tag.clone());
let task = Task::new(
"1".to_string(),
"Test task".to_string(),
"Description".to_string(),
);
phase.add_task(task);
storage.update_group(&tag, &phase).unwrap();
storage.set_active_group(&tag).unwrap();
sync_phase(&phase, &tag).unwrap();
let tasks_dir = claude_tasks_dir();
let task_file = tasks_dir.join(format!("scud-{}.json", tag));
let mut claude_list: ClaudeTaskList =
serde_json::from_str(&std::fs::read_to_string(&task_file).unwrap()).unwrap();
claude_list.tasks[0].status = "completed".to_string();
std::fs::write(&task_file, serde_json::to_string(&claude_list).unwrap()).unwrap();
run(Some(tmp.path().to_path_buf())).unwrap();
let updated_phase = storage.load_group(&tag).unwrap();
let updated_task = updated_phase.get_task("1").unwrap();
assert_eq!(updated_task.status, TaskStatus::Done);
let _ = std::fs::remove_file(&task_file);
}
#[test]
fn test_sync_ignores_unchanged_status() {
let tag = format!("sync-test-unchanged-{}", std::process::id());
let tmp = TempDir::new().unwrap();
let storage = Storage::new(Some(tmp.path().to_path_buf()));
storage.initialize().unwrap();
let mut phase = Phase::new(tag.clone());
let mut task = Task::new(
"1".to_string(),
"Test task".to_string(),
"Description".to_string(),
);
task.set_status(TaskStatus::Done);
phase.add_task(task);
storage.update_group(&tag, &phase).unwrap();
storage.set_active_group(&tag).unwrap();
let saved_phase = storage.load_group(&tag).unwrap();
let saved_task = saved_phase.get_task("1").unwrap();
assert_eq!(
saved_task.status,
TaskStatus::Done,
"Task should be saved as Done"
);
sync_phase(&phase, &tag).unwrap();
run(Some(tmp.path().to_path_buf())).unwrap();
let updated_phase = storage.load_group(&tag).unwrap();
let updated_task = updated_phase.get_task("1").unwrap();
assert_eq!(updated_task.status, TaskStatus::Done);
let tasks_dir = claude_tasks_dir();
let task_file = tasks_dir.join(format!("scud-{}.json", tag));
let _ = std::fs::remove_file(&task_file);
}
#[test]
fn test_sync_silently_exits_non_scud_project() {
let tmp = TempDir::new().unwrap();
let result = run(Some(tmp.path().to_path_buf()));
assert!(result.is_ok());
}
}