1use anyhow::{Context, Result};
2use chrono::Local;
3use std::fs::{self, OpenOptions};
4use std::io::Write;
5use std::path::PathBuf;
6
7use crate::storage::Storage;
8
9pub fn run(
13 project_root: Option<PathBuf>,
14 task_id: &str,
15 summary: &str,
16 tag: Option<&str>,
17) -> Result<()> {
18 let storage = Storage::new(project_root);
19
20 if !storage.is_initialized() {
21 anyhow::bail!("SCUD not initialized. Run: scud init");
22 }
23
24 let active_tag = match tag {
26 Some(t) => t.to_string(),
27 None => storage
28 .get_active_group()?
29 .ok_or_else(|| anyhow::anyhow!("No active tag. Use --tag or run: scud tags <tag>"))?,
30 };
31
32 let phase = storage.load_group(&active_tag)?;
34 if phase.get_task(task_id).is_none() {
35 anyhow::bail!("Task '{}' not found in tag '{}'", task_id, active_tag);
36 }
37
38 let logs_dir = storage.scud_dir().join("logs");
40 fs::create_dir_all(&logs_dir).context("Failed to create logs directory")?;
41
42 let log_file = logs_dir.join(format!("{}.log", task_id));
44 let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S");
45
46 let mut file = OpenOptions::new()
47 .create(true)
48 .append(true)
49 .open(&log_file)
50 .context("Failed to open log file")?;
51
52 writeln!(file, "--- {} ---", timestamp)?;
53 writeln!(file, "{}", summary.trim())?;
54 writeln!(file)?;
55
56 println!("✓ Log entry added to {}", log_file.display());
57 Ok(())
58}
59
60pub fn show(project_root: Option<PathBuf>, task_id: &str) -> Result<()> {
62 let storage = Storage::new(project_root);
63
64 let logs_dir = storage.scud_dir().join("logs");
65 let log_file = logs_dir.join(format!("{}.log", task_id));
66
67 if !log_file.exists() {
68 println!("No log entries for task '{}'", task_id);
69 return Ok(());
70 }
71
72 let content = fs::read_to_string(&log_file).context("Failed to read log file")?;
73 print!("{}", content);
74 Ok(())
75}
76
77pub fn show_all(project_root: Option<PathBuf>, limit: usize, tag: Option<&str>) -> Result<()> {
79 let storage = Storage::new(project_root);
80 let logs_dir = storage.scud_dir().join("logs");
81
82 if !logs_dir.exists() {
83 println!("No logs directory found. Use 'scud log <task_id> <summary>' to create entries.");
84 return Ok(());
85 }
86
87 let mut entries: Vec<(String, String, String)> = Vec::new(); let tag_task_ids: Option<std::collections::HashSet<String>> = if let Some(t) = tag {
92 let phase = storage.load_group(t)?;
93 Some(phase.tasks.iter().map(|task| task.id.clone()).collect())
94 } else {
95 None
96 };
97
98 for entry in fs::read_dir(&logs_dir).context("Failed to read logs directory")? {
100 let entry = entry?;
101 let path = entry.path();
102
103 if path.extension().map_or(false, |ext| ext == "log") {
104 let task_id = path
105 .file_stem()
106 .and_then(|s| s.to_str())
107 .unwrap_or("unknown")
108 .to_string();
109
110 if let Some(ref ids) = tag_task_ids {
112 if !ids.contains(&task_id) {
113 continue;
114 }
115 }
116
117 let content = fs::read_to_string(&path).unwrap_or_default();
118
119 let mut current_timestamp = String::new();
121 let mut current_content = String::new();
122
123 for line in content.lines() {
124 if line.starts_with("--- ") && line.ends_with(" ---") {
125 if !current_timestamp.is_empty() && !current_content.trim().is_empty() {
127 entries.push((
128 current_timestamp.clone(),
129 task_id.clone(),
130 current_content.trim().to_string(),
131 ));
132 }
133 current_timestamp = line
135 .trim_start_matches("--- ")
136 .trim_end_matches(" ---")
137 .to_string();
138 current_content.clear();
139 } else if !line.is_empty() {
140 current_content.push_str(line);
141 current_content.push('\n');
142 }
143 }
144 if !current_timestamp.is_empty() && !current_content.trim().is_empty() {
146 entries.push((current_timestamp, task_id, current_content.trim().to_string()));
147 }
148 }
149 }
150
151 if entries.is_empty() {
152 println!("No log entries found.");
153 return Ok(());
154 }
155
156 entries.sort_by(|a, b| b.0.cmp(&a.0));
158
159 let entries: Vec<_> = entries.into_iter().take(limit).collect();
161
162 println!("=== Recent Log Entries ({} shown) ===\n", entries.len());
164
165 for (timestamp, task_id, content) in entries {
166 println!("[{}] Task {}", timestamp, task_id);
167 for line in content.lines() {
168 println!(" {}", line);
169 }
170 println!();
171 }
172
173 Ok(())
174}