summaryrefslogtreecommitdiff
path: root/Omni
diff options
context:
space:
mode:
Diffstat (limited to 'Omni')
-rw-r--r--Omni/Task.hs48
-rw-r--r--Omni/Task/Core.hs32
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