diff options
| author | Omni Worker <bot@omni.agent> | 2025-11-21 18:07:21 -0500 |
|---|---|---|
| committer | Omni Worker <bot@omni.agent> | 2025-11-21 18:07:21 -0500 |
| commit | 45521bda259c54458404ca8cda18615f0d36b492 (patch) | |
| tree | f21fa0a82406beafe588517f0ca6d1501d16abc0 /Omni | |
| parent | 523ed1966850e2bb16416d611fe2db3088421e4d (diff) | |
feat: add description field to tasks
Amp-Thread-ID:
https://ampcode.com/threads/T-79499d9e-f4f4-40de-893c-524c32a45483
Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'Omni')
| -rw-r--r-- | Omni/Task.hs | 58 | ||||
| -rw-r--r-- | Omni/Task/Core.hs | 47 | ||||
| -rw-r--r-- | Omni/Task/RaceTest.hs | 4 |
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 |
