An AI agent built to do Ralph loops - plan mode for planning and ralph mode for implementing.
at main 76 lines 2.2 kB view raw
1use chrono::{DateTime, Utc}; 2use serde::{Deserialize, Serialize}; 3use std::fs; 4use std::path::Path; 5 6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 7#[serde(rename_all = "snake_case")] 8pub enum TaskStatus { 9 Pending, 10 InProgress, 11 Complete, 12 Blocked, 13} 14 15#[derive(Debug, Clone, Serialize, Deserialize)] 16pub struct Task { 17 pub id: String, 18 pub title: String, 19 pub description: String, 20 pub acceptance_criteria: Vec<String>, 21 pub status: TaskStatus, 22 #[serde(skip_serializing_if = "Option::is_none")] 23 pub blocked_reason: Option<String>, 24 #[serde(skip_serializing_if = "Option::is_none")] 25 pub completed_at: Option<DateTime<Utc>>, 26} 27 28#[derive(Debug, Clone, Serialize, Deserialize)] 29pub struct Spec { 30 pub name: String, 31 pub description: String, 32 pub branch_name: String, 33 pub created_at: DateTime<Utc>, 34 pub tasks: Vec<Task>, 35 pub learnings: Vec<String>, 36} 37 38impl Spec { 39 /// Load a spec from a JSON file 40 pub fn load(path: impl AsRef<Path>) -> anyhow::Result<Self> { 41 let content = fs::read_to_string(path)?; 42 let spec = serde_json::from_str(&content)?; 43 Ok(spec) 44 } 45 46 /// Save the spec to a JSON file 47 pub fn save(&self, path: impl AsRef<Path>) -> anyhow::Result<()> { 48 let path = path.as_ref(); 49 50 // Create parent directories if they don't exist 51 if let Some(parent) = path.parent() { 52 fs::create_dir_all(parent)?; 53 } 54 55 let json = serde_json::to_string_pretty(self)?; 56 fs::write(path, json)?; 57 Ok(()) 58 } 59 60 /// Find the next task that is pending (not in progress, complete, or blocked) 61 pub fn find_next_task(&self) -> Option<&Task> { 62 self.tasks 63 .iter() 64 .find(|task| task.status == TaskStatus::Pending) 65 } 66 67 /// Find a task by ID and return a mutable reference 68 pub fn find_task_mut(&mut self, task_id: &str) -> Option<&mut Task> { 69 self.tasks.iter_mut().find(|task| task.id == task_id) 70 } 71 72 /// Add a learning to the spec 73 pub fn add_learning(&mut self, learning: String) { 74 self.learnings.push(learning); 75 } 76}