From 8ae33333b0fc0ca0876681dbcd54f962b89328fe Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Sun, 9 Nov 2025 07:53:04 -0500 Subject: Protect production task database from tests and add migration - Add TASK_TEST_MODE environment variable to use separate test database - All file operations now use getTasksFilePath to respect test mode - Tests use .tasks/tasks-test.jsonl instead of production database - Add automatic migration from old task format (taskProject field) to new format - Migrated tasks convert taskProject to WorkTask type with empty parent - Old [Text] dependencies converted to [Dependency] with Blocks type - Restore actual tasks from commit 3bf1691 (were lost during testing) This prevents accidental data loss when running tests and provides backward compatibility for existing task databases. --- .tasks/tasks-backup.jsonl | 12 ++++++++ .tasks/tasks-test.jsonl | 8 +++++ .tasks/tasks.jsonl | 24 +++++++-------- Omni/Task.hs | 17 +++++++---- Omni/Task/Core.hs | 76 ++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 109 insertions(+), 28 deletions(-) create mode 100644 .tasks/tasks-backup.jsonl create mode 100644 .tasks/tasks-test.jsonl diff --git a/.tasks/tasks-backup.jsonl b/.tasks/tasks-backup.jsonl new file mode 100644 index 0000000..3c975e8 --- /dev/null +++ b/.tasks/tasks-backup.jsonl @@ -0,0 +1,12 @@ +{"taskCreatedAt":"2025-11-08T20:03:50.230851965Z","taskDependencies":[],"taskId":"t-a1b2c3","taskProject":"task-manager","taskStatus":"Done","taskTitle":"Show help text when task invoked without args","taskUpdatedAt":"2025-11-08T20:06:02.605878048Z"} +{"taskCreatedAt":"2025-11-08T20:03:53.429072631Z","taskDependencies":[],"taskId":"t-d4e5f6","taskProject":"task-manager","taskStatus":"Done","taskTitle":"Move dev instructions from README.md to AGENTS.md","taskUpdatedAt":"2025-11-08T20:06:22.732392229Z"} +{"taskCreatedAt":"2025-11-08T20:06:27.395834401Z","taskDependencies":[],"taskId":"t-g7h8i9","taskProject":"task-manager","taskStatus":"Done","taskTitle":"Task ids should be shorter. Use the sqids package in haskell to generate ids","taskUpdatedAt":"2025-11-08T21:00:37.311865046Z"} +{"taskCreatedAt":"2025-11-08T20:09:35.590622249Z","taskDependencies":[],"taskId":"t-j0k1L2","taskProject":"task-manager","taskStatus":"Open","taskTitle":"Tasks should have an optional namespace associated with them. Namespaces are first class citizens in this monorepo","taskUpdatedAt":"2025-11-08T20:09:35.590622249Z"} +{"taskCreatedAt":"2025-11-08T20:10:09.944217463Z","taskDependencies":[],"taskId":"t-m3n4o5","taskProject":"task-manager","taskStatus":"Open","taskTitle":"There should be a command to list all projects.","taskUpdatedAt":"2025-11-08T20:10:09.944217463Z"} +{"taskCreatedAt":"2025-11-08T20:20:38.785442739Z","taskDependencies":[],"taskId":"t-p6q7r8","taskProject":"task-manager","taskStatus":"Done","taskTitle":"Instruct agents too use git-branchless and a patch based workflow rather than traditional git commands if and when they need to record things in git.","taskUpdatedAt":"2025-11-08T21:09:06.854871964Z"} +{"taskCreatedAt":"2025-11-08T20:22:20.116289616Z","taskDependencies":[],"taskId":"t-s9T0u1","taskProject":"task-manager","taskStatus":"Open","taskTitle":"instruct agents to include tests with all new features and bug fixes","taskUpdatedAt":"2025-11-08T20:22:20.116289616Z"} +{"taskCreatedAt":"2025-11-08T20:45:12.764939794Z","taskDependencies":[],"taskId":"t-v2w3x4","taskProject":"task-manager","taskStatus":"Open","taskTitle":"instruct agents to run 'bild --test' and 'lint' for whatever namespace(s) they are working on after completing a task and fix any reported errors","taskUpdatedAt":"2025-11-08T20:45:12.764939794Z"} +{"taskCreatedAt":"2025-11-08T20:48:43.183226361Z","taskDependencies":[],"taskId":"t-y5z6A7","taskProject":"ide","taskStatus":"Open","taskTitle":"The script Omni/Ide/typecheck.sh needs to support Haskell type checking in a similar fashion as how Omni/Ide/repl.sh is able to handle multiple languages","taskUpdatedAt":"2025-11-08T20:48:43.183226361Z"} +{"taskCreatedAt":"2025-11-08T21:00:27.020241869Z","taskDependencies":[],"taskId":"t-1ky7gJ2","taskProject":"task-manager","taskStatus":"Done","taskTitle":"Test shorter IDs","taskUpdatedAt":"2025-11-08T21:04:00.990704969Z"} +{"taskCreatedAt":"2025-11-08T21:00:29.901677247Z","taskDependencies":[],"taskId":"t-1kyjmjN","taskProject":"task-manager","taskStatus":"Done","taskTitle":"Another test task","taskUpdatedAt":"2025-11-08T21:04:04.081664205Z"} +{"taskCreatedAt":"2025-11-08T21:11:41.013924674Z","taskDependencies":[],"taskId":"t-1lhJhgS","taskProject":"misc","taskStatus":"Open","taskTitle":"Remove the old aider config in .aider* files and directories. Aider stinks and we will use amp going forward","taskUpdatedAt":"2025-11-08T21:11:41.013924674Z"} diff --git a/.tasks/tasks-test.jsonl b/.tasks/tasks-test.jsonl new file mode 100644 index 0000000..abc13ef --- /dev/null +++ b/.tasks/tasks-test.jsonl @@ -0,0 +1,8 @@ +{"taskCreatedAt":"2025-11-09T12:51:46.56267588Z","taskDependencies":[],"taskId":"t-OxPCj7","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Test task","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:51:46.56267588Z"} +{"taskCreatedAt":"2025-11-09T12:51:46.564712781Z","taskDependencies":[],"taskId":"t-OxPCPZ","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Test task for list","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:51:46.564712781Z"} +{"taskCreatedAt":"2025-11-09T12:51:46.573780484Z","taskDependencies":[],"taskId":"t-OxPFce","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"First task","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:51:46.573780484Z"} +{"taskCreatedAt":"2025-11-09T12:51:46.57535537Z","taskDependencies":[{"depId":"t-OxPFce","depType":"Blocks"}],"taskId":"t-OxPFBE","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Blocked task","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:51:46.57535537Z"} +{"taskCreatedAt":"2025-11-09T12:51:46.57629654Z","taskDependencies":[],"taskId":"t-OxPFQO","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Original task","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:51:46.57629654Z"} +{"taskCreatedAt":"2025-11-09T12:51:46.576622253Z","taskDependencies":[{"depId":"t-OxPFQO","depType":"DiscoveredFrom"}],"taskId":"t-OxPFW5","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Discovered work","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:51:46.576622253Z"} +{"taskCreatedAt":"2025-11-09T12:51:46.577425682Z","taskDependencies":[],"taskId":"t-OxPG92","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Task A","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:51:46.577425682Z"} +{"taskCreatedAt":"2025-11-09T12:51:46.577613884Z","taskDependencies":[{"depId":"t-OxPG92","depType":"Related"}],"taskId":"t-OxPGc4","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Task B","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:51:46.577613884Z"} diff --git a/.tasks/tasks.jsonl b/.tasks/tasks.jsonl index ff2a394..be3699f 100644 --- a/.tasks/tasks.jsonl +++ b/.tasks/tasks.jsonl @@ -1,12 +1,12 @@ -{"taskCreatedAt":"2025-11-09T12:28:48.574806406Z","taskDependencies":[],"taskId":"t-N2zIIk","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Test task","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:28:48.574806406Z"} -{"taskCreatedAt":"2025-11-09T12:28:48.591902984Z","taskDependencies":[],"taskId":"t-N2zNa5","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"First task","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:28:48.591902984Z"} -{"taskCreatedAt":"2025-11-09T12:28:48.592205598Z","taskDependencies":[{"depId":"t-N2zNa5","depType":"Blocks"}],"taskId":"t-N2zNeY","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Blocked task","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:28:48.592205598Z"} -{"taskCreatedAt":"2025-11-09T12:28:48.593123597Z","taskDependencies":[],"taskId":"t-N2zNtM","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Original task","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:28:48.593123597Z"} -{"taskCreatedAt":"2025-11-09T12:28:48.59342775Z","taskDependencies":[{"depId":"t-N2zNtM","depType":"DiscoveredFrom"}],"taskId":"t-N2zNyG","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Discovered work","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:28:48.59342775Z"} -{"taskCreatedAt":"2025-11-09T12:28:48.594193628Z","taskDependencies":[],"taskId":"t-N2zNL2","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Task A","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:28:48.594193628Z"} -{"taskCreatedAt":"2025-11-09T12:28:48.594472081Z","taskDependencies":[{"depId":"t-N2zNL2","depType":"Related"}],"taskId":"t-N2zNPx","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Task B","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:28:48.594472081Z"} -{"taskCreatedAt":"2025-11-09T12:30:08.108951426Z","taskDependencies":[],"taskId":"t-N7Xrb7","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Test task A","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:30:08.108951426Z"} -{"taskCreatedAt":"2025-11-09T12:30:08.2734999Z","taskDependencies":[{"depId":"t-MTdiPS","depType":"Blocks"}],"taskId":"t-N7Y7Z8","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Test task B with blocking dep","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:30:08.2734999Z"} -{"taskCreatedAt":"2025-11-09T12:30:08.459060403Z","taskDependencies":[{"depId":"t-MTdiPS","depType":"DiscoveredFrom"}],"taskId":"t-N7YUg2","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Discovered work","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:30:08.459060403Z"} -{"taskCreatedAt":"2025-11-09T12:30:45.984751589Z","taskDependencies":[],"taskId":"t-Nawmp8","taskNamespace":null,"taskParent":null,"taskStatus":"Done","taskTitle":"Blocking task example","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:30:54.668780601Z"} -{"taskCreatedAt":"2025-11-09T12:30:49.990290546Z","taskDependencies":[{"depId":"t-Nawmp8","depType":"Blocks"}],"taskId":"t-NaNaqA","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Dependent task","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T12:30:49.990290546Z"} +{"taskCreatedAt":"2025-11-08T20:03:50.230851965Z","taskDependencies":[],"taskId":"t-a1b2c3","taskNamespace":null,"taskParent":null,"taskStatus":"Done","taskTitle":"Show help text when task invoked without args","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T20:06:02.605878048Z"} +{"taskCreatedAt":"2025-11-08T20:03:53.429072631Z","taskDependencies":[],"taskId":"t-d4e5f6","taskNamespace":null,"taskParent":null,"taskStatus":"Done","taskTitle":"Move dev instructions from README.md to AGENTS.md","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T20:06:22.732392229Z"} +{"taskCreatedAt":"2025-11-08T20:06:27.395834401Z","taskDependencies":[],"taskId":"t-g7h8i9","taskNamespace":null,"taskParent":null,"taskStatus":"Done","taskTitle":"Task ids should be shorter. Use the sqids package in haskell to generate ids","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T21:00:37.311865046Z"} +{"taskCreatedAt":"2025-11-08T20:09:35.590622249Z","taskDependencies":[],"taskId":"t-j0k1L2","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Tasks should have an optional namespace associated with them. Namespaces are first class citizens in this monorepo","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T20:09:35.590622249Z"} +{"taskCreatedAt":"2025-11-08T20:10:09.944217463Z","taskDependencies":[],"taskId":"t-m3n4o5","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"There should be a command to list all projects.","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T20:10:09.944217463Z"} +{"taskCreatedAt":"2025-11-08T20:20:38.785442739Z","taskDependencies":[],"taskId":"t-p6q7r8","taskNamespace":null,"taskParent":null,"taskStatus":"Done","taskTitle":"Instruct agents too use git-branchless and a patch based workflow rather than traditional git commands if and when they need to record things in git.","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T21:09:06.854871964Z"} +{"taskCreatedAt":"2025-11-08T20:22:20.116289616Z","taskDependencies":[],"taskId":"t-s9T0u1","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"instruct agents to include tests with all new features and bug fixes","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T20:22:20.116289616Z"} +{"taskCreatedAt":"2025-11-08T20:45:12.764939794Z","taskDependencies":[],"taskId":"t-v2w3x4","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"instruct agents to run 'bild --test' and 'lint' for whatever namespace(s) they are working on after completing a task and fix any reported errors","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T20:45:12.764939794Z"} +{"taskCreatedAt":"2025-11-08T20:48:43.183226361Z","taskDependencies":[],"taskId":"t-y5z6A7","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"The script Omni/Ide/typecheck.sh needs to support Haskell type checking in a similar fashion as how Omni/Ide/repl.sh is able to handle multiple languages","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T20:48:43.183226361Z"} +{"taskCreatedAt":"2025-11-08T21:00:27.020241869Z","taskDependencies":[],"taskId":"t-1ky7gJ2","taskNamespace":null,"taskParent":null,"taskStatus":"Done","taskTitle":"Test shorter IDs","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T21:04:00.990704969Z"} +{"taskCreatedAt":"2025-11-08T21:00:29.901677247Z","taskDependencies":[],"taskId":"t-1kyjmjN","taskNamespace":null,"taskParent":null,"taskStatus":"Done","taskTitle":"Another test task","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T21:04:04.081664205Z"} +{"taskCreatedAt":"2025-11-08T21:11:41.013924674Z","taskDependencies":[],"taskId":"t-1lhJhgS","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Remove the old aider config in .aider* files and directories. Aider stinks and we will use amp going forward","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T21:11:41.013924674Z"} 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 -- cgit v1.2.3