summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2025-11-20 11:00:45 -0500
committerBen Sima <ben@bsima.me>2025-11-20 11:35:18 -0500
commit0fea1f9ce76a2e3df5e4e69f7c50995acd4a0f81 (patch)
treecdc5d1ca0c9b4605e01397291296f7184cede612
parent5974c919ac505f01e7fbe454b906162b94b1ddd6 (diff)
task: sync database
-rw-r--r--.tasks/tasks.jsonl239
-rw-r--r--AGENTS.md180
-rw-r--r--Omni/Task.hs254
-rw-r--r--Omni/Task/Core.hs53
4 files changed, 559 insertions, 167 deletions
diff --git a/.tasks/tasks.jsonl b/.tasks/tasks.jsonl
index 9d24f70..58fa769 100644
--- a/.tasks/tasks.jsonl
+++ b/.tasks/tasks.jsonl
@@ -1,114 +1,125 @@
-{"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":"Done","taskTitle":"Tasks should have an optional namespace associated with them. Namespaces are first class citizens in this monorepo","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:14:05.775741617Z"}
-{"taskCreatedAt":"2025-11-08T20:10:09.944217463Z","taskDependencies":[],"taskId":"t-m3n4o5","taskNamespace":null,"taskParent":null,"taskStatus":"Done","taskTitle":"There should be a command to list all projects.","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:44:57.393279815Z"}
-{"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":"Done","taskTitle":"instruct agents to include tests with all new features and bug fixes","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T21:24:54.004658966Z"}
-{"taskCreatedAt":"2025-11-08T20:45:12.764939794Z","taskDependencies":[],"taskId":"t-v2w3x4","taskNamespace":null,"taskParent":null,"taskStatus":"Done","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-08T21:25:10.756670871Z"}
-{"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-16T08:13:51.934598506Z"}
-{"taskCreatedAt":"2025-11-08T21:11:41.013924674Z","taskDependencies":[],"taskId":"t-1lhJhgS","taskNamespace":null,"taskParent":null,"taskStatus":"Done","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:28:34.875747622Z"}
-{"taskCreatedAt":"2025-11-09T13:05:06.468930038Z","taskDependencies":[],"taskId":"t-PpXWsU","taskNamespace":"Omni/Task.hs","taskParent":null,"taskStatus":"Open","taskTitle":"Task Manager Improvements","taskType":"Epic","taskUpdatedAt":"2025-11-09T13:05:06.468930038Z"}
-{"taskCreatedAt":"2025-11-09T13:05:06.718797697Z","taskDependencies":[],"taskId":"t-PpYZt2","taskNamespace":"Omni/Task.hs","taskParent":"t-PpXWsU","taskStatus":"Open","taskTitle":"Implement child ID generation (t-abc123.1)","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:06.718797697Z"}
-{"taskCreatedAt":"2025-11-09T13:05:06.746734115Z","taskDependencies":[],"taskId":"t-PpZ6JC","taskNamespace":"Omni/Task.hs","taskParent":"t-PpXWsU","taskStatus":"Open","taskTitle":"Add child_counters storage","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:06.746734115Z"}
-{"taskCreatedAt":"2025-11-09T13:05:06.774903465Z","taskDependencies":[],"taskId":"t-PpZe3X","taskNamespace":"Omni/Task.hs","taskParent":"t-PpXWsU","taskStatus":"Open","taskTitle":"Update createTask to auto-generate child IDs","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:06.774903465Z"}
-{"taskCreatedAt":"2025-11-09T13:05:06.802295008Z","taskDependencies":[],"taskId":"t-PpZlbL","taskNamespace":"Omni/Task.hs","taskParent":"t-PpXWsU","taskStatus":"Done","taskTitle":"Implement task tree visualization command","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:47:12.411364105Z"}
-{"taskCreatedAt":"2025-11-09T13:05:06.829842253Z","taskDependencies":[],"taskId":"t-PpZsm4","taskNamespace":"Omni/Task.hs","taskParent":"t-PpXWsU","taskStatus":"Open","taskTitle":"Implement task stats command","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:06.829842253Z"}
-{"taskCreatedAt":"2025-11-09T13:05:06.85771202Z","taskDependencies":[],"taskId":"t-PpZzBA","taskNamespace":"Omni/Task.hs","taskParent":"t-PpXWsU","taskStatus":"Open","taskTitle":"Implement epic progress tracking","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:06.85771202Z"}
-{"taskCreatedAt":"2025-11-09T13:05:06.88583862Z","taskDependencies":[],"taskId":"t-PpZGVf","taskNamespace":"Omni/Task.hs","taskParent":"t-PpXWsU","taskStatus":"Done","taskTitle":"Add filtering by type and parent (list improvements)","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:17:51.373969453Z"}
-{"taskCreatedAt":"2025-11-09T13:05:18.344932105Z","taskDependencies":[],"taskId":"t-PqLLXk","taskNamespace":"Omni/Task.hs","taskParent":"t-PpXWsU","taskStatus":"Done","taskTitle":"Implement epic and task types","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:18.406381682Z"}
-{"taskCreatedAt":"2025-11-09T13:05:18.445111257Z","taskDependencies":[],"taskId":"t-PqMc17","taskNamespace":"Omni/Task.hs","taskParent":"t-PpXWsU","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":"t-PpXWsU","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":"t-PpXWsU","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":[{"depId":"t-PpZlbL","depType":"DiscoveredFrom"}],"taskId":"t-Uumhrq","taskNamespace":"Omni/Task.hs","taskParent":null,"taskStatus":"Open","taskTitle":"Investigate and implement prettier tree drawing with box characters","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T14:22:32.038937583Z"}
-{"taskCreatedAt":"2025-11-09T16:48:40.260201423Z","taskDependencies":[],"taskId":"t-143KQl2","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskStatus":"Open","taskTitle":"PodcastItLater: Path to Paid Product","taskType":"Epic","taskUpdatedAt":"2025-11-09T16:48:40.260201423Z"}
-{"taskCreatedAt":"2025-11-09T16:48:47.076581674Z","taskDependencies":[],"taskId":"t-144drAE","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","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":"t-143KQl2","taskStatus":"Done","taskTitle":"Add Stripe integration for billing","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T23:04:23.856763018Z"}
-{"taskCreatedAt":"2025-11-09T16:48:47.388960509Z","taskDependencies":[],"taskId":"t-144eKR1","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskStatus":"Done","taskTitle":"Implement usage tracking and limits","taskType":"WorkTask","taskUpdatedAt":"2025-11-19T03:27:25.707745105Z"}
-{"taskCreatedAt":"2025-11-09T16:48:47.589181852Z","taskDependencies":[],"taskId":"t-144fAWn","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskStatus":"Done","taskTitle":"Add email notifications (transactional)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T01:35:54.519545888Z"}
-{"taskCreatedAt":"2025-11-09T16:48:47.737218185Z","taskDependencies":[],"taskId":"t-144gds4","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskStatus":"Done","taskTitle":"Migrate from SQLite to PostgreSQL","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T01:35:54.70061831Z"}
-{"taskCreatedAt":"2025-11-09T16:48:47.887102357Z","taskDependencies":[],"taskId":"t-144gQry","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskStatus":"Open","taskTitle":"Create basic admin dashboard","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T16:48:47.887102357Z"}
-{"taskCreatedAt":"2025-11-09T16:48:48.072927212Z","taskDependencies":[],"taskId":"t-144hCMJ","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskStatus":"Open","taskTitle":"Complete comprehensive test suite","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T16:48:48.072927212Z"}
-{"taskCreatedAt":"2025-11-09T17:48:34.522286485Z","taskDependencies":[],"taskId":"t-17Z0069","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskStatus":"Done","taskTitle":"Fix Recent Episodes refresh to prepend instead of reload (interrupts audio playback)","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T19:42:22.105902786Z"}
-{"taskCreatedAt":"2025-11-09T22:19:27.303689497Z","taskDependencies":[],"taskId":"t-1pIV0ZF","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskStatus":"Done","taskTitle":"Implement billing page UI component with pricing and upgrade options","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T23:04:20.974801117Z"}
-{"taskCreatedAt":"2025-11-09T22:38:46.235799803Z","taskDependencies":[],"taskId":"t-1qZlMb4","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Add a 'task show <id>' command that prints out a long, easy to read (for humans) version of the task. Include dependencies and all information fields in the output","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T22:38:46.235799803Z"}
-{"taskCreatedAt":"2025-11-09T22:56:18.897655607Z","taskDependencies":[],"taskId":"t-1s8ADC0","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskStatus":"Done","taskTitle":"Make PodcastItLater UI mobile-friendly and responsive","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T23:09:16.712244322Z"}
-{"taskCreatedAt":"2025-11-10T01:32:42.893029428Z","taskDependencies":[],"taskId":"t-64tkB5","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskStatus":"Done","taskTitle":"Add dark mode support to PodcastItLater UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-10T02:16:39.202726983Z"}
-{"taskCreatedAt":"2025-11-13T16:32:05.496080694Z","taskDependencies":[],"taskId":"t-12YqUKr","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskStatus":"Done","taskTitle":"Simplify billing to single paid plan","taskType":"Epic","taskUpdatedAt":"2025-11-13T16:37:49.407332883Z"}
-{"taskCreatedAt":"2025-11-13T16:32:16.514172804Z","taskDependencies":[],"taskId":"t-12Zb93B","taskNamespace":null,"taskParent":"t-12YqUKr","taskStatus":"Done","taskTitle":"Separate navbar into user navbar and callout box for plan info","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:34:44.480359383Z"}
-{"taskCreatedAt":"2025-11-13T16:32:16.718245548Z","taskDependencies":[],"taskId":"t-12Zc095","taskNamespace":null,"taskParent":"t-12YqUKr","taskStatus":"Done","taskTitle":"Make 'Upgrade Now' button go directly to Stripe checkout (not /billing page)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:34:44.530482584Z"}
-{"taskCreatedAt":"2025-11-13T16:32:16.899253732Z","taskDependencies":[],"taskId":"t-12ZcLez","taskNamespace":null,"taskParent":"t-12YqUKr","taskStatus":"Done","taskTitle":"Add plan details to callout box (unlimited articles, $12/month)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:34:44.579475578Z"}
-{"taskCreatedAt":"2025-11-13T16:32:17.077566618Z","taskDependencies":[],"taskId":"t-12ZdvCB","taskNamespace":null,"taskParent":"t-12YqUKr","taskStatus":"Done","taskTitle":"Delete /billing page and all related code (billing_page, BillingPage component)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:36:41.708746244Z"}
-{"taskCreatedAt":"2025-11-13T16:32:17.264388472Z","taskDependencies":[],"taskId":"t-12ZeidQ","taskNamespace":null,"taskParent":"t-12YqUKr","taskStatus":"Done","taskTitle":"Update billing_checkout to use 'paid' tier instead of 'pro'","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:36:41.758424911Z"}
-{"taskCreatedAt":"2025-11-13T16:32:17.411379982Z","taskDependencies":[],"taskId":"t-12ZeUsG","taskNamespace":null,"taskParent":"t-12YqUKr","taskStatus":"Done","taskTitle":"Update success/cancel URLs to redirect to / instead of /billing","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:36:41.808119038Z"}
-{"taskCreatedAt":"2025-11-13T16:32:17.557115348Z","taskDependencies":[],"taskId":"t-12Zfwnf","taskNamespace":null,"taskParent":"t-12YqUKr","taskStatus":"Done","taskTitle":"Remove 'Billing' button from navbar (paid users will use Stripe portal link in callout)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:34:44.628587871Z"}
-{"taskCreatedAt":"2025-11-13T16:32:17.738052991Z","taskDependencies":[],"taskId":"t-12ZghrB","taskNamespace":null,"taskParent":"t-12YqUKr","taskStatus":"Done","taskTitle":"Test the complete flow and verify all changes","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:37:49.356932049Z"}
-{"taskCreatedAt":"2025-11-13T19:38:07.804316976Z","taskDependencies":[],"taskId":"t-1f9QP23","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"General Code Quality Refactor","taskType":"Epic","taskUpdatedAt":"2025-11-13T19:38:07.804316976Z"}
-{"taskCreatedAt":"2025-11-13T19:38:08.01779309Z","taskDependencies":[],"taskId":"t-1f9RIzd","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Account Management Page","taskType":"Epic","taskUpdatedAt":"2025-11-13T19:38:08.01779309Z"}
-{"taskCreatedAt":"2025-11-13T19:38:08.176692694Z","taskDependencies":[],"taskId":"t-1f9SnU7","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Queue Status Improvements","taskType":"Epic","taskUpdatedAt":"2025-11-13T19:38:08.176692694Z"}
-{"taskCreatedAt":"2025-11-13T19:38:08.37344762Z","taskDependencies":[],"taskId":"t-1f9Td4U","taskNamespace":null,"taskParent":null,"taskStatus":"Open","taskTitle":"Navbar Styling Cleanup","taskType":"Epic","taskUpdatedAt":"2025-11-13T19:38:08.37344762Z"}
-{"taskCreatedAt":"2025-11-13T19:38:32.95559213Z","taskDependencies":[],"taskId":"t-1fbym1M","taskNamespace":null,"taskParent":"t-1f9QP23","taskStatus":"Done","taskTitle":"Remove BLE001 noqa for bare Exception catches - use specific exceptions","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:43:29.049855419Z"}
-{"taskCreatedAt":"2025-11-13T19:38:33.139120541Z","taskDependencies":[],"taskId":"t-1fbz7LV","taskNamespace":null,"taskParent":"t-1f9QP23","taskStatus":"Done","taskTitle":"Fix PLR0913 violations - refactor functions with too many parameters","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:44:09.820023426Z"}
-{"taskCreatedAt":"2025-11-13T19:38:33.309222802Z","taskDependencies":[],"taskId":"t-1fbzQ1v","taskNamespace":null,"taskParent":"t-1f9QP23","taskStatus":"Done","taskTitle":"Extract format_duration utility to shared UI or Core module (used only in Web.py)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:45:49.402934404Z"}
-{"taskCreatedAt":"2025-11-13T19:38:33.491331064Z","taskDependencies":[],"taskId":"t-1fbABoD","taskNamespace":null,"taskParent":"t-1f9QP23","taskStatus":"Done","taskTitle":"Extract extract_og_metadata and send_magic_link to Core module for reusability","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:46:04.679290775Z"}
-{"taskCreatedAt":"2025-11-13T19:38:33.674140035Z","taskDependencies":[],"taskId":"t-1fbBmXa","taskNamespace":null,"taskParent":"t-1f9QP23","taskStatus":"Done","taskTitle":"Review and fix type: ignore comments - improve type safety","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:47:09.583640045Z"}
-{"taskCreatedAt":"2025-11-13T19:38:33.85804778Z","taskDependencies":[],"taskId":"t-1fbC8Nq","taskNamespace":null,"taskParent":"t-1f9QP23","taskStatus":"Done","taskTitle":"Remove PLR2004 magic number - use constant for month check","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:47:45.120428021Z"}
-{"taskCreatedAt":"2025-11-13T19:38:34.035597081Z","taskDependencies":[],"taskId":"t-1fbCSZd","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Done","taskTitle":"Implement cancel subscription functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.709672316Z"}
-{"taskCreatedAt":"2025-11-13T19:38:34.194926176Z","taskDependencies":[],"taskId":"t-1fbDyr2","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Open","taskTitle":"Implement delete account functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.194926176Z"}
-{"taskCreatedAt":"2025-11-13T19:38:34.384489707Z","taskDependencies":[],"taskId":"t-1fbElKv","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Open","taskTitle":"Implement change email address functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.384489707Z"}
-{"taskCreatedAt":"2025-11-13T19:38:34.561871604Z","taskDependencies":[],"taskId":"t-1fbF5Tv","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Done","taskTitle":"Add logout button to account page","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.65796855Z"}
-{"taskCreatedAt":"2025-11-13T19:38:34.777721397Z","taskDependencies":[],"taskId":"t-1fbG02X","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Done","taskTitle":"Replace Coming Soon placeholder with full account management UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.606196024Z"}
-{"taskCreatedAt":"2025-11-13T19:38:34.962196629Z","taskDependencies":[],"taskId":"t-1fbGM2m","taskNamespace":null,"taskParent":"t-1f9SnU7","taskStatus":"Done","taskTitle":"Add remove button to queue status items","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:20:10.941908917Z"}
-{"taskCreatedAt":"2025-11-13T19:38:35.119686179Z","taskDependencies":[],"taskId":"t-1fbHr0w","taskNamespace":null,"taskParent":"t-1f9Td4U","taskStatus":"Done","taskTitle":"Remove button classes from navbar links (make them regular nav links)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.185088389Z"}
-{"taskCreatedAt":"2025-11-13T19:38:35.311151364Z","taskDependencies":[],"taskId":"t-1fbIeOF","taskNamespace":null,"taskParent":"t-1f9Td4U","taskStatus":"Done","taskTitle":"Remove 'Logged in as' text from navbar","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.23552934Z"}
-{"taskCreatedAt":"2025-11-13T19:38:35.476139354Z","taskDependencies":[],"taskId":"t-1fbIVJL","taskNamespace":null,"taskParent":"t-1f9Td4U","taskStatus":"Done","taskTitle":"Left-align navbar links instead of right-aligned buttons","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.285578917Z"}
-{"taskCreatedAt":"2025-11-13T19:38:35.65125955Z","taskDependencies":[],"taskId":"t-1fbJFic","taskNamespace":null,"taskParent":"t-1f9Td4U","taskStatus":"Done","taskTitle":"Remove logout button from navbar (will be in account page)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.336546723Z"}
-{"taskCreatedAt":"2025-11-13T19:54:08.34625259Z","taskDependencies":[],"taskId":"t-1gcR9RV","taskNamespace":"Omni/Bild.nix","taskParent":null,"taskStatus":"Open","taskTitle":"Add ruff to the developer environment, the 'env' attribute in Bild.nix","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:54:08.34625259Z"}
-{"taskCreatedAt":"2025-11-13T20:02:50.914482516Z","taskDependencies":[],"taskId":"t-1gMdNJK","taskNamespace":null,"taskParent":"t-1f9QP23","taskStatus":"Done","taskTitle":"Fix dev mode banner styling and pre-fill login email","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:03:45.644107089Z"}
-{"taskCreatedAt":"2025-11-13T21:01:35.331063546Z","taskDependencies":[{"depId":"t-1fbG02X","depType":"DiscoveredFrom"}],"taskId":"t-1kCJTuu","taskNamespace":null,"taskParent":null,"taskStatus":"Done","taskTitle":"Fix Stripe portal error handling and account page padding","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T21:03:19.701792229Z"}
-{"taskCreatedAt":"2025-11-14T18:19:16.584321849Z","taskDependencies":[],"taskId":"t-19ZF6A8","taskNamespace":null,"taskParent":null,"taskStatus":"Done","taskTitle":"Parallel Target Builds - Epic","taskType":"Epic","taskUpdatedAt":"2025-11-14T19:03:02.525200039Z"}
-{"taskCreatedAt":"2025-11-14T18:19:33.701736325Z","taskDependencies":[],"taskId":"t-1a0OVBs","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Add mapConcurrentlyBounded helper using QSemN","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:20:20.979870628Z"}
-{"taskCreatedAt":"2025-11-14T18:19:37.810028305Z","taskDependencies":[],"taskId":"t-1a16ame","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Refactor build function to extract buildTarget worker","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:20:58.231039244Z"}
-{"taskCreatedAt":"2025-11-14T18:19:45.688391211Z","taskDependencies":[],"taskId":"t-1a1DdSB","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Replace forM with mapConcurrentlyBounded in build","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:20:58.290149792Z"}
-{"taskCreatedAt":"2025-11-14T18:19:45.716079624Z","taskDependencies":[],"taskId":"t-1a1Dl5c","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Test basic parallel builds without UI changes","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:31:57.019839638Z"}
-{"taskCreatedAt":"2025-11-14T18:19:45.744631636Z","taskDependencies":[],"taskId":"t-1a1DsvI","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Research ansi-terminal and design LineManager API","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:32:29.399532791Z"}
-{"taskCreatedAt":"2025-11-14T18:19:45.772108017Z","taskDependencies":[],"taskId":"t-1a1DzES","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Create Omni/Log/Concurrent.hs module with LineManager","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:33:02.794492847Z"}
-{"taskCreatedAt":"2025-11-14T18:19:45.800202144Z","taskDependencies":[],"taskId":"t-1a1DGY0","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Implement line reservation and release logic","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:33:02.855747669Z"}
-{"taskCreatedAt":"2025-11-14T18:19:45.82813327Z","taskDependencies":[],"taskId":"t-1a1DOev","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Implement concurrent line update with ANSI codes","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:33:02.915807677Z"}
-{"taskCreatedAt":"2025-11-14T18:19:45.857123437Z","taskDependencies":[],"taskId":"t-1a1DVM5","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Add terminal capability detection","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:33:02.975985146Z"}
-{"taskCreatedAt":"2025-11-14T18:19:45.886073324Z","taskDependencies":[],"taskId":"t-1a1E3j1","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Thread LineManager through build/nixBuild functions","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:38:03.516198105Z"}
-{"taskCreatedAt":"2025-11-14T18:19:45.914626247Z","taskDependencies":[],"taskId":"t-1a1EaJy","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Create runWithLineManager and logsToLine functions","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:55:54.836022471Z"}
-{"taskCreatedAt":"2025-11-14T18:19:45.94320795Z","taskDependencies":[],"taskId":"t-1a1Eiay","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Test parallel builds with ANSI multi-line output","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T19:01:40.850177474Z"}
-{"taskCreatedAt":"2025-11-14T18:19:45.971879353Z","taskDependencies":[],"taskId":"t-1a1EpCZ","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Add fallback for dumb terminals","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T19:02:18.706108207Z"}
-{"taskCreatedAt":"2025-11-14T18:19:45.999699368Z","taskDependencies":[],"taskId":"t-1a1EwRH","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Test in emacs and narrow terminals","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T19:02:18.766470937Z"}
-{"taskCreatedAt":"2025-11-14T18:19:46.028016768Z","taskDependencies":[],"taskId":"t-1a1EEer","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Handle edge cases and polish UX","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T19:02:18.827147429Z"}
-{"taskCreatedAt":"2025-11-14T18:19:46.056655181Z","taskDependencies":[],"taskId":"t-1a1ELGl","taskNamespace":null,"taskParent":"t-19ZF6A8","taskStatus":"Done","taskTitle":"Update documentation","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T19:02:47.319855049Z"}
-{"taskCreatedAt":"2025-11-16T04:06:48.014952363Z","taskDependencies":[],"taskId":"t-ga8V8O","taskNamespace":null,"taskParent":null,"taskStatus":"Done","taskTitle":"PodcastItLater: Public Feed, Metrics & Audio Improvements","taskType":"Epic","taskUpdatedAt":"2025-11-16T08:57:42.45932002Z"}
-{"taskCreatedAt":"2025-11-16T04:06:57.071621037Z","taskDependencies":[],"taskId":"t-gaKVc7","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add database migrations for new columns (is_public, user_episodes table, episode_metrics table, original_url_hash)","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:09:27.336080901Z"}
-{"taskCreatedAt":"2025-11-16T04:06:57.609993104Z","taskDependencies":[],"taskId":"t-gaNbfx","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Implement URL hashing and normalization function for episode deduplication","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:09:27.896576613Z"}
-{"taskCreatedAt":"2025-11-16T04:06:58.132246645Z","taskDependencies":[],"taskId":"t-gaPn6Z","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add Core.py database functions for public episodes (mark_public, unmark_public, get_public_episodes)","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:09:28.463907822Z"}
-{"taskCreatedAt":"2025-11-16T04:06:58.665794496Z","taskDependencies":[],"taskId":"t-gaRBUA","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add Core.py database functions for user_episodes junction table","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:09:29.027348473Z"}
-{"taskCreatedAt":"2025-11-16T04:06:59.199139475Z","taskDependencies":[],"taskId":"t-gaTQEV","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add Core.py database functions for episode metrics tracking","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:09:29.602931183Z"}
-{"taskCreatedAt":"2025-11-16T04:07:07.307576303Z","taskDependencies":[],"taskId":"t-gbrS2a","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Modify submission flow to check for existing episodes by URL hash","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:13:56.073214768Z"}
-{"taskCreatedAt":"2025-11-16T04:07:07.834181871Z","taskDependencies":[],"taskId":"t-gbu51O","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add /public route to display public feed","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:16:43.926763164Z"}
-{"taskCreatedAt":"2025-11-16T04:07:08.369657826Z","taskDependencies":[],"taskId":"t-gbwkkw","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add /public.rss route for public RSS feed generation","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:16:44.383466957Z"}
-{"taskCreatedAt":"2025-11-16T04:07:08.906237761Z","taskDependencies":[],"taskId":"t-gbyzV2","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Update home page to show public feed when user is logged out","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:16:44.848713835Z"}
-{"taskCreatedAt":"2025-11-16T04:07:09.433392796Z","taskDependencies":[],"taskId":"t-gbAN3x","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add admin toggle button to episode cards for public/private status","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:13:58.676381973Z"}
-{"taskCreatedAt":"2025-11-16T04:07:17.092115521Z","taskDependencies":[],"taskId":"t-gc6Vrk","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add POST /admin/episode/{id}/toggle-public endpoint","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:13:58.727479053Z"}
-{"taskCreatedAt":"2025-11-16T04:07:17.6266109Z","taskDependencies":[],"taskId":"t-gc9aud","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add '+ Add to your feed' button on episode pages for logged-in users","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:22:35.253656788Z"}
-{"taskCreatedAt":"2025-11-16T04:07:18.165342861Z","taskDependencies":[],"taskId":"t-gcbqDl","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add POST /episode/{id}/add-to-feed endpoint","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:22:35.305050805Z"}
-{"taskCreatedAt":"2025-11-16T04:07:18.700573408Z","taskDependencies":[],"taskId":"t-gcdFSb","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add POST /episode/{id}/track endpoint for metrics tracking","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:30:51.238117273Z"}
-{"taskCreatedAt":"2025-11-16T04:07:19.229153372Z","taskDependencies":[],"taskId":"t-gcfTnG","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add JavaScript to episode player for tracking play events","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:30:51.289470508Z"}
-{"taskCreatedAt":"2025-11-16T04:07:27.174644219Z","taskDependencies":[],"taskId":"t-gcNemK","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Enhance Worker.py to extract publication date and author metadata from articles","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.196162661Z"}
-{"taskCreatedAt":"2025-11-16T04:07:27.700527081Z","taskDependencies":[],"taskId":"t-gcPraJ","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add intro TTS generation with metadata (title, author, date)","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.247694148Z"}
-{"taskCreatedAt":"2025-11-16T04:07:28.221004581Z","taskDependencies":[],"taskId":"t-gcRCzw","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add outro TTS generation with title and author","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.298838151Z"}
-{"taskCreatedAt":"2025-11-16T04:07:28.74867703Z","taskDependencies":[],"taskId":"t-gcTPQn","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Combine intro, pauses, article content, and outro in Worker.py","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.360155369Z"}
-{"taskCreatedAt":"2025-11-16T04:07:29.289653388Z","taskDependencies":[],"taskId":"t-gcW6zN","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Write tests for public feed functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.410867588Z"}
-{"taskCreatedAt":"2025-11-16T04:07:35.447349966Z","taskDependencies":[],"taskId":"t-gdlWtu","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Write tests for episode deduplication","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.461656748Z"}
-{"taskCreatedAt":"2025-11-16T04:07:35.995113703Z","taskDependencies":[],"taskId":"t-gdoeYo","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Write tests for metrics tracking","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.513956262Z"}
-{"taskCreatedAt":"2025-11-16T04:07:36.52315156Z","taskDependencies":[],"taskId":"t-gdqsl7","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Write tests for audio intro/outro generation","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.574397661Z"}
-{"taskCreatedAt":"2025-11-16T04:07:37.059671738Z","taskDependencies":[],"taskId":"t-gdsHUA","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Create admin metrics dashboard view","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:57:35.681938898Z"}
-{"taskCreatedAt":"2025-11-20T15:04:38.423818806Z","taskDependencies":[],"taskId":"t-XfkJyy","taskNamespace":"Omni/Task.hs","taskParent":null,"taskStatus":"Done","taskTitle":"Add JSON output flag","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T15:07:40.861538248Z"}
-{"taskCreatedAt":"2025-11-20T15:07:33.14012157Z","taskDependencies":[],"taskId":"t-Xr9Pfs","taskNamespace":"Omni/Task.hs","taskParent":null,"taskStatus":"Done","taskTitle":"Test JSON output","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T15:07:41.093795231Z"}
+{"taskCreatedAt":"2025-11-08T20:03:50.230851965Z","taskDependencies":[],"taskId":"t-a1b2c3","taskNamespace":null,"taskParent":null,"taskPriority":"P2","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,"taskPriority":"P2","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,"taskPriority":"P2","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,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Tasks should have an optional namespace associated with them. Namespaces are first class citizens in this monorepo","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:14:05.775741617Z"}
+{"taskCreatedAt":"2025-11-08T20:10:09.944217463Z","taskDependencies":[],"taskId":"t-m3n4o5","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"There should be a command to list all projects.","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:44:57.393279815Z"}
+{"taskCreatedAt":"2025-11-08T20:20:38.785442739Z","taskDependencies":[],"taskId":"t-p6q7r8","taskNamespace":null,"taskParent":null,"taskPriority":"P2","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,"taskPriority":"P2","taskStatus":"Done","taskTitle":"instruct agents to include tests with all new features and bug fixes","taskType":"WorkTask","taskUpdatedAt":"2025-11-08T21:24:54.004658966Z"}
+{"taskCreatedAt":"2025-11-08T20:45:12.764939794Z","taskDependencies":[],"taskId":"t-v2w3x4","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","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-08T21:25:10.756670871Z"}
+{"taskCreatedAt":"2025-11-08T20:48:43.183226361Z","taskDependencies":[],"taskId":"t-y5z6A7","taskNamespace":null,"taskParent":null,"taskPriority":"P2","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,"taskPriority":"P2","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,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Another test task","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:13:51.934598506Z"}
+{"taskCreatedAt":"2025-11-08T21:11:41.013924674Z","taskDependencies":[],"taskId":"t-1lhJhgS","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","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:28:34.875747622Z"}
+{"taskCreatedAt":"2025-11-09T13:05:06.468930038Z","taskDependencies":[],"taskId":"t-PpXWsU","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Task Manager Improvements","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:06.468930038Z"}
+{"taskCreatedAt":"2025-11-09T13:05:06.718797697Z","taskDependencies":[],"taskId":"t-PpYZt2","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Implement child ID generation (t-abc123.1)","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:06.718797697Z"}
+{"taskCreatedAt":"2025-11-09T13:05:06.746734115Z","taskDependencies":[],"taskId":"t-PpZ6JC","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Add child_counters storage","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:06.746734115Z"}
+{"taskCreatedAt":"2025-11-09T13:05:06.774903465Z","taskDependencies":[],"taskId":"t-PpZe3X","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Update createTask to auto-generate child IDs","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:06.774903465Z"}
+{"taskCreatedAt":"2025-11-09T13:05:06.802295008Z","taskDependencies":[],"taskId":"t-PpZlbL","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Implement task tree visualization command","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:47:12.411364105Z"}
+{"taskCreatedAt":"2025-11-09T13:05:06.829842253Z","taskDependencies":[],"taskId":"t-PpZsm4","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Implement task stats command","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:06.829842253Z"}
+{"taskCreatedAt":"2025-11-09T13:05:06.85771202Z","taskDependencies":[],"taskId":"t-PpZzBA","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Implement epic progress tracking","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:06.85771202Z"}
+{"taskCreatedAt":"2025-11-09T13:05:06.88583862Z","taskDependencies":[],"taskId":"t-PpZGVf","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add filtering by type and parent (list improvements)","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:17:51.373969453Z"}
+{"taskCreatedAt":"2025-11-09T13:05:18.344932105Z","taskDependencies":[],"taskId":"t-PqLLXk","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Implement epic and task types","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T13:05:18.406381682Z"}
+{"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-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"}
+{"taskCreatedAt":"2025-11-09T16:48:47.388960509Z","taskDependencies":[],"taskId":"t-144eKR1","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Implement usage tracking and limits","taskType":"WorkTask","taskUpdatedAt":"2025-11-19T03:27:25.707745105Z"}
+{"taskCreatedAt":"2025-11-09T16:48:47.589181852Z","taskDependencies":[],"taskId":"t-144fAWn","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add email notifications (transactional)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T01:35:54.519545888Z"}
+{"taskCreatedAt":"2025-11-09T16:48:47.737218185Z","taskDependencies":[],"taskId":"t-144gds4","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Migrate from SQLite to PostgreSQL","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T01:35:54.70061831Z"}
+{"taskCreatedAt":"2025-11-09T16:48:47.887102357Z","taskDependencies":[],"taskId":"t-144gQry","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Create basic admin dashboard","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T16:48:47.887102357Z"}
+{"taskCreatedAt":"2025-11-09T16:48:48.072927212Z","taskDependencies":[],"taskId":"t-144hCMJ","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Complete comprehensive test suite","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T16:48:48.072927212Z"}
+{"taskCreatedAt":"2025-11-09T17:48:34.522286485Z","taskDependencies":[],"taskId":"t-17Z0069","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Fix Recent Episodes refresh to prepend instead of reload (interrupts audio playback)","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T19:42:22.105902786Z"}
+{"taskCreatedAt":"2025-11-09T22:19:27.303689497Z","taskDependencies":[],"taskId":"t-1pIV0ZF","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Implement billing page UI component with pricing and upgrade options","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T23:04:20.974801117Z"}
+{"taskCreatedAt":"2025-11-09T22:38:46.235799803Z","taskDependencies":[],"taskId":"t-1qZlMb4","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Add a 'task show <id>' command that prints out a long, easy to read (for humans) version of the task. Include dependencies and all information fields in the output","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T22:38:46.235799803Z"}
+{"taskCreatedAt":"2025-11-09T22:56:18.897655607Z","taskDependencies":[],"taskId":"t-1s8ADC0","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Make PodcastItLater UI mobile-friendly and responsive","taskType":"WorkTask","taskUpdatedAt":"2025-11-09T23:09:16.712244322Z"}
+{"taskCreatedAt":"2025-11-10T01:32:42.893029428Z","taskDependencies":[],"taskId":"t-64tkB5","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add dark mode support to PodcastItLater UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-10T02:16:39.202726983Z"}
+{"taskCreatedAt":"2025-11-13T16:32:05.496080694Z","taskDependencies":[],"taskId":"t-12YqUKr","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Simplify billing to single paid plan","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:37:49.407332883Z"}
+{"taskCreatedAt":"2025-11-13T16:32:16.514172804Z","taskDependencies":[],"taskId":"t-12Zb93B","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Separate navbar into user navbar and callout box for plan info","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:34:44.480359383Z"}
+{"taskCreatedAt":"2025-11-13T16:32:16.718245548Z","taskDependencies":[],"taskId":"t-12Zc095","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Make 'Upgrade Now' button go directly to Stripe checkout (not /billing page)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:34:44.530482584Z"}
+{"taskCreatedAt":"2025-11-13T16:32:16.899253732Z","taskDependencies":[],"taskId":"t-12ZcLez","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add plan details to callout box (unlimited articles, $12/month)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:34:44.579475578Z"}
+{"taskCreatedAt":"2025-11-13T16:32:17.077566618Z","taskDependencies":[],"taskId":"t-12ZdvCB","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Delete /billing page and all related code (billing_page, BillingPage component)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:36:41.708746244Z"}
+{"taskCreatedAt":"2025-11-13T16:32:17.264388472Z","taskDependencies":[],"taskId":"t-12ZeidQ","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Update billing_checkout to use 'paid' tier instead of 'pro'","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:36:41.758424911Z"}
+{"taskCreatedAt":"2025-11-13T16:32:17.411379982Z","taskDependencies":[],"taskId":"t-12ZeUsG","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Update success/cancel URLs to redirect to / instead of /billing","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:36:41.808119038Z"}
+{"taskCreatedAt":"2025-11-13T16:32:17.557115348Z","taskDependencies":[],"taskId":"t-12Zfwnf","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Remove 'Billing' button from navbar (paid users will use Stripe portal link in callout)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:34:44.628587871Z"}
+{"taskCreatedAt":"2025-11-13T16:32:17.738052991Z","taskDependencies":[],"taskId":"t-12ZghrB","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Test the complete flow and verify all changes","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T16:37:49.356932049Z"}
+{"taskCreatedAt":"2025-11-13T19:38:07.804316976Z","taskDependencies":[],"taskId":"t-1f9QP23","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"General Code Quality Refactor","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:07.804316976Z"}
+{"taskCreatedAt":"2025-11-13T19:38:08.01779309Z","taskDependencies":[],"taskId":"t-1f9RIzd","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Account Management Page","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:08.01779309Z"}
+{"taskCreatedAt":"2025-11-13T19:38:08.176692694Z","taskDependencies":[],"taskId":"t-1f9SnU7","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Queue Status Improvements","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:08.176692694Z"}
+{"taskCreatedAt":"2025-11-13T19:38:08.37344762Z","taskDependencies":[],"taskId":"t-1f9Td4U","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Navbar Styling Cleanup","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:08.37344762Z"}
+{"taskCreatedAt":"2025-11-13T19:38:32.95559213Z","taskDependencies":[],"taskId":"t-1fbym1M","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Remove BLE001 noqa for bare Exception catches - use specific exceptions","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:43:29.049855419Z"}
+{"taskCreatedAt":"2025-11-13T19:38:33.139120541Z","taskDependencies":[],"taskId":"t-1fbz7LV","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Fix PLR0913 violations - refactor functions with too many parameters","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:44:09.820023426Z"}
+{"taskCreatedAt":"2025-11-13T19:38:33.309222802Z","taskDependencies":[],"taskId":"t-1fbzQ1v","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Extract format_duration utility to shared UI or Core module (used only in Web.py)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:45:49.402934404Z"}
+{"taskCreatedAt":"2025-11-13T19:38:33.491331064Z","taskDependencies":[],"taskId":"t-1fbABoD","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Extract extract_og_metadata and send_magic_link to Core module for reusability","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:46:04.679290775Z"}
+{"taskCreatedAt":"2025-11-13T19:38:33.674140035Z","taskDependencies":[],"taskId":"t-1fbBmXa","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Review and fix type: ignore comments - improve type safety","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:47:09.583640045Z"}
+{"taskCreatedAt":"2025-11-13T19:38:33.85804778Z","taskDependencies":[],"taskId":"t-1fbC8Nq","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Remove PLR2004 magic number - use constant for month check","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:47:45.120428021Z"}
+{"taskCreatedAt":"2025-11-13T19:38:34.035597081Z","taskDependencies":[],"taskId":"t-1fbCSZd","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Implement cancel subscription functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.709672316Z"}
+{"taskCreatedAt":"2025-11-13T19:38:34.194926176Z","taskDependencies":[],"taskId":"t-1fbDyr2","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Implement delete account functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.194926176Z"}
+{"taskCreatedAt":"2025-11-13T19:38:34.384489707Z","taskDependencies":[],"taskId":"t-1fbElKv","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Implement change email address functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.384489707Z"}
+{"taskCreatedAt":"2025-11-13T19:38:34.561871604Z","taskDependencies":[],"taskId":"t-1fbF5Tv","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add logout button to account page","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.65796855Z"}
+{"taskCreatedAt":"2025-11-13T19:38:34.777721397Z","taskDependencies":[],"taskId":"t-1fbG02X","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Replace Coming Soon placeholder with full account management UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.606196024Z"}
+{"taskCreatedAt":"2025-11-13T19:38:34.962196629Z","taskDependencies":[],"taskId":"t-1fbGM2m","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add remove button to queue status items","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:20:10.941908917Z"}
+{"taskCreatedAt":"2025-11-13T19:38:35.119686179Z","taskDependencies":[],"taskId":"t-1fbHr0w","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Remove button classes from navbar links (make them regular nav links)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.185088389Z"}
+{"taskCreatedAt":"2025-11-13T19:38:35.311151364Z","taskDependencies":[],"taskId":"t-1fbIeOF","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Remove 'Logged in as' text from navbar","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.23552934Z"}
+{"taskCreatedAt":"2025-11-13T19:38:35.476139354Z","taskDependencies":[],"taskId":"t-1fbIVJL","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Left-align navbar links instead of right-aligned buttons","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.285578917Z"}
+{"taskCreatedAt":"2025-11-13T19:38:35.65125955Z","taskDependencies":[],"taskId":"t-1fbJFic","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Remove logout button from navbar (will be in account page)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.336546723Z"}
+{"taskCreatedAt":"2025-11-13T19:54:08.34625259Z","taskDependencies":[],"taskId":"t-1gcR9RV","taskNamespace":"Omni/Bild.nix","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Add ruff to the developer environment, the 'env' attribute in Bild.nix","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:54:08.34625259Z"}
+{"taskCreatedAt":"2025-11-13T20:02:50.914482516Z","taskDependencies":[],"taskId":"t-1gMdNJK","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Fix dev mode banner styling and pre-fill login email","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:03:45.644107089Z"}
+{"taskCreatedAt":"2025-11-13T21:01:35.331063546Z","taskDependencies":[],"taskId":"t-1kCJTuu","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Fix Stripe portal error handling and account page padding","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T21:03:19.701792229Z"}
+{"taskCreatedAt":"2025-11-14T18:19:16.584321849Z","taskDependencies":[],"taskId":"t-19ZF6A8","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Parallel Target Builds - Epic","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T19:03:02.525200039Z"}
+{"taskCreatedAt":"2025-11-14T18:19:33.701736325Z","taskDependencies":[],"taskId":"t-1a0OVBs","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add mapConcurrentlyBounded helper using QSemN","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:20:20.979870628Z"}
+{"taskCreatedAt":"2025-11-14T18:19:37.810028305Z","taskDependencies":[],"taskId":"t-1a16ame","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Refactor build function to extract buildTarget worker","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:20:58.231039244Z"}
+{"taskCreatedAt":"2025-11-14T18:19:45.688391211Z","taskDependencies":[],"taskId":"t-1a1DdSB","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Replace forM with mapConcurrentlyBounded in build","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:20:58.290149792Z"}
+{"taskCreatedAt":"2025-11-14T18:19:45.716079624Z","taskDependencies":[],"taskId":"t-1a1Dl5c","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Test basic parallel builds without UI changes","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:31:57.019839638Z"}
+{"taskCreatedAt":"2025-11-14T18:19:45.744631636Z","taskDependencies":[],"taskId":"t-1a1DsvI","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Research ansi-terminal and design LineManager API","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:32:29.399532791Z"}
+{"taskCreatedAt":"2025-11-14T18:19:45.772108017Z","taskDependencies":[],"taskId":"t-1a1DzES","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Create Omni/Log/Concurrent.hs module with LineManager","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:33:02.794492847Z"}
+{"taskCreatedAt":"2025-11-14T18:19:45.800202144Z","taskDependencies":[],"taskId":"t-1a1DGY0","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Implement line reservation and release logic","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:33:02.855747669Z"}
+{"taskCreatedAt":"2025-11-14T18:19:45.82813327Z","taskDependencies":[],"taskId":"t-1a1DOev","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Implement concurrent line update with ANSI codes","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:33:02.915807677Z"}
+{"taskCreatedAt":"2025-11-14T18:19:45.857123437Z","taskDependencies":[],"taskId":"t-1a1DVM5","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add terminal capability detection","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:33:02.975985146Z"}
+{"taskCreatedAt":"2025-11-14T18:19:45.886073324Z","taskDependencies":[],"taskId":"t-1a1E3j1","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Thread LineManager through build/nixBuild functions","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:38:03.516198105Z"}
+{"taskCreatedAt":"2025-11-14T18:19:45.914626247Z","taskDependencies":[],"taskId":"t-1a1EaJy","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Create runWithLineManager and logsToLine functions","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T18:55:54.836022471Z"}
+{"taskCreatedAt":"2025-11-14T18:19:45.94320795Z","taskDependencies":[],"taskId":"t-1a1Eiay","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Test parallel builds with ANSI multi-line output","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T19:01:40.850177474Z"}
+{"taskCreatedAt":"2025-11-14T18:19:45.971879353Z","taskDependencies":[],"taskId":"t-1a1EpCZ","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add fallback for dumb terminals","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T19:02:18.706108207Z"}
+{"taskCreatedAt":"2025-11-14T18:19:45.999699368Z","taskDependencies":[],"taskId":"t-1a1EwRH","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Test in emacs and narrow terminals","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T19:02:18.766470937Z"}
+{"taskCreatedAt":"2025-11-14T18:19:46.028016768Z","taskDependencies":[],"taskId":"t-1a1EEer","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Handle edge cases and polish UX","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T19:02:18.827147429Z"}
+{"taskCreatedAt":"2025-11-14T18:19:46.056655181Z","taskDependencies":[],"taskId":"t-1a1ELGl","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Update documentation","taskType":"WorkTask","taskUpdatedAt":"2025-11-14T19:02:47.319855049Z"}
+{"taskCreatedAt":"2025-11-16T04:06:48.014952363Z","taskDependencies":[],"taskId":"t-ga8V8O","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"PodcastItLater: Public Feed, Metrics & Audio Improvements","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:57:42.45932002Z"}
+{"taskCreatedAt":"2025-11-16T04:06:57.071621037Z","taskDependencies":[],"taskId":"t-gaKVc7","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add database migrations for new columns (is_public, user_episodes table, episode_metrics table, original_url_hash)","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:09:27.336080901Z"}
+{"taskCreatedAt":"2025-11-16T04:06:57.609993104Z","taskDependencies":[],"taskId":"t-gaNbfx","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Implement URL hashing and normalization function for episode deduplication","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:09:27.896576613Z"}
+{"taskCreatedAt":"2025-11-16T04:06:58.132246645Z","taskDependencies":[],"taskId":"t-gaPn6Z","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add Core.py database functions for public episodes (mark_public, unmark_public, get_public_episodes)","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:09:28.463907822Z"}
+{"taskCreatedAt":"2025-11-16T04:06:58.665794496Z","taskDependencies":[],"taskId":"t-gaRBUA","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add Core.py database functions for user_episodes junction table","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:09:29.027348473Z"}
+{"taskCreatedAt":"2025-11-16T04:06:59.199139475Z","taskDependencies":[],"taskId":"t-gaTQEV","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add Core.py database functions for episode metrics tracking","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:09:29.602931183Z"}
+{"taskCreatedAt":"2025-11-16T04:07:07.307576303Z","taskDependencies":[],"taskId":"t-gbrS2a","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Modify submission flow to check for existing episodes by URL hash","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:13:56.073214768Z"}
+{"taskCreatedAt":"2025-11-16T04:07:07.834181871Z","taskDependencies":[],"taskId":"t-gbu51O","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add /public route to display public feed","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:16:43.926763164Z"}
+{"taskCreatedAt":"2025-11-16T04:07:08.369657826Z","taskDependencies":[],"taskId":"t-gbwkkw","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add /public.rss route for public RSS feed generation","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:16:44.383466957Z"}
+{"taskCreatedAt":"2025-11-16T04:07:08.906237761Z","taskDependencies":[],"taskId":"t-gbyzV2","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Update home page to show public feed when user is logged out","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:16:44.848713835Z"}
+{"taskCreatedAt":"2025-11-16T04:07:09.433392796Z","taskDependencies":[],"taskId":"t-gbAN3x","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add admin toggle button to episode cards for public/private status","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:13:58.676381973Z"}
+{"taskCreatedAt":"2025-11-16T04:07:17.092115521Z","taskDependencies":[],"taskId":"t-gc6Vrk","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add POST /admin/episode/{id}/toggle-public endpoint","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:13:58.727479053Z"}
+{"taskCreatedAt":"2025-11-16T04:07:17.6266109Z","taskDependencies":[],"taskId":"t-gc9aud","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add '+ Add to your feed' button on episode pages for logged-in users","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:22:35.253656788Z"}
+{"taskCreatedAt":"2025-11-16T04:07:18.165342861Z","taskDependencies":[],"taskId":"t-gcbqDl","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add POST /episode/{id}/add-to-feed endpoint","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:22:35.305050805Z"}
+{"taskCreatedAt":"2025-11-16T04:07:18.700573408Z","taskDependencies":[],"taskId":"t-gcdFSb","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add POST /episode/{id}/track endpoint for metrics tracking","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:30:51.238117273Z"}
+{"taskCreatedAt":"2025-11-16T04:07:19.229153372Z","taskDependencies":[],"taskId":"t-gcfTnG","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add JavaScript to episode player for tracking play events","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:30:51.289470508Z"}
+{"taskCreatedAt":"2025-11-16T04:07:27.174644219Z","taskDependencies":[],"taskId":"t-gcNemK","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Enhance Worker.py to extract publication date and author metadata from articles","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.196162661Z"}
+{"taskCreatedAt":"2025-11-16T04:07:27.700527081Z","taskDependencies":[],"taskId":"t-gcPraJ","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add intro TTS generation with metadata (title, author, date)","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.247694148Z"}
+{"taskCreatedAt":"2025-11-16T04:07:28.221004581Z","taskDependencies":[],"taskId":"t-gcRCzw","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add outro TTS generation with title and author","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.298838151Z"}
+{"taskCreatedAt":"2025-11-16T04:07:28.74867703Z","taskDependencies":[],"taskId":"t-gcTPQn","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Combine intro, pauses, article content, and outro in Worker.py","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.360155369Z"}
+{"taskCreatedAt":"2025-11-16T04:07:29.289653388Z","taskDependencies":[],"taskId":"t-gcW6zN","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Write tests for public feed functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.410867588Z"}
+{"taskCreatedAt":"2025-11-16T04:07:35.447349966Z","taskDependencies":[],"taskId":"t-gdlWtu","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Write tests for episode deduplication","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.461656748Z"}
+{"taskCreatedAt":"2025-11-16T04:07:35.995113703Z","taskDependencies":[],"taskId":"t-gdoeYo","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Write tests for metrics tracking","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.513956262Z"}
+{"taskCreatedAt":"2025-11-16T04:07:36.52315156Z","taskDependencies":[],"taskId":"t-gdqsl7","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Write tests for audio intro/outro generation","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:46:43.574397661Z"}
+{"taskCreatedAt":"2025-11-16T04:07:37.059671738Z","taskDependencies":[],"taskId":"t-gdsHUA","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Create admin metrics dashboard view","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T08:57:35.681938898Z"}
+{"taskCreatedAt":"2025-11-20T15:04:38.423818806Z","taskDependencies":[],"taskId":"t-XfkJyy","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add JSON output flag","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T15:07:40.861538248Z"}
+{"taskCreatedAt":"2025-11-20T15:07:33.14012157Z","taskDependencies":[],"taskId":"t-Xr9Pfs","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Test JSON output","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T15:07:41.093795231Z"}
+{"taskCreatedAt":"2025-11-20T15:14:01.809791032Z","taskDependencies":[],"taskId":"t-XRsDZb","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add comprehensive CLI tests for task command","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T15:14:09.958477456Z"}
+{"taskCreatedAt":"2025-11-20T15:25:13.591317838Z","taskDependencies":[],"taskId":"t-YAVn30","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add priority flag support to task create","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T16:00:44.985924365Z"}
+{"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":"Open","taskTitle":"Docopt flag order matters incorrectly","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T15:58:11.740041636Z"}
+{"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"}
diff --git a/AGENTS.md b/AGENTS.md
index 5b37334..d19ecf5 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -6,6 +6,100 @@ in English, then in code.
This document describes how AI agents should interact with this repo, the "omnirepo".
+## Important Rules for AI Agents
+
+**CRITICAL**: This project uses `task` for ALL issue tracking. You MUST follow these rules:
+
+- ✅ Use `task` for ALL task/TODO tracking
+- ✅ Always use `--json` flag for programmatic operations
+- ✅ Link discovered work with `--discovered-from=<parent-id>`
+- ✅ File bugs IMMEDIATELY when you discover unexpected behavior
+- ✅ Run `task ready` before asking "what should I work on?"
+- ✅ Store AI planning docs in `_/llm` directory (NEVER in repo root)
+- ✅ Run `task sync` at end of session to commit changes locally
+- ❌ Do NOT use `todo_write` tool
+- ❌ Do NOT create markdown TODO lists or task checklists
+- ❌ Do NOT put TODO/FIXME comments in code
+- ❌ Do NOT use external issue trackers
+- ❌ Do NOT duplicate tracking systems
+- ❌ Do NOT clutter repo root with planning documents
+
+### Session Checklist
+
+**First time in this repo?**
+```bash
+task init --quiet # Non-interactive initialization
+```
+
+**Standard workflow:**
+```bash
+# 1. Find ready work
+task ready --json
+
+# 2. Claim a task
+task update <id> --status in_progress --json
+
+# 3. During work: create discovered issues
+task create "Fix type error found" --discovered-from=<current-id> --json
+
+# 4. Complete the task
+task update <id> --status done --json
+
+# 5. End of session: sync to git (local commit only)
+task sync
+```
+
+### Bug Discovery Pattern
+
+**When you discover a bug or unexpected behavior:**
+```bash
+# CORRECT: Immediately file a task
+task create "Command X fails when Y" --discovered-from=<current-task-id> --json
+
+# WRONG: Ignoring it and moving on
+# WRONG: Leaving a TODO comment
+# WRONG: Mentioning it but not filing a task
+```
+
+**Examples of bugs you MUST file:**
+- "Expected `--flag value` to work but only `--flag=value` works"
+- "Documentation says X but actual behavior is Y"
+- "Combining two flags causes parsing error"
+- "Feature is missing that would be useful"
+
+### Forbidden Patterns
+
+**Markdown checklist (NEVER do this):**
+```markdown
+❌ Wrong:
+- [ ] Refactor auth module
+- [ ] Add tests
+- [ ] Update docs
+
+✅ Correct:
+task create "Refactor auth module" -p 2 --json
+task create "Add tests for auth" -p 2 --json
+task create "Update auth docs" -p 3 --json
+```
+
+**todo_write tool (NEVER do this):**
+```
+❌ Wrong: todo_write({todos: [{content: "Fix bug", ...}]})
+✅ Correct: task create "Fix bug in parser" -p 1 --json
+```
+
+**Inline code comments (NEVER do this):**
+```python
+❌ Wrong:
+# TODO: write tests for this function
+# FIXME: handle edge case
+
+✅ Correct:
+# Create task instead:
+task create "Write tests for parse_config" -p 2 --namespace="Omni/Config" --json
+task create "Handle edge case in parser" -p 1 --discovered-from=<current-id> --json
+```
+
## About Omnirepo
Resources defined in the repo can be used to quickly create and release
@@ -55,7 +149,18 @@ The task manager is a dependency-aware issue tracker inspired by beads. It uses:
- **Dependencies**: Tasks can block other tasks
- **Ready work detection**: Automatically finds unblocked tasks
-**IMPORTANT**: This project uses `task` for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods.
+**IMPORTANT**: You MUST use `task` for ALL issue tracking. NEVER use markdown TODOs, todo_write, task lists, or any other tracking methods.
+
+### Human Setup vs Agent Usage
+
+**If you see "database not found" or similar errors:**
+```bash
+task init --quiet # Non-interactive, auto-setup, no prompts
+```
+
+**Why `--quiet`?** The regular `task init` may have interactive prompts. The `--quiet` flag makes it fully non-interactive and safe for agent-driven setup.
+
+**If `task init --quiet` fails:** Ask the human to run `task init` manually, then continue.
### Create a Task
```bash
@@ -180,11 +285,15 @@ task import -i /path/to/backup.jsonl
### Initialize (First Time)
```bash
-task init
+task init --quiet # Non-interactive (recommended for agents)
+# OR
+task init # Interactive (for humans)
```
Creates `.tasks/` directory and `tasks.jsonl` file.
+**Agents MUST use `--quiet` flag** to avoid interactive prompts.
+
### Common Workflows
#### Starting New Work
@@ -255,21 +364,32 @@ task list --type=epic
### Agent Best Practices
-#### 1. Always Check Ready Work First
-Before asking what to do, check `task ready` to see unblocked tasks.
+#### 1. ALWAYS Check Ready Work First
+Before asking what to do, you MUST check `task ready --json` to see unblocked tasks.
-#### 2. Create Tasks for Discovered Work
-When you encounter work during implementation:
+#### 2. ALWAYS Create Tasks for Discovered Work
+When you encounter work during implementation, you MUST create linked tasks:
```bash
-task create "Fix type error in auth module" --discovered-from=t-abc123
-task create "Add missing test coverage" --discovered-from=t-abc123
+task create "Fix type error in auth module" --discovered-from=t-abc123 --json
+task create "Add missing test coverage" --discovered-from=t-abc123 --json
```
+**CRITICAL: File bugs immediately when you discover them:**
+- If a command doesn't work as documented → create a task
+- If a command doesn't work as you expected → create a task
+- If behavior is inconsistent or confusing → create a task
+- If documentation is wrong or misleading → create a task
+- If you find yourself working around a limitation → create a task
+
+**NEVER leave TODO comments in code.** Create a task instead.
+
+**NEVER ignore bugs or unexpected behavior.** File a task for it immediately.
+
#### 3. Track Dependencies
If work depends on other work, use `--deps`:
```bash
# Can't write tests until implementation is done
-task create "Test auth flow" --deps=t-20241108120000 --dep-type=blocks
+task create "Test auth flow" --deps=t-20241108120000 --dep-type=blocks --json
```
#### 4. Use Descriptive Titles
@@ -278,16 +398,17 @@ Bad: `"Fix auth"`
#### 5. Use Epics for Organization
Organize related work using epics:
-- Create an epic for larger features: `task create "Feature Name" --type=epic`
+- Create an epic for larger features: `task create "Feature Name" --type=epic --json`
- Add tasks to the epic using `--parent=<epic-id>`
- Use `--discovered-from` to track work found during implementation
-#### 6. Store AI planning docs in `_/llm` directory
+#### 6. ALWAYS Store AI Planning Docs in `_/llm` Directory
AI assistants often create planning and design documents during development:
- PLAN.md, DESIGN.md, TESTING_GUIDE.md, tmp, and similar files
-- **Best Practice: Use a dedicated directory for these ephemeral files.**
+- **You MUST use a dedicated directory for these ephemeral files**
- Store ALL AI-generated planning/design docs in `_/llm`
- The `_` directory is ignored by git and all of our temporary files related to the omnirepo go there
+- NEVER commit planning docs to the repo root
### Dependency Rules
@@ -400,17 +521,24 @@ task ready
# Shows: "Write storage tests" and "Fix edge case in ID generation"
```
-### Important Rules
+### Reinforcement: Critical Rules
+
+Remember these non-negotiable rules:
+
+- ✅ Use `task` for ALL task tracking (with `--json` flag)
+- ✅ Link discovered work with `--discovered-from` dependencies
+- ✅ File bugs IMMEDIATELY when you discover unexpected behavior
+- ✅ Check `task ready --json` before asking "what should I work on?"
+- ✅ Store AI planning docs in `_/llm` directory
+- ✅ Run `task sync` at end of every session (commits locally, does NOT push)
+- ❌ NEVER use `todo_write` tool
+- ❌ NEVER create markdown TODO lists or task checklists
+- ❌ NEVER put TODOs or FIXMEs in code comments
+- ❌ NEVER use external issue trackers
+- ❌ NEVER duplicate tracking systems
+- ❌ NEVER clutter repo root with planning documents
-- Use `task` for ALL task tracking
-- Link discovered work with `discovered-from` dependencies
-- Check `task ready` before asking "what should I work on?"
-- Store AI planning docs in `_/llm` directory
-- Do NOT create markdown TODO lists
-- Do NOT put TODOs or FIXMEs in code comments
-- Do NOT use external issue trackers
-- Do NOT duplicate tracking systems
-- Do NOT clutter repo root with planning documents
+**If you find yourself about to use todo_write or create a markdown checklist, STOP and use `task create` instead.**
## Development Guide and Tools
@@ -574,6 +702,14 @@ git restack
- Broken or untested code
- Temporary debugging changes
+**NEVER do these git operations without explicit user request:**
+- ❌ `git push` - NEVER push to remote unless explicitly asked
+- ❌ `git pull` - NEVER pull from remote unless explicitly asked
+- ❌ Force pushes or destructive operations
+- ❌ Branch deletion or remote branch operations
+
+**Why:** The user maintains control over when code is shared with collaborators. Always ask before syncing with remote repositories.
+
### Workflow Best Practices
1. **Make small, focused commits** - Each commit should represent one logical change
diff --git a/Omni/Task.hs b/Omni/Task.hs
index ef912f9..e3f89dc 100644
--- a/Omni/Task.hs
+++ b/Omni/Task.hs
@@ -7,13 +7,17 @@
module Omni.Task where
import Alpha
+import qualified Data.Aeson as Aeson
+import qualified Data.ByteString.Lazy.Char8 as BLC
import qualified Data.Text as T
import qualified Omni.Cli as Cli
import qualified Omni.Namespace as Namespace
import Omni.Task.Core
import qualified Omni.Test as Test
+import qualified System.Console.Docopt as Docopt
import System.Directory (doesFileExist, removeFile)
import System.Environment (setEnv)
+import System.Process (callCommand)
main :: IO ()
main = Cli.main plan
@@ -33,15 +37,16 @@ help =
task
Usage:
- task init
- task create <title> [--type=<type>] [--parent=<id>] [--deps=<ids>] [--dep-type=<type>] [--discovered-from=<id>] [--namespace=<ns>]
- task list [--type=<type>] [--parent=<id>] [--status=<status>] [--namespace=<ns>]
- task ready
- task update <id> <status>
- task deps <id>
- task tree [<id>]
+ task init [--quiet]
+ task create <title> [--type=<type>] [--parent=<id>] [--priority=<p>] [--deps=<ids>] [--dep-type=<type>] [--discovered-from=<id>] [--namespace=<ns>] [--json]
+ task list [--type=<type>] [--parent=<id>] [--status=<status>] [--namespace=<ns>] [--json]
+ task ready [--json]
+ task update <id> <status> [--json]
+ task deps <id> [--json]
+ task tree [<id>] [--json]
task export [--flush]
task import -i <file>
+ task sync
task test
task (-h | --help)
@@ -55,18 +60,22 @@ Commands:
tree Show task tree (epics with children, or all epics if no ID given)
export Export and consolidate tasks to JSONL
import Import tasks from JSONL file
+ sync Export and commit tasks to git (does NOT push)
test Run tests
Options:
-h --help Show this help
--type=<type> Task type: epic or task (default: task)
--parent=<id> Parent epic ID
+ --priority=<p> Priority: 0-4 (0=critical, 4=backlog, default: 2)
--status=<status> Filter by status: open, in-progress, done
--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
+ --json Output in JSON format (for agent use)
+ --quiet Non-interactive mode (for agents)
-i <file> Input file for import
Arguments:
@@ -76,9 +85,24 @@ Arguments:
<file> JSONL file to import
|]
+-- Helper to check if JSON output is requested
+isJsonMode :: Cli.Arguments -> Bool
+isJsonMode args = args `Cli.has` Cli.longOption "json"
+
+-- Helper to output JSON
+outputJson :: (Aeson.ToJSON a) => a -> IO ()
+outputJson val = BLC.putStrLn <| Aeson.encode val
+
+-- Helper for success message in JSON
+outputSuccess :: Text -> IO ()
+outputSuccess msg = outputJson <| Aeson.object ["success" Aeson..= True, "message" Aeson..= msg]
+
move :: Cli.Arguments -> IO ()
move args
- | args `Cli.has` Cli.command "init" = initTaskDb
+ | args `Cli.has` Cli.command "init" = do
+ let quiet = args `Cli.has` Cli.longOption "quiet"
+ initTaskDb
+ unless quiet <| putText "Task database initialized. Use 'task create' to add tasks."
| args `Cli.has` Cli.command "create" = do
title <- getArgText args "title"
taskType <- case Cli.getArg args (Cli.longOption "type") of
@@ -109,6 +133,16 @@ move args
let deps = map (\did -> Dependency {depId = did, depType = depType}) depIds
+ -- Parse priority (default to P2 = medium)
+ priority <- case Cli.getArg args (Cli.longOption "priority") of
+ Nothing -> pure P2
+ Just "0" -> pure P0
+ Just "1" -> pure P1
+ Just "2" -> pure P2
+ Just "3" -> pure P3
+ Just "4" -> pure P4
+ Just other -> panic <| "Invalid priority: " <> T.pack other <> ". Use: 0-4"
+
namespace <- case Cli.getArg args (Cli.longOption "namespace") of
Nothing -> pure Nothing
Just ns -> do
@@ -116,8 +150,10 @@ move args
let validNs = Namespace.fromHaskellModule ns
nsPath = T.pack <| Namespace.toPath validNs
pure <| Just nsPath
- createdTask <- createTask title taskType parent namespace deps
- putStrLn <| "Created task: " <> T.unpack (taskId createdTask)
+ createdTask <- createTask title taskType parent namespace priority deps
+ if isJsonMode args
+ then outputJson createdTask
+ else putStrLn <| "Created task: " <> T.unpack (taskId createdTask)
| args `Cli.has` Cli.command "list" = do
maybeType <- case Cli.getArg args (Cli.longOption "type") of
Nothing -> pure Nothing
@@ -140,11 +176,16 @@ move args
nsPath = T.pack <| Namespace.toPath validNs
pure <| Just nsPath
tasks <- listTasks maybeType maybeParent maybeStatus maybeNamespace
- traverse_ printTask tasks
+ if isJsonMode args
+ then outputJson tasks
+ else traverse_ printTask tasks
| args `Cli.has` Cli.command "ready" = do
tasks <- getReadyTasks
- putText "Ready tasks:"
- traverse_ printTask tasks
+ if isJsonMode args
+ then outputJson tasks
+ else do
+ putText "Ready tasks:"
+ traverse_ printTask tasks
| args `Cli.has` Cli.command "update" = do
tid <- getArgText args "id"
statusStr <- getArgText args "status"
@@ -154,24 +195,43 @@ move args
"done" -> Done
_ -> panic "Invalid status. Use: open, in-progress, or done"
updateTaskStatus tid newStatus
- putStrLn <| "Updated task " <> T.unpack tid
- -- Remind user to commit changes (pre-commit hook will auto-export)
- putText "Note: Task changes will be committed automatically on your next git commit."
+ if isJsonMode args
+ then outputSuccess <| "Updated task " <> tid
+ else do
+ putStrLn <| "Updated task " <> T.unpack tid
+ putText "Note: Task changes will be committed automatically on your next git commit."
| args `Cli.has` Cli.command "deps" = do
tid <- getArgText args "id"
- showDependencyTree tid
+ if isJsonMode args
+ then do
+ deps <- getDependencyTree tid
+ outputJson deps
+ else showDependencyTree tid
| args `Cli.has` Cli.command "tree" = do
maybeId <- case Cli.getArg args (Cli.argument "id") of
Nothing -> pure Nothing
Just idStr -> pure <| Just (T.pack idStr)
- showTaskTree maybeId
+ if isJsonMode args
+ then do
+ tree <- getTaskTree maybeId
+ outputJson tree
+ else showTaskTree maybeId
| args `Cli.has` Cli.command "export" = do
exportTasks
putText "Exported and consolidated tasks to .tasks/tasks.jsonl"
| args `Cli.has` Cli.command "import" = do
- file <- getArgText args "file"
+ -- Note: -i <file> means the value is stored in option 'i', not argument "file"
+ file <- case Cli.getArg args (Cli.shortOption 'i') of
+ Nothing -> panic "import requires -i <file>"
+ Just f -> pure (T.pack f)
importTasks (T.unpack file)
putText <| "Imported tasks from " <> file
+ | args `Cli.has` Cli.command "sync" = do
+ -- Export tasks and commit locally only
+ exportTasks
+ callCommand "git add .tasks/tasks.jsonl"
+ callCommand "git commit -m 'task: sync database' || true"
+ putText "Synced tasks: exported and committed to git (use 'git push' to share with remote)"
| otherwise = putText (T.pack <| Cli.usage help)
where
getArgText :: Cli.Arguments -> String -> IO Text
@@ -182,7 +242,7 @@ move args
Just val -> pure (T.pack val)
test :: Test.Tree
-test = Test.group "Omni.Task" [unitTests]
+test = Test.group "Omni.Task" [unitTests, cliTests]
unitTests :: Test.Tree
unitTests =
@@ -199,36 +259,174 @@ unitTests =
initTaskDb
True Test.@?= True,
Test.unit "can create task" <| do
- task <- createTask "Test task" WorkTask Nothing Nothing []
+ task <- createTask "Test task" WorkTask Nothing Nothing P2 []
taskTitle task Test.@?= "Test task"
taskType task Test.@?= WorkTask
taskStatus task Test.@?= Open
+ taskPriority task Test.@?= P2
null (taskDependencies task) Test.@?= True,
Test.unit "can list tasks" <| do
- _ <- createTask "Test task for list" WorkTask Nothing Nothing []
+ _ <- createTask "Test task for list" WorkTask Nothing Nothing P2 []
tasks <- listTasks Nothing Nothing Nothing Nothing
not (null tasks) Test.@?= True,
Test.unit "ready tasks exclude blocked ones" <| do
- task1 <- createTask "First task" WorkTask Nothing Nothing []
+ task1 <- createTask "First task" WorkTask Nothing Nothing P2 []
let blockingDep = Dependency {depId = taskId task1, depType = Blocks}
- task2 <- createTask "Blocked task" WorkTask Nothing Nothing [blockingDep]
+ task2 <- createTask "Blocked task" WorkTask Nothing Nothing P2 [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 []
+ task1 <- createTask "Original task" WorkTask Nothing Nothing P2 []
let discDep = Dependency {depId = taskId task1, depType = DiscoveredFrom}
- task2 <- createTask "Discovered work" WorkTask Nothing Nothing [discDep]
+ task2 <- createTask "Discovered work" WorkTask Nothing Nothing P2 [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 []
+ task1 <- createTask "Task A" WorkTask Nothing Nothing P2 []
let relDep = Dependency {depId = taskId task1, depType = Related}
- task2 <- createTask "Task B" WorkTask Nothing Nothing [relDep]
+ task2 <- createTask "Task B" WorkTask Nothing Nothing P2 [relDep]
ready <- getReadyTasks
-- Both should be ready since Related doesn't block
(taskId task1 `elem` map taskId ready) Test.@?= True
(taskId task2 `elem` map taskId ready) Test.@?= True
]
+
+-- | Test CLI argument parsing to ensure docopt string matches actual usage
+cliTests :: Test.Tree
+cliTests =
+ Test.group
+ "CLI argument parsing"
+ [ Test.unit "init command" <| do
+ let result = Docopt.parseArgs help ["init"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'init': " <> show err
+ Right args -> args `Cli.has` Cli.command "init" Test.@?= True,
+ Test.unit "init with --quiet flag" <| do
+ let result = Docopt.parseArgs help ["init", "--quiet"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'init --quiet': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "init" Test.@?= True
+ args `Cli.has` Cli.longOption "quiet" Test.@?= True,
+ Test.unit "create with title" <| do
+ let result = Docopt.parseArgs help ["create", "Test task"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'create': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "create" Test.@?= True
+ Cli.getArg args (Cli.argument "title") Test.@?= Just "Test task",
+ Test.unit "create with --json flag" <| do
+ let result = Docopt.parseArgs help ["create", "Test", "--json"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'create --json': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "create" Test.@?= True
+ args `Cli.has` Cli.longOption "json" Test.@?= True,
+ Test.unit "create with --namespace flag" <| do
+ let result = Docopt.parseArgs help ["create", "Test", "--namespace=Omni/Task"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'create --namespace': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "create" Test.@?= True
+ Cli.getArg args (Cli.longOption "namespace") Test.@?= Just "Omni/Task",
+ Test.unit "create with --discovered-from flag" <| do
+ let result = Docopt.parseArgs help ["create", "Test", "--discovered-from=t-abc123"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'create --discovered-from': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "create" Test.@?= True
+ Cli.getArg args (Cli.longOption "discovered-from") Test.@?= Just "t-abc123",
+ Test.unit "create with --priority flag" <| do
+ let result = Docopt.parseArgs help ["create", "Test", "--priority=1"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'create --priority': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "create" Test.@?= True
+ Cli.getArg args (Cli.longOption "priority") Test.@?= Just "1",
+ Test.unit "list command" <| do
+ let result = Docopt.parseArgs help ["list"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'list': " <> show err
+ Right args -> args `Cli.has` Cli.command "list" Test.@?= True,
+ Test.unit "list with --json flag" <| do
+ let result = Docopt.parseArgs help ["list", "--json"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'list --json': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "list" Test.@?= True
+ args `Cli.has` Cli.longOption "json" Test.@?= True,
+ Test.unit "list with --status filter" <| do
+ let result = Docopt.parseArgs help ["list", "--status=open"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'list --status': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "list" Test.@?= True
+ Cli.getArg args (Cli.longOption "status") Test.@?= Just "open",
+ Test.unit "ready command" <| do
+ let result = Docopt.parseArgs help ["ready"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'ready': " <> show err
+ Right args -> args `Cli.has` Cli.command "ready" Test.@?= True,
+ Test.unit "ready with --json flag" <| do
+ let result = Docopt.parseArgs help ["ready", "--json"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'ready --json': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "ready" Test.@?= True
+ args `Cli.has` Cli.longOption "json" Test.@?= True,
+ Test.unit "update command" <| do
+ let result = Docopt.parseArgs help ["update", "t-abc123", "done"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'update': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "update" Test.@?= True
+ Cli.getArg args (Cli.argument "id") Test.@?= Just "t-abc123"
+ Cli.getArg args (Cli.argument "status") Test.@?= Just "done",
+ Test.unit "update with --json flag" <| do
+ let result = Docopt.parseArgs help ["update", "t-abc123", "done", "--json"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'update --json': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "update" Test.@?= True
+ args `Cli.has` Cli.longOption "json" Test.@?= True,
+ Test.unit "deps command" <| do
+ let result = Docopt.parseArgs help ["deps", "t-abc123"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'deps': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "deps" Test.@?= True
+ Cli.getArg args (Cli.argument "id") Test.@?= Just "t-abc123",
+ Test.unit "tree command" <| do
+ let result = Docopt.parseArgs help ["tree"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'tree': " <> show err
+ Right args -> args `Cli.has` Cli.command "tree" Test.@?= True,
+ Test.unit "tree with id" <| do
+ let result = Docopt.parseArgs help ["tree", "t-abc123"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'tree <id>': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "tree" Test.@?= True
+ Cli.getArg args (Cli.argument "id") Test.@?= Just "t-abc123",
+ Test.unit "export command" <| do
+ let result = Docopt.parseArgs help ["export"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'export': " <> show err
+ Right args -> args `Cli.has` Cli.command "export" Test.@?= True,
+ Test.unit "import command" <| do
+ let result = Docopt.parseArgs help ["import", "-i", "tasks.jsonl"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'import': " <> show err
+ Right args -> do
+ args `Cli.has` Cli.command "import" Test.@?= True
+ -- Note: -i is a short option, not an argument
+ Cli.getArg args (Cli.shortOption 'i') Test.@?= Just "tasks.jsonl",
+ Test.unit "sync command" <| do
+ 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
+ ]
diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs
index 0351cf4..6c472c5 100644
--- a/Omni/Task/Core.hs
+++ b/Omni/Task/Core.hs
@@ -25,6 +25,7 @@ data Task = Task
taskParent :: Maybe Text, -- Parent epic ID
taskNamespace :: Maybe Text, -- Optional namespace (e.g., "Omni/Task", "Biz/Cloud")
taskStatus :: Status,
+ taskPriority :: Priority, -- Priority level (0-4)
taskDependencies :: [Dependency], -- List of dependencies with types
taskCreatedAt :: UTCTime,
taskUpdatedAt :: UTCTime
@@ -37,6 +38,10 @@ data TaskType = Epic | WorkTask
data Status = Open | InProgress | Done
deriving (Show, Eq, Generic)
+-- Priority levels (matching beads convention)
+data Priority = P0 | P1 | P2 | P3 | P4
+ deriving (Show, Eq, Ord, Generic)
+
data Dependency = Dependency
{ depId :: Text, -- ID of the task this depends on
depType :: DependencyType -- Type of dependency relationship
@@ -58,6 +63,10 @@ instance ToJSON Status
instance FromJSON Status
+instance ToJSON Priority
+
+instance FromJSON Priority
+
instance ToJSON DependencyType
instance FromJSON DependencyType
@@ -134,7 +143,7 @@ loadTasks = do
Just task -> Just task
Nothing -> migrateOldTask line
- -- Migrate old task format (with taskProject field) to new format
+ -- Migrate old task format (with taskProject field or missing priority) to new format
migrateOldTask :: Text -> Maybe Task
migrateOldTask line = case Aeson.decode (BLC.pack <| T.unpack line) :: Maybe Aeson.Object of
Nothing -> Nothing
@@ -151,6 +160,8 @@ loadTasks = do
taskType' = WorkTask -- Old tasks become WorkTask by default
taskParent' = Nothing
taskNamespace' = KM.lookup "taskNamespace" obj +> parseMaybe Aeson.parseJSON
+ -- Default priority to P2 (medium) for old tasks
+ taskPriority' = fromMaybe P2 (KM.lookup "taskPriority" obj +> parseMaybe Aeson.parseJSON)
in case (taskId', taskTitle', taskStatus', taskCreatedAt', taskUpdatedAt') of
(Just tid, Just title, Just status, Just created, Just updated) ->
Just
@@ -161,6 +172,7 @@ loadTasks = do
taskParent = taskParent',
taskNamespace = taskNamespace',
taskStatus = status,
+ taskPriority = taskPriority',
taskDependencies = newDeps,
taskCreatedAt = created,
taskUpdatedAt = updated
@@ -175,8 +187,8 @@ saveTask task = do
BLC.appendFile tasksFile (json <> "\n")
-- Create a new task
-createTask :: Text -> TaskType -> Maybe Text -> Maybe Text -> [Dependency] -> IO Task
-createTask title taskType parent namespace deps = do
+createTask :: Text -> TaskType -> Maybe Text -> Maybe Text -> Priority -> [Dependency] -> IO Task
+createTask title taskType parent namespace priority deps = do
tid <- generateId
now <- getCurrentTime
let task =
@@ -187,6 +199,7 @@ createTask title taskType parent namespace deps = do
taskParent = parent,
taskNamespace = namespace,
taskStatus = Open,
+ taskPriority = priority,
taskDependencies = deps,
taskCreatedAt = now,
taskUpdatedAt = now
@@ -241,6 +254,20 @@ getReadyTasks = do
isReady task = all (`elem` doneIds) (blockingDepIds task)
pure <| filter isReady openTasks
+-- Get dependency tree for a task (returns tasks)
+getDependencyTree :: Text -> IO [Task]
+getDependencyTree tid = do
+ tasks <- loadTasks
+ case filter (\t -> taskId t == tid) tasks of
+ [] -> pure []
+ (task : _) -> pure <| collectDeps tasks task
+ where
+ collectDeps :: [Task] -> Task -> [Task]
+ collectDeps allTasks task =
+ let depIds = map depId (taskDependencies task)
+ deps = filter (\t -> taskId t `elem` depIds) allTasks
+ in task : concatMap (collectDeps allTasks) deps
+
-- Show dependency tree for a task
showDependencyTree :: Text -> IO ()
showDependencyTree tid = do
@@ -256,6 +283,26 @@ showDependencyTree tid = do
deps = filter (\t -> taskId t `elem` depIds) allTasks
traverse_ (\dep -> printTree allTasks dep (indent + 1)) deps
+-- Get task tree (returns tasks hierarchically)
+getTaskTree :: Maybe Text -> IO [Task]
+getTaskTree maybeId = do
+ tasks <- loadTasks
+ case maybeId of
+ Nothing -> do
+ -- Return all epics with their children
+ let epics = filter (\t -> taskType t == Epic) tasks
+ in pure <| concatMap (collectChildren tasks) epics
+ Just tid -> do
+ -- Return specific task/epic with its children
+ case filter (\t -> taskId t == tid) tasks of
+ [] -> pure []
+ (task : _) -> pure <| collectChildren tasks task
+ where
+ collectChildren :: [Task] -> Task -> [Task]
+ collectChildren allTasks task =
+ let children = filter (\t -> taskParent t == Just (taskId task)) allTasks
+ in task : concatMap (collectChildren allTasks) children
+
-- Show task tree (epic with children, or all epics if no ID given)
showTaskTree :: Maybe Text -> IO ()
showTaskTree maybeId = do