diff options
Diffstat (limited to 'Omni')
| -rw-r--r-- | Omni/Task.hs | 48 | ||||
| -rw-r--r-- | Omni/Task/Core.hs | 32 |
2 files changed, 70 insertions, 10 deletions
diff --git a/Omni/Task.hs b/Omni/Task.hs index 153f899..571c72e 100644 --- a/Omni/Task.hs +++ b/Omni/Task.hs @@ -33,7 +33,7 @@ task Usage: task init - task create <title> [--type=<type>] [--parent=<id>] [--deps=<ids>] [--namespace=<ns>] + task create <title> [--type=<type>] [--parent=<id>] [--deps=<ids>] [--dep-type=<type>] [--discovered-from=<id>] [--namespace=<ns>] task list [--type=<type>] [--parent=<id>] task ready task update <id> <status> @@ -59,6 +59,8 @@ Options: --type=<type> Task type: epic or task (default: task) --parent=<id> Parent epic ID --deps=<ids> Comma-separated list of dependency IDs + --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) --flush Force immediate export -i <file> Input file for import @@ -83,9 +85,26 @@ move args parent <- case Cli.getArg args (Cli.longOption "parent") of Nothing -> pure Nothing Just p -> pure <| Just (T.pack p) - deps <- case Cli.getArg args (Cli.longOption "deps") of - Nothing -> pure [] - Just depStr -> pure <| T.splitOn "," (T.pack depStr) + + -- Handle --discovered-from as shortcut + (depIds, depType) <- case Cli.getArg args (Cli.longOption "discovered-from") of + Just discoveredId -> pure ([T.pack discoveredId], DiscoveredFrom) + Nothing -> do + -- Parse regular --deps and --dep-type + ids <- case Cli.getArg args (Cli.longOption "deps") of + Nothing -> pure [] + Just depStr -> pure <| T.splitOn "," (T.pack depStr) + dtype <- case Cli.getArg args (Cli.longOption "dep-type") of + Nothing -> pure Blocks + Just "blocks" -> pure Blocks + Just "discovered-from" -> pure DiscoveredFrom + Just "parent-child" -> pure ParentChild + Just "related" -> pure Related + Just other -> panic <| "Invalid dependency type: " <> T.pack other <> ". Use: blocks, discovered-from, parent-child, or related" + pure (ids, dtype) + + let deps = map (\did -> Dependency {depId = did, depType = depType}) depIds + namespace <- case Cli.getArg args (Cli.longOption "namespace") of Nothing -> pure Nothing Just ns -> do @@ -162,8 +181,25 @@ unitTests = not (null tasks) Test.@?= True, Test.unit "ready tasks exclude blocked ones" <| do task1 <- createTask "First task" WorkTask Nothing Nothing [] - task2 <- createTask "Blocked task" WorkTask Nothing Nothing [taskId task1] + let blockingDep = Dependency {depId = taskId task1, depType = Blocks} + task2 <- createTask "Blocked task" WorkTask Nothing Nothing [blockingDep] + 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 [] + let discDep = Dependency {depId = taskId task1, depType = DiscoveredFrom} + task2 <- createTask "Discovered work" WorkTask Nothing Nothing [discDep] + 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 [] + let relDep = Dependency {depId = taskId task1, depType = Related} + task2 <- createTask "Task B" WorkTask Nothing Nothing [relDep] ready <- getReadyTasks + -- Both should be ready since Related doesn't block (taskId task1 `elem` map taskId ready) Test.@?= True - (taskId task2 `notElem` map taskId ready) Test.@?= True + (taskId task2 `elem` map taskId ready) Test.@?= True ] diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs index 6d9856f..1137d8d 100644 --- a/Omni/Task/Core.hs +++ b/Omni/Task/Core.hs @@ -21,7 +21,7 @@ data Task = Task taskParent :: Maybe Text, -- Parent epic ID taskNamespace :: Maybe Text, -- Optional namespace (e.g., "Omni/Task", "Biz/Cloud") taskStatus :: Status, - taskDependencies :: [Text], -- List of task IDs this depends on + taskDependencies :: [Dependency], -- List of dependencies with types taskCreatedAt :: UTCTime, taskUpdatedAt :: UTCTime } @@ -33,6 +33,19 @@ data TaskType = Epic | WorkTask data Status = Open | InProgress | Done deriving (Show, Eq, Generic) +data Dependency = Dependency + { depId :: Text, -- ID of the task this depends on + depType :: DependencyType -- Type of dependency relationship + } + deriving (Show, Eq, Generic) + +data DependencyType + = Blocks -- Hard dependency, blocks ready work queue + | DiscoveredFrom -- Work discovered during other work + | ParentChild -- Epic/subtask relationship + | Related -- Soft relationship, doesn't block + deriving (Show, Eq, Generic) + instance ToJSON TaskType instance FromJSON TaskType @@ -41,6 +54,14 @@ instance ToJSON Status instance FromJSON Status +instance ToJSON DependencyType + +instance FromJSON DependencyType + +instance ToJSON Dependency + +instance FromJSON Dependency + instance ToJSON Task instance FromJSON Task @@ -104,7 +125,7 @@ saveTask task = do BLC.appendFile ".tasks/tasks.jsonl" (json <> "\n") -- Create a new task -createTask :: Text -> TaskType -> Maybe Text -> Maybe Text -> [Text] -> IO Task +createTask :: Text -> TaskType -> Maybe Text -> Maybe Text -> [Dependency] -> IO Task createTask title taskType parent namespace deps = do tid <- generateId now <- getCurrentTime @@ -158,7 +179,9 @@ getReadyTasks = do allTasks <- loadTasks let openTasks = filter (\t -> taskStatus t /= Done) allTasks doneIds = map taskId <| filter (\t -> taskStatus t == Done) allTasks - isReady task = all (`elem` doneIds) (taskDependencies task) + -- Only Blocks and ParentChild dependencies block ready work + blockingDepIds task = [depId dep | dep <- taskDependencies task, depType dep `elem` [Blocks, ParentChild]] + isReady task = all (`elem` doneIds) (blockingDepIds task) pure <| filter isReady openTasks -- Show dependency tree for a task @@ -172,7 +195,8 @@ showDependencyTree tid = do printTree :: [Task] -> Task -> Int -> IO () printTree allTasks task indent = do putText <| T.pack (replicate (indent * 2) ' ') <> taskId task <> ": " <> taskTitle task - let deps = filter (\t -> taskId t `elem` taskDependencies task) allTasks + let depIds = map depId (taskDependencies task) + deps = filter (\t -> taskId t `elem` depIds) allTasks traverse_ (\dep -> printTree allTasks dep (indent + 1)) deps -- Helper to print a task |
