summaryrefslogtreecommitdiff
path: root/Omni
diff options
context:
space:
mode:
Diffstat (limited to 'Omni')
-rw-r--r--Omni/Task.hs58
-rw-r--r--Omni/Task/Core.hs47
-rw-r--r--Omni/Task/RaceTest.hs4
3 files changed, 71 insertions, 38 deletions
diff --git a/Omni/Task.hs b/Omni/Task.hs
index 65e5c42..36b318b 100644
--- a/Omni/Task.hs
+++ b/Omni/Task.hs
@@ -82,6 +82,7 @@ Options:
--dep-type=<type> Dependency type: blocks, discovered-from, parent-child, related (default: blocks)
--discovered-from=<id> Shortcut for --deps=<id> --dep-type=discovered-from
--namespace=<ns> Optional namespace (e.g., Omni/Task, Biz/Cloud)
+ --description=<desc> Task description
--flush Force immediate export
--json Output in JSON format (for agent use)
--quiet Non-interactive mode (for agents)
@@ -159,7 +160,12 @@ move args
let validNs = Namespace.fromHaskellModule ns
nsPath = T.pack <| Namespace.toPath validNs
pure <| Just nsPath
- createdTask <- createTask title taskType parent namespace priority deps
+
+ description <- case Cli.getArg args (Cli.longOption "description") of
+ Nothing -> pure Nothing
+ Just d -> pure <| Just (T.pack d)
+
+ createdTask <- createTask title taskType parent namespace priority deps description
if isJsonMode args
then outputJson createdTask
else putStrLn <| "Created task: " <> T.unpack (taskId createdTask)
@@ -301,60 +307,63 @@ unitTests =
initTaskDb
True Test.@?= True,
Test.unit "can create task" <| do
- task <- createTask "Test task" WorkTask Nothing Nothing P2 []
+ task <- createTask "Test task" WorkTask Nothing Nothing P2 [] Nothing
taskTitle task Test.@?= "Test task"
taskType task Test.@?= WorkTask
taskStatus task Test.@?= Open
taskPriority task Test.@?= P2
null (taskDependencies task) Test.@?= True,
+ Test.unit "can create task with description" <| do
+ task <- createTask "Test task" WorkTask Nothing Nothing P2 [] (Just "My description")
+ taskDescription task Test.@?= Just "My description",
Test.unit "can list tasks" <| do
- _ <- createTask "Test task for list" WorkTask Nothing Nothing P2 []
+ _ <- createTask "Test task for list" WorkTask Nothing Nothing P2 [] Nothing
tasks <- listTasks Nothing Nothing Nothing Nothing
not (null tasks) Test.@?= True,
Test.unit "ready tasks exclude blocked ones" <| do
- task1 <- createTask "First task" WorkTask Nothing Nothing P2 []
+ task1 <- createTask "First task" WorkTask Nothing Nothing P2 [] Nothing
let blockingDep = Dependency {depId = taskId task1, depType = Blocks}
- task2 <- createTask "Blocked task" WorkTask Nothing Nothing P2 [blockingDep]
+ task2 <- createTask "Blocked task" WorkTask Nothing Nothing P2 [blockingDep] Nothing
ready <- getReadyTasks
(taskId task1 `elem` map taskId ready) Test.@?= True
(taskId task2 `notElem` map taskId ready) Test.@?= True,
Test.unit "discovered-from dependencies don't block" <| do
- task1 <- createTask "Original task" WorkTask Nothing Nothing P2 []
+ task1 <- createTask "Original task" WorkTask Nothing Nothing P2 [] Nothing
let discDep = Dependency {depId = taskId task1, depType = DiscoveredFrom}
- task2 <- createTask "Discovered work" WorkTask Nothing Nothing P2 [discDep]
+ task2 <- createTask "Discovered work" WorkTask Nothing Nothing P2 [discDep] Nothing
ready <- getReadyTasks
-- Both should be ready since DiscoveredFrom doesn't block
(taskId task1 `elem` map taskId ready) Test.@?= True
(taskId task2 `elem` map taskId ready) Test.@?= True,
Test.unit "related dependencies don't block" <| do
- task1 <- createTask "Task A" WorkTask Nothing Nothing P2 []
+ task1 <- createTask "Task A" WorkTask Nothing Nothing P2 [] Nothing
let relDep = Dependency {depId = taskId task1, depType = Related}
- task2 <- createTask "Task B" WorkTask Nothing Nothing P2 [relDep]
+ task2 <- createTask "Task B" WorkTask Nothing Nothing P2 [relDep] Nothing
ready <- getReadyTasks
-- Both should be ready since Related doesn't block
(taskId task1 `elem` map taskId ready) Test.@?= True
(taskId task2 `elem` map taskId ready) Test.@?= True,
Test.unit "child task gets sequential ID" <| do
- parent <- createTask "Parent" Epic Nothing Nothing P2 []
- child1 <- createTask "Child 1" WorkTask (Just (taskId parent)) Nothing P2 []
- child2 <- createTask "Child 2" WorkTask (Just (taskId parent)) Nothing P2 []
+ parent <- createTask "Parent" Epic Nothing Nothing P2 [] Nothing
+ child1 <- createTask "Child 1" WorkTask (Just (taskId parent)) Nothing P2 [] Nothing
+ child2 <- createTask "Child 2" WorkTask (Just (taskId parent)) Nothing P2 [] Nothing
taskId child1 Test.@?= taskId parent <> ".1"
taskId child2 Test.@?= taskId parent <> ".2",
Test.unit "grandchild task gets sequential ID" <| do
- parent <- createTask "Grandparent" Epic Nothing Nothing P2 []
- child <- createTask "Parent" Epic (Just (taskId parent)) Nothing P2 []
- grandchild <- createTask "Grandchild" WorkTask (Just (taskId child)) Nothing P2 []
+ parent <- createTask "Grandparent" Epic Nothing Nothing P2 [] Nothing
+ child <- createTask "Parent" Epic (Just (taskId parent)) Nothing P2 [] Nothing
+ grandchild <- createTask "Grandchild" WorkTask (Just (taskId child)) Nothing P2 [] Nothing
taskId grandchild Test.@?= taskId parent <> ".1.1",
Test.unit "siblings of grandchild task get sequential ID" <| do
- parent <- createTask "Grandparent" Epic Nothing Nothing P2 []
- child <- createTask "Parent" Epic (Just (taskId parent)) Nothing P2 []
- grandchild1 <- createTask "Grandchild 1" WorkTask (Just (taskId child)) Nothing P2 []
- grandchild2 <- createTask "Grandchild 2" WorkTask (Just (taskId child)) Nothing P2 []
+ parent <- createTask "Grandparent" Epic Nothing Nothing P2 [] Nothing
+ child <- createTask "Parent" Epic (Just (taskId parent)) Nothing P2 [] Nothing
+ grandchild1 <- createTask "Grandchild 1" WorkTask (Just (taskId child)) Nothing P2 [] Nothing
+ grandchild2 <- createTask "Grandchild 2" WorkTask (Just (taskId child)) Nothing P2 [] Nothing
taskId grandchild1 Test.@?= taskId parent <> ".1.1"
taskId grandchild2 Test.@?= taskId parent <> ".1.2",
Test.unit "child ID generation skips gaps" <| do
- parent <- createTask "Parent with gaps" Epic Nothing Nothing P2 []
- child1 <- createTask "Child 1" WorkTask (Just (taskId parent)) Nothing P2 []
+ parent <- createTask "Parent with gaps" Epic Nothing Nothing P2 [] Nothing
+ child1 <- createTask "Child 1" WorkTask (Just (taskId parent)) Nothing P2 [] Nothing
-- Manually create a task with .3 suffix to simulate a gap (or deleted task)
let child3Id = taskId parent <> ".3"
child3 =
@@ -368,15 +377,16 @@ unitTests =
taskPriority = P2,
taskDependencies = [],
taskCreatedAt = taskCreatedAt child1,
- taskUpdatedAt = taskUpdatedAt child1
+ taskUpdatedAt = taskUpdatedAt child1,
+ taskDescription = Nothing
}
saveTask child3
-- Create a new child, it should get .4, not .2
- child4 <- createTask "Child 4" WorkTask (Just (taskId parent)) Nothing P2 []
+ child4 <- createTask "Child 4" WorkTask (Just (taskId parent)) Nothing P2 [] Nothing
taskId child4 Test.@?= taskId parent <> ".4",
Test.unit "task lookup is case insensitive" <| do
- task <- createTask "Case sensitive" WorkTask Nothing Nothing P2 []
+ task <- createTask "Case sensitive" WorkTask Nothing Nothing P2 [] Nothing
let tid = taskId task
upperTid = T.toUpper tid
tasks <- loadTasks
diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs
index 1441a54..24bb31c 100644
--- a/Omni/Task/Core.hs
+++ b/Omni/Task/Core.hs
@@ -33,6 +33,7 @@ data Task = Task
taskStatus :: Status,
taskPriority :: Priority, -- Priority level (0-4)
taskDependencies :: [Dependency], -- List of dependencies with types
+ taskDescription :: Maybe Text, -- Optional detailed description
taskCreatedAt :: UTCTime,
taskUpdatedAt :: UTCTime
}
@@ -255,11 +256,11 @@ loadTasksInternal = do
then Nothing
else case decode (BLC.pack <| T.unpack line) of
Just task -> Just task
- Nothing -> migrateOldTask line
+ Nothing -> migrateTask line
- -- Migrate old task format (with taskProject field or missing priority) to new format
- migrateOldTask :: Text -> Maybe Task
- migrateOldTask line = case Aeson.decode (BLC.pack <| T.unpack line) :: Maybe Aeson.Object of
+ -- Migrate old task formats to new format
+ migrateTask :: Text -> Maybe Task
+ migrateTask line = case Aeson.decode (BLC.pack <| T.unpack line) :: Maybe Aeson.Object of
Nothing -> Nothing
Just obj ->
let taskId' = KM.lookup "taskId" obj +> parseMaybe Aeson.parseJSON
@@ -267,12 +268,22 @@ loadTasksInternal = do
taskStatus' = KM.lookup "taskStatus" obj +> parseMaybe Aeson.parseJSON
taskCreatedAt' = KM.lookup "taskCreatedAt" obj +> parseMaybe Aeson.parseJSON
taskUpdatedAt' = KM.lookup "taskUpdatedAt" obj +> parseMaybe Aeson.parseJSON
- -- Extract old taskDependencies (could be [Text] or [Dependency])
- oldDeps = KM.lookup "taskDependencies" obj +> parseMaybe Aeson.parseJSON :: Maybe [Text]
- newDeps = maybe [] (map (\tid -> Dependency {depId = tid, depType = Blocks})) oldDeps
+
+ -- Extract taskDescription (new field)
+ taskDescription' = KM.lookup "taskDescription" obj +> parseMaybe Aeson.parseJSON
+
+ -- Extract dependencies (handle V1 [Dependency] and V0 [Text])
+ v1Deps = KM.lookup "taskDependencies" obj +> parseMaybe Aeson.parseJSON :: Maybe [Dependency]
+ v0Deps = KM.lookup "taskDependencies" obj +> parseMaybe Aeson.parseJSON :: Maybe [Text]
+ finalDeps = case v1Deps of
+ Just ds -> ds
+ Nothing -> case v0Deps of
+ Just ts -> map (\tid -> Dependency {depId = tid, depType = Blocks}) ts
+ Nothing -> []
+
-- taskProject is ignored in new format (use epics instead)
- taskType' = WorkTask -- Old tasks become WorkTask by default
- taskParent' = Nothing
+ taskType' = fromMaybe WorkTask (KM.lookup "taskType" obj +> parseMaybe Aeson.parseJSON)
+ taskParent' = KM.lookup "taskParent" obj +> parseMaybe Aeson.parseJSON
taskNamespace' = KM.lookup "taskNamespace" obj +> parseMaybe Aeson.parseJSON
-- Default priority to P2 (medium) for old tasks
taskPriority' = fromMaybe P2 (KM.lookup "taskPriority" obj +> parseMaybe Aeson.parseJSON)
@@ -287,7 +298,8 @@ loadTasksInternal = do
taskNamespace = taskNamespace',
taskStatus = status,
taskPriority = taskPriority',
- taskDependencies = newDeps,
+ taskDependencies = finalDeps,
+ taskDescription = taskDescription',
taskCreatedAt = created,
taskUpdatedAt = updated
}
@@ -304,8 +316,8 @@ saveTaskInternal task = do
BLC.appendFile tasksFile (json <> "\n")
-- Create a new task
-createTask :: Text -> TaskType -> Maybe Text -> Maybe Text -> Priority -> [Dependency] -> IO Task
-createTask title taskType parent namespace priority deps =
+createTask :: Text -> TaskType -> Maybe Text -> Maybe Text -> Priority -> [Dependency] -> Maybe Text -> IO Task
+createTask title taskType parent namespace priority deps description =
withTaskWriteLock <| do
tid <- case parent of
Nothing -> generateId
@@ -323,6 +335,7 @@ createTask title taskType parent namespace priority deps =
taskStatus = Open,
taskPriority = priority,
taskDependencies = deps,
+ taskDescription = description,
taskCreatedAt = now,
taskUpdatedAt = now
}
@@ -632,6 +645,16 @@ showTaskDetailed t = do
putText ""
putText "Dependencies:"
traverse_ printDependency (taskDependencies t)
+
+ -- Show description
+ case taskDescription t of
+ Nothing -> pure ()
+ Just desc -> do
+ putText ""
+ putText "Description:"
+ -- Indent description for better readability
+ let indented = T.unlines <| map (" " <>) (T.lines desc)
+ putText indented
putText ""
where
diff --git a/Omni/Task/RaceTest.hs b/Omni/Task/RaceTest.hs
index 10d3451..cfadaca 100644
--- a/Omni/Task/RaceTest.hs
+++ b/Omni/Task/RaceTest.hs
@@ -29,7 +29,7 @@ raceTest =
initTaskDb
-- Create a parent epic
- parent <- createTask "Parent Epic" Epic Nothing Nothing P2 []
+ parent <- createTask "Parent Epic" Epic Nothing Nothing P2 [] Nothing
let parentId = taskId parent
-- Create multiple children concurrently
@@ -40,7 +40,7 @@ raceTest =
-- Run concurrent creations
children <-
mapConcurrently
- (\i -> createTask ("Child " <> tshow i) WorkTask (Just parentId) Nothing P2 [])
+ (\i -> createTask ("Child " <> tshow i) WorkTask (Just parentId) Nothing P2 [] Nothing)
indices
-- Check for duplicates in generated IDs