#include "cli/service/agent/todo_manager.h" #include #include #include #include #include #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "util/platform_paths.h" #ifdef YAZE_WITH_JSON #include "nlohmann/json.hpp" using json = nlohmann::json; #endif namespace yaze { namespace cli { namespace agent { namespace { std::string CurrentTimestamp() { auto now = absl::Now(); return absl::FormatTime("%Y-%m-%d %H:%M:%S", now, absl::LocalTimeZone()); } } // namespace std::string TodoItem::StatusToString() const { switch (status) { case Status::PENDING: return "pending"; case Status::IN_PROGRESS: return "in_progress"; case Status::COMPLETED: return "completed"; case Status::BLOCKED: return "blocked"; case Status::CANCELLED: return "cancelled"; default: return "unknown"; } } TodoItem::Status TodoItem::StringToStatus(const std::string& str) { if (str == "pending") return Status::PENDING; if (str == "in_progress") return Status::IN_PROGRESS; if (str == "completed") return Status::COMPLETED; if (str == "blocked") return Status::BLOCKED; if (str == "cancelled") return Status::CANCELLED; return Status::PENDING; } TodoManager::TodoManager() { auto result = util::PlatformPaths::GetAppDataSubdirectory("agent"); if (result.ok()) { data_dir_ = *result; } else { data_dir_ = std::filesystem::current_path() / ".yaze" / "agent"; } todos_file_ = (std::filesystem::path(data_dir_) / "todos.json").string(); } TodoManager::TodoManager(const std::string& data_dir) : data_dir_(data_dir), todos_file_((std::filesystem::path(data_dir) / "todos.json").string()) {} absl::Status TodoManager::Initialize() { auto status = util::PlatformPaths::EnsureDirectoryExists(data_dir_); if (!status.ok()) { return status; } // Try to load existing TODOs if (util::PlatformPaths::Exists(todos_file_)) { return Load(); } return absl::OkStatus(); } std::string TodoManager::GenerateId() { return absl::StrFormat("todo_%d", next_id_++); } std::string TodoManager::GetTimestamp() const { return CurrentTimestamp(); } absl::StatusOr TodoManager::CreateTodo( const std::string& description, const std::string& category, int priority) { TodoItem item; item.id = GenerateId(); item.description = description; item.category = category; item.priority = priority; item.status = TodoItem::Status::PENDING; item.created_at = GetTimestamp(); item.updated_at = item.created_at; todos_.push_back(item); auto status = Save(); if (!status.ok()) { todos_.pop_back(); // Rollback return status; } return item; } absl::Status TodoManager::UpdateTodo(const std::string& id, const TodoItem& item) { auto it = std::find_if(todos_.begin(), todos_.end(), [&id](const TodoItem& t) { return t.id == id; }); if (it == todos_.end()) { return absl::NotFoundError(absl::StrFormat("TODO with ID %s not found", id)); } TodoItem updated = item; updated.id = id; // Preserve ID updated.updated_at = GetTimestamp(); *it = updated; return Save(); } absl::Status TodoManager::UpdateStatus(const std::string& id, TodoItem::Status status) { auto it = std::find_if(todos_.begin(), todos_.end(), [&id](const TodoItem& t) { return t.id == id; }); if (it == todos_.end()) { return absl::NotFoundError(absl::StrFormat("TODO with ID %s not found", id)); } it->status = status; it->updated_at = GetTimestamp(); return Save(); } absl::StatusOr TodoManager::GetTodo(const std::string& id) const { auto it = std::find_if(todos_.begin(), todos_.end(), [&id](const TodoItem& t) { return t.id == id; }); if (it == todos_.end()) { return absl::NotFoundError(absl::StrFormat("TODO with ID %s not found", id)); } return *it; } std::vector TodoManager::GetAllTodos() const { return todos_; } std::vector TodoManager::GetTodosByStatus(TodoItem::Status status) const { std::vector result; std::copy_if(todos_.begin(), todos_.end(), std::back_inserter(result), [status](const TodoItem& t) { return t.status == status; }); return result; } std::vector TodoManager::GetTodosByCategory(const std::string& category) const { std::vector result; std::copy_if(todos_.begin(), todos_.end(), std::back_inserter(result), [&category](const TodoItem& t) { return t.category == category; }); return result; } bool TodoManager::CanExecute(const TodoItem& item) const { // Check if all dependencies are completed for (const auto& dep_id : item.dependencies) { auto dep_result = GetTodo(dep_id); if (!dep_result.ok()) { return false; // Dependency not found } if (dep_result->status != TodoItem::Status::COMPLETED) { return false; // Dependency not completed } } return true; } absl::StatusOr TodoManager::GetNextActionableTodo() const { // Find pending/blocked TODOs std::vector actionable; for (const auto& item : todos_) { if ((item.status == TodoItem::Status::PENDING || item.status == TodoItem::Status::BLOCKED) && CanExecute(item)) { actionable.push_back(item); } } if (actionable.empty()) { return absl::NotFoundError("No actionable TODOs found"); } // Sort by priority (descending) std::sort(actionable.begin(), actionable.end(), [](const TodoItem& a, const TodoItem& b) { return a.priority > b.priority; }); return actionable[0]; } absl::Status TodoManager::DeleteTodo(const std::string& id) { auto it = std::find_if(todos_.begin(), todos_.end(), [&id](const TodoItem& t) { return t.id == id; }); if (it == todos_.end()) { return absl::NotFoundError(absl::StrFormat("TODO with ID %s not found", id)); } todos_.erase(it); return Save(); } absl::Status TodoManager::ClearCompleted() { auto it = std::remove_if(todos_.begin(), todos_.end(), [](const TodoItem& t) { return t.status == TodoItem::Status::COMPLETED; }); todos_.erase(it, todos_.end()); return Save(); } absl::StatusOr> TodoManager::GenerateExecutionPlan() const { std::vector plan; std::vector pending; // Get all pending TODOs std::copy_if(todos_.begin(), todos_.end(), std::back_inserter(pending), [](const TodoItem& t) { return t.status == TodoItem::Status::PENDING || t.status == TodoItem::Status::BLOCKED; }); // Topological sort based on dependencies std::vector sorted; std::set completed_ids; while (!pending.empty()) { bool made_progress = false; for (auto it = pending.begin(); it != pending.end(); ) { bool can_add = true; for (const auto& dep_id : it->dependencies) { if (completed_ids.find(dep_id) == completed_ids.end()) { can_add = false; break; } } if (can_add) { sorted.push_back(*it); completed_ids.insert(it->id); it = pending.erase(it); made_progress = true; } else { ++it; } } if (!made_progress && !pending.empty()) { return absl::FailedPreconditionError( "Circular dependency detected in TODOs"); } } // Sort by priority within dependency levels std::stable_sort(sorted.begin(), sorted.end(), [](const TodoItem& a, const TodoItem& b) { return a.priority > b.priority; }); return sorted; } absl::Status TodoManager::Save() { #ifdef YAZE_WITH_JSON json j_todos = json::array(); for (const auto& item : todos_) { json j_item; j_item["id"] = item.id; j_item["description"] = item.description; j_item["status"] = item.StatusToString(); j_item["category"] = item.category; j_item["priority"] = item.priority; j_item["dependencies"] = item.dependencies; j_item["tools_needed"] = item.tools_needed; j_item["created_at"] = item.created_at; j_item["updated_at"] = item.updated_at; j_item["notes"] = item.notes; j_todos.push_back(j_item); } std::ofstream file(todos_file_); if (!file.is_open()) { return absl::InternalError( absl::StrFormat("Failed to open file: %s", todos_file_)); } file << j_todos.dump(2); return absl::OkStatus(); #else return absl::UnimplementedError("JSON support required for TODO persistence"); #endif } absl::Status TodoManager::Load() { #ifdef YAZE_WITH_JSON std::ifstream file(todos_file_); if (!file.is_open()) { return absl::InternalError( absl::StrFormat("Failed to open file: %s", todos_file_)); } json j_todos; try { file >> j_todos; } catch (const std::exception& e) { return absl::InternalError( absl::StrFormat("Failed to parse JSON: %s", e.what())); } todos_.clear(); for (const auto& j_item : j_todos) { TodoItem item; item.id = j_item.value("id", ""); item.description = j_item.value("description", ""); item.status = TodoItem::StringToStatus(j_item.value("status", "pending")); item.category = j_item.value("category", ""); item.priority = j_item.value("priority", 0); item.dependencies = j_item.value("dependencies", std::vector{}); item.tools_needed = j_item.value("tools_needed", std::vector{}); item.created_at = j_item.value("created_at", ""); item.updated_at = j_item.value("updated_at", ""); item.notes = j_item.value("notes", ""); todos_.push_back(item); // Update next_id_ if (item.id.find("todo_") == 0) { int id_num = std::stoi(item.id.substr(5)); if (id_num >= next_id_) { next_id_ = id_num + 1; } } } return absl::OkStatus(); #else return absl::UnimplementedError("JSON support required for TODO persistence"); #endif } std::string TodoManager::ExportAsJson() const { #ifdef YAZE_WITH_JSON json j_todos = json::array(); for (const auto& item : todos_) { json j_item; j_item["id"] = item.id; j_item["description"] = item.description; j_item["status"] = item.StatusToString(); j_item["category"] = item.category; j_item["priority"] = item.priority; j_item["dependencies"] = item.dependencies; j_item["tools_needed"] = item.tools_needed; j_item["created_at"] = item.created_at; j_item["updated_at"] = item.updated_at; j_item["notes"] = item.notes; j_todos.push_back(j_item); } return j_todos.dump(2); #else return "{}"; #endif } absl::Status TodoManager::ImportFromJson(const std::string& json_str) { #ifdef YAZE_WITH_JSON try { json j_todos = json::parse(json_str); todos_.clear(); for (const auto& j_item : j_todos) { TodoItem item; item.id = j_item.value("id", ""); item.description = j_item.value("description", ""); item.status = TodoItem::StringToStatus(j_item.value("status", "pending")); item.category = j_item.value("category", ""); item.priority = j_item.value("priority", 0); item.dependencies = j_item.value("dependencies", std::vector{}); item.tools_needed = j_item.value("tools_needed", std::vector{}); item.created_at = j_item.value("created_at", ""); item.updated_at = j_item.value("updated_at", ""); item.notes = j_item.value("notes", ""); todos_.push_back(item); } return Save(); } catch (const std::exception& e) { return absl::InternalError( absl::StrFormat("Failed to parse JSON: %s", e.what())); } #else return absl::UnimplementedError("JSON support required for TODO import"); #endif } } // namespace agent } // namespace cli } // namespace yaze