1pub mod todo_write;
2pub mod types;
3
4#[cfg(test)]
5mod test;
6
7pub use todo_write::todo_write;
8pub use types::{
9 Nag, PlanError, TodoItem, TodoItemInput, TodoList, TodoStatus, TodoWriteInput, TodoWriteOutput,
10};
11
12use crate::ToolDescription;
13
14pub struct Plan {
17 list: TodoList,
18
19 pub description_todo_write: ToolDescription,
21}
22
23impl Default for Plan {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl Plan {
30 #[must_use]
32 pub fn new() -> Self {
33 Self {
34 list: TodoList::default(),
35 description_todo_write: serde_json::json!({
36 "name": "plan_todo_write",
37 "description": concat!(
38 "Write the full TODO list. This replaces the previous list ",
39 "entirely: send ALL tasks on every call, including unchanged ",
40 "ones. To mark a task in_progress, completed, or cancelled, ",
41 "rewrite the whole list with the updated status. Exactly one ",
42 "task may be in_progress at a time. Ids (task-1..N) are ",
43 "assigned in listed order; use the optional per-item `key` so ",
44 "sibling tasks can reference each other in `depends_on` within ",
45 "the same call. Call with an empty list to clear the plan.\n",
46 "Example:\n",
47 "{\"todos\": [{\"description\": \"install deps\", \"key\": \"deps\"}, ",
48 "{\"description\": \"write tests\", \"status\": \"pending\", ",
49 "\"depends_on\": [\"deps\"]}]}"
50 ),
51 "inputSchema": {
52 "type": "object",
53 "properties": {
54 "todos": {
55 "type": "array",
56 "description": "The complete task list, replacing the current TODO state. Ids are assigned as task-1..N in listed order.",
57 "items": {
58 "type": "object",
59 "properties": {
60 "key": { "type": "string", "description": "Optional alias other tasks in this same call reference in depends_on; resolved to the real task-N id" },
61 "description": { "type": "string", "description": "Task description" },
62 "status": { "type": "string", "enum": ["pending", "in_progress", "completed", "cancelled"], "description": "Defaults to pending when omitted. Exactly one task may be in_progress." },
63 "depends_on": { "type": "array", "items": { "type": "string" }, "description": "Sibling keys or task ids this task depends on" }
64 },
65 "required": ["description"]
66 }
67 }
68 },
69 "required": ["todos"]
70 }
71 }),
72 }
73 }
74
75 #[must_use]
77 pub const fn list(&self) -> &TodoList {
78 &self.list
79 }
80
81 pub fn restore_list(&mut self, list: TodoList) {
83 self.list = list;
84 }
85
86 pub fn todo_write(&mut self, todos: &[TodoItemInput]) -> Result<TodoWriteOutput, PlanError> {
92 let output = todo_write(todos)?;
93 self.list = output.list.clone();
94 Ok(output)
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::Plan;
101
102 #[test]
103 fn new_plan_has_an_empty_list() {
104 let plan = Plan::new();
105 assert!(plan.list().items.is_empty());
106 }
107}