summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-11-22 13:55:28 -0500
committerBen Sima <ben@bensima.com>2025-11-22 13:55:28 -0500
commit10a2b488519c061eb86c9147cc96b38c82f51ef8 (patch)
tree14abeeab9654353259e1baaf8cf462322c58d659
parent8697fd8a11a1cf368db1e6c05afddf87906e8de3 (diff)
parent960f7226139abd5408454e5285ead2024e0da643 (diff)
task: complete t-rWcqsDZFM.2 (Merge)
Amp-Thread-ID: https://ampcode.com/threads/T-ca3b086b-5a85-422a-b13d-256784c04221 Co-authored-by: Amp <amp@ampcode.com>
-rw-r--r--.tasks/tasks.jsonl6
-rw-r--r--Omni/Agent.hs101
-rw-r--r--Omni/Agent/Git.hs31
-rwxr-xr-xOmni/Agent/harvest-tasks.sh62
-rwxr-xr-xOmni/Agent/merge-tasks.sh30
-rwxr-xr-xOmni/Agent/setup-worker.sh31
-rwxr-xr-xOmni/Agent/sync-tasks.sh46
7 files changed, 134 insertions, 173 deletions
diff --git a/.tasks/tasks.jsonl b/.tasks/tasks.jsonl
index 0243477..c3e2940 100644
--- a/.tasks/tasks.jsonl
+++ b/.tasks/tasks.jsonl
@@ -188,9 +188,9 @@
{"taskCreatedAt":"2025-11-22T10:51:08.813653444Z","taskDependencies":[],"taskDescription":"Create an agent or script that iterates through every namespace in the project and runs 'bild' (e.g. 'bild --time 0 **/*'). For every build failure encountered, it should automatically create a new task with the error details and link it to this epic (or the discovery context).","taskId":"t-rWcmRMaWX.4","taskNamespace":"Omni/Bild.hs","taskParent":"t-rWcmRMaWX","taskPriority":"P2","taskStatus":"Done","taskTitle":"Audit codebase builds and file repair tasks","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T11:51:55.014259557Z"}
{"taskCreatedAt":"2025-11-22T11:27:59.621730567Z","taskDependencies":[],"taskDescription":"Update Omni/Agent/Worker.hs to read the content of AGENTS.md and include a relevant summary or the full content in the initial system prompt provided to the Amp agent. This ensures the worker knows about repository conventions, testing standards, and tool usage.","taskId":"t-rWcpiE3LO","taskNamespace":"Omni/Agent.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Include AGENTS.md context in Worker initial prompt","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T11:36:14.542146518Z"}
{"taskCreatedAt":"2025-11-22T11:45:43.502171517Z","taskDependencies":[],"taskDescription":"Remove unused test files, migrate useful tests to the main suite, and remove legacy bash prototype scripts replaced by the Haskell implementation.","taskId":"t-rWcqsDZFM","taskNamespace":"Omni/Agent.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Cleanup Omni/Agent files and tests","taskType":"Epic","taskUpdatedAt":"2025-11-22T11:45:43.502171517Z"}
-{"taskCreatedAt":"2025-11-22T11:45:49.548163416Z","taskDependencies":[],"taskDescription":"Omni/Agent/LogTest.hs is currently unused by the main 'bild --test Omni/Agent.hs' command. Review its contents, move any valuable tests to Omni/Agent.hs (or Omni/Agent/Log.hs's test section), and delete the file.","taskId":"t-rWcqsDZFM.1","taskNamespace":"Omni/Agent.hs","taskParent":"t-rWcqsDZFM","taskPriority":"P2","taskStatus":"Done","taskTitle":"Consolidate LogTest.hs into main test suite","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T18:53:41.872641961Z"}
-{"taskCreatedAt":"2025-11-22T11:45:57.926946967Z","taskDependencies":[],"taskDescription":"Remove bash scripts that have been superseded by the Haskell agent implementation. Candidates for removal: harvest-tasks.sh, merge-tasks.sh, sync-tasks.sh, setup-worker.sh. Ensure functionality is covered by Haskell code before deletion.","taskId":"t-rWcqsDZFM.2","taskNamespace":"Omni/Agent.hs","taskParent":"t-rWcqsDZFM","taskPriority":"P2","taskStatus":"Open","taskTitle":"Remove legacy bash prototype scripts","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T11:45:57.926946967Z"}
-{"taskCreatedAt":"2025-11-22T11:46:03.875940421Z","taskDependencies":[],"taskDescription":"We have both 'monitor.sh' and 'monitor-worker.sh'. Consolidate them into a single 'monitor.sh' script and remove the duplicate.","taskId":"t-rWcqsDZFM.3","taskNamespace":"Omni/Agent.hs","taskParent":"t-rWcqsDZFM","taskPriority":"P2","taskStatus":"Open","taskTitle":"Consolidate monitor scripts","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T11:46:03.875940421Z"}
+{"taskCreatedAt":"2025-11-22T11:45:49.548163416Z","taskDependencies":[],"taskDescription":"Omni/Agent/LogTest.hs is currently unused by the main 'bild --test Omni/Agent.hs' command. Review its contents, move any valuable tests to Omni/Agent.hs (or Omni/Agent/Log.hs's test section), and delete the file.","taskId":"t-rWcqsDZFM.1","taskNamespace":"Omni/Agent.hs","taskParent":"t-rWcqsDZFM","taskPriority":"P2","taskStatus":"Review","taskTitle":"Consolidate LogTest.hs into main test suite","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T18:55:02.921497946Z"}
+{"taskCreatedAt":"2025-11-22T11:45:57.926946967Z","taskDependencies":[],"taskDescription":"Remove bash scripts that have been superseded by the Haskell agent implementation. Candidates for removal: harvest-tasks.sh, merge-tasks.sh, sync-tasks.sh, setup-worker.sh. Ensure functionality is covered by Haskell code before deletion.","taskId":"t-rWcqsDZFM.2","taskNamespace":"Omni/Agent.hs","taskParent":"t-rWcqsDZFM","taskPriority":"P2","taskStatus":"Done","taskTitle":"Remove legacy bash prototype scripts","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T18:54:20.854014849Z"}
+{"taskCreatedAt":"2025-11-22T11:46:03.875940421Z","taskDependencies":[],"taskDescription":"We have both 'monitor.sh' and 'monitor-worker.sh'. Consolidate them into a single 'monitor.sh' script and remove the duplicate.","taskId":"t-rWcqsDZFM.3","taskNamespace":"Omni/Agent.hs","taskParent":"t-rWcqsDZFM","taskPriority":"P2","taskStatus":"InProgress","taskTitle":"Consolidate monitor scripts","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T18:55:07.182401744Z"}
{"taskCreatedAt":"2025-11-22T12:42:35.9228659Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1o2bk9tzanj","taskNamespace":"Omni/Agent.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Capture Amp summary for commit message","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T12:48:02.872211474Z"}
{"taskCreatedAt":"2025-11-22T12:42:39.927855226Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1o2bk9wd4x9","taskNamespace":"Omni/Agent.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Update Amp prompt to forbid git commits","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T12:48:07.355031023Z"}
{"taskCreatedAt":"2025-11-22T12:57:29.984013645Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1o2bkoma4nf","taskNamespace":"Omni.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Update AGENTS.md with commit message guidelines","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T12:59:00.994108608Z"}
diff --git a/Omni/Agent.hs b/Omni/Agent.hs
index f3fc29d..bad2737 100644
--- a/Omni/Agent.hs
+++ b/Omni/Agent.hs
@@ -9,12 +9,21 @@ module Omni.Agent where
import Alpha
import qualified Data.Text as Text
+import qualified Data.Text.IO as TIO
import qualified Omni.Agent.Core as Core
+import qualified Omni.Agent.Git as Git
import qualified Omni.Agent.Log as Log
import qualified Omni.Agent.Worker as Worker
import qualified Omni.Cli as Cli
+import qualified Omni.Task.Core as TaskCore
import qualified Omni.Test as Test
import qualified System.Console.Docopt as Docopt
+import qualified System.Directory as Directory
+import qualified System.Environment as Env
+import qualified System.Exit as Exit
+import System.FilePath ((</>))
+import qualified System.IO.Temp as Temp
+import qualified System.Process as Process
main :: IO ()
main = Cli.main plan
@@ -35,6 +44,9 @@ agent
Usage:
agent start <name> [--path=<path>]
+ agent harvest [--path=<path>]
+ agent merge-driver <ours> <theirs>
+ agent setup <name>
agent test
agent --help
@@ -61,8 +73,85 @@ move args
}
Worker.start worker
+ | args `Cli.has` Cli.command "harvest" = harvest args
+ | args `Cli.has` Cli.command "merge-driver" = mergeDriver args
+ | args `Cli.has` Cli.command "setup" = setup args
| otherwise = putStrLn (Cli.usage help)
+harvest :: Cli.Arguments -> IO ()
+harvest args = do
+ let path = Cli.getArgWithDefault args "." (Cli.longOption "path")
+ putText "Harvesting task updates from workers..."
+
+ branches <- Git.listBranches path "omni-worker-*"
+ if null branches
+ then putText "No worker branches found."
+ else do
+ updated <- foldlM (processBranch path) False branches
+ when updated <| do
+ -- Consolidate
+ Directory.setCurrentDirectory path
+ TaskCore.exportTasks
+
+ -- Commit if changed
+ Git.commit path "task: harvest updates from workers"
+ putText "Success: Task database updated and committed."
+
+processBranch :: FilePath -> Bool -> Text -> IO Bool
+processBranch repo updated branch = do
+ putText <| "Checking " <> branch <> "..."
+ maybeContent <- Git.showFile repo branch ".tasks/tasks.jsonl"
+ case maybeContent of
+ Nothing -> do
+ putText <| " Warning: Could not read .tasks/tasks.jsonl from " <> branch
+ pure updated
+ Just content -> do
+ -- Write to temp file
+ Temp.withSystemTempFile "worker-tasks.jsonl" <| \tempPath h -> do
+ TIO.hPutStr h content
+ IO.hClose h
+ -- Import
+ -- We need to ensure we are in the repo directory for TaskCore to find .tasks/tasks.jsonl
+ Directory.setCurrentDirectory repo
+ TaskCore.importTasks tempPath
+ putText <| " Imported tasks from " <> branch
+ pure True
+
+mergeDriver :: Cli.Arguments -> IO ()
+mergeDriver args = do
+ ours <- Cli.getArgOrExit args (Cli.argument "ours")
+ theirs <- Cli.getArgOrExit args (Cli.argument "theirs")
+
+ -- Set TASK_DB_PATH to ours (the file git provided as the current version)
+ Env.setEnv "TASK_DB_PATH" ours
+ TaskCore.importTasks theirs
+ Exit.exitSuccess
+
+setup :: Cli.Arguments -> IO ()
+setup args = do
+ nameStr <- Cli.getArgOrExit args (Cli.argument "name")
+ let name = Text.pack nameStr
+ root <- Git.getRepoRoot "."
+ let worktreePath = root <> "/../" <> nameStr
+
+ putText <| "Creating worktree '" <> Text.pack worktreePath <> "' on branch '" <> name <> "' (from live)..."
+
+ -- git worktree add -b <name> <path> live
+ Git.runGit root ["worktree", "add", "-b", nameStr, worktreePath, "live"]
+
+ -- Copy .envrc.local if exists
+ let envrc = root </> ".envrc.local"
+ exists <- Directory.doesFileExist envrc
+ when exists <| do
+ putText "Copying .envrc.local..."
+ Directory.copyFile envrc (worktreePath </> ".envrc.local")
+
+ -- Config git
+ Git.runGit worktreePath ["config", "user.name", "Omni Worker"]
+ Git.runGit worktreePath ["config", "user.email", "bot@omni.agent"]
+
+ putText <| "Worker setup complete at " <> Text.pack worktreePath
+
test :: Test.Tree
test = Test.group "Omni.Agent" [unitTests, logTests]
@@ -84,5 +173,15 @@ unitTests =
let result = Docopt.parseArgs help ["start", "worker-1"]
case result of
Left err -> Test.assertFailure <| "Failed to parse 'start': " <> show err
- Right args -> args `Cli.has` Cli.command "start" Test.@?= True
+ Right args -> args `Cli.has` Cli.command "start" Test.@?= True,
+ Test.unit "can parse harvest command" <| do
+ let result = Docopt.parseArgs help ["harvest"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'harvest': " <> show err
+ Right args -> args `Cli.has` Cli.command "harvest" Test.@?= True,
+ Test.unit "can parse setup command" <| do
+ let result = Docopt.parseArgs help ["setup", "worker-2"]
+ case result of
+ Left err -> Test.assertFailure <| "Failed to parse 'setup': " <> show err
+ Right args -> args `Cli.has` Cli.command "setup" Test.@?= True
]
diff --git a/Omni/Agent/Git.hs b/Omni/Agent/Git.hs
index b1978f2..4c06cf6 100644
--- a/Omni/Agent/Git.hs
+++ b/Omni/Agent/Git.hs
@@ -13,6 +13,10 @@ module Omni.Agent.Git
getCurrentBranch,
branchExists,
isMerged,
+ listBranches,
+ showFile,
+ getRepoRoot,
+ runGit,
main,
test,
)
@@ -199,3 +203,30 @@ isMerged repo branch target = do
let cmd = (Process.proc "git" ["merge-base", "--is-ancestor", Text.unpack branch, Text.unpack target]) {Process.cwd = Just repo}
(code, _, _) <- Process.readCreateProcessWithExitCode cmd ""
pure (code == Exit.ExitSuccess)
+
+listBranches :: FilePath -> Text -> IO [Text]
+listBranches repo pat = do
+ let cmd = (Process.proc "git" ["branch", "--list", Text.unpack pat, "--format=%(refname:short)"]) {Process.cwd = Just repo}
+ (code, out, _) <- Process.readCreateProcessWithExitCode cmd ""
+ case code of
+ Exit.ExitSuccess -> pure <| filter (not <. Text.null) (Text.lines (Text.pack out))
+ _ -> panic "git branch list failed"
+
+showFile :: FilePath -> Text -> FilePath -> IO (Maybe Text)
+showFile repo branch path = do
+ let cmd = (Process.proc "git" ["show", Text.unpack branch <> ":" <> path]) {Process.cwd = Just repo}
+ (code, out, _) <- Process.readCreateProcessWithExitCode cmd ""
+ case code of
+ Exit.ExitSuccess -> pure <| Just (Text.pack out)
+ _ -> pure Nothing
+
+getRepoRoot :: FilePath -> IO FilePath
+getRepoRoot dir = do
+ let cmd = (Process.proc "git" ["rev-parse", "--show-toplevel"]) {Process.cwd = Just dir}
+ (code, out, _) <- Process.readCreateProcessWithExitCode cmd ""
+ case code of
+ Exit.ExitSuccess -> pure <| strip out
+ _ -> panic "git rev-parse failed"
+
+runGit :: FilePath -> [String] -> IO ()
+runGit = git
diff --git a/Omni/Agent/harvest-tasks.sh b/Omni/Agent/harvest-tasks.sh
deleted file mode 100755
index 44c2322..0000000
--- a/Omni/Agent/harvest-tasks.sh
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/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
-
- LAST_MSG=$(git log -1 --pretty=%s)
- if [[ "$LAST_MSG" == "task: harvest updates from workers" ]]; then
- echo "Squashing with previous harvest commit..."
- git commit --amend --no-edit
- else
- git commit -m "task: harvest updates from workers"
- fi
- 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/merge-tasks.sh b/Omni/Agent/merge-tasks.sh
deleted file mode 100755
index 833afcf..0000000
--- a/Omni/Agent/merge-tasks.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env bash
-# Omni/Ide/merge-tasks.sh
-# Git merge driver for .tasks/tasks.jsonl
-# Usage: merge-tasks.sh %O %A %B
-# %O = ancestor, %A = current (ours), %B = other (theirs)
-
-# ANCESTOR="$1" (unused)
-OURS="$2"
-THEIRS="$3"
-
-# We want to merge THEIRS into OURS using the task tool's import logic.
-REPO_ROOT="$(git rev-parse --show-toplevel)"
-TASK_BIN="$REPO_ROOT/_/bin/task"
-
-# If binary doesn't exist, try to build it? Or just fail safely.
-if [ ! -x "$TASK_BIN" ]; then
- # Try to find it in the build output if _/bin isn't populated
- # But for now, let's just fail if not found, forcing manual merge
- exit 1
-fi
-
-# Use the task tool to merge
-# We tell it that the DB is the 'OURS' file
-# And we import the 'THEIRS' file
-export TASK_DB_PATH="$OURS"
-if "$TASK_BIN" import -i "$THEIRS" >/dev/null 2>&1; then
- exit 0
-else
- exit 1
-fi
diff --git a/Omni/Agent/setup-worker.sh b/Omni/Agent/setup-worker.sh
deleted file mode 100755
index 42b7fc9..0000000
--- a/Omni/Agent/setup-worker.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-if [ -z "$1" ]; then
- echo "Usage: $0 <worker-name>"
- echo "Example: $0 omni-worker-1"
- exit 1
-fi
-
-WORKER_NAME="$1"
-REPO_ROOT="$(git rev-parse --show-toplevel)"
-WORKTREE_PATH="$REPO_ROOT/../$WORKER_NAME"
-
-# We create a new branch for the worker based on 'live'
-# This avoids the "branch already checked out" error if 'live' is checked out elsewhere
-BRANCH_NAME="${WORKER_NAME}"
-echo "Creating worktree '$WORKTREE_PATH' on branch '$BRANCH_NAME' (from live)..."
-git worktree add -b "$BRANCH_NAME" "$WORKTREE_PATH" live
-
-# Copy .envrc.local if it exists (user-specific config)
-if [ -f "$REPO_ROOT/.envrc.local" ]; then
- echo "Copying .envrc.local..."
- cp "$REPO_ROOT/.envrc.local" "$WORKTREE_PATH/"
-fi
-
-# Configure git identity for the worker
-echo "Configuring git identity for worker..."
-git -C "$WORKTREE_PATH" config user.name "Omni Worker"
-git -C "$WORKTREE_PATH" config user.email "bot@omni.agent"
-
-echo "Worker setup complete at $WORKTREE_PATH"
diff --git a/Omni/Agent/sync-tasks.sh b/Omni/Agent/sync-tasks.sh
deleted file mode 100755
index f4669b7..0000000
--- a/Omni/Agent/sync-tasks.sh
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-# Omni/Ide/sync-tasks.sh
-# Synchronizes the task database with the live branch safely.
-# Usage: sync-tasks.sh [--commit]
-
-COMMIT=0
-if [[ "$1" == "--commit" ]]; then
- COMMIT=1
-fi
-
-REPO_ROOT="$(git rev-parse --show-toplevel)"
-cd "$REPO_ROOT"
-
-echo "Syncing tasks..."
-
-# 1. Import latest tasks from 'live' branch
-# We use git show to get the file content from the reference branch without checking it out
-mkdir -p .tasks
-git show live:.tasks/tasks.jsonl > .tasks/live-tasks.jsonl
-
-# 2. Merge logic: Import live tasks into our local DB
-# The 'task import' command uses timestamps to resolve conflicts (last write wins)
-if [ -s .tasks/live-tasks.jsonl ]; then
- echo "Importing tasks from live branch..."
- "$REPO_ROOT/_/bin/task" import -i .tasks/live-tasks.jsonl
-fi
-
-# 3. Clean up
-rm .tasks/live-tasks.jsonl
-
-# 4. Export current state to ensure it's clean/deduplicated
-"$REPO_ROOT/_/bin/task" export --flush
-
-# 5. Commit changes to .tasks/tasks.jsonl if requested and there are changes
-if [[ "$COMMIT" -eq 1 ]]; then
- if [[ -n $(git status --porcelain .tasks/tasks.jsonl) ]]; then
- echo "Committing task updates..."
- git add .tasks/tasks.jsonl
- git commit -m "task: sync database" || true
- echo "Task updates committed to current branch."
- else
- echo "No task changes to commit."
- fi
-fi