package sdlc import ( "fmt" "time" ) // Task represents an implementation task within a feature. type Task struct { ID string `yaml:"id" json:"id"` Title string `yaml:"title" json:"title"` Status TaskStatus `yaml:"status" json:"status"` Spec string `yaml:"spec,omitempty" json:"spec,omitempty"` Files []string `yaml:"files,omitempty" json:"files,omitempty"` Patterns []string `yaml:"patterns,omitempty" json:"patterns,omitempty"` DependsOn []string `yaml:"depends_on,omitempty" json:"depends_on,omitempty"` StartedAt *time.Time `yaml:"started_at,omitempty" json:"started_at,omitempty"` DoneAt *time.Time `yaml:"done_at,omitempty" json:"done_at,omitempty"` Notes string `yaml:"notes,omitempty" json:"notes,omitempty"` } // StartTask marks a task as in-progress. func StartTask(tasks []Task, taskID string) ([]Task, error) { for i, t := range tasks { if t.ID == taskID { if t.Status != TaskPending && t.Status != TaskBlocked { return tasks, fmt.Errorf("task %s is %s, not startable", taskID, t.Status) } now := time.Now().UTC() tasks[i].Status = TaskInProgress tasks[i].StartedAt = &now return tasks, nil } } return tasks, ErrTaskNotFound } // CompleteTask marks a task as complete. func CompleteTask(tasks []Task, taskID string) ([]Task, error) { for i, t := range tasks { if t.ID == taskID { if t.Status != TaskInProgress { return tasks, fmt.Errorf("task %s is %s, not completable", taskID, t.Status) } now := time.Now().UTC() tasks[i].Status = TaskComplete tasks[i].DoneAt = &now return tasks, nil } } return tasks, ErrTaskNotFound } // BlockTask marks a task as blocked. func BlockTask(tasks []Task, taskID string) ([]Task, error) { for i, t := range tasks { if t.ID == taskID { tasks[i].Status = TaskBlocked return tasks, nil } } return tasks, ErrTaskNotFound } // AddTask appends a new task with an auto-generated ID. // The ID is based on the max existing ID + 1 to avoid collisions after deletions. func AddTask(tasks []Task, title string) []Task { maxNum := 0 for _, t := range tasks { var num int if _, err := fmt.Sscanf(t.ID, "task-%d", &num); err == nil { if num > maxNum { maxNum = num } } } id := fmt.Sprintf("task-%03d", maxNum+1) return append(tasks, Task{ ID: id, Title: title, Status: TaskPending, }) } // PendingTasks returns tasks that are pending or blocked. func PendingTasks(tasks []Task) []Task { var result []Task for _, t := range tasks { if t.Status == TaskPending { result = append(result, t) } } return result } // NextTask returns the first pending task, or nil if none. func NextTask(tasks []Task) *Task { for i, t := range tasks { if t.Status == TaskPending { return &tasks[i] } } return nil } // AllTasksComplete returns true if every task is in the complete state. func AllTasksComplete(tasks []Task) bool { if len(tasks) == 0 { return false } for _, t := range tasks { if t.Status != TaskComplete { return false } } return true } // TaskSummary returns counts by status. type TaskSummary struct { Total int `json:"total"` Completed int `json:"completed"` InProgress int `json:"in_progress"` Pending int `json:"pending"` Blocked int `json:"blocked"` } // SummarizeTasks computes a TaskSummary from a task list. func SummarizeTasks(tasks []Task) TaskSummary { s := TaskSummary{Total: len(tasks)} for _, t := range tasks { switch t.Status { case TaskComplete: s.Completed++ case TaskInProgress: s.InProgress++ case TaskPending: s.Pending++ case TaskBlocked: s.Blocked++ } } return s }