diff options
| -rw-r--r-- | .tasks/tasks.jsonl | 8 | ||||
| -rw-r--r-- | AGENTS.md | 5 | ||||
| -rw-r--r-- | Omni/Task.hs | 13 | ||||
| -rw-r--r-- | Omni/Task/Core.hs | 28 |
4 files changed, 39 insertions, 15 deletions
diff --git a/.tasks/tasks.jsonl b/.tasks/tasks.jsonl index e069305..10715dd 100644 --- a/.tasks/tasks.jsonl +++ b/.tasks/tasks.jsonl @@ -22,7 +22,7 @@ {"taskCreatedAt":"2025-11-09T13:05:18.445111257Z","taskDependencies":[],"taskId":"t-PqMc17","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add enhanced dependency types (blocks, discovered-from, related)","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:18.50495798Z"} {"taskCreatedAt":"2025-11-09T13:05:18.543055749Z","taskDependencies":[],"taskId":"t-PqMBuS","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Protect production database from tests","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:18.602787251Z"} {"taskCreatedAt":"2025-11-09T13:05:18.64074361Z","taskDependencies":[],"taskId":"t-PqN0Uu","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add migration support for old task format","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:18.703048004Z"} -{"taskCreatedAt":"2025-11-09T14:22:32.038937583Z","taskDependencies":[],"taskId":"t-Uumhrq","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Investigate and implement prettier tree drawing with box characters","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T14:22:32.038937583Z"} +{"taskCreatedAt":"2025-11-09T14:22:32.038937583Z","taskDependencies":[],"taskId":"t-Uumhrq","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Investigate and implement prettier tree drawing with box characters","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T18:40:33.764590135Z"} {"taskCreatedAt":"2025-11-09T16:48:40.260201423Z","taskDependencies":[],"taskId":"t-143KQl2","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"PodcastItLater: Path to Paid Product","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T16:48:40.260201423Z"} {"taskCreatedAt":"2025-11-09T16:48:47.076581674Z","taskDependencies":[],"taskId":"t-144drAE","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Adopt Bootstrap CSS for UI improvements","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T17:00:05.424532832Z"} {"taskCreatedAt":"2025-11-09T16:48:47.237113366Z","taskDependencies":[],"taskId":"t-144e7lF","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add Stripe integration for billing","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T23:04:23.856763018Z"} @@ -117,9 +117,5 @@ {"taskCreatedAt":"2025-11-20T15:25:27.424518009Z","taskDependencies":[],"taskId":"t-YBRpHe","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"CLI parsing fails with multiple flags","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T15:58:11.990663284Z"} {"taskCreatedAt":"2025-11-20T15:25:27.720568105Z","taskDependencies":[],"taskId":"t-YBSEIe","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Namespace filter broken","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T15:58:12.269456015Z"} {"taskCreatedAt":"2025-11-20T15:25:27.948491266Z","taskDependencies":[],"taskId":"t-YBTC0p","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Discovered-from flag broken","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T15:58:12.685064773Z"} -{"taskCreatedAt":"2025-11-20T15:57:33.643445015Z","taskDependencies":[],"taskId":"t-10IdDer","taskNamespace":"Omni/Test.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Test multi-flag","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T15:57:33.643445015Z"} -{"taskCreatedAt":"2025-11-20T15:57:33.870558362Z","taskDependencies":[],"taskId":"t-10IeAjy","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Test","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T15:57:33.870558362Z"} -{"taskCreatedAt":"2025-11-20T15:57:34.151624098Z","taskDependencies":[],"taskId":"t-10IfLqS","taskNamespace":"Omni/Test.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Test","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T15:57:34.151624098Z"} {"taskCreatedAt":"2025-11-20T15:58:11.740041636Z","taskDependencies":[],"taskId":"t-10KNtTF","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Docopt flag order matters incorrectly","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T18:35:44.798128524Z"} -{"taskCreatedAt":"2025-11-20T16:00:40.024108922Z","taskDependencies":[],"taskId":"t-10UPFmb","taskNamespace":null,"taskParent":null,"taskPriority":"P1","taskStatus":"Open","taskTitle":"High priority bug","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T16:00:40.024108922Z"} -{"taskCreatedAt":"2025-11-20T16:00:40.275992513Z","taskDependencies":[],"taskId":"t-10UQISP","taskNamespace":null,"taskParent":null,"taskPriority":"P3","taskStatus":"Open","taskTitle":"Low priority polish","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T16:00:40.275992513Z"} +{"taskCreatedAt":"2025-11-20T18:44:29.330834039Z","taskDependencies":[{"depId":"t-Uumhrq","depType":"DiscoveredFrom"}],"taskId":"t-1bE2r3q","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Document TASK_TEST_MODE in AGENTS.md","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T18:53:22.852670919Z"} @@ -435,7 +435,7 @@ Each line in `tasks.jsonl` is a JSON object representing a task. ### Testing and Development -**IMPORTANT**: When writing or testing code that modifies tasks, use the test database: +**CRITICAL**: When manually testing task functionality (like tree visualization, flag ordering, etc.), you MUST use the test database: ```bash # Set test mode to protect production database @@ -444,6 +444,7 @@ export TASK_TEST_MODE=1 # Now all task operations use .tasks/tasks-test.jsonl task create "Test task" --type=task task list +task tree # Unset when done unset TASK_TEST_MODE @@ -451,7 +452,7 @@ unset TASK_TEST_MODE **The test suite automatically uses test mode** - you don't need to set it manually when running `task test` or `bild --test Omni/Task.hs`. -**Never run destructive tests against the production database** (`.tasks/tasks.jsonl`) as this will delete real task data. +**NEVER run manual tests against the production database** (`.tasks/tasks.jsonl`). This pollutes it with test data that must be manually cleaned up. Always use `TASK_TEST_MODE=1` for experimentation. ## Integration with Git diff --git a/Omni/Task.hs b/Omni/Task.hs index eeca4c6..6d2da71 100644 --- a/Omni/Task.hs +++ b/Omni/Task.hs @@ -38,7 +38,7 @@ task Usage: task init [--quiet] - task create <title> [--type=<type>] [--parent=<id>] [--priority=<p>] [--deps=<ids>] [--dep-type=<type>] [--discovered-from=<id>] [--namespace=<ns>] [--json] + task create <title> [options] task list [options] task ready [--json] task show <id> [--json] @@ -453,5 +453,14 @@ cliTests = let result = Docopt.parseArgs help ["sync"] case result of Left err -> Test.assertFailure <| "Failed to parse 'sync': " <> show err - Right args -> args `Cli.has` Cli.command "sync" Test.@?= True + Right args -> args `Cli.has` Cli.command "sync" Test.@?= True, + Test.unit "create with flags in different order" <| do + let result = Docopt.parseArgs help ["create", "Test", "--json", "--priority=1", "--namespace=Omni/Task"] + case result of + Left err -> Test.assertFailure <| "Failed to parse 'create' with reordered flags: " <> show err + Right args -> do + args `Cli.has` Cli.command "create" Test.@?= True + args `Cli.has` Cli.longOption "json" Test.@?= True + Cli.getArg args (Cli.longOption "priority") Test.@?= Just "1" + Cli.getArg args (Cli.longOption "namespace") Test.@?= Just "Omni/Task" ] diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs index 1dc31a8..f463040 100644 --- a/Omni/Task/Core.hs +++ b/Omni/Task/Core.hs @@ -10,6 +10,7 @@ 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.List as List import qualified Data.Text as T import qualified Data.Text.IO as TIO import Data.Time (UTCTime, diffTimeToPicoseconds, getCurrentTime, utctDayTime) @@ -324,9 +325,19 @@ showTaskTree maybeId = do printEpicTree allTasks task = printTreeNode allTasks task 0 printTreeNode :: [Task] -> Task -> Int -> IO () - printTreeNode allTasks task indent = do - let prefix = T.pack (replicate (indent * 2) ' ') - children = filter (\t -> taskParent t == Just (taskId task)) allTasks + printTreeNode allTasks task indent = printTreeNode' allTasks task indent [] + + printTreeNode' :: [Task] -> Task -> Int -> [Bool] -> IO () + printTreeNode' allTasks task indent ancestry = do + let children = filter (\t -> taskParent t == Just (taskId task)) allTasks + -- Build tree prefix using box-drawing characters + prefix = + if indent == 0 + then "" + else + let ancestorPrefixes = map (\hasMore -> if hasMore then "│ " else " ") (List.init ancestry) + myPrefix = if List.last ancestry then "├── " else "└── " + in T.pack <| concat ancestorPrefixes ++ myPrefix -- For epics, show progress count [completed/total]; for tasks, show status checkbox statusStr = case taskType task of Epic -> @@ -349,8 +360,15 @@ showTaskTree maybeId = do else taskTitle task putText <| prefix <> taskId task <> " " <> statusStr <> " " <> nsStr <> truncatedTitle - -- Print children - traverse_ (\child -> printTreeNode allTasks child (indent + 1)) children + -- Print children with updated ancestry + let indexedChildren = zip [1 ..] children + totalChildren = length children + traverse_ + ( \(idx, child) -> + let hasMoreSiblings = idx < totalChildren + in printTreeNode' allTasks child (indent + 1) (ancestry ++ [hasMoreSiblings]) + ) + indexedChildren -- Helper to print a task printTask :: Task -> IO () |
