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 __EMSCRIPTEN__
17#endif
18
19#ifdef YAZE_WITH_JSON
20#include "nlohmann/json.hpp"
21using json = nlohmann::json;
22#endif
23
24namespace yaze {
25namespace cli {
26namespace agent {
27
28namespace {
29
30std::string CurrentTimestamp() {
31 auto now = absl::Now();
32 return absl::FormatTime("%Y-%m-%d %H:%M:%S", now, absl::LocalTimeZone());
33}
34
35} // namespace
36
37std::string TodoItem::StatusToString() const {
38 switch (status) {
39 case Status::PENDING:
40 return "pending";
42 return "in_progress";
44 return "completed";
45 case Status::BLOCKED:
46 return "blocked";
48 return "cancelled";
49 default:
50 return "unknown";
51 }
52}
53
55 if (str == "pending")
56 return Status::PENDING;
57 if (str == "in_progress")
59 if (str == "completed")
60 return Status::COMPLETED;
61 if (str == "blocked")
62 return Status::BLOCKED;
63 if (str == "cancelled")
64 return Status::CANCELLED;
65 return Status::PENDING;
66}
67
70 if (result.ok()) {
71 data_dir_ = result->string();
72 } else {
73 data_dir_ = (std::filesystem::current_path() / ".yaze" / "agent").string();
74 }
75 todos_file_ = (std::filesystem::path(data_dir_) / "todos.json").string();
76}
77
78TodoManager::TodoManager(const std::string& data_dir)
79 : data_dir_(data_dir),
80 todos_file_((std::filesystem::path(data_dir) / "todos.json").string()) {}
81
83#ifdef __EMSCRIPTEN__
84 // For WASM, we try to load from IndexedDB immediately
85 return Load();
86#else
88 if (!status.ok()) {
89 return status;
90 }
91
92 // Try to load existing TODOs
94 return Load();
95 }
96
97 return absl::OkStatus();
98#endif
99}
100
102 return absl::StrFormat("todo_%d", next_id_++);
103}
104
105std::string TodoManager::GetTimestamp() const {
106 return CurrentTimestamp();
107}
108
109absl::StatusOr<TodoItem> TodoManager::CreateTodo(const std::string& description,
110 const std::string& category,
111 int priority) {
112 TodoItem item;
113 item.id = GenerateId();
115 item.category = category;
116 item.priority = priority;
118 item.created_at = GetTimestamp();
119 item.updated_at = item.created_at;
120
121 todos_.push_back(item);
122
123 auto status = Save();
124 if (!status.ok()) {
125 todos_.pop_back(); // Rollback
126 return status;
127 }
128
129 return item;
130}
131
132absl::Status TodoManager::UpdateTodo(const std::string& id,
133 const TodoItem& item) {
134 auto it = std::find_if(todos_.begin(), todos_.end(),
135 [&id](const TodoItem& t) { return t.id == id; });
136
137 if (it == todos_.end()) {
138 return absl::NotFoundError(
139 absl::StrFormat("TODO with ID %s not found", id));
140 }
141
142 TodoItem updated = item;
143 updated.id = id; // Preserve ID
144 updated.updated_at = GetTimestamp();
145
146 *it = updated;
147 return Save();
148}
149
150absl::Status TodoManager::UpdateStatus(const std::string& id,
151 TodoItem::Status status) {
152 auto it = std::find_if(todos_.begin(), todos_.end(),
153 [&id](const TodoItem& t) { return t.id == id; });
154
155 if (it == todos_.end()) {
156 return absl::NotFoundError(
157 absl::StrFormat("TODO with ID %s not found", id));
158 }
159
160 it->status = status;
161 it->updated_at = GetTimestamp();
162
163 return Save();
164}
165
166absl::StatusOr<TodoItem> TodoManager::GetTodo(const std::string& id) const {
167 auto it = std::find_if(todos_.begin(), todos_.end(),
168 [&id](const TodoItem& t) { return t.id == id; });
169
170 if (it == todos_.end()) {
171 return absl::NotFoundError(
172 absl::StrFormat("TODO with ID %s not found", id));
173 }
174
175 return *it;
176}
177
178std::vector<TodoItem> TodoManager::GetAllTodos() const {
179 return todos_;
180}
181
182std::vector<TodoItem> TodoManager::GetTodosByStatus(
183 TodoItem::Status status) const {
184 std::vector<TodoItem> result;
185 std::copy_if(todos_.begin(), todos_.end(), std::back_inserter(result),
186 [status](const TodoItem& t) { return t.status == status; });
187 return result;
188}
189
191 const std::string& category) const {
192 std::vector<TodoItem> result;
193 std::copy_if(
194 todos_.begin(), todos_.end(), std::back_inserter(result),
195 [&category](const TodoItem& t) { return t.category == category; });
196 return result;
197}
198
199bool TodoManager::CanExecute(const TodoItem& item) const {
200 // Check if all dependencies are completed
201 for (const auto& dep_id : item.dependencies) {
202 auto dep_result = GetTodo(dep_id);
203 if (!dep_result.ok()) {
204 return false; // Dependency not found
205 }
206 if (dep_result->status != TodoItem::Status::COMPLETED) {
207 return false; // Dependency not completed
208 }
209 }
210 return true;
211}
212
213absl::StatusOr<TodoItem> TodoManager::GetNextActionableTodo() const {
214 // Find pending/blocked TODOs
215 std::vector<TodoItem> actionable;
216 for (const auto& item : todos_) {
217 if ((item.status == TodoItem::Status::PENDING ||
218 item.status == TodoItem::Status::BLOCKED) &&
219 CanExecute(item)) {
220 actionable.push_back(item);
221 }
222 }
223
224 if (actionable.empty()) {
225 return absl::NotFoundError("No actionable TODOs found");
226 }
227
228 // Sort by priority (descending)
229 std::sort(actionable.begin(), actionable.end(),
230 [](const TodoItem& a, const TodoItem& b) {
231 return a.priority > b.priority;
232 });
233
234 return actionable[0];
235}
236
237absl::Status TodoManager::DeleteTodo(const std::string& id) {
238 auto it = std::find_if(todos_.begin(), todos_.end(),
239 [&id](const TodoItem& t) { return t.id == id; });
240
241 if (it == todos_.end()) {
242 return absl::NotFoundError(
243 absl::StrFormat("TODO with ID %s not found", id));
244 }
245
246 todos_.erase(it);
247 return Save();
248}
249
251 auto it = std::remove_if(todos_.begin(), todos_.end(), [](const TodoItem& t) {
252 return t.status == TodoItem::Status::COMPLETED;
253 });
254 todos_.erase(it, todos_.end());
255 return Save();
256}
257
258absl::StatusOr<std::vector<TodoItem>> TodoManager::GenerateExecutionPlan()
259 const {
260 std::vector<TodoItem> plan;
261 std::vector<TodoItem> pending;
262
263 // Get all pending TODOs
264 std::copy_if(todos_.begin(), todos_.end(), std::back_inserter(pending),
265 [](const TodoItem& t) {
266 return t.status == TodoItem::Status::PENDING ||
267 t.status == TodoItem::Status::BLOCKED;
268 });
269
270 // Topological sort based on dependencies
271 std::vector<TodoItem> sorted;
272 std::set<std::string> completed_ids;
273
274 while (!pending.empty()) {
275 bool made_progress = false;
276
277 for (auto it = pending.begin(); it != pending.end();) {
278 bool can_add = true;
279 for (const auto& dep_id : it->dependencies) {
280 if (completed_ids.find(dep_id) == completed_ids.end()) {
281 can_add = false;
282 break;
283 }
284 }
285
286 if (can_add) {
287 sorted.push_back(*it);
288 completed_ids.insert(it->id);
289 it = pending.erase(it);
290 made_progress = true;
291 } else {
292 ++it;
293 }
294 }
295
296 if (!made_progress && !pending.empty()) {
297 return absl::FailedPreconditionError(
298 "Circular dependency detected in TODOs");
299 }
300 }
301
302 // Sort by priority within dependency levels
303 std::stable_sort(sorted.begin(), sorted.end(),
304 [](const TodoItem& a, const TodoItem& b) {
305 return a.priority > b.priority;
306 });
307
308 return sorted;
309}
310
311absl::Status TodoManager::Save() {
312#ifdef YAZE_WITH_JSON
313 json j_todos = json::array();
314
315 for (const auto& item : todos_) {
316 json j_item;
317 j_item["id"] = item.id;
318 j_item["description"] = item.description;
319 j_item["status"] = item.StatusToString();
320 j_item["category"] = item.category;
321 j_item["priority"] = item.priority;
322 j_item["dependencies"] = item.dependencies;
323 j_item["tools_needed"] = item.tools_needed;
324 j_item["created_at"] = item.created_at;
325 j_item["updated_at"] = item.updated_at;
326 j_item["notes"] = item.notes;
327
328 j_todos.push_back(j_item);
329 }
330
331#ifdef __EMSCRIPTEN__
332 // For WASM, save to IndexedDB via WasmStorage
333 // We use "agent_todos" as the project key
334 using yaze::platform::WasmStorage;
335 return WasmStorage::SaveProject("agent_todos", j_todos.dump());
336#else
337 std::ofstream file(todos_file_);
338 if (!file.is_open()) {
339 return absl::InternalError(
340 absl::StrFormat("Failed to open file: %s", todos_file_));
341 }
342
343 file << j_todos.dump(2);
344 return absl::OkStatus();
345#endif
346
347#else
348 return absl::UnimplementedError("JSON support required for TODO persistence");
349#endif
350}
351
352absl::Status TodoManager::Load() {
353#ifdef YAZE_WITH_JSON
354 json j_todos;
355
356#ifdef __EMSCRIPTEN__
357 // For WASM, load from IndexedDB via WasmStorage
358 using yaze::platform::WasmStorage;
359 auto result = WasmStorage::LoadProject("agent_todos");
360 if (!result.ok()) {
361 // If not found, it might be first run, which is fine
362 if (absl::IsNotFound(result.status())) {
363 todos_.clear();
364 return absl::OkStatus();
365 }
366 return result.status();
367 }
368
369 try {
370 j_todos = json::parse(*result);
371 } catch (const std::exception& e) {
372 return absl::InternalError(
373 absl::StrFormat("Failed to parse persisted JSON: %s", e.what()));
374 }
375#else
376 std::ifstream file(todos_file_);
377 if (!file.is_open()) {
378 return absl::InternalError(
379 absl::StrFormat("Failed to open file: %s", todos_file_));
380 }
381
382 try {
383 file >> j_todos;
384 } catch (const std::exception& e) {
385 return absl::InternalError(
386 absl::StrFormat("Failed to parse JSON: %s", e.what()));
387 }
388#endif
389
390 todos_.clear();
391 for (const auto& j_item : j_todos) {
392 TodoItem item;
393 item.id = j_item.value("id", "");
394 item.description = j_item.value("description", "");
395 item.status = TodoItem::StringToStatus(j_item.value("status", "pending"));
396 item.category = j_item.value("category", "");
397 item.priority = j_item.value("priority", 0);
398 item.dependencies =
399 j_item.value("dependencies", std::vector<std::string>{});
400 item.tools_needed =
401 j_item.value("tools_needed", std::vector<std::string>{});
402 item.created_at = j_item.value("created_at", "");
403 item.updated_at = j_item.value("updated_at", "");
404 item.notes = j_item.value("notes", "");
405
406 todos_.push_back(item);
407
408 // Update next_id_
409 if (item.id.find("todo_") == 0) {
410 int id_num = std::stoi(item.id.substr(5));
411 if (id_num >= next_id_) {
412 next_id_ = id_num + 1;
413 }
414 }
415 }
416
417 return absl::OkStatus();
418#else
419 return absl::UnimplementedError("JSON support required for TODO persistence");
420#endif
421}
422
423std::string TodoManager::ExportAsJson() const {
424#ifdef YAZE_WITH_JSON
425 json j_todos = json::array();
426
427 for (const auto& item : todos_) {
428 json j_item;
429 j_item["id"] = item.id;
430 j_item["description"] = item.description;
431 j_item["status"] = item.StatusToString();
432 j_item["category"] = item.category;
433 j_item["priority"] = item.priority;
434 j_item["dependencies"] = item.dependencies;
435 j_item["tools_needed"] = item.tools_needed;
436 j_item["created_at"] = item.created_at;
437 j_item["updated_at"] = item.updated_at;
438 j_item["notes"] = item.notes;
439
440 j_todos.push_back(j_item);
441 }
442
443 return j_todos.dump(2);
444#else
445 return "{}";
446#endif
447}
448
449absl::Status TodoManager::ImportFromJson(const std::string& json_str) {
450#ifdef YAZE_WITH_JSON
451 try {
452 json j_todos = json::parse(json_str);
453
454 todos_.clear();
455 for (const auto& j_item : j_todos) {
456 TodoItem item;
457 item.id = j_item.value("id", "");
458 item.description = j_item.value("description", "");
459 item.status = TodoItem::StringToStatus(j_item.value("status", "pending"));
460 item.category = j_item.value("category", "");
461 item.priority = j_item.value("priority", 0);
462 item.dependencies =
463 j_item.value("dependencies", std::vector<std::string>{});
464 item.tools_needed =
465 j_item.value("tools_needed", std::vector<std::string>{});
466 item.created_at = j_item.value("created_at", "");
467 item.updated_at = j_item.value("updated_at", "");
468 item.notes = j_item.value("notes", "");
469
470 todos_.push_back(item);
471 }
472
473 return Save();
474 } catch (const std::exception& e) {
475 return absl::InternalError(
476 absl::StrFormat("Failed to parse JSON: %s", e.what()));
477 }
478#else
479 return absl::UnimplementedError("JSON support required for TODO import");
480#endif
481}
482
483} // namespace agent
484} // namespace cli
485} // 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.
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