From 62728ac31f4a5a86678bf8830bf76b7ebf05436f Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 21 Nov 2025 00:49:19 -0500 Subject: feat: implement t-1rcIwc8 --- Omni/Task.hs | 17 ++++++++++++++--- Omni/Task/Core.hs | 45 +++++++++++++++++++++++++++++++++------------ 2 files changed, 47 insertions(+), 15 deletions(-) (limited to 'Omni') diff --git a/Omni/Task.hs b/Omni/Task.hs index 24e528b..8151487 100644 --- a/Omni/Task.hs +++ b/Omni/Task.hs @@ -45,7 +45,7 @@ Usage: task update [--json] task deps [--json] task tree [] [--json] - task stats [--json] + task stats [--epic=] [--json] task export [--flush] task import -i task sync @@ -73,6 +73,7 @@ Options: --parent= Parent epic ID --priority=

Priority: 0-4 (0=critical, 4=backlog, default: 2) --status= Filter by status: open, in-progress, review, done + --epic= Filter stats by epic (recursive) --deps= Comma-separated list of dependency IDs --dep-type= Dependency type: blocks, discovered-from, parent-child, related (default: blocks) --discovered-from= Shortcut for --deps= --dep-type=discovered-from @@ -232,11 +233,14 @@ move args outputJson tree else showTaskTree maybeId | args `Cli.has` Cli.command "stats" = do + maybeEpic <- case Cli.getArg args (Cli.longOption "epic") of + Nothing -> pure Nothing + Just e -> pure <| Just (T.pack e) if isJsonMode args then do - stats <- getTaskStats + stats <- getTaskStats maybeEpic outputJson stats - else showTaskStats + else showTaskStats maybeEpic | args `Cli.has` Cli.command "export" = do exportTasks putText "Exported and consolidated tasks to .tasks/tasks.jsonl" @@ -516,6 +520,13 @@ cliTests = Right args -> do args `Cli.has` Cli.command "stats" Test.@?= True args `Cli.has` Cli.longOption "json" Test.@?= True, + Test.unit "stats with --epic flag" <| do + let result = Docopt.parseArgs help ["stats", "--epic=t-abc123"] + case result of + Left err -> Test.assertFailure <| "Failed to parse 'stats --epic': " <> show err + Right args -> do + args `Cli.has` Cli.command "stats" Test.@?= True + Cli.getArg args (Cli.longOption "epic") Test.@?= Just "t-abc123", Test.unit "create with flags in different order" <| do let result = Docopt.parseArgs help ["create", "Test", "--json", "--priority=1", "--namespace=Omni/Task"] case result of diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs index e4f1086..2ff7302 100644 --- a/Omni/Task/Core.hs +++ b/Omni/Task/Core.hs @@ -518,18 +518,31 @@ instance ToJSON TaskStats instance FromJSON TaskStats -- Get task statistics -getTaskStats :: IO TaskStats -getTaskStats = do - tasks <- loadTasks - ready <- getReadyTasks - let total = length tasks +getTaskStats :: Maybe Text -> IO TaskStats +getTaskStats maybeEpicId = do + allTasks <- loadTasks + + targetTasks <- case maybeEpicId of + Nothing -> pure allTasks + Just epicId -> + case filter (\t -> taskId t == epicId) allTasks of + [] -> panic "Epic not found" + _ -> pure <| getAllDescendants allTasks epicId + + globalReady <- getReadyTasks + let readyIds = map taskId globalReady + -- Filter ready tasks to only include those in our target set + readyCount = length <| filter (\t -> taskId t `elem` readyIds) targetTasks + + tasks = targetTasks + total = length tasks open = length <| filter (\t -> taskStatus t == Open) tasks inProg = length <| filter (\t -> taskStatus t == InProgress) tasks review = length <| filter (\t -> taskStatus t == Review) tasks done = length <| filter (\t -> taskStatus t == Done) tasks epics = length <| filter (\t -> taskType t == Epic) tasks - readyCount = length ready - blockedCount = total - readyCount - done + readyCount' = readyCount + blockedCount = total - readyCount' - done -- Count tasks by priority byPriority = [ (P0, length <| filter (\t -> taskPriority t == P0) tasks), @@ -550,18 +563,26 @@ getTaskStats = do reviewTasks = review, doneTasks = done, totalEpics = epics, - readyTasks = readyCount, + readyTasks = readyCount', blockedTasks = blockedCount, tasksByPriority = byPriority, tasksByNamespace = byNamespace } +-- Helper to get all descendants of a task (recursive) +getAllDescendants :: [Task] -> Text -> [Task] +getAllDescendants allTasks parentId = + let children = filter (\t -> taskParent t == Just parentId) allTasks + in children ++ concatMap (\t -> getAllDescendants allTasks (taskId t)) children + -- Show task statistics (human-readable) -showTaskStats :: IO () -showTaskStats = do - stats <- getTaskStats +showTaskStats :: Maybe Text -> IO () +showTaskStats maybeEpicId = do + stats <- getTaskStats maybeEpicId putText "" - putText "Task Statistics" + case maybeEpicId of + Nothing -> putText "Task Statistics" + Just epicId -> putText <| "Task Statistics for Epic " <> epicId putText "" putText <| "Total tasks: " <> T.pack (show (totalTasks stats)) putText <| " Open: " <> T.pack (show (openTasks stats)) -- cgit v1.2.3