diff options
| author | Ben Sima <ben@bsima.me> | 2025-11-20 14:09:57 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bsima.me> | 2025-11-20 14:09:57 -0500 |
| commit | eab575ad7ce423f053c87c45225853dd51aa252f (patch) | |
| tree | 55cc492c568ac27e3d2c7bdf869fa3e33cadc991 /Omni/Task | |
| parent | a5faf8c31f619142e6f43d688f52d87c4edff341 (diff) | |
task: implement stats command
- Add 'task stats' command to show task statistics - Display total
tasks, status breakdown (open/in-progress/done) - Show epic count,
ready vs blocked tasks - Show task counts by priority (P0-P4) and
namespace - Support both human-readable and JSON output (--json flag)
- Add tests for stats command and stats --json - TaskStats data type
with ToJSON/FromJSON instances
All 31 tests passing.
Amp-Thread-ID:
https://ampcode.com/threads/T-4e6225cf-3e78-4538-963c-5377bbbccee8
Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'Omni/Task')
| -rw-r--r-- | Omni/Task/Core.hs | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs index f463040..e9da38e 100644 --- a/Omni/Task/Core.hs +++ b/Omni/Task/Core.hs @@ -437,6 +437,97 @@ exportTasks = do TIO.writeFile tasksFile "" traverse_ saveTask tasks +-- Task statistics +data TaskStats = TaskStats + { totalTasks :: Int, + openTasks :: Int, + inProgressTasks :: Int, + doneTasks :: Int, + totalEpics :: Int, + readyTasks :: Int, + blockedTasks :: Int, + tasksByPriority :: [(Priority, Int)], + tasksByNamespace :: [(Text, Int)] + } + deriving (Show, Eq, Generic) + +instance ToJSON TaskStats + +instance FromJSON TaskStats + +-- Get task statistics +getTaskStats :: IO TaskStats +getTaskStats = do + tasks <- loadTasks + ready <- getReadyTasks + let total = length tasks + open = length <| filter (\t -> taskStatus t == Open) tasks + inProg = length <| filter (\t -> taskStatus t == InProgress) tasks + done = length <| filter (\t -> taskStatus t == Done) tasks + epics = length <| filter (\t -> taskType t == Epic) tasks + readyCount = length ready + blockedCount = total - readyCount - done + -- Count tasks by priority + byPriority = + [ (P0, length <| filter (\t -> taskPriority t == P0) tasks), + (P1, length <| filter (\t -> taskPriority t == P1) tasks), + (P2, length <| filter (\t -> taskPriority t == P2) tasks), + (P3, length <| filter (\t -> taskPriority t == P3) tasks), + (P4, length <| filter (\t -> taskPriority t == P4) tasks) + ] + -- Count tasks by namespace + namespaces = mapMaybe taskNamespace tasks + uniqueNs = List.nub namespaces + byNamespace = map (\ns -> (ns, length <| filter (\t -> taskNamespace t == Just ns) tasks)) uniqueNs + pure + TaskStats + { totalTasks = total, + openTasks = open, + inProgressTasks = inProg, + doneTasks = done, + totalEpics = epics, + readyTasks = readyCount, + blockedTasks = blockedCount, + tasksByPriority = byPriority, + tasksByNamespace = byNamespace + } + +-- Show task statistics (human-readable) +showTaskStats :: IO () +showTaskStats = do + stats <- getTaskStats + putText "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + putText "Task Statistics" + putText "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + putText <| "Total tasks: " <> T.pack (show (totalTasks stats)) + putText <| " Open: " <> T.pack (show (openTasks stats)) + putText <| " In Progress: " <> T.pack (show (inProgressTasks stats)) + putText <| " Done: " <> T.pack (show (doneTasks stats)) + putText "" + putText <| "Epics: " <> T.pack (show (totalEpics stats)) + putText "" + putText <| "Ready to work: " <> T.pack (show (readyTasks stats)) + putText <| "Blocked: " <> T.pack (show (blockedTasks stats)) + putText "" + putText "By Priority:" + traverse_ printPriority (tasksByPriority stats) + unless (null (tasksByNamespace stats)) <| do + putText "" + putText "By Namespace:" + traverse_ printNamespace (tasksByNamespace stats) + putText "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + where + printPriority (p, count) = + let label = case p of + P0 -> "P0 (Critical)" + P1 -> "P1 (High)" + P2 -> "P2 (Medium)" + P3 -> "P3 (Low)" + P4 -> "P4 (Backlog)" + in putText <| " " <> T.pack (show count) <> " " <> label + printNamespace (ns, count) = + putText <| " " <> T.pack (show count) <> " " <> ns + -- Import tasks: Read from another JSONL file and merge with existing tasks importTasks :: FilePath -> IO () importTasks filePath = do |
