summaryrefslogtreecommitdiff
path: root/Omni/Task
diff options
context:
space:
mode:
authorOmni Worker <bot@omni.agent>2025-11-21 04:44:32 -0500
committerOmni Worker <bot@omni.agent>2025-11-21 04:44:32 -0500
commit5f709412759f8c3f7c81f1343fa9a94ece5117d9 (patch)
treea0696d09a500b324515a5184472cc86a79f34f31 /Omni/Task
parent65c0b02b23a8b3125b0c10112d48c1a637f01cf9 (diff)
parent7801b1dc328b07a8589d651d4af843cc6acb4552 (diff)
Merge live into task/t-rWacMb1av
Amp-Thread-ID: https://ampcode.com/threads/T-7109f8d0-feb4-4a24-bc4b-37743227e2cb Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'Omni/Task')
-rw-r--r--Omni/Task/Core.hs93
1 files changed, 76 insertions, 17 deletions
diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs
index 2b00bca..3f665da 100644
--- a/Omni/Task/Core.hs
+++ b/Omni/Task/Core.hs
@@ -57,6 +57,14 @@ data DependencyType
| Related -- Soft relationship, doesn't block
deriving (Show, Eq, Generic)
+data TaskProgress = TaskProgress
+ { progressTaskId :: Text,
+ progressTotal :: Int,
+ progressCompleted :: Int,
+ progressPercentage :: Int
+ }
+ deriving (Show, Eq, Generic)
+
instance ToJSON TaskType
instance FromJSON TaskType
@@ -87,7 +95,11 @@ matchesId id1 id2 = T.toLower id1 == T.toLower id2
-- | Find a task by ID (case-insensitive)
findTask :: Text -> [Task] -> Maybe Task
-findTask tid tasks = List.find (\t -> matchesId (taskId t) tid) tasks
+findTask tid = List.find (\t -> matchesId (taskId t) tid)
+
+instance ToJSON TaskProgress
+
+instance FromJSON TaskProgress
-- Get the tasks database file path (use test file if TASK_TEST_MODE is set)
getTasksFilePath :: IO FilePath
@@ -317,6 +329,32 @@ getDependencyTree tid = do
deps = filter (\t -> any (matchesId (taskId t)) depIds) allTasks
in task : concatMap (collectDeps allTasks) deps
+-- Get task progress
+getTaskProgress :: Text -> IO TaskProgress
+getTaskProgress tid = do
+ tasks <- loadTasks
+ -- Verify task exists (optional, but good for error handling)
+ case filter (\t -> taskId t == tid) tasks of
+ [] -> panic "Task not found"
+ _ -> do
+ let children = filter (\child -> taskParent child == Just tid) tasks
+ total = length children
+ completed = length <| filter (\child -> taskStatus child == Done) children
+ percentage = if total == 0 then 0 else (completed * 100) `div` total
+ pure
+ TaskProgress
+ { progressTaskId = tid,
+ progressTotal = total,
+ progressCompleted = completed,
+ progressPercentage = percentage
+ }
+
+-- Show task progress
+showTaskProgress :: Text -> IO ()
+showTaskProgress tid = do
+ progress <- getTaskProgress tid
+ putText <| "Progress for " <> tid <> ": " <> T.pack (show (progressCompleted progress)) <> "/" <> T.pack (show (progressTotal progress)) <> " (" <> T.pack (show (progressPercentage progress)) <> "%)"
+
-- Show dependency tree for a task
showDependencyTree :: Text -> IO ()
showDependencyTree tid = do
@@ -349,7 +387,7 @@ getTaskTree maybeId = do
where
collectChildren :: [Task] -> Task -> [Task]
collectChildren allTasks task =
- let children = filter (\t -> maybe False (`matchesId` taskId task) (taskParent t)) allTasks
+ let children = filter (maybe False (`matchesId` taskId task) <. taskParent) allTasks
in task : concatMap (collectChildren allTasks) children
-- Show task tree (epic with children, or all epics if no ID given)
@@ -377,7 +415,7 @@ showTaskTree maybeId = do
printTreeNode' :: [Task] -> Task -> Int -> [Bool] -> IO ()
printTreeNode' allTasks task indent ancestry = do
- let children = filter (\t -> maybe False (`matchesId` taskId task) (taskParent t)) allTasks
+ let children = filter (maybe False (`matchesId` taskId task) <. taskParent) allTasks
-- Build tree prefix using box-drawing characters
prefix =
if indent == 0
@@ -426,7 +464,7 @@ printTask t = do
let progressInfo =
if taskType t == Epic
then
- let children = filter (\child -> maybe False (`matchesId` taskId t) (taskParent child)) tasks
+ let children = filter (maybe False (`matchesId` taskId t) <. taskParent) tasks
total = length children
completed = length <| filter (\child -> taskStatus child == Done) children
in " [" <> T.pack (show completed) <> "/" <> T.pack (show total) <> "]"
@@ -464,7 +502,7 @@ showTaskDetailed t = do
-- Show epic progress if this is an epic
when (taskType t == Epic) <| do
- let children = filter (\child -> maybe False (`matchesId` taskId t) (taskParent child)) tasks
+ let children = filter (maybe False (`matchesId` taskId t) <. taskParent) tasks
total = length children
completed = length <| filter (\child -> taskStatus child == Done) children
percentage = if total == 0 then 0 else (completed * 100) `div` total
@@ -526,18 +564,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 findTask epicId allTasks of
+ Nothing -> panic "Epic not found"
+ Just task -> pure <| getAllDescendants allTasks (taskId task)
+
+ 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),
@@ -558,18 +609,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 (maybe False (`matchesId` parentId) <. taskParent) allTasks
+ in children ++ concatMap (getAllDescendants allTasks <. taskId) 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))