ceylon_next/goal/
mod.rs

1//! Goal-oriented task planning and tracking system.
2//!
3//! This module provides structures for representing goals, sub-goals, and success criteria,
4//! enabling agents to break down complex tasks and track progress systematically.
5
6use serde::{Deserialize, Serialize};
7
8// ============================================
9// Goal Understanding Structures
10// ============================================
11
12/// Represents a structured goal with sub-goals and success criteria.
13///
14/// Goals help agents understand what they're trying to achieve and how to
15/// measure success. Each goal can have multiple sub-goals and success criteria.
16///
17/// # Examples
18///
19/// ```rust
20/// use ceylon_next::goal::{Goal, GoalStatus};
21///
22/// let mut goal = Goal::new("Build a REST API".to_string());
23///
24/// // Add success criteria
25/// goal.add_criterion("API responds to HTTP requests".to_string());
26/// goal.add_criterion("All endpoints return valid JSON".to_string());
27///
28/// // Add sub-goals in priority order
29/// goal.add_sub_goal("Design API endpoints".to_string(), 1);
30/// goal.add_sub_goal("Implement request handlers".to_string(), 2);
31/// goal.add_sub_goal("Add error handling".to_string(), 3);
32///
33/// // Mark sub-goal as complete
34/// goal.complete_sub_goal(0, Some("Endpoints designed".to_string()));
35///
36/// println!("{}", goal.get_summary());
37/// ```
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct Goal {
40    /// What we're trying to accomplish
41    pub description: String,
42
43    /// How to know when we're successful
44    pub success_criteria: Vec<SuccessCriterion>,
45
46    /// Smaller steps to achieve the goal
47    pub sub_goals: Vec<SubGoal>,
48
49    /// Current status
50    pub status: GoalStatus,
51}
52
53/// A criterion that must be met for the goal to be considered successful.
54///
55/// Success criteria are measurable conditions that define what "done" means.
56///
57/// # Examples
58///
59/// ```rust
60/// use ceylon_next::goal::SuccessCriterion;
61///
62/// let criterion = SuccessCriterion {
63///     description: "All tests pass".to_string(),
64///     is_met: false,
65///     reason: None,
66/// };
67/// ```
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct SuccessCriterion {
70    /// What needs to be true
71    pub description: String,
72
73    /// Is this criterion met?
74    pub is_met: bool,
75
76    /// Why is it met or not met?
77    pub reason: Option<String>,
78}
79
80/// A smaller goal that's part of achieving the main goal.
81///
82/// Sub-goals break down complex tasks into manageable steps.
83///
84/// # Examples
85///
86/// ```rust
87/// use ceylon_next::goal::SubGoal;
88///
89/// let sub_goal = SubGoal {
90///     description: "Set up database schema".to_string(),
91///     completed: false,
92///     priority: 1,
93///     notes: None,
94/// };
95/// ```
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct SubGoal {
98    /// What this sub-goal achieves
99    pub description: String,
100
101    /// Is this sub-goal complete?
102    pub completed: bool,
103
104    /// Order to work on (lower = first)
105    pub priority: u32,
106
107    /// Notes about progress
108    pub notes: Option<String>,
109}
110
111/// The current status of a goal.
112#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
113pub enum GoalStatus {
114    /// Just created, not started yet
115    NotStarted,
116
117    /// Currently working on it
118    InProgress,
119
120    /// Successfully completed
121    Completed,
122
123    /// Could not complete
124    Failed,
125
126    /// User stopped it
127    Cancelled,
128}
129
130impl Goal {
131    /// Creates a new goal with the given description.
132    ///
133    /// # Arguments
134    ///
135    /// * `description` - A description of what the goal aims to achieve
136    pub fn new(description: String) -> Self {
137        Self {
138            description,
139            success_criteria: Vec::new(),
140            sub_goals: Vec::new(),
141            status: GoalStatus::NotStarted,
142        }
143    }
144
145    /// Adds a success criterion to the goal.
146    ///
147    /// # Arguments
148    ///
149    /// * `description` - The condition that must be true for success
150    pub fn add_criterion(&mut self, description: String) {
151        self.success_criteria.push(SuccessCriterion {
152            description,
153            is_met: false,
154            reason: None,
155        });
156    }
157
158    /// Adds a sub-goal to the goal.
159    ///
160    /// # Arguments
161    ///
162    /// * `description` - What this sub-goal accomplishes
163    /// * `priority` - Execution order (lower numbers are executed first)
164    pub fn add_sub_goal(&mut self, description: String, priority: u32) {
165        self.sub_goals.push(SubGoal {
166            description,
167            completed: false,
168            priority,
169            notes: None,
170        });
171    }
172
173    /// Marks a success criterion as met.
174    ///
175    /// # Arguments
176    ///
177    /// * `index` - The index of the criterion to mark
178    /// * `reason` - Why the criterion is now met
179    pub fn mark_criterion_met(&mut self, index: usize, reason: String) {
180        if let Some(criterion) = self.success_criteria.get_mut(index) {
181            criterion.is_met = true;
182            criterion.reason = Some(reason);
183        }
184    }
185
186    /// Marks a sub-goal as complete.
187    ///
188    /// # Arguments
189    ///
190    /// * `index` - The index of the sub-goal to complete
191    /// * `notes` - Optional notes about the completion
192    pub fn complete_sub_goal(&mut self, index: usize, notes: Option<String>) {
193        if let Some(sub_goal) = self.sub_goals.get_mut(index) {
194            sub_goal.completed = true;
195            sub_goal.notes = notes;
196        }
197    }
198
199    /// Checks if all success criteria are met.
200    ///
201    /// # Returns
202    ///
203    /// `true` if all criteria are met, `false` otherwise
204    pub fn is_successful(&self) -> bool {
205        !self.success_criteria.is_empty()
206            && self.success_criteria.iter().all(|c| c.is_met)
207    }
208
209    /// Checks if all sub-goals are complete.
210    ///
211    /// # Returns
212    ///
213    /// `true` if all sub-goals are complete, `false` otherwise
214    pub fn all_sub_goals_complete(&self) -> bool {
215        !self.sub_goals.is_empty()
216            && self.sub_goals.iter().all(|sg| sg.completed)
217    }
218
219    /// Returns the next sub-goal to work on.
220    ///
221    /// Returns the incomplete sub-goal with the lowest priority number.
222    ///
223    /// # Returns
224    ///
225    /// The next sub-goal to work on, or `None` if all are complete
226    pub fn get_next_sub_goal(&self) -> Option<&SubGoal> {
227        self.sub_goals
228            .iter()
229            .filter(|sg| !sg.completed)
230            .min_by_key(|sg| sg.priority)
231    }
232
233    /// Calculates the progress percentage.
234    ///
235    /// # Returns
236    ///
237    /// Progress as a percentage (0-100)
238    pub fn get_progress(&self) -> u32 {
239        if self.sub_goals.is_empty() {
240            return 0;
241        }
242
243        let completed = self.sub_goals.iter().filter(|sg| sg.completed).count();
244        ((completed as f32 / self.sub_goals.len() as f32) * 100.0) as u32
245    }
246
247    /// Returns a formatted summary of the goal's current state.
248    ///
249    /// # Returns
250    ///
251    /// A multi-line string showing the goal, status, progress, criteria, and sub-goals
252    pub fn get_summary(&self) -> String {
253        let mut summary = format!("Goal: {}\n", self.description);
254        summary.push_str(&format!("Status: {:?}\n", self.status));
255        summary.push_str(&format!("Progress: {}%\n\n", self.get_progress()));
256
257        // Success criteria
258        summary.push_str("Success Criteria:\n");
259        for (i, criterion) in self.success_criteria.iter().enumerate() {
260            let status = if criterion.is_met { "✓" } else { "☐" };
261            summary.push_str(&format!("  {} {}. {}\n", status, i + 1, criterion.description));
262        }
263
264        // Sub-goals
265        summary.push_str("\nSub-goals:\n");
266        for (i, sub_goal) in self.sub_goals.iter().enumerate() {
267            let status = if sub_goal.completed { "✓" } else { "☐" };
268            summary.push_str(&format!("  {} {}. {}\n", status, i + 1, sub_goal.description));
269        }
270
271        summary
272    }
273}
274
275// ============================================
276// Goal Analyzer - Helps create goals from tasks
277// ============================================
278
279/// Utility for helping LLMs analyze tasks and create structured goals.
280///
281/// `GoalAnalyzer` provides methods to generate prompts that guide LLMs
282/// in breaking down tasks into goals with sub-goals and success criteria.
283pub struct GoalAnalyzer;
284
285impl GoalAnalyzer {
286    /// Creates a prompt for LLM to analyze a task and create a goal structure.
287    ///
288    /// # Arguments
289    ///
290    /// * `task_description` - The task to analyze
291    ///
292    /// # Returns
293    ///
294    /// A formatted prompt string for the LLM
295    pub fn create_analysis_prompt(task_description: &str) -> String {
296        format!(
297            r#"Analyze this task and break it down into a clear goal structure.
298
299TASK: "{}"
300
301Please provide:
3021. MAIN GOAL: What is the overall objective? (one sentence)
3032. SUCCESS CRITERIA: What must be true when we're done? (list 2-5 specific things)
3043. SUB-GOALS: What are the steps to achieve this? (list 3-7 ordered steps)
305
306Format your response as JSON:
307{{
308  "main_goal": "description of the main goal",
309  "success_criteria": [
310    "criterion 1",
311    "criterion 2",
312    "criterion 3"
313  ],
314  "sub_goals": [
315    "step 1",
316    "step 2",
317    "step 3"
318  ]
319}}
320
321Be specific and actionable. Think about what information you need and what the final result should look like."#,
322            task_description
323        )
324    }
325}
326
327// ============================================
328// Goal Analysis Response
329// ============================================
330
331/// Response structure from LLM when analyzing a task into a goal.
332///
333/// This structure is used to deserialize the JSON response from the LLM
334/// when it analyzes a task using [`GoalAnalyzer::create_analysis_prompt`].
335///
336/// # Examples
337///
338/// ```rust
339/// use ceylon_next::goal::{GoalAnalysis, Goal};
340/// use serde_json::json;
341///
342/// let json = json!({
343///     "main_goal": "Build a web server",
344///     "success_criteria": ["Server responds to HTTP requests", "Handles errors gracefully"],
345///     "sub_goals": ["Set up HTTP listener", "Implement request routing", "Add error handling"]
346/// });
347///
348/// let analysis: GoalAnalysis = serde_json::from_value(json).unwrap();
349/// let goal = analysis.to_goal();
350/// ```
351#[derive(Debug, Serialize, Deserialize)]
352pub struct GoalAnalysis {
353    /// The main goal description
354    pub main_goal: String,
355    /// List of success criteria
356    pub success_criteria: Vec<String>,
357    /// List of sub-goals in execution order
358    pub sub_goals: Vec<String>,
359}
360
361impl GoalAnalysis {
362    /// Converts this analysis into a [`Goal`] structure.
363    ///
364    /// Sub-goals are assigned priorities based on their order in the list.
365    ///
366    /// # Returns
367    ///
368    /// A new `Goal` with status set to `NotStarted`
369    pub fn to_goal(&self) -> Goal {
370        let mut goal = Goal::new(self.main_goal.clone());
371
372        // Add success criteria
373        for criterion in &self.success_criteria {
374            goal.add_criterion(criterion.clone());
375        }
376
377        // Add sub-goals with priority based on order
378        for (i, sub_goal) in self.sub_goals.iter().enumerate() {
379            goal.add_sub_goal(sub_goal.clone(), i as u32);
380        }
381
382        goal.status = GoalStatus::NotStarted;
383        goal
384    }
385}