feat: add uncomplete command to restore completed tasks to pending

Dedicated command that sets status back to pending and clears End time.
Unlike undo, works on any completed task regardless of when it was
completed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 15:22:51 +01:00
parent 2fa1316f0d
commit 07d1a78dfc
2 changed files with 91 additions and 1 deletions
+88
View File
@@ -0,0 +1,88 @@
package cmd
import (
"fmt"
"os"
"git.jnss.me/joakim/opal/internal/engine"
"github.com/spf13/cobra"
)
var uncompleteCmd = &cobra.Command{
Use: "uncomplete [filter...]",
Short: "Restore a completed task to pending",
Long: `Restore a completed task back to pending status.
Unlike undo, this is a targeted action that works on any completed task
regardless of when it was completed.
Examples:
opal 2 uncomplete
opal uncomplete +errand`,
Run: func(cmd *cobra.Command, args []string) {
parsed := getParsedArgs(cmd)
if err := uncompleteTasks(parsed.Filters); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
},
}
func uncompleteTasks(args []string) error {
if len(args) == 0 {
return fmt.Errorf("no task specified")
}
filter, err := engine.ParseFilter(args)
if err != nil {
return fmt.Errorf("failed to parse filter: %w", err)
}
ws, err := engine.LoadWorkingSet()
if err != nil {
return fmt.Errorf("failed to load working set: %w", err)
}
var tasks []*engine.Task
if len(filter.IDs) > 0 {
for _, id := range filter.IDs {
task, err := ws.GetTaskByDisplayID(id)
if err != nil {
return err
}
tasks = append(tasks, task)
}
} else {
tasks, err = engine.GetTasks(filter)
if err != nil {
return fmt.Errorf("failed to get tasks: %w", err)
}
}
if len(tasks) == 0 {
return fmt.Errorf("no tasks matched filter")
}
uncompleted := 0
for _, task := range tasks {
if task.Status != engine.StatusCompleted {
fmt.Fprintf(os.Stderr, "Warning: task %s is not completed, skipping\n", task.UUID)
continue
}
task.Status = engine.StatusPending
task.End = nil
if err := task.Save(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to uncomplete task %s: %v\n", task.UUID, err)
} else {
uncompleted++
}
}
if uncompleted == 1 {
fmt.Printf("Restored task %s to pending\n", engine.FormatTaskSummary(tasks[0], ws))
} else {
fmt.Printf("Restored %d task(s) to pending.\n", uncompleted)
}
return nil
}