From c09f9e52f36f73ea06f6ca0ac2bc5861227515f5 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 21 Nov 2025 00:45:16 -0500 Subject: feat: implement t-1rcIr6X --- Omni/Task/Core.hs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'Omni') diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs index e4f1086..17a5ecf 100644 --- a/Omni/Task/Core.hs +++ b/Omni/Task/Core.hs @@ -57,6 +57,14 @@ data DependencyType | Related -- Soft relationship, doesn't block deriving (Show, Eq, Generic) +data TaskProgress = TaskProgress + { progressTaskId :: Text, + progressTotal :: Int, + progressCompleted :: Int, + progressPercentage :: Int + } + deriving (Show, Eq, Generic) + instance ToJSON TaskType instance FromJSON TaskType @@ -81,6 +89,10 @@ instance ToJSON Task instance FromJSON Task +instance ToJSON TaskProgress + +instance FromJSON TaskProgress + -- Get the tasks database file path (use test file if TASK_TEST_MODE is set) getTasksFilePath :: IO FilePath getTasksFilePath = do @@ -309,6 +321,32 @@ getDependencyTree tid = do deps = filter (\t -> taskId t `elem` depIds) allTasks in task : concatMap (collectDeps allTasks) deps +-- Get task progress +getTaskProgress :: Text -> IO TaskProgress +getTaskProgress tid = do + tasks <- loadTasks + -- Verify task exists (optional, but good for error handling) + case filter (\t -> taskId t == tid) tasks of + [] -> panic "Task not found" + _ -> do + let children = filter (\child -> taskParent child == Just tid) tasks + total = length children + completed = length <| filter (\child -> taskStatus child == Done) children + percentage = if total == 0 then 0 else (completed * 100) `div` total + pure + TaskProgress + { progressTaskId = tid, + progressTotal = total, + progressCompleted = completed, + progressPercentage = percentage + } + +-- Show task progress +showTaskProgress :: Text -> IO () +showTaskProgress tid = do + progress <- getTaskProgress tid + putText <| "Progress for " <> tid <> ": " <> T.pack (show (progressCompleted progress)) <> "/" <> T.pack (show (progressTotal progress)) <> " (" <> T.pack (show (progressPercentage progress)) <> "%)" + -- Show dependency tree for a task showDependencyTree :: Text -> IO () showDependencyTree tid = do -- cgit v1.2.3 From 62728ac31f4a5a86678bf8830bf76b7ebf05436f Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 21 Nov 2025 00:49:19 -0500 Subject: feat: implement t-1rcIwc8 --- Omni/Task.hs | 17 ++++++++++++++--- Omni/Task/Core.hs | 45 +++++++++++++++++++++++++++++++++------------ 2 files changed, 47 insertions(+), 15 deletions(-) (limited to 'Omni') diff --git a/Omni/Task.hs b/Omni/Task.hs index 24e528b..8151487 100644 --- a/Omni/Task.hs +++ b/Omni/Task.hs @@ -45,7 +45,7 @@ Usage: task update [--json] task deps [--json] task tree [] [--json] - task stats [--json] + task stats [--epic=] [--json] task export [--flush] task import -i task sync @@ -73,6 +73,7 @@ Options: --parent= Parent epic ID --priority=

Priority: 0-4 (0=critical, 4=backlog, default: 2) --status= Filter by status: open, in-progress, review, done + --epic= Filter stats by epic (recursive) --deps= Comma-separated list of dependency IDs --dep-type= Dependency type: blocks, discovered-from, parent-child, related (default: blocks) --discovered-from= Shortcut for --deps= --dep-type=discovered-from @@ -232,11 +233,14 @@ move args outputJson tree else showTaskTree maybeId | args `Cli.has` Cli.command "stats" = do + maybeEpic <- case Cli.getArg args (Cli.longOption "epic") of + Nothing -> pure Nothing + Just e -> pure <| Just (T.pack e) if isJsonMode args then do - stats <- getTaskStats + stats <- getTaskStats maybeEpic outputJson stats - else showTaskStats + else showTaskStats maybeEpic | args `Cli.has` Cli.command "export" = do exportTasks putText "Exported and consolidated tasks to .tasks/tasks.jsonl" @@ -516,6 +520,13 @@ cliTests = Right args -> do args `Cli.has` Cli.command "stats" Test.@?= True args `Cli.has` Cli.longOption "json" Test.@?= True, + Test.unit "stats with --epic flag" <| do + let result = Docopt.parseArgs help ["stats", "--epic=t-abc123"] + case result of + Left err -> Test.assertFailure <| "Failed to parse 'stats --epic': " <> show err + Right args -> do + args `Cli.has` Cli.command "stats" Test.@?= True + Cli.getArg args (Cli.longOption "epic") Test.@?= Just "t-abc123", Test.unit "create with flags in different order" <| do let result = Docopt.parseArgs help ["create", "Test", "--json", "--priority=1", "--namespace=Omni/Task"] case result of diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs index e4f1086..2ff7302 100644 --- a/Omni/Task/Core.hs +++ b/Omni/Task/Core.hs @@ -518,18 +518,31 @@ instance ToJSON TaskStats instance FromJSON TaskStats -- Get task statistics -getTaskStats :: IO TaskStats -getTaskStats = do - tasks <- loadTasks - ready <- getReadyTasks - let total = length tasks +getTaskStats :: Maybe Text -> IO TaskStats +getTaskStats maybeEpicId = do + allTasks <- loadTasks + + targetTasks <- case maybeEpicId of + Nothing -> pure allTasks + Just epicId -> + case filter (\t -> taskId t == epicId) allTasks of + [] -> panic "Epic not found" + _ -> pure <| getAllDescendants allTasks epicId + + globalReady <- getReadyTasks + let readyIds = map taskId globalReady + -- Filter ready tasks to only include those in our target set + readyCount = length <| filter (\t -> taskId t `elem` readyIds) targetTasks + + tasks = targetTasks + total = length tasks open = length <| filter (\t -> taskStatus t == Open) tasks inProg = length <| filter (\t -> taskStatus t == InProgress) tasks review = length <| filter (\t -> taskStatus t == Review) tasks done = length <| filter (\t -> taskStatus t == Done) tasks epics = length <| filter (\t -> taskType t == Epic) tasks - readyCount = length ready - blockedCount = total - readyCount - done + readyCount' = readyCount + blockedCount = total - readyCount' - done -- Count tasks by priority byPriority = [ (P0, length <| filter (\t -> taskPriority t == P0) tasks), @@ -550,18 +563,26 @@ getTaskStats = do reviewTasks = review, doneTasks = done, totalEpics = epics, - readyTasks = readyCount, + readyTasks = readyCount', blockedTasks = blockedCount, tasksByPriority = byPriority, tasksByNamespace = byNamespace } +-- Helper to get all descendants of a task (recursive) +getAllDescendants :: [Task] -> Text -> [Task] +getAllDescendants allTasks parentId = + let children = filter (\t -> taskParent t == Just parentId) allTasks + in children ++ concatMap (\t -> getAllDescendants allTasks (taskId t)) children + -- Show task statistics (human-readable) -showTaskStats :: IO () -showTaskStats = do - stats <- getTaskStats +showTaskStats :: Maybe Text -> IO () +showTaskStats maybeEpicId = do + stats <- getTaskStats maybeEpicId putText "" - putText "Task Statistics" + case maybeEpicId of + Nothing -> putText "Task Statistics" + Just epicId -> putText <| "Task Statistics for Epic " <> epicId putText "" putText <| "Total tasks: " <> T.pack (show (totalTasks stats)) putText <| " Open: " <> T.pack (show (openTasks stats)) -- cgit v1.2.3 From c94e2230bd1cd807eb279202f9d6e40cc3c54b36 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 21 Nov 2025 02:59:16 -0500 Subject: feat: implement t-rWa5yilwM.1 --- Omni/Agent/Git.hs | 139 +++++++++++++++++++++++++++++++++++++++++++++ Omni/Bild.hs | 3 +- Omni/Bild/Deps/Haskell.nix | 1 + 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 Omni/Agent/Git.hs (limited to 'Omni') diff --git a/Omni/Agent/Git.hs b/Omni/Agent/Git.hs new file mode 100644 index 0000000..cbb63bd --- /dev/null +++ b/Omni/Agent/Git.hs @@ -0,0 +1,139 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NoImplicitPrelude #-} + +-- | Git operations for the agent. +-- +-- : out omni-agent-git +-- : dep temporary +module Omni.Agent.Git + ( checkout, + main, + test, + ) +where + +import Alpha +import qualified Data.Text as Text +import qualified Omni.Log as Log +import Omni.Test ((@=?)) +import qualified Omni.Test as Test +import qualified System.Directory as Directory +import qualified System.Exit as Exit +import qualified System.IO.Temp as Temp +import qualified System.Process as Process + +main :: IO () +main = Test.run test + +test :: Test.Tree +test = + Test.group + "Omni.Agent.Git" + [ Test.unit "checkout works" <| do + Temp.withSystemTempDirectory "omni-agent-git-test" <| \tmpDir -> do + let repo = tmpDir <> "/repo" + Directory.createDirectory repo + -- init repo + git repo ["init"] + git repo ["branch", "-m", "master"] + git repo ["config", "user.email", "you@example.com"] + git repo ["config", "user.name", "Your Name"] + + -- commit A + writeFile (repo <> "/a.txt") "A" + git repo ["add", "a.txt"] + git repo ["commit", "-m", "A"] + shaA <- getSha repo "HEAD" + + -- create branch dev + git repo ["checkout", "-b", "dev"] + + -- commit B + writeFile (repo <> "/b.txt") "B" + git repo ["add", "b.txt"] + git repo ["commit", "-m", "B"] + shaB <- getSha repo "HEAD" + + -- switch back to master + git repo ["checkout", "master"] + + -- Test 1: checkout dev + checkout repo "dev" + current <- getSha repo "HEAD" + shaB @=? current + + -- Test 2: checkout master + checkout repo "master" + current' <- getSha repo "HEAD" + shaA @=? current' + + -- Test 3: dirty state + writeFile (repo <> "/a.txt") "DIRTY" + checkout repo "dev" + current'' <- getSha repo "HEAD" + shaB @=? current'' + -- Verify dirty file is gone/overwritten (b.txt should exist, a.txt should be A from master? No, a.txt is in A and B) + -- Wait, in dev, a.txt is "A". + content <- readFile (repo <> "/a.txt") + "A" @=? content + + -- Test 4: untracked file + writeFile (repo <> "/untracked.txt") "DELETE ME" + checkout repo "master" + exists <- Directory.doesFileExist (repo <> "/untracked.txt") + False @=? exists + ] + +getSha :: FilePath -> String -> IO String +getSha dir ref = do + let cmd = (Process.proc "git" ["rev-parse", ref]) {Process.cwd = Just dir} + (code, out, _) <- Process.readCreateProcessWithExitCode cmd "" + case code of + Exit.ExitSuccess -> pure <| strip out + _ -> panic "getSha failed" + +-- | Checkout a specific ref (SHA, branch, tag) in the given repository path. +-- This function ensures the repository is in the correct state by: +-- 1. Fetching all updates +-- 2. Checking out the ref (forcing overwrites of local changes) +-- 3. Resetting hard to the ref (to ensure clean state) +-- 4. Cleaning untracked files +-- 5. Updating submodules +checkout :: FilePath -> Text -> IO () +checkout repoPath ref = do + let r = Text.unpack ref + + Log.info ["git", "checkout", ref, "in", Text.pack repoPath] + + -- Fetch all refs to ensure we have the target + git repoPath ["fetch", "--all", "--tags"] + + -- Checkout the ref, discarding local changes + git repoPath ["checkout", "--force", r] + + -- Reset hard to ensure we are exactly at the target state + git repoPath ["reset", "--hard", r] + + -- Remove untracked files and directories + git repoPath ["clean", "-fdx"] + + -- Update submodules + git repoPath ["submodule", "update", "--init", "--recursive"] + + Log.good ["git", "checkout", "complete"] + Log.br + +-- | Run a git command in the given directory. +git :: FilePath -> [String] -> IO () +git dir args = do + let cmd = (Process.proc "git" args) {Process.cwd = Just dir} + (exitCode, out, err) <- Process.readCreateProcessWithExitCode cmd "" + case exitCode of + Exit.ExitSuccess -> pure () + Exit.ExitFailure code -> do + Log.fail ["git command failed", Text.pack (show args), "code: " <> show code] + Log.info [Text.pack out] + Log.info [Text.pack err] + Log.br + panic <| "git command failed: git " <> show args diff --git a/Omni/Bild.hs b/Omni/Bild.hs index 53d96a5..ba54c93 100644 --- a/Omni/Bild.hs +++ b/Omni/Bild.hs @@ -653,7 +653,8 @@ analyzeAll isPlanMode nss = do contentLines |> Meta.detectAll "--" |> \Meta.Parsed {..} -> - detectHaskellImports mempty contentLines +> \(langdeps, srcs) -> do + detectHaskellImports mempty contentLines +> \(autoDeps, srcs) -> do + let langdeps = autoDeps <> pdep graph <- buildHsModuleGraph namespace quapath srcs pure <| Just diff --git a/Omni/Bild/Deps/Haskell.nix b/Omni/Bild/Deps/Haskell.nix index 5d6abbb..6930860 100644 --- a/Omni/Bild/Deps/Haskell.nix +++ b/Omni/Bild/Deps/Haskell.nix @@ -54,6 +54,7 @@ "tasty" "tasty-hunit" "tasty-quickcheck" + "temporary" "text" "time" "transformers" -- cgit v1.2.3 From dac383e812422ba8c3da87c734751d4414202b1f Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 21 Nov 2025 03:05:55 -0500 Subject: feat: implement t-rWa5yilwM.3 --- Omni/Agent/Log.hs | 65 ++++++++++++++++++++++++++++++++++++++++++++++ Omni/Agent/LogTest.hs | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 Omni/Agent/Log.hs create mode 100644 Omni/Agent/LogTest.hs (limited to 'Omni') diff --git a/Omni/Agent/Log.hs b/Omni/Agent/Log.hs new file mode 100644 index 0000000..33b9722 --- /dev/null +++ b/Omni/Agent/Log.hs @@ -0,0 +1,65 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NoImplicitPrelude #-} + +module Omni.Agent.Log + ( LogEntry (..), + parseLine, + format, + ) +where + +import Alpha +import Data.Aeson (FromJSON (..), (.:), (.:?)) +import qualified Data.Aeson as Aeson +import qualified Data.ByteString.Lazy as BSL + +data LogEntry = LogEntry + { leMessage :: Text, + leLevel :: Maybe Text, + leToolName :: Maybe Text, + leBatches :: Maybe [[Text]], + leMethod :: Maybe Text, + lePath :: Maybe Text + } + deriving (Show, Eq, Generic) + +instance FromJSON LogEntry where + parseJSON = Aeson.withObject "LogEntry" <| \v -> + LogEntry + <$> v .: "message" + <*> v .:? "level" + <*> v .:? "toolName" + <*> v .:? "batches" + <*> v .:? "method" + <*> v .:? "path" + +parseLine :: Text -> Maybe LogEntry +parseLine line = Aeson.decode <| BSL.fromStrict <| encodeUtf8 line + +format :: LogEntry -> Maybe Text +format e = + case leMessage e of + "executing 1 tools in 1 batch(es)" -> + let tool = case leBatches e of + Just ((t : _) : _) -> t + _ -> "unknown" + in Just <| "🤖 THOUGHT: Planning tool execution (" <> tool <> ")" + "Tool Bash permitted - action: allow" -> + Just "🔧 TOOL: Bash command executed" + msg + | "Processing tool completion for ledger" == msg && isJust (leToolName e) -> + Just <| "✅ TOOL: " <> fromMaybe "" (leToolName e) <> " completed" + "ide-fs" -> + case leMethod e of + Just "readFile" -> Just <| "📂 READ: " <> fromMaybe "" (lePath e) + _ -> Nothing + "System prompt build complete (no changes)" -> + Just "🧠 THINKING..." + "System prompt build complete (first build)" -> + Just "🚀 STARTING new task context" + msg -> + case leLevel e of + Just "error" -> Just <| "❌ ERROR: " <> msg + _ -> Nothing diff --git a/Omni/Agent/LogTest.hs b/Omni/Agent/LogTest.hs new file mode 100644 index 0000000..0d085b1 --- /dev/null +++ b/Omni/Agent/LogTest.hs @@ -0,0 +1,72 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NoImplicitPrelude #-} + +-- : out agent-log-test +module Omni.Agent.LogTest where + +import Alpha +import Omni.Agent.Log +import qualified Omni.Test as Test + +main :: IO () +main = Test.run tests + +tests :: Test.Tree +tests = + Test.group + "Omni.Agent.Log" + [ Test.unit "Parse LogEntry" testParse, + Test.unit "Format LogEntry" testFormat + ] + +testParse :: IO () +testParse = do + let json = "{\"message\": \"executing 1 tools in 1 batch(es)\", \"batches\": [[\"grep\"]]}" + let expected = + LogEntry + { leMessage = "executing 1 tools in 1 batch(es)", + leLevel = Nothing, + leToolName = Nothing, + leBatches = Just [["grep"]], + leMethod = Nothing, + lePath = Nothing + } + parseLine json @?= Just expected + +testFormat :: IO () +testFormat = do + let entry = + LogEntry + { leMessage = "executing 1 tools in 1 batch(es)", + leLevel = Nothing, + leToolName = Nothing, + leBatches = Just [["grep"]], + leMethod = Nothing, + lePath = Nothing + } + format entry @?= Just "🤖 THOUGHT: Planning tool execution (grep)" + + let entry2 = + LogEntry + { leMessage = "some random log", + leLevel = Nothing, + leToolName = Nothing, + leBatches = Nothing, + leMethod = Nothing, + lePath = Nothing + } + format entry2 @?= Nothing + + let entry3 = + LogEntry + { leMessage = "some error", + leLevel = Just "error", + leToolName = Nothing, + leBatches = Nothing, + leMethod = Nothing, + lePath = Nothing + } + format entry3 @?= Just "❌ ERROR: some error" + +(@?=) :: (Eq a, Show a) => a -> a -> IO () +(@?=) = (Test.@?=) -- cgit v1.2.3 From a8bf9a7342c040cb7bd4b839764bbfe80d97f91e Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 21 Nov 2025 03:59:23 -0500 Subject: fix: expose task progress command in CLI --- Omni/Task.hs | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) (limited to 'Omni') diff --git a/Omni/Task.hs b/Omni/Task.hs index 24e528b..19fc005 100644 --- a/Omni/Task.hs +++ b/Omni/Task.hs @@ -45,6 +45,7 @@ Usage: task update [--json] task deps [--json] task tree [] [--json] + task progress [--json] task stats [--json] task export [--flush] task import -i @@ -61,6 +62,7 @@ Commands: update Update task status deps Show dependency tree tree Show task tree (epics with children, or all epics if no ID given) + progress Show progress for an epic stats Show task statistics export Export and consolidate tasks to JSONL import Import tasks from JSONL file @@ -231,6 +233,13 @@ move args tree <- getTaskTree maybeId outputJson tree else showTaskTree maybeId + | args `Cli.has` Cli.command "progress" = do + tid <- getArgText args "id" + if isJsonMode args + then do + progress <- getTaskProgress tid + outputJson progress + else showTaskProgress tid | args `Cli.has` Cli.command "stats" = do if isJsonMode args then do @@ -336,20 +345,21 @@ unitTests = child1 <- createTask "Child 1" WorkTask (Just (taskId parent)) Nothing P2 [] -- Manually create a task with .3 suffix to simulate a gap (or deleted task) let child3Id = taskId parent <> ".3" - child3 = Task - { taskId = child3Id, - taskTitle = "Child 3", - taskType = WorkTask, - taskParent = Just (taskId parent), - taskNamespace = Nothing, - taskStatus = Open, - taskPriority = P2, - taskDependencies = [], - taskCreatedAt = taskCreatedAt child1, - taskUpdatedAt = taskUpdatedAt child1 - } + child3 = + Task + { taskId = child3Id, + taskTitle = "Child 3", + taskType = WorkTask, + taskParent = Just (taskId parent), + taskNamespace = Nothing, + taskStatus = Open, + taskPriority = P2, + taskDependencies = [], + taskCreatedAt = taskCreatedAt child1, + taskUpdatedAt = taskUpdatedAt child1 + } saveTask child3 - + -- Create a new child, it should get .4, not .2 child4 <- createTask "Child 4" WorkTask (Just (taskId parent)) Nothing P2 [] taskId child4 Test.@?= taskId parent <> ".4" -- cgit v1.2.3 From 04c9043b89be694d8e74cf9e11b8648488416aee Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 21 Nov 2025 04:25:10 -0500 Subject: fix: lint errors in Omni/Agent/Git.hs and Log.hs Amp-Thread-ID: https://ampcode.com/threads/T-7109f8d0-feb4-4a24-bc4b-37743227e2cb Co-authored-by: Amp --- Omni/Agent/Git.hs | 21 ++++++++++----------- Omni/Agent/Log.hs | 24 +++++++++++++++--------- 2 files changed, 25 insertions(+), 20 deletions(-) (limited to 'Omni') diff --git a/Omni/Agent/Git.hs b/Omni/Agent/Git.hs index cbb63bd..a7afb20 100644 --- a/Omni/Agent/Git.hs +++ b/Omni/Agent/Git.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE NoImplicitPrelude #-} @@ -39,7 +38,7 @@ test = git repo ["branch", "-m", "master"] git repo ["config", "user.email", "you@example.com"] git repo ["config", "user.name", "Your Name"] - + -- commit A writeFile (repo <> "/a.txt") "A" git repo ["add", "a.txt"] @@ -48,26 +47,26 @@ test = -- create branch dev git repo ["checkout", "-b", "dev"] - + -- commit B writeFile (repo <> "/b.txt") "B" git repo ["add", "b.txt"] git repo ["commit", "-m", "B"] shaB <- getSha repo "HEAD" - + -- switch back to master git repo ["checkout", "master"] - + -- Test 1: checkout dev checkout repo "dev" current <- getSha repo "HEAD" shaB @=? current - + -- Test 2: checkout master checkout repo "master" current' <- getSha repo "HEAD" shaA @=? current' - + -- Test 3: dirty state writeFile (repo <> "/a.txt") "DIRTY" checkout repo "dev" @@ -77,7 +76,7 @@ test = -- Wait, in dev, a.txt is "A". content <- readFile (repo <> "/a.txt") "A" @=? content - + -- Test 4: untracked file writeFile (repo <> "/untracked.txt") "DELETE ME" checkout repo "master" @@ -103,9 +102,9 @@ getSha dir ref = do checkout :: FilePath -> Text -> IO () checkout repoPath ref = do let r = Text.unpack ref - + Log.info ["git", "checkout", ref, "in", Text.pack repoPath] - + -- Fetch all refs to ensure we have the target git repoPath ["fetch", "--all", "--tags"] @@ -120,7 +119,7 @@ checkout repoPath ref = do -- Update submodules git repoPath ["submodule", "update", "--init", "--recursive"] - + Log.good ["git", "checkout", "complete"] Log.br diff --git a/Omni/Agent/Log.hs b/Omni/Agent/Log.hs index 33b9722..c93479b 100644 --- a/Omni/Agent/Log.hs +++ b/Omni/Agent/Log.hs @@ -1,5 +1,4 @@ {-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE NoImplicitPrelude #-} @@ -26,14 +25,21 @@ data LogEntry = LogEntry deriving (Show, Eq, Generic) instance FromJSON LogEntry where - parseJSON = Aeson.withObject "LogEntry" <| \v -> - LogEntry - <$> v .: "message" - <*> v .:? "level" - <*> v .:? "toolName" - <*> v .:? "batches" - <*> v .:? "method" - <*> v .:? "path" + parseJSON = + Aeson.withObject "LogEntry" <| \v -> + ( LogEntry + v + .:? "level" + <*> v + .:? "toolName" + <*> v + .:? "batches" + <*> v + .:? "method" + <*> v + .:? "path" parseLine :: Text -> Maybe LogEntry parseLine line = Aeson.decode <| BSL.fromStrict <| encodeUtf8 line -- cgit v1.2.3 From f99aee1aa31f621a804e0c98ed4f84260da1b3eb Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 21 Nov 2025 04:30:47 -0500 Subject: infra: improve worker workflow (attribution, cleaner history) --- Omni/Agent/WORKER_AGENT_GUIDE.md | 61 +++++++++++++++++++++------------------- Omni/Agent/harvest-tasks.sh | 9 +++++- Omni/Agent/setup-worker.sh | 7 +++++ 3 files changed, 47 insertions(+), 30 deletions(-) (limited to 'Omni') diff --git a/Omni/Agent/WORKER_AGENT_GUIDE.md b/Omni/Agent/WORKER_AGENT_GUIDE.md index af81bb0..5bae08f 100644 --- a/Omni/Agent/WORKER_AGENT_GUIDE.md +++ b/Omni/Agent/WORKER_AGENT_GUIDE.md @@ -55,13 +55,10 @@ task update t-123 in-progress 2. **Check for Unmerged Work**: Look for dependencies that have existing branches (e.g., `task/t-parent-id`) which are NOT yet merged into `live`. 3. **Select Base**: * 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`). + * Otherwise, start from fresh live code: `git fetch origin live && git checkout -b task/t-123 origin/live`. -4. **Create/Checkout Feature Branch**: - ```bash - # Try to switch to existing branch, otherwise create new one - git checkout task/t-123 || git checkout -b task/t-123 - ``` +4. **Implement**: + (Proceed to implementation) ### Step 4: Implement @@ -70,29 +67,35 @@ task update t-123 in-progress 3. **Run Tests**: `bild --test Omni/YourNamespace.hs` ### Step 5: Submit for Review - -1. **Commit Implementation**: - ```bash - 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. - - ```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 - ``` + + 1. **Update Status and Commit**: + Bundle the task status update with your implementation to keep history clean. + + ```bash + # 1. Mark task for review (updates .tasks/tasks.jsonl) + task update t-123 review + + # 2. Commit changes + task update + git add . + git commit -m "feat: implement t-123" + ``` + + 2. **Signal Review Readiness**: + Update the worker branch to signal the planner. + + ```bash + # Switch to base branch + git checkout omni-worker-1 + + # Sync to get latest state + ./Omni/Agent/sync-tasks.sh + + # Ensure the task is marked review here too (for harvest visibility) + 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`.* diff --git a/Omni/Agent/harvest-tasks.sh b/Omni/Agent/harvest-tasks.sh index 282beab..44c2322 100755 --- a/Omni/Agent/harvest-tasks.sh +++ b/Omni/Agent/harvest-tasks.sh @@ -45,7 +45,14 @@ if [ "$UPDATED" -eq 1 ]; then # 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" + + 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." diff --git a/Omni/Agent/setup-worker.sh b/Omni/Agent/setup-worker.sh index 28c29b1..42b7fc9 100755 --- a/Omni/Agent/setup-worker.sh +++ b/Omni/Agent/setup-worker.sh @@ -22,3 +22,10 @@ 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" -- cgit v1.2.3