summaryrefslogtreecommitdiff
path: root/Omni
diff options
context:
space:
mode:
Diffstat (limited to 'Omni')
-rw-r--r--Omni/Task.hs17
-rw-r--r--Omni/Task/Core.hs76
2 files changed, 77 insertions, 16 deletions
diff --git a/Omni/Task.hs b/Omni/Task.hs
index 571c72e..f10ae25 100644
--- a/Omni/Task.hs
+++ b/Omni/Task.hs
@@ -13,6 +13,7 @@ import qualified Omni.Namespace as Namespace
import Omni.Task.Core
import qualified Omni.Test as Test
import System.Directory (doesFileExist, removeFile)
+import System.Environment (setEnv)
main :: IO ()
main = Cli.main plan
@@ -165,18 +166,24 @@ unitTests :: Test.Tree
unitTests =
Test.group
"Unit tests"
- [ Test.unit "can create task" <| do
- -- Clean up before test
- exists <- doesFileExist ".tasks/tasks.jsonl"
- when exists <| removeFile ".tasks/tasks.jsonl"
-
+ [ Test.unit "setup test database" <| do
+ -- Set up test mode for all tests
+ setEnv "TASK_TEST_MODE" "1"
+
+ -- Clean up test database before all tests
+ let testFile = ".tasks/tasks-test.jsonl"
+ exists <- doesFileExist testFile
+ when exists <| removeFile testFile
initTaskDb
+ True Test.@?= True,
+ Test.unit "can create task" <| do
task <- createTask "Test task" WorkTask Nothing Nothing []
taskTitle task Test.@?= "Test task"
taskType task Test.@?= WorkTask
taskStatus task Test.@?= Open
null (taskDependencies task) Test.@?= True,
Test.unit "can list tasks" <| do
+ _ <- createTask "Test task for list" WorkTask Nothing Nothing []
tasks <- listTasks Nothing Nothing
not (null tasks) Test.@?= True,
Test.unit "ready tasks exclude blocked ones" <| do
diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs
index 1137d8d..6285ef7 100644
--- a/Omni/Task/Core.hs
+++ b/Omni/Task/Core.hs
@@ -5,13 +5,18 @@
module Omni.Task.Core where
import Alpha
+import Control.Monad ((>>=))
import Data.Aeson (FromJSON, ToJSON, decode, encode)
+import qualified Data.Aeson as Aeson
+import qualified Data.Aeson.KeyMap as KM
+import Data.Aeson.Types (parseMaybe)
import qualified Data.ByteString.Lazy.Char8 as BLC
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import Data.Time (UTCTime, diffTimeToPicoseconds, getCurrentTime, utctDayTime)
import GHC.Generics ()
import System.Directory (createDirectoryIfMissing, doesFileExist)
+import System.Environment (lookupEnv)
-- Core data types
data Task = Task
@@ -66,14 +71,23 @@ instance ToJSON Task
instance FromJSON Task
+-- Get the tasks database file path (use test file if TASK_TEST_MODE is set)
+getTasksFilePath :: IO FilePath
+getTasksFilePath = do
+ testMode <- lookupEnv "TASK_TEST_MODE"
+ pure <| case testMode of
+ Just "1" -> ".tasks/tasks-test.jsonl"
+ _ -> ".tasks/tasks.jsonl"
+
-- Initialize the task database
initTaskDb :: IO ()
initTaskDb = do
createDirectoryIfMissing True ".tasks"
- exists <- doesFileExist ".tasks/tasks.jsonl"
+ tasksFile <- getTasksFilePath
+ exists <- doesFileExist tasksFile
unless exists <| do
- TIO.writeFile ".tasks/tasks.jsonl" ""
- putText "Initialized task database at .tasks/tasks.jsonl"
+ TIO.writeFile tasksFile ""
+ putText <| "Initialized task database at " <> T.pack tasksFile
-- Generate a short ID using base62 encoding of timestamp
generateId :: IO Text
@@ -101,13 +115,14 @@ toBase62 n = reverse <| go n
[] -> '0' -- Fallback (should never happen)
in char : go q
--- Load all tasks from JSONL file
+-- Load all tasks from JSONL file (with migration support)
loadTasks :: IO [Task]
loadTasks = do
- exists <- doesFileExist ".tasks/tasks.jsonl"
+ tasksFile <- getTasksFilePath
+ exists <- doesFileExist tasksFile
if exists
then do
- content <- TIO.readFile ".tasks/tasks.jsonl"
+ content <- TIO.readFile tasksFile
let taskLines = T.lines content
pure <| mapMaybe decodeTask taskLines
else pure []
@@ -116,13 +131,49 @@ loadTasks = do
decodeTask line =
if T.null line
then Nothing
- else decode (BLC.pack <| T.unpack line)
+ else case decode (BLC.pack <| T.unpack line) of
+ Just task -> Just task
+ Nothing -> migrateOldTask line
+
+ -- Migrate old task format (with taskProject field) to new format
+ migrateOldTask :: Text -> Maybe Task
+ migrateOldTask 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
+ taskTitle' = KM.lookup "taskTitle" obj +> parseMaybe Aeson.parseJSON
+ 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
+ -- taskProject is ignored in new format (use epics instead)
+ taskType' = WorkTask -- Old tasks become WorkTask by default
+ taskParent' = Nothing
+ taskNamespace' = KM.lookup "taskNamespace" obj +> parseMaybe Aeson.parseJSON
+ in case (taskId', taskTitle', taskStatus', taskCreatedAt', taskUpdatedAt') of
+ (Just tid, Just title, Just status, Just created, Just updated) ->
+ Just
+ Task
+ { taskId = tid,
+ taskTitle = title,
+ taskType = taskType',
+ taskParent = taskParent',
+ taskNamespace = taskNamespace',
+ taskStatus = status,
+ taskDependencies = newDeps,
+ taskCreatedAt = created,
+ taskUpdatedAt = updated
+ }
+ _ -> Nothing
-- Save a single task (append to JSONL)
saveTask :: Task -> IO ()
saveTask task = do
+ tasksFile <- getTasksFilePath
let json = encode task
- BLC.appendFile ".tasks/tasks.jsonl" (json <> "\n")
+ BLC.appendFile tasksFile (json <> "\n")
-- Create a new task
createTask :: Text -> TaskType -> Maybe Text -> Maybe Text -> [Dependency] -> IO Task
@@ -155,7 +206,8 @@ updateTaskStatus tid newStatus = do
then t {taskStatus = newStatus, taskUpdatedAt = now}
else t
-- Rewrite the entire file (simple approach for MVP)
- TIO.writeFile ".tasks/tasks.jsonl" ""
+ tasksFile <- getTasksFilePath
+ TIO.writeFile tasksFile ""
traverse_ saveTask updatedTasks
-- List tasks, optionally filtered by type or parent
@@ -225,7 +277,8 @@ exportTasks :: IO ()
exportTasks = do
tasks <- loadTasks
-- Rewrite the entire file with deduplicated tasks
- TIO.writeFile ".tasks/tasks.jsonl" ""
+ tasksFile <- getTasksFilePath
+ TIO.writeFile tasksFile ""
traverse_ saveTask tasks
-- Import tasks: Read from another JSONL file and merge with existing tasks
@@ -252,7 +305,8 @@ importTasks filePath = do
allTasks = updatedTasks ++ newTasks
-- Rewrite tasks.jsonl with merged data
- TIO.writeFile ".tasks/tasks.jsonl" ""
+ tasksFile <- getTasksFilePath
+ TIO.writeFile tasksFile ""
traverse_ saveTask allTasks
where
decodeTask :: Text -> Maybe Task