diff options
| author | Ben Sima <ben@bensima.com> | 2025-11-22 13:55:28 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-11-22 13:55:28 -0500 |
| commit | 10a2b488519c061eb86c9147cc96b38c82f51ef8 (patch) | |
| tree | 14abeeab9654353259e1baaf8cf462322c58d659 | |
| parent | 8697fd8a11a1cf368db1e6c05afddf87906e8de3 (diff) | |
| parent | 960f7226139abd5408454e5285ead2024e0da643 (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.jsonl | 6 | ||||
| -rw-r--r-- | Omni/Agent.hs | 101 | ||||
| -rw-r--r-- | Omni/Agent/Git.hs | 31 | ||||
| -rwxr-xr-x | Omni/Agent/harvest-tasks.sh | 62 | ||||
| -rwxr-xr-x | Omni/Agent/merge-tasks.sh | 30 | ||||
| -rwxr-xr-x | Omni/Agent/setup-worker.sh | 31 | ||||
| -rwxr-xr-x | Omni/Agent/sync-tasks.sh | 46 |
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 |
