yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
todo_manager.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <chrono>
5#include <fstream>
6#include <set>
7#include <sstream>
8
9#include "absl/strings/str_cat.h"
10#include "absl/strings/str_format.h"
11#include "absl/time/clock.h"
12#include "absl/time/time.h"
13#include "util/platform_paths.h"
14
15#ifdef YAZE_WITH_JSON
16#include "nlohmann/json.hpp"
17using json = nlohmann::json;
18#endif
19
20namespace yaze {
21namespace cli {
22namespace agent {
23
24namespace {
25
26std::string CurrentTimestamp() {
27 auto now = absl::Now();
28 return absl::FormatTime("%Y-%m-%d %H:%M:%S", now, absl::LocalTimeZone());
29}
30
31} // namespace
32
33std::string TodoItem::StatusToString() const {
34 switch (status) {
35 case Status::PENDING: return "pending";
36 case Status::IN_PROGRESS: return "in_progress";
37 case Status::COMPLETED: return "completed";
38 case Status::BLOCKED: return "blocked";
39 case Status::CANCELLED: return "cancelled";
40 default: return "unknown";
41 }
42}
43
45 if (str == "pending") return Status::PENDING;
46 if (str == "in_progress") return Status::IN_PROGRESS;
47 if (str == "completed") return Status::COMPLETED;
48 if (str == "blocked") return Status::BLOCKED;
49 if (str == "cancelled") return Status::CANCELLED;
50 return Status::PENDING;
51}
52
55 if (result.ok()) {
56 data_dir_ = result->string();
57 } else {
58 data_dir_ = (std::filesystem::current_path() / ".yaze" / "agent").string();
59 }
60 todos_file_ = (std::filesystem::path(data_dir_) / "todos.json").string();
61}
62
63TodoManager::TodoManager(const std::string& data_dir)
64 : data_dir_(data_dir),
65 todos_file_((std::filesystem::path(data_dir) / "todos.json").string()) {}
66
69 if (!status.ok()) {
70 return status;
71 }
72
73 // Try to load existing TODOs
75 return Load();
76 }
77
78 return absl::OkStatus();
79}
80
82 return absl::StrFormat("todo_%d", next_id_++);
83}
84
85std::string TodoManager::GetTimestamp() const {
86 return CurrentTimestamp();
87}
88
89absl::StatusOr<TodoItem> TodoManager::CreateTodo(
90 const std::string& description,
91 const std::string& category,
92 int priority) {
93 TodoItem item;
94 item.id = GenerateId();
95 item.description = description;
96 item.category = category;
97 item.priority = priority;
99 item.created_at = GetTimestamp();
100 item.updated_at = item.created_at;
101
102 todos_.push_back(item);
103
104 auto status = Save();
105 if (!status.ok()) {
106 todos_.pop_back(); // Rollback
107 return status;
108 }
109
110 return item;
111}
112
113absl::Status TodoManager::UpdateTodo(const std::string& id, const TodoItem& item) {
114 auto it = std::find_if(todos_.begin(), todos_.end(),
115 [&id](const TodoItem& t) { return t.id == id; });
116
117 if (it == todos_.end()) {
118 return absl::NotFoundError(absl::StrFormat("TODO with ID %s not found", id));
119 }
120
121 TodoItem updated = item;
122 updated.id = id; // Preserve ID
123 updated.updated_at = GetTimestamp();
124
125 *it = updated;
126 return Save();
127}
128
129absl::Status TodoManager::UpdateStatus(const std::string& id, TodoItem::Status status) {
130 auto it = std::find_if(todos_.begin(), todos_.end(),
131 [&id](const TodoItem& t) { return t.id == id; });
132
133 if (it == todos_.end()) {
134 return absl::NotFoundError(absl::StrFormat("TODO with ID %s not found", id));
135 }
136
137 it->status = status;
138 it->updated_at = GetTimestamp();
139
140 return Save();
141}
142
143absl::StatusOr<TodoItem> TodoManager::GetTodo(const std::string& id) const {
144 auto it = std::find_if(todos_.begin(), todos_.end(),
145 [&id](const TodoItem& t) { return t.id == id; });
146
147 if (it == todos_.end()) {
148 return absl::NotFoundError(absl::StrFormat("TODO with ID %s not found", id));
149 }
150
151 return *it;
152}
153
154std::vector<TodoItem> TodoManager::GetAllTodos() const {
155 return todos_;
156}
157
158std::vector<TodoItem> TodoManager::GetTodosByStatus(TodoItem::Status status) const {
159 std::vector<TodoItem> result;
160 std::copy_if(todos_.begin(), todos_.end(), std::back_inserter(result),
161 [status](const TodoItem& t) { return t.status == status; });
162 return result;
163}
164
165std::vector<TodoItem> TodoManager::GetTodosByCategory(const std::string& category) const {
166 std::vector<TodoItem> result;
167 std::copy_if(todos_.begin(), todos_.end(), std::back_inserter(result),
168 [&category](const TodoItem& t) { return t.category == category; });
169 return result;
170}
171
172bool TodoManager::CanExecute(const TodoItem& item) const {
173 // Check if all dependencies are completed
174 for (const auto& dep_id : item.dependencies) {
175 auto dep_result = GetTodo(dep_id);
176 if (!dep_result.ok()) {
177 return false; // Dependency not found
178 }
179 if (dep_result->status != TodoItem::Status::COMPLETED) {
180 return false; // Dependency not completed
181 }
182 }
183 return true;
184}
185
186absl::StatusOr<TodoItem> TodoManager::GetNextActionableTodo() const {
187 // Find pending/blocked TODOs
188 std::vector<TodoItem> actionable;
189 for (const auto& item : todos_) {
190 if ((item.status == TodoItem::Status::PENDING ||
191 item.status == TodoItem::Status::BLOCKED) &&
192 CanExecute(item)) {
193 actionable.push_back(item);
194 }
195 }
196
197 if (actionable.empty()) {
198 return absl::NotFoundError("No actionable TODOs found");
199 }
200
201 // Sort by priority (descending)
202 std::sort(actionable.begin(), actionable.end(),
203 [](const TodoItem& a, const TodoItem& b) {
204 return a.priority > b.priority;
205 });
206
207 return actionable[0];
208}
209
210absl::Status TodoManager::DeleteTodo(const std::string& id) {
211 auto it = std::find_if(todos_.begin(), todos_.end(),
212 [&id](const TodoItem& t) { return t.id == id; });
213
214 if (it == todos_.end()) {
215 return absl::NotFoundError(absl::StrFormat("TODO with ID %s not found", id));
216 }
217
218 todos_.erase(it);
219 return Save();
220}
221
223 auto it = std::remove_if(todos_.begin(), todos_.end(),
224 [](const TodoItem& t) {
225 return t.status == TodoItem::Status::COMPLETED;
226 });
227 todos_.erase(it, todos_.end());
228 return Save();
229}
230
231absl::StatusOr<std::vector<TodoItem>> TodoManager::GenerateExecutionPlan() const {
232 std::vector<TodoItem> plan;
233 std::vector<TodoItem> pending;
234
235 // Get all pending TODOs
236 std::copy_if(todos_.begin(), todos_.end(), std::back_inserter(pending),
237 [](const TodoItem& t) {
238 return t.status == TodoItem::Status::PENDING ||
239 t.status == TodoItem::Status::BLOCKED;
240 });
241
242 // Topological sort based on dependencies
243 std::vector<TodoItem> sorted;
244 std::set<std::string> completed_ids;
245
246 while (!pending.empty()) {
247 bool made_progress = false;
248
249 for (auto it = pending.begin(); it != pending.end(); ) {
250 bool can_add = true;
251 for (const auto& dep_id : it->dependencies) {
252 if (completed_ids.find(dep_id) == completed_ids.end()) {
253 can_add = false;
254 break;
255 }
256 }
257
258 if (can_add) {
259 sorted.push_back(*it);
260 completed_ids.insert(it->id);
261 it = pending.erase(it);
262 made_progress = true;
263 } else {
264 ++it;
265 }
266 }
267
268 if (!made_progress && !pending.empty()) {
269 return absl::FailedPreconditionError(
270 "Circular dependency detected in TODOs");
271 }
272 }
273
274 // Sort by priority within dependency levels
275 std::stable_sort(sorted.begin(), sorted.end(),
276 [](const TodoItem& a, const TodoItem& b) {
277 return a.priority > b.priority;
278 });
279
280 return sorted;
281}
282
283absl::Status TodoManager::Save() {
284#ifdef YAZE_WITH_JSON
285 json j_todos = json::array();
286
287 for (const auto& item : todos_) {
288 json j_item;
289 j_item["id"] = item.id;
290 j_item["description"] = item.description;
291 j_item["status"] = item.StatusToString();
292 j_item["category"] = item.category;
293 j_item["priority"] = item.priority;
294 j_item["dependencies"] = item.dependencies;
295 j_item["tools_needed"] = item.tools_needed;
296 j_item["created_at"] = item.created_at;
297 j_item["updated_at"] = item.updated_at;
298 j_item["notes"] = item.notes;
299
300 j_todos.push_back(j_item);
301 }
302
303 std::ofstream file(todos_file_);
304 if (!file.is_open()) {
305 return absl::InternalError(
306 absl::StrFormat("Failed to open file: %s", todos_file_));
307 }
308
309 file << j_todos.dump(2);
310 return absl::OkStatus();
311#else
312 return absl::UnimplementedError("JSON support required for TODO persistence");
313#endif
314}
315
316absl::Status TodoManager::Load() {
317#ifdef YAZE_WITH_JSON
318 std::ifstream file(todos_file_);
319 if (!file.is_open()) {
320 return absl::InternalError(
321 absl::StrFormat("Failed to open file: %s", todos_file_));
322 }
323
324 json j_todos;
325 try {
326 file >> j_todos;
327 } catch (const std::exception& e) {
328 return absl::InternalError(
329 absl::StrFormat("Failed to parse JSON: %s", e.what()));
330 }
331
332 todos_.clear();
333 for (const auto& j_item : j_todos) {
334 TodoItem item;
335 item.id = j_item.value("id", "");
336 item.description = j_item.value("description", "");
337 item.status = TodoItem::StringToStatus(j_item.value("status", "pending"));
338 item.category = j_item.value("category", "");
339 item.priority = j_item.value("priority", 0);
340 item.dependencies = j_item.value("dependencies", std::vector<std::string>{});
341 item.tools_needed = j_item.value("tools_needed", std::vector<std::string>{});
342 item.created_at = j_item.value("created_at", "");
343 item.updated_at = j_item.value("updated_at", "");
344 item.notes = j_item.value("notes", "");
345
346 todos_.push_back(item);
347
348 // Update next_id_
349 if (item.id.find("todo_") == 0) {
350 int id_num = std::stoi(item.id.substr(5));
351 if (id_num >= next_id_) {
352 next_id_ = id_num + 1;
353 }
354 }
355 }
356
357 return absl::OkStatus();
358#else
359 return absl::UnimplementedError("JSON support required for TODO persistence");
360#endif
361}
362
363std::string TodoManager::ExportAsJson() const {
364#ifdef YAZE_WITH_JSON
365 json j_todos = json::array();
366
367 for (const auto& item : todos_) {
368 json j_item;
369 j_item["id"] = item.id;
370 j_item["description"] = item.description;
371 j_item["status"] = item.StatusToString();
372 j_item["category"] = item.category;
373 j_item["priority"] = item.priority;
374 j_item["dependencies"] = item.dependencies;
375 j_item["tools_needed"] = item.tools_needed;
376 j_item["created_at"] = item.created_at;
377 j_item["updated_at"] = item.updated_at;
378 j_item["notes"] = item.notes;
379
380 j_todos.push_back(j_item);
381 }
382
383 return j_todos.dump(2);
384#else
385 return "{}";
386#endif
387}
388
389absl::Status TodoManager::ImportFromJson(const std::string& json_str) {
390#ifdef YAZE_WITH_JSON
391 try {
392 json j_todos = json::parse(json_str);
393
394 todos_.clear();
395 for (const auto& j_item : j_todos) {
396 TodoItem item;
397 item.id = j_item.value("id", "");
398 item.description = j_item.value("description", "");
399 item.status = TodoItem::StringToStatus(j_item.value("status", "pending"));
400 item.category = j_item.value("category", "");
401 item.priority = j_item.value("priority", 0);
402 item.dependencies = j_item.value("dependencies", std::vector<std::string>{});
403 item.tools_needed = j_item.value("tools_needed", std::vector<std::string>{});
404 item.created_at = j_item.value("created_at", "");
405 item.updated_at = j_item.value("updated_at", "");
406 item.notes = j_item.value("notes", "");
407
408 todos_.push_back(item);
409 }
410
411 return Save();
412 } catch (const std::exception& e) {
413 return absl::InternalError(
414 absl::StrFormat("Failed to parse JSON: %s", e.what()));
415 }
416#else
417 return absl::UnimplementedError("JSON support required for TODO import");
418#endif
419}
420
421} // namespace agent
422} // namespace cli
423} // namespace yaze
absl::Status UpdateTodo(const std::string &id, const TodoItem &item)
Update an existing TODO item.
absl::Status ClearCompleted()
Clear all completed TODOs.
absl::Status ImportFromJson(const std::string &json)
Import TODOs from JSON string.
absl::Status Save()
Save TODOs to persistent storage.
absl::StatusOr< std::vector< TodoItem > > GenerateExecutionPlan() const
Generate an execution plan based on dependencies.
std::string GetTimestamp() const
absl::StatusOr< TodoItem > CreateTodo(const std::string &description, const std::string &category="", int priority=0)
Create a new TODO item.
absl::StatusOr< TodoItem > GetNextActionableTodo() const
Get the next actionable TODO (respecting dependencies and priority)
std::string ExportAsJson() const
Export TODOs as JSON string.
std::vector< TodoItem > GetTodosByCategory(const std::string &category) const
Get TODO items by category.
bool CanExecute(const TodoItem &item) const
absl::Status Load()
Load TODOs from persistent storage.
absl::Status DeleteTodo(const std::string &id)
Delete a TODO item.
absl::Status UpdateStatus(const std::string &id, TodoItem::Status status)
Update TODO status.
absl::Status Initialize()
Initialize the TODO manager and load persisted data.
std::vector< TodoItem > GetTodosByStatus(TodoItem::Status status) const
Get TODO items by status.
absl::StatusOr< TodoItem > GetTodo(const std::string &id) const
Get a TODO item by ID.
std::vector< TodoItem > todos_
std::vector< TodoItem > GetAllTodos() const
Get all TODO items.
static absl::StatusOr< std::filesystem::path > GetAppDataSubdirectory(const std::string &subdir)
Get a subdirectory within the app data folder.
static absl::Status EnsureDirectoryExists(const std::filesystem::path &path)
Ensure a directory exists, creating it if necessary.
static bool Exists(const std::filesystem::path &path)
Check if a file or directory exists.
Main namespace for the application.
Represents a single TODO item for task management.
enum yaze::cli::agent::TodoItem::Status status
static Status StringToStatus(const std::string &str)
std::vector< std::string > dependencies
std::vector< std::string > tools_needed
std::string StatusToString() const