summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.envrc1
-rw-r--r--.tasks/tasks.jsonl8
-rw-r--r--Omni/Agent/WORKER_AGENT_GUIDE.md68
-rwxr-xr-xOmni/Agent/harvest-tasks.sh55
-rwxr-xr-xOmni/Agent/start-worker.sh60
-rw-r--r--Omni/Bild.nix1
-rw-r--r--Omni/Task/Core.hs11
7 files changed, 158 insertions, 46 deletions
diff --git a/.envrc b/.envrc
index 09cc654..9a5e7c8 100644
--- a/.envrc
+++ b/.envrc
@@ -67,6 +67,7 @@
[[ -n "${CI}" ]] && exit 0
#
# create third-party tags
+ mkdir -p "$CODEROOT"/_/src
[[ -L "$CODEROOT"/_/src/.ctags.d ]] || ln -s "$CODEROOT"/.ctags.d "$CODEROOT"/_/src/.ctags.d
function MakeExternalTags {
"$CODEROOT"/Omni/Ide/MakeTags.py --external $(tr ':' '\n' <<< "$ALL_SOURCES")
diff --git a/.tasks/tasks.jsonl b/.tasks/tasks.jsonl
index 28fb6b1..57cf5db 100644
--- a/.tasks/tasks.jsonl
+++ b/.tasks/tasks.jsonl
@@ -57,7 +57,7 @@
{"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":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Implement delete account functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:54:43.703079464Z"}
-{"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.384489707Z","taskDependencies":[],"taskId":"t-1fbElKv","taskNamespace":"Biz/PodcastItLater.hs","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"}
@@ -130,3 +130,9 @@
{"taskCreatedAt":"2025-11-20T21:41:20.067644599Z","taskDependencies":[],"taskId":"t-1ne80pJ","taskNamespace":"Biz/Dragons.hs","taskParent":"t-1f9QP23","taskPriority":"P2","taskStatus":"Review","taskTitle":"Store generated JWK in persistent file","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T22:12:31.22213044Z"}
{"taskCreatedAt":"2025-11-20T21:41:32.113815607Z","taskDependencies":[],"taskId":"t-1neWyaO","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-144hCMJ","taskPriority":"P2","taskStatus":"Open","taskTitle":"Add tests for Admin dashboard","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:41:32.113815607Z"}
{"taskCreatedAt":"2025-11-20T21:41:32.132888832Z","taskDependencies":[],"taskId":"t-1neWD8r","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-144hCMJ","taskPriority":"P2","taskStatus":"Open","taskTitle":"Add error handling tests for Worker","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T21:41:32.132888832Z"}
+{"taskCreatedAt":"2025-11-20T22:42:03.728732682Z","taskDependencies":[],"taskId":"t-1rcIr6X","taskNamespace":"Omni/Task.hs","taskParent":"t-PpXWsU","taskPriority":"P2","taskStatus":"Open","taskTitle":"Implement 'task progress <epic-id>' command","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T22:42:03.728732682Z"}
+{"taskCreatedAt":"2025-11-20T22:42:03.748273499Z","taskDependencies":[],"taskId":"t-1rcIwc8","taskNamespace":"Omni/Task.hs","taskParent":"t-PpXWsU","taskPriority":"P2","taskStatus":"Open","taskTitle":"Implement 'task stats --epic=<id>' filtering","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T22:42:03.748273499Z"}
+{"taskCreatedAt":"2025-11-20T22:42:03.767665854Z","taskDependencies":[],"taskId":"t-1rcIBeU","taskNamespace":"Omni/Task.hs","taskParent":"t-PpXWsU","taskPriority":"P2","taskStatus":"Open","taskTitle":"Add colored output to 'task list' and 'task tree'","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T22:42:03.767665854Z"}
+{"taskCreatedAt":"2025-11-20T22:42:18.766787128Z","taskDependencies":[],"taskId":"t-1rdJxcd","taskNamespace":"Omni/Task/hs.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Namespace normalization incorrect for Haskell files ending in .hs","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T22:42:18.766787128Z"}
+{"taskCreatedAt":"2025-11-20T22:42:37.706495845Z","taskDependencies":[],"taskId":"t-1rf10ho","taskNamespace":"Biz/PodcastItLater/hs.hs","taskParent":"t-143KQl2","taskPriority":"P3","taskStatus":"Open","taskTitle":"Research and add intro/outro sound effects","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T22:42:37.706495845Z"}
+{"taskCreatedAt":"2025-11-20T22:42:37.725796962Z","taskDependencies":[],"taskId":"t-1rf15iH","taskNamespace":"Biz/PodcastItLater/hs.hs","taskParent":"t-143KQl2","taskPriority":"P3","taskStatus":"Open","taskTitle":"Implement audio crossfading for intro/outro","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T22:42:37.725796962Z"}
diff --git a/Omni/Agent/WORKER_AGENT_GUIDE.md b/Omni/Agent/WORKER_AGENT_GUIDE.md
index f1f6f60..782d4d5 100644
--- a/Omni/Agent/WORKER_AGENT_GUIDE.md
+++ b/Omni/Agent/WORKER_AGENT_GUIDE.md
@@ -22,6 +22,10 @@ The Worker Agent should run the following loop continuously:
# Go to worker directory
cd ../omni-worker-1
+# Update base branch with latest live code
+# We merge the local 'live' branch directly since we share the git dir
+git merge live --no-edit
+
# Sync tasks from the live branch
./Omni/Agent/sync-tasks.sh
@@ -51,9 +55,10 @@ task update t-123 in-progress
* If you find an unmerged dependency branch, check it out: `git checkout task/t-parent-id`.
* Otherwise, start from fresh live code: `git checkout omni-worker-1` (which tracks `live`).
-4. **Create Feature Branch**:
+4. **Create/Checkout Feature Branch**:
```bash
- git checkout -b task/t-123
+ # Try to switch to existing branch, otherwise create new one
+ git checkout task/t-123 || git checkout -b task/t-123
```
### Step 4: Implement
@@ -64,38 +69,45 @@ task update t-123 in-progress
### Step 5: Submit for Review
-```bash
-# 1. Mark for review
-task update t-123 review
+1. **Commit Implementation**:
+ ```bash
+ git add .
+ git commit -m "feat: implement t-123"
+ ```
-# 2. Commit implementation
-git add .
-git commit -m "feat: implement t-123"
+2. **Signal Review Readiness**:
+ The Planner checks the `omni-worker-X` branch for status updates. You must switch back and update the status there.
-# 3. Switch back to worker base branch to prepare for next loop
-# This detaches the worker from the task branch so the Planner can check it out
-git checkout omni-worker-1
-./Omni/Agent/sync-tasks.sh
-```
+ ```bash
+ # Switch to base branch
+ git checkout omni-worker-1
+
+ # Sync to get latest state (and any manual merges)
+ ./Omni/Agent/sync-tasks.sh
+
+ # Mark task for review
+ task update t-123 review
+
+ # Commit this status change to the worker branch
+ ./Omni/Agent/sync-tasks.sh --commit
+ ```
+
+ *Note: The Planner will now see 't-123' in 'Review' when it runs `harvest-tasks.sh`.*
## 3. Planner (Reviewer) Workflow
The Planner Agent (running in the main repo) will:
-1. See tasks in `Review` status (after checking out the worker's branch or waiting for them to be merged).
- * *Note: Since workers commit to local branches, you verify work by checking out their branch.*
-2. Check out the worker's branch: `git checkout task/t-123`.
-3. Review code and tests.
-4. Merge to `live`:
- ```bash
- git checkout live
- git merge task/t-123
- # Conflicts in tasks.jsonl are handled automatically by .gitattributes driver
- ```
-5. Mark Done:
- ```bash
- task update t-123 done
- git commit -am "task: t-123 done"
- ```
+1. **Harvest Updates**: Run `./Omni/Agent/harvest-tasks.sh` to pull "In Progress" and "Review" statuses from workers.
+2. **Find Reviews**: Run `task list --status=review`.
+3. **Review Code**:
+ * Check out the feature branch: `git checkout task/t-123`.
+ * Run tests and review code.
+4. **Merge**:
+ * `git checkout live`
+ * `git merge task/t-123`
+5. **Complete**:
+ * `task update t-123 done`
+ * `git commit -am "task: t-123 done"`
## Troubleshooting
diff --git a/Omni/Agent/harvest-tasks.sh b/Omni/Agent/harvest-tasks.sh
new file mode 100755
index 0000000..282beab
--- /dev/null
+++ b/Omni/Agent/harvest-tasks.sh
@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+set -e
+
+# Omni/Agent/harvest-tasks.sh
+# Imports task updates from all worker branches into the current branch (usually live).
+
+REPO_ROOT="$(git rev-parse --show-toplevel)"
+cd "$REPO_ROOT"
+
+echo "Harvesting task updates from workers..."
+
+# Find all worker branches (assuming naming convention omni-worker-*)
+# We filter for local branches
+WORKER_BRANCHES=$(git branch --list "omni-worker-*" --format="%(refname:short)")
+
+if [ -z "$WORKER_BRANCHES" ]; then
+ echo "No worker branches found."
+ exit 0
+fi
+
+UPDATED=0
+
+for branch in $WORKER_BRANCHES; do
+ echo "Checking $branch..."
+
+ # Extract tasks.jsonl from the worker branch
+ if git show "$branch:.tasks/tasks.jsonl" > .tasks/worker-tasks.jsonl 2>/dev/null; then
+ # Import into current DB
+ # The import command handles deduplication and timestamp conflict resolution
+ if "$REPO_ROOT/_/bin/task" import -i .tasks/worker-tasks.jsonl >/dev/null; then
+ echo " Imported tasks from $branch"
+ UPDATED=1
+ fi
+ else
+ echo " Warning: Could not read .tasks/tasks.jsonl from $branch"
+ fi
+done
+
+rm -f .tasks/worker-tasks.jsonl
+
+if [ "$UPDATED" -eq 1 ]; then
+ # Consolidate
+ "$REPO_ROOT/_/bin/task" export --flush
+
+ # Commit if there are changes
+ if [[ -n $(git status --porcelain .tasks/tasks.jsonl) ]]; then
+ git add .tasks/tasks.jsonl
+ git commit -m "task: harvest updates from workers"
+ echo "Success: Task database updated and committed."
+ else
+ echo "No effective changes found."
+ fi
+else
+ echo "No updates found."
+fi
diff --git a/Omni/Agent/start-worker.sh b/Omni/Agent/start-worker.sh
index 2c5eee4..e3a7200 100755
--- a/Omni/Agent/start-worker.sh
+++ b/Omni/Agent/start-worker.sh
@@ -2,18 +2,25 @@
set -e
# Omni/Agent/start-worker.sh
-# Launches an Amp worker agent in the specified worktree.
-# Usage: ./Omni/Agent/start-worker.sh [worker-directory-name]
+# Launches an Amp worker agent in the specified worktree in a loop.
+# Usage: ./Omni/Agent/start-worker.sh [worker-directory-name-or-path]
# Example: ./Omni/Agent/start-worker.sh omni-worker-1
-WORKER_NAME="${1:-omni-worker-1}"
-REPO_ROOT="$(git rev-parse --show-toplevel)"
-WORKER_PATH="$REPO_ROOT/../$WORKER_NAME"
-AMP_BIN="$REPO_ROOT/node_modules/.bin/amp"
+TARGET="${1:-omni-worker-1}"
-if [ ! -d "$WORKER_PATH" ]; then
- echo "Error: Worker directory '$WORKER_PATH' does not exist."
- echo "Please run './Omni/Agent/setup-worker.sh $WORKER_NAME' first."
+# 1. Find the Main Repo (where node_modules lives)
+# The first worktree listed is always the main one
+MAIN_REPO="$(git worktree list --porcelain | grep '^worktree ' | head -n 1 | cut -d ' ' -f 2)"
+AMP_BIN="$MAIN_REPO/node_modules/.bin/amp"
+
+# 2. Resolve Worker Path
+# If TARGET is a directory, use it. Otherwise assume it's a sibling of MAIN_REPO.
+if [ -d "$TARGET" ]; then
+ WORKER_PATH="$(realpath "$TARGET")"
+elif [ -d "$MAIN_REPO/../$TARGET" ]; then
+ WORKER_PATH="$(realpath "$MAIN_REPO/../$TARGET")"
+else
+ echo "Error: Worker directory for '$TARGET' not found."
exit 1
fi
@@ -23,12 +30,35 @@ if [ ! -x "$AMP_BIN" ]; then
exit 1
fi
-echo "Starting Worker Agent in '$WORKER_PATH'..."
-echo "Using Amp binary: $AMP_BIN"
+echo "Starting Worker Agent Loop"
+echo " Worker Path: $WORKER_PATH"
+echo " Amp Binary: $AMP_BIN"
+echo " Log File: $WORKER_PATH/_/llm/amp.log"
+echo " Monitor: tail -f $WORKER_PATH/_/llm/amp.log"
+echo " Press Ctrl+C to stop."
cd "$WORKER_PATH"
-# Launch Amp with the worker persona and instructions
-"$AMP_BIN" -- "You are a Worker Agent. Your goal is to process tasks from the task manager.
-Please read Omni/Agent/WORKER_AGENT_GUIDE.md and follow the 'Worker Loop' instructions exactly.
-Start by syncing tasks and checking for ready work."
+# 3. The Worker Loop
+while true; do
+ echo "----------------------------------------------------------------"
+ echo "$(date): Starting new agent session..."
+
+ mkdir -p _/llm
+ "$AMP_BIN" --log-file "_/llm/amp.log" --dangerously-allow-all -x "You are a Worker Agent. Your goal is to process tasks from the task manager.
+
+ Step 1: Read 'Omni/Agent/WORKER_AGENT_GUIDE.md' to understand your workflow.
+ Step 2: Follow the 'Worker Loop' instructions exactly.
+
+ Start by syncing tasks and checking for ready work. If no work is ready, exit."
+
+ EXIT_CODE=$?
+
+ if [ $EXIT_CODE -ne 0 ]; then
+ echo "Agent exited with error code $EXIT_CODE. Sleeping for 10s..."
+ sleep 10
+ else
+ echo "Agent session finished. Sleeping for 5s..."
+ sleep 5
+ fi
+done
diff --git a/Omni/Bild.nix b/Omni/Bild.nix
index aae82db..446f4ae 100644
--- a/Omni/Bild.nix
+++ b/Omni/Bild.nix
@@ -271,6 +271,7 @@
lolcat
ormolu
ripgrep
+ ruff
sqlite
stripe-cli
tree
diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs
index af105de..f7b7915 100644
--- a/Omni/Task/Core.hs
+++ b/Omni/Task/Core.hs
@@ -246,15 +246,22 @@ listTasks maybeType maybeParent maybeStatus maybeNamespace = do
filterByNamespace Nothing ts = ts
filterByNamespace (Just ns) ts = filter (\t -> taskNamespace t == Just ns) ts
--- Get ready tasks (not blocked by dependencies)
+-- Get ready tasks (not blocked by dependencies and not a parent)
getReadyTasks :: IO [Task]
getReadyTasks = do
allTasks <- loadTasks
let openTasks = filter (\t -> taskStatus t /= Done) allTasks
doneIds = map taskId <| filter (\t -> taskStatus t == Done) allTasks
+
+ -- Find all tasks that act as parents
+ parentIds = mapMaybe taskParent allTasks
+ isParent tid = tid `elem` parentIds
+
-- Only Blocks and ParentChild dependencies block ready work
blockingDepIds task = [depId dep | dep <- taskDependencies task, depType dep `elem` [Blocks, ParentChild]]
- isReady task = all (`elem` doneIds) (blockingDepIds task)
+ isReady task =
+ not (isParent (taskId task))
+ && all (`elem` doneIds) (blockingDepIds task)
pure <| filter isReady openTasks
-- Get dependency tree for a task (returns tasks)