summaryrefslogtreecommitdiff
path: root/Omni
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2025-11-21 04:02:19 -0500
committerBen Sima <ben@bsima.me>2025-11-21 04:02:19 -0500
commit3a6d471da72ce927aaeb65d5c344641c8146ec05 (patch)
treefd7b413c218db369de24de192d2850ba5b3cfe2d /Omni
parent62728ac31f4a5a86678bf8830bf76b7ebf05436f (diff)
parentfedb1a8343d04596e5dc0f69b82d6387005430e6 (diff)
Merge live into task/t-1rcIwc8
Amp-Thread-ID: https://ampcode.com/threads/T-7109f8d0-feb4-4a24-bc4b-37743227e2cb Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'Omni')
-rw-r--r--Omni/Task.hs36
-rw-r--r--Omni/Task/Core.hs46
2 files changed, 65 insertions, 17 deletions
diff --git a/Omni/Task.hs b/Omni/Task.hs
index 8151487..f3baf15 100644
--- a/Omni/Task.hs
+++ b/Omni/Task.hs
@@ -45,6 +45,7 @@ Usage:
task update <id> <status> [--json]
task deps <id> [--json]
task tree [<id>] [--json]
+ task progress <id> [--json]
task stats [--epic=<id>] [--json]
task export [--flush]
task import -i <file>
@@ -61,6 +62,7 @@ Commands:
update Update task status
deps Show dependency tree
tree Show task tree (epics with children, or all epics if no ID given)
+ progress Show progress for an epic
stats Show task statistics
export Export and consolidate tasks to JSONL
import Import tasks from JSONL file
@@ -232,6 +234,13 @@ move args
tree <- getTaskTree maybeId
outputJson tree
else showTaskTree maybeId
+ | args `Cli.has` Cli.command "progress" = do
+ tid <- getArgText args "id"
+ if isJsonMode args
+ then do
+ progress <- getTaskProgress tid
+ outputJson progress
+ else showTaskProgress tid
| args `Cli.has` Cli.command "stats" = do
maybeEpic <- case Cli.getArg args (Cli.longOption "epic") of
Nothing -> pure Nothing
@@ -340,20 +349,21 @@ unitTests =
child1 <- createTask "Child 1" WorkTask (Just (taskId parent)) Nothing P2 []
-- Manually create a task with .3 suffix to simulate a gap (or deleted task)
let child3Id = taskId parent <> ".3"
- child3 = Task
- { taskId = child3Id,
- taskTitle = "Child 3",
- taskType = WorkTask,
- taskParent = Just (taskId parent),
- taskNamespace = Nothing,
- taskStatus = Open,
- taskPriority = P2,
- taskDependencies = [],
- taskCreatedAt = taskCreatedAt child1,
- taskUpdatedAt = taskUpdatedAt child1
- }
+ child3 =
+ Task
+ { taskId = child3Id,
+ taskTitle = "Child 3",
+ taskType = WorkTask,
+ taskParent = Just (taskId parent),
+ taskNamespace = Nothing,
+ taskStatus = Open,
+ taskPriority = P2,
+ taskDependencies = [],
+ taskCreatedAt = taskCreatedAt child1,
+ taskUpdatedAt = taskUpdatedAt child1
+ }
saveTask child3
-
+
-- Create a new child, it should get .4, not .2
child4 <- createTask "Child 4" WorkTask (Just (taskId parent)) Nothing P2 []
taskId child4 Test.@?= taskId parent <> ".4"
diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs
index 2ff7302..622e8de 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
@@ -81,6 +89,10 @@ instance ToJSON Task
instance FromJSON Task
+instance ToJSON TaskProgress
+
+instance FromJSON TaskProgress
+
-- Get the tasks database file path (use test file if TASK_TEST_MODE is set)
getTasksFilePath :: IO FilePath
getTasksFilePath = do
@@ -309,6 +321,32 @@ getDependencyTree tid = do
deps = filter (\t -> taskId t `elem` 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
@@ -521,10 +559,10 @@ instance FromJSON TaskStats
getTaskStats :: Maybe Text -> IO TaskStats
getTaskStats maybeEpicId = do
allTasks <- loadTasks
-
+
targetTasks <- case maybeEpicId of
Nothing -> pure allTasks
- Just epicId ->
+ Just epicId ->
case filter (\t -> taskId t == epicId) allTasks of
[] -> panic "Epic not found"
_ -> pure <| getAllDescendants allTasks epicId
@@ -533,7 +571,7 @@ getTaskStats maybeEpicId = do
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
@@ -573,7 +611,7 @@ getTaskStats maybeEpicId = do
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
+ in children ++ concatMap (getAllDescendants allTasks <. taskId) children
-- Show task statistics (human-readable)
showTaskStats :: Maybe Text -> IO ()