# Aidderall MCP Server - Hierarchical task management for AI assistants
# Copyright (C) 2024 Briam R. <briamr@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
import uuid
from abc import ABC
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any, Dict, List, Optional
class TaskStatus(str, Enum):
PENDING = "pending"
CURRENT = "current"
COMPLETED = "completed"
@dataclass
class Task(ABC):
title: str
body: str
id: str = field(default_factory=lambda: str(uuid.uuid4()))
status: TaskStatus = TaskStatus.PENDING
created_at: datetime = field(default_factory=datetime.now)
completed_at: Optional[datetime] = None
def __post_init__(self) -> None:
if len(self.title) > 256:
raise ValueError("Task title must be 256 characters or less")
def to_dict(self) -> Dict[str, Any]:
return {
"id": self.id,
"title": self.title,
"body": self.body,
"status": self.status.value,
"created_at": self.created_at.isoformat(),
"completed_at": (
self.completed_at.isoformat() if self.completed_at else None
),
}
@dataclass
class SubTask(Task):
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> SubTask:
task = cls.__new__(cls)
task.id = data["id"]
task.title = data["title"]
task.body = data["body"]
task.status = TaskStatus(data["status"])
task.created_at = datetime.fromisoformat(data["created_at"])
task.completed_at = (
datetime.fromisoformat(data["completed_at"])
if data.get("completed_at")
else None
)
return task
@dataclass
class MainTask(Task):
sub_tasks: List[SubTask] = field(default_factory=list)
def add_sub_task(self, sub_task: SubTask) -> None:
self.sub_tasks.append(sub_task)
def get_rightmost_task(self) -> Task:
if self.sub_tasks:
return self.sub_tasks[-1]
return self
def has_incomplete_sub_tasks(self) -> bool:
return any(task.status != TaskStatus.COMPLETED for task in self.sub_tasks)
def remove_rightmost_sub_task(self) -> Optional[SubTask]:
if self.sub_tasks:
return self.sub_tasks.pop()
return None
def to_dict(self) -> Dict[str, Any]:
data = super().to_dict()
data["sub_tasks"] = [sub.to_dict() for sub in self.sub_tasks]
return data
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> MainTask:
task = cls.__new__(cls)
task.id = data["id"]
task.title = data["title"]
task.body = data["body"]
task.status = TaskStatus(data["status"])
task.created_at = datetime.fromisoformat(data["created_at"])
task.completed_at = (
datetime.fromisoformat(data["completed_at"])
if data.get("completed_at")
else None
)
task.sub_tasks = [
SubTask.from_dict(sub) for sub in data.get("sub_tasks", [])
]
return task