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}