1use crate::db::Database;
8use crate::persistence::Persistence;
9
10pub async fn track_progress(
12 db: &Database,
13 session_id: &str,
14 tool_name: &str,
15 _tool_args: &str,
16 tool_result: &str,
17) {
18 let entry = match tool_name {
19 "Write" => extract_write_progress(tool_result),
20 "Edit" => extract_edit_progress(tool_result),
21 "Delete" => extract_delete_progress(tool_result),
22 "Bash" => extract_bash_progress(tool_result),
23 _ => None,
24 };
25
26 if let Some(entry) = entry {
27 append_progress(db, session_id, &entry).await;
28 }
29}
30
31pub async fn get_progress_summary(db: &Database, session_id: &str) -> Option<String> {
33 match db.get_metadata(session_id, "progress").await {
34 Ok(Some(progress)) if !progress.is_empty() => Some(format!(
35 "\n## Session Progress\n\
36 The following actions have been completed this session:\n\
37 {progress}"
38 )),
39 _ => None,
40 }
41}
42
43async fn append_progress(db: &Database, session_id: &str, entry: &str) {
44 let existing = db
45 .get_metadata(session_id, "progress")
46 .await
47 .ok()
48 .flatten()
49 .unwrap_or_default();
50
51 let lines: Vec<&str> = existing.lines().collect();
53 let mut updated = if lines.len() >= 20 {
54 lines[lines.len() - 15..].join("\n")
56 } else {
57 existing
58 };
59
60 if !updated.is_empty() {
61 updated.push('\n');
62 }
63 updated.push_str(entry);
64
65 let _ = db.set_metadata(session_id, "progress", &updated).await;
66}
67
68fn extract_write_progress(result: &str) -> Option<String> {
69 if result.contains("Created") || result.contains("Wrote") {
71 let path = result.lines().next().unwrap_or(result).trim();
72 Some(format!("- \u{2705} {path}"))
73 } else {
74 None
75 }
76}
77
78fn extract_edit_progress(result: &str) -> Option<String> {
79 if result.contains("Applied") || result.contains("edited") || result.contains("replacement") {
80 let first_line = result.lines().next().unwrap_or(result).trim();
81 let short = if first_line.len() > 80 {
82 format!("{}...", &first_line[..80])
83 } else {
84 first_line.to_string()
85 };
86 Some(format!("- \u{270f}\u{fe0f} {short}"))
87 } else {
88 None
89 }
90}
91
92fn extract_delete_progress(result: &str) -> Option<String> {
93 if result.contains("Deleted") || result.contains("removed") {
94 let first_line = result.lines().next().unwrap_or(result).trim();
95 Some(format!("- \u{1f5d1}\u{fe0f} {first_line}"))
96 } else {
97 None
98 }
99}
100
101fn extract_bash_progress(result: &str) -> Option<String> {
102 let lower = result.to_lowercase();
104 if lower.contains("test result: ok") || lower.contains("tests passed") {
105 Some("- \u{2705} Tests passed".to_string())
106 } else if lower.contains("test result: failed") || lower.contains("tests failed") {
107 Some("- \u{274c} Tests failed".to_string())
108 } else if lower.contains("build succeeded")
109 || lower.contains("finished") && lower.contains("target")
110 {
111 Some("- \u{1f3d7}\u{fe0f} Build succeeded".to_string())
112 } else if lower.contains("error:") && lower.contains("could not compile") {
113 Some("- \u{274c} Build failed".to_string())
114 } else {
115 None
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn test_write_progress() {
125 assert!(extract_write_progress("Created file: src/main.rs").is_some());
126 assert!(extract_write_progress("Wrote 100 bytes to foo.rs").is_some());
127 assert!(extract_write_progress("Error: permission denied").is_none());
128 }
129
130 #[test]
131 fn test_edit_progress() {
132 assert!(extract_edit_progress("Applied 2 replacements to src/lib.rs").is_some());
133 assert!(extract_edit_progress("No changes needed").is_none());
134 }
135
136 #[test]
137 fn test_bash_progress() {
138 assert!(extract_bash_progress("test result: ok. 50 passed").is_some());
139 assert!(extract_bash_progress("test result: FAILED. 1 failed").is_some());
140 assert!(extract_bash_progress("hello world").is_none());
141 }
142
143 #[test]
144 fn test_delete_progress() {
145 assert!(extract_delete_progress("Deleted src/old.rs").is_some());
146 assert!(extract_delete_progress("File not found").is_none());
147 }
148}