← Back to task

Commit 7e03d8f3

commit 7e03d8f323d44b972aeceb009928c977ee64fed2
Author: Ben Sima <ben@bensima.com>
Date:   Mon Dec 1 13:51:21 2025

    Replace HumanTask type with NeedsHelp status
    
    - Remove HumanTask from TaskType enum (now Epic | WorkTask only)
    - Add NeedsHelp to Status enum for tasks requiring human guidance
    - Update getReadyTasks to filter NeedsHelp instead of HumanTask
    - Rename humanTasks to tasksNeedingHelp in HumanActionItems
    - Add CLI parsing for needs-help status in list/update commands
    - Add badge styling for NeedsHelp (amber/yellow theme)
    - Update all status pattern matches in tree view and print functions
    - Update tests to verify NeedsHelp exclusion from ready queue
    
    Task-Id: t-210

diff --git a/Omni/Jr/Web.hs b/Omni/Jr/Web.hs
index 2e4ee667..85085609 100644
--- a/Omni/Jr/Web.hs
+++ b/Omni/Jr/Web.hs
@@ -680,6 +680,7 @@ statusBadge status =
         TaskCore.Review -> ("badge badge-review", "Review")
         TaskCore.Approved -> ("badge badge-approved", "Approved")
         TaskCore.Done -> ("badge badge-done", "Done")
+        TaskCore.NeedsHelp -> ("badge badge-needshelp", "Needs Help")
    in Lucid.span_ [Lucid.class_ cls] label
 
 complexityBadge :: (Monad m) => Int -> Lucid.HtmlT m ()
@@ -770,6 +771,7 @@ clickableBadge status _tid =
         TaskCore.Review -> ("badge badge-review status-badge-clickable", "Review")
         TaskCore.Approved -> ("badge badge-approved status-badge-clickable", "Approved")
         TaskCore.Done -> ("badge badge-done status-badge-clickable", "Done")
+        TaskCore.NeedsHelp -> ("badge badge-needshelp status-badge-clickable", "Needs Help")
    in Lucid.span_
         [ Lucid.class_ cls,
           Lucid.tabindex_ "0",
@@ -797,6 +799,7 @@ statusDropdownOptions currentStatus tid =
       statusOption TaskCore.Review currentStatus tid
       statusOption TaskCore.Approved currentStatus tid
       statusOption TaskCore.Done currentStatus tid
+      statusOption TaskCore.NeedsHelp currentStatus tid
 
 statusOption :: (Monad m) => TaskCore.Status -> TaskCore.Status -> Text -> Lucid.HtmlT m ()
 statusOption opt currentStatus tid =
@@ -807,6 +810,7 @@ statusOption opt currentStatus tid =
         TaskCore.Review -> ("badge badge-review", "Review")
         TaskCore.Approved -> ("badge badge-approved", "Approved")
         TaskCore.Done -> ("badge badge-done", "Done")
+        TaskCore.NeedsHelp -> ("badge badge-needshelp", "Needs Help")
       isSelected = opt == currentStatus
       optClass = cls <> " status-dropdown-option" <> if isSelected then " selected" else ""
    in Lucid.form_
@@ -1083,8 +1087,8 @@ instance Lucid.ToHtml InterventionPage where
     let crumbs = [Breadcrumb "Jr" (Just "/"), Breadcrumb "Needs Human Action" Nothing]
         failed = TaskCore.failedTasks actionItems
         epicsReady = TaskCore.epicsInReview actionItems
-        human = TaskCore.humanTasks actionItems
-        totalCount = length failed + length epicsReady + length human
+        needsHelp = TaskCore.tasksNeedingHelp actionItems
+        totalCount = length failed + length epicsReady + length needsHelp
      in Lucid.doctypehtml_ <| do
           pageHead "Needs Human Action - Jr"
           pageBodyWithCrumbs crumbs <| do
@@ -1103,10 +1107,10 @@ instance Lucid.ToHtml InterventionPage where
                     Lucid.h2_ [Lucid.class_ "section-header"] <| Lucid.toHtml ("Epics Ready for Review (" <> tshow (length epicsReady) <> ")")
                     Lucid.p_ [Lucid.class_ "info-msg"] "Epics with all children completed. Verify before closing."
                     Lucid.div_ [Lucid.class_ "task-list"] <| traverse_ renderEpicReviewCard epicsReady
-                  unless (null human) <| do
-                    Lucid.h2_ [Lucid.class_ "section-header"] <| Lucid.toHtml ("Human Tasks (" <> tshow (length human) <> ")")
-                    Lucid.p_ [Lucid.class_ "info-msg"] "Tasks explicitly marked as needing human work."
-                    Lucid.div_ [Lucid.class_ "task-list"] <| traverse_ renderTaskCard (sortTasks currentSort human)
+                  unless (null needsHelp) <| do
+                    Lucid.h2_ [Lucid.class_ "section-header"] <| Lucid.toHtml ("Needs Help (" <> tshow (length needsHelp) <> ")")
+                    Lucid.p_ [Lucid.class_ "info-msg"] "Tasks where Jr needs human guidance or decisions."
+                    Lucid.div_ [Lucid.class_ "task-list"] <| traverse_ renderTaskCard (sortTasks currentSort needsHelp)
 
 renderEpicReviewCard :: (Monad m) => TaskCore.EpicForReview -> Lucid.HtmlT m ()
 renderEpicReviewCard epicReview = do
diff --git a/Omni/Jr/Web/Style.hs b/Omni/Jr/Web/Style.hs
index 08fda5d8..9a0c12d8 100644
--- a/Omni/Jr/Web/Style.hs
+++ b/Omni/Jr/Web/Style.hs
@@ -584,6 +584,9 @@ statusBadges = do
   ".badge-done" ? do
     backgroundColor "#d1fae5"
     color "#065f46"
+  ".badge-needshelp" ? do
+    backgroundColor "#fef3c7"
+    color "#92400e"
   ".status-badge-dropdown" ? do
     position relative
     display inlineBlock
@@ -1864,6 +1867,9 @@ darkModeStyles =
     ".badge-done" ? do
       backgroundColor "#064e3b"
       color "#6ee7b7"
+    ".badge-needshelp" ? do
+      backgroundColor "#78350f"
+      color "#fcd34d"
     ".badge-p0" ? do
       backgroundColor "#7f1d1d"
       color "#fca5a5"
diff --git a/Omni/Task.hs b/Omni/Task.hs
index ce26f418..1385a4b2 100644
--- a/Omni/Task.hs
+++ b/Omni/Task.hs
@@ -85,11 +85,11 @@ Commands:
 Options:
   -h --help                     Show this help
   --title=<title>               Task title
-  --type=<type>                 Task type: epic, task, or human (default: task)
+  --type=<type>                 Task type: epic or task (default: task)
   --parent=<id>                 Parent epic ID
   --priority=<p>                Priority: 0-4 (0=critical, 4=backlog, default: 2)
   --complexity=<c>              Complexity: 1-5 for model selection (1=trivial, 5=expert)
-  --status=<status>             Filter by status: draft, open, in-progress, review, approved, done
+  --status=<status>             Filter by status: draft, open, in-progress, review, approved, done, needs-help
   --epic=<id>                   Filter stats by epic (recursive)
   --deps=<ids>                  Comma-separated list of dependency IDs
   --dep-type=<type>             Dependency type: blocks, discovered-from, parent-child, related
@@ -109,7 +109,7 @@ Options:
 Arguments:
   <title>                       Task title
   <id>                          Task ID
-  <status>                      Task status (draft, open, in-progress, review, approved, done)
+  <status>                      Task status (draft, open, in-progress, review, approved, done, needs-help)
   <message>                     Comment message
   <file>                        JSONL file to import
 |]
@@ -147,8 +147,7 @@ move' args
         Nothing -> pure WorkTask
         Just "epic" -> pure Epic
         Just "task" -> pure WorkTask
-        Just "human" -> pure HumanTask
-        Just other -> panic <| "Invalid task type: " <> T.pack other <> ". Use: epic, task, or human"
+        Just other -> panic <| "Invalid task type: " <> T.pack other <> ". Use: epic or task"
       parent <- case Cli.getArg args (Cli.longOption "parent") of
         Nothing -> pure Nothing
         Just p -> pure <| Just (T.pack p)
@@ -295,7 +294,6 @@ move' args
         Nothing -> pure Nothing
         Just "epic" -> pure <| Just Epic
         Just "task" -> pure <| Just WorkTask
-        Just "human" -> pure <| Just HumanTask
         Just other -> panic <| "Invalid task type: " <> T.pack other
       maybeParent <- case Cli.getArg args (Cli.longOption "parent") of
         Nothing -> pure Nothing
@@ -308,7 +306,8 @@ move' args
         Just "review" -> pure <| Just Review
         Just "approved" -> pure <| Just Approved
         Just "done" -> pure <| Just Done
-        Just other -> panic <| "Invalid status: " <> T.pack other <> ". Use: draft, open, in-progress, review, approved, or done"
+        Just "needs-help" -> pure <| Just NeedsHelp
+        Just other -> panic <| "Invalid status: " <> T.pack other <> ". Use: draft, open, in-progress, review, approved, done, or needs-help"
       maybeNamespace <- case Cli.getArg args (Cli.longOption "namespace") of
         Nothing -> pure Nothing
         Just ns -> do
@@ -362,7 +361,8 @@ move' args
             "review" -> Review
             "approved" -> Approved
             "done" -> Done
-            _ -> panic "Invalid status. Use: draft, open, in-progress, review, approved, or done"
+            "needs-help" -> NeedsHelp
+            _ -> panic "Invalid status. Use: draft, open, in-progress, review, approved, done, or needs-help"
 
       -- Show verification checklist warning when marking Done without --verified
       when (newStatus == Done && not isVerified && not (isJsonMode args)) <| do
@@ -620,11 +620,9 @@ unitTests =
         taskStatus task Test.@?= Open
         taskPriority task Test.@?= P2
         null (taskDependencies task) Test.@?= True,
-      Test.unit "can create human task" <| do
-        task <- createTask "Human Task" HumanTask Nothing Nothing P2 Nothing [] "Human task description"
-        taskType task Test.@?= HumanTask,
-      Test.unit "ready tasks exclude human tasks" <| do
-        task <- createTask "Human Task" HumanTask Nothing Nothing P2 Nothing [] "Human task"
+      Test.unit "ready tasks exclude NeedsHelp tasks" <| do
+        task <- createTask "Needs Help Task" WorkTask Nothing Nothing P2 Nothing [] "Task needing help"
+        updateTaskStatus (taskId task) NeedsHelp []
         ready <- getReadyTasks
         (taskId task `notElem` map taskId ready) Test.@?= True,
       Test.unit "ready tasks exclude draft tasks" <| do
diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs
index 35f3ea7d..8badf6be 100644
--- a/Omni/Task/Core.hs
+++ b/Omni/Task/Core.hs
@@ -45,10 +45,10 @@ data Task = Task
   }
   deriving (Show, Eq, Generic)
 
-data TaskType = Epic | WorkTask | HumanTask
+data TaskType = Epic | WorkTask
   deriving (Show, Eq, Read, Generic)
 
-data Status = Draft | Open | InProgress | Review | Approved | Done
+data Status = Draft | Open | InProgress | Review | Approved | Done | NeedsHelp
   deriving (Show, Eq, Read, Generic)
 
 -- Priority levels (matching beads convention)
@@ -86,7 +86,7 @@ data EpicForReview = EpicForReview
 data HumanActionItems = HumanActionItems
   { failedTasks :: [Task],
     epicsInReview :: [EpicForReview],
-    humanTasks :: [Task]
+    tasksNeedingHelp :: [Task]
   }
   deriving (Show, Eq, Generic)
 
@@ -806,8 +806,8 @@ getReadyTasks = do
           /= Epic
           && not (isParent (taskId task))
           && all (`elem` doneIds) (blockingDepIds task)
-          && taskType task
-          /= HumanTask
+          && taskStatus task
+          /= NeedsHelp
           && taskId task
           `notElem` needsInterventionIds
   pure <| filter isReady openTasks
@@ -920,6 +920,7 @@ showTaskTree maybeId = do
               Review -> "[?]"
               Approved -> "[+]"
               Done -> "[✓]"
+              NeedsHelp -> "[!]"
 
           coloredStatusStr = case taskType task of
             Epic -> magenta statusStr
@@ -930,6 +931,7 @@ showTaskTree maybeId = do
               Review -> magenta statusStr
               Approved -> green statusStr
               Done -> green statusStr
+              NeedsHelp -> yellow statusStr
 
           nsStr = case taskNamespace task of
             Nothing -> ""
@@ -988,6 +990,7 @@ printTask t = do
               Review -> magenta s
               Approved -> green s
               Done -> green s
+              NeedsHelp -> yellow s
 
       coloredTitle = if taskType t == Epic then bold (taskTitle t) else taskTitle t
       coloredProgress = if taskType t == Epic then magenta progressInfo else progressInfo
@@ -1556,12 +1559,12 @@ getHumanActionItems = do
             let completed = length [c | c <- children, taskStatus c == Done],
             completed == total
         ]
-      human = [t | t <- allTasks, taskType t == HumanTask, taskStatus t == Open]
+      needingHelp = [t | t <- allTasks, taskStatus t == NeedsHelp]
   pure
     HumanActionItems
       { failedTasks = failed,
         epicsInReview = epicsReady,
-        humanTasks = human
+        tasksNeedingHelp = needingHelp
       }
 
 -- | Get all retry contexts from the database