refactor: Update CMake configurations and enhance TODO management features

- Replaced `${CMAKE_SOURCE_DIR}` with `${PROJECT_SOURCE_DIR}` in CMake files for consistent path handling.
- Introduced new `todo_commands` and `todo_manager` components to manage TODO items, including creation, listing, updating, and deletion functionalities.
- Added detailed implementations for handling TODO commands in the CLI, improving task management capabilities.
- Enhanced the `TodoManager` class to support persistence and execution planning for TODO items.
- Updated CMake configurations to include new source files related to TODO management, ensuring proper integration into the build system.
This commit is contained in:
scawful
2025-10-05 00:19:57 -04:00
parent 6387352ecc
commit eec9f84fb0
8 changed files with 932 additions and 55 deletions

View File

@@ -0,0 +1,423 @@
#include "cli/service/agent/todo_manager.h"
#include <algorithm>
#include <chrono>
#include <fstream>
#include <set>
#include <sstream>
#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<TodoItem> 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<TodoItem> 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<TodoItem> TodoManager::GetAllTodos() const {
return todos_;
}
std::vector<TodoItem> TodoManager::GetTodosByStatus(TodoItem::Status status) const {
std::vector<TodoItem> result;
std::copy_if(todos_.begin(), todos_.end(), std::back_inserter(result),
[status](const TodoItem& t) { return t.status == status; });
return result;
}
std::vector<TodoItem> TodoManager::GetTodosByCategory(const std::string& category) const {
std::vector<TodoItem> 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<TodoItem> TodoManager::GetNextActionableTodo() const {
// Find pending/blocked TODOs
std::vector<TodoItem> 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<std::vector<TodoItem>> TodoManager::GenerateExecutionPlan() const {
std::vector<TodoItem> plan;
std::vector<TodoItem> 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<TodoItem> sorted;
std::set<std::string> 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<std::string>{});
item.tools_needed = j_item.value("tools_needed", std::vector<std::string>{});
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<std::string>{});
item.tools_needed = j_item.value("tools_needed", std::vector<std::string>{});
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

View File

@@ -0,0 +1,156 @@
#ifndef YAZE_CLI_SERVICE_AGENT_TODO_MANAGER_H_
#define YAZE_CLI_SERVICE_AGENT_TODO_MANAGER_H_
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
namespace yaze {
namespace cli {
namespace agent {
/**
* @struct TodoItem
* @brief Represents a single TODO item for task management
*/
struct TodoItem {
std::string id;
std::string description;
enum class Status {
PENDING,
IN_PROGRESS,
COMPLETED,
BLOCKED,
CANCELLED
} status = Status::PENDING;
std::string category; // e.g., "rom_edit", "ai_task", "build", "test"
int priority = 0; // Higher = more important
std::vector<std::string> dependencies; // IDs of tasks that must complete first
std::vector<std::string> tools_needed; // Tools/functions required
std::string created_at;
std::string updated_at;
std::string notes;
// Convert status enum to string
std::string StatusToString() const;
static Status StringToStatus(const std::string& str);
};
/**
* @class TodoManager
* @brief Manages TODO lists for z3ed agent task execution
*
* Enables the AI agent to:
* - Create and track TODO lists for complex tasks
* - Break down goals into executable steps
* - Track dependencies between tasks
* - Persist state between sessions
* - Generate execution plans
*/
class TodoManager {
public:
TodoManager();
explicit TodoManager(const std::string& data_dir);
/**
* @brief Initialize the TODO manager and load persisted data
*/
absl::Status Initialize();
/**
* @brief Create a new TODO item
*/
absl::StatusOr<TodoItem> CreateTodo(
const std::string& description,
const std::string& category = "",
int priority = 0);
/**
* @brief Update an existing TODO item
*/
absl::Status UpdateTodo(const std::string& id, const TodoItem& item);
/**
* @brief Update TODO status
*/
absl::Status UpdateStatus(const std::string& id, TodoItem::Status status);
/**
* @brief Get a TODO item by ID
*/
absl::StatusOr<TodoItem> GetTodo(const std::string& id) const;
/**
* @brief Get all TODO items
*/
std::vector<TodoItem> GetAllTodos() const;
/**
* @brief Get TODO items by status
*/
std::vector<TodoItem> GetTodosByStatus(TodoItem::Status status) const;
/**
* @brief Get TODO items by category
*/
std::vector<TodoItem> GetTodosByCategory(const std::string& category) const;
/**
* @brief Get the next actionable TODO (respecting dependencies and priority)
*/
absl::StatusOr<TodoItem> GetNextActionableTodo() const;
/**
* @brief Delete a TODO item
*/
absl::Status DeleteTodo(const std::string& id);
/**
* @brief Clear all completed TODOs
*/
absl::Status ClearCompleted();
/**
* @brief Save TODOs to persistent storage
*/
absl::Status Save();
/**
* @brief Load TODOs from persistent storage
*/
absl::Status Load();
/**
* @brief Generate an execution plan based on dependencies
*/
absl::StatusOr<std::vector<TodoItem>> GenerateExecutionPlan() const;
/**
* @brief Export TODOs as JSON string
*/
std::string ExportAsJson() const;
/**
* @brief Import TODOs from JSON string
*/
absl::Status ImportFromJson(const std::string& json);
private:
std::string data_dir_;
std::string todos_file_;
std::vector<TodoItem> todos_;
int next_id_ = 1;
std::string GenerateId();
std::string GetTimestamp() const;
bool CanExecute(const TodoItem& item) const;
};
} // namespace agent
} // namespace cli
} // namespace yaze
#endif // YAZE_CLI_SERVICE_AGENT_TODO_MANAGER_H_