summaryrefslogtreecommitdiff
path: root/Omni/Jr.hs
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-11-30 22:16:00 -0500
committerBen Sima <ben@bensima.com>2025-11-30 22:16:00 -0500
commit2a39efab63ed672ba54bc0adae05922ea1222edc (patch)
tree9abf63637561cb9cc41afa77161df58e44f263d7 /Omni/Jr.hs
parent725b98000aed836ac5808b3afbda4ce869956156 (diff)
Generate summary comment when epic children complete
The task **t-193.2: Generate summary comment when epic children complete 1. ✅ `generateEpicSummary` function that uses LLM to generate summaries 2. ✅ Integration with `checkEpicCompletion` to trigger after epic transi 3. ✅ Prompt construction with epic info and child task details 4. ✅ Comment addition via `TaskCore.addComment` 5. ✅ Error handling for missing API keys and LLM failures 1. ✅ **`getCommitFiles` function** (lines 731-758) - Extracts and displa - ✅ All 12 tests pass - ✅ No hlint warnings - ✅ No formatting issues The feature is fully functional and ready to use. When all children of a 1. Transition the epic to Review status 2. Generate an AI summary using Claude Sonnet 4.5 3. Add that summary as a comment on the epic task 4. Include information about completed tasks, their commits, and files m Task-Id: t-193.2
Diffstat (limited to 'Omni/Jr.hs')
-rwxr-xr-xOmni/Jr.hs111
1 files changed, 111 insertions, 0 deletions
diff --git a/Omni/Jr.hs b/Omni/Jr.hs
index f45ed2f..714c5fb 100755
--- a/Omni/Jr.hs
+++ b/Omni/Jr.hs
@@ -17,6 +17,7 @@ import qualified Data.ByteString.Lazy.Char8 as BLC
import qualified Data.List as List
import qualified Data.Text as Text
import qualified Omni.Agent.Core as AgentCore
+import qualified Omni.Agent.Engine as Engine
import qualified Omni.Agent.Worker as AgentWorker
import qualified Omni.Cli as Cli
import qualified Omni.Fact as Fact
@@ -27,6 +28,7 @@ import qualified Omni.Test as Test
import qualified System.Console.Docopt as Docopt
import qualified System.Directory as Directory
import System.Environment (withArgs)
+import qualified System.Environment as Env
import qualified System.Exit as Exit
import System.FilePath (takeFileName)
import qualified System.IO as IO
@@ -648,6 +650,113 @@ parseFiles raw
then []
else map Text.strip (Text.splitOn "," inner)
+-- | Generate a summary comment for an epic when all children are complete
+generateEpicSummary :: Text -> TaskCore.Task -> [TaskCore.Task] -> IO ()
+generateEpicSummary epicId epic children = do
+ putText "[epic] Generating summary for completed epic..."
+
+ -- Try to get API key
+ maybeApiKey <- Env.lookupEnv "OPENROUTER_API_KEY"
+ case maybeApiKey of
+ Nothing -> do
+ putText "[epic] Warning: OPENROUTER_API_KEY not set, skipping summary generation"
+ pure ()
+ Just apiKey -> do
+ -- Build the prompt for LLM
+ prompt <- buildEpicSummaryPrompt epic children
+
+ -- Call LLM
+ let llm = Engine.defaultLLM {Engine.llmApiKey = Text.pack apiKey}
+ messages = [Engine.Message Engine.User prompt Nothing Nothing]
+
+ result <- Engine.chat llm [] messages
+ case result of
+ Left err -> do
+ putText ("[epic] Failed to generate summary: " <> err)
+ Right msg -> do
+ let summary = Engine.msgContent msg
+ _ <- TaskCore.addComment epicId summary
+ putText "[epic] Summary comment added to epic"
+
+-- | Build a prompt for the LLM to summarize an epic
+buildEpicSummaryPrompt :: TaskCore.Task -> [TaskCore.Task] -> IO Text
+buildEpicSummaryPrompt epic children = do
+ -- Get commit info for each child task
+ childSummaries <- traverse summarizeChildTask children
+
+ pure
+ <| Text.unlines
+ [ "Generate a concise summary comment for this completed epic.",
+ "",
+ "## Epic Information",
+ "**Title:** " <> TaskCore.taskTitle epic,
+ "**Description:**",
+ TaskCore.taskDescription epic,
+ "",
+ "## Completed Child Tasks (" <> tshow (length children) <> ")",
+ Text.unlines childSummaries,
+ "",
+ "## Instructions",
+ "Create a markdown summary that includes:",
+ "1. A brief overview of what was accomplished",
+ "2. List of completed tasks with their titles",
+ "3. Key changes or files modified (if mentioned in task descriptions)",
+ "4. Any notable patterns or themes across the work",
+ "",
+ "Format the summary as a markdown comment starting with '## Epic Summary'.",
+ "Keep it concise but informative."
+ ]
+
+-- | Summarize a single child task for the epic summary
+summarizeChildTask :: TaskCore.Task -> IO Text
+summarizeChildTask task = do
+ -- Try to get commit info
+ let grepArg = "--grep=" <> Text.unpack (TaskCore.taskId task)
+ (code, shaOut, _) <-
+ Process.readProcessWithExitCode
+ "git"
+ ["log", "--pretty=format:%h %s", "-n", "1", grepArg]
+ ""
+
+ let commitInfo =
+ if code == Exit.ExitSuccess && not (null shaOut)
+ then " [" <> Text.pack (take 80 shaOut) <> "]"
+ else ""
+
+ -- Get files changed in the commit
+ filesInfo <- getCommitFiles (TaskCore.taskId task)
+
+ pure <| "- **" <> TaskCore.taskId task <> "**: " <> TaskCore.taskTitle task <> commitInfo <> filesInfo
+
+-- | Get files modified in a commit for a task
+getCommitFiles :: Text -> IO Text
+getCommitFiles taskId = do
+ let grepArg = "--grep=" <> Text.unpack taskId
+ (code, shaOut, _) <-
+ Process.readProcessWithExitCode
+ "git"
+ ["log", "--pretty=format:%H", "-n", "1", grepArg]
+ ""
+
+ if code /= Exit.ExitSuccess || null shaOut
+ then pure ""
+ else do
+ let sha = List.head (List.lines shaOut)
+ (fileCode, filesOut, _) <-
+ Process.readProcessWithExitCode
+ "git"
+ ["diff-tree", "--no-commit-id", "--name-only", "-r", sha]
+ ""
+ if fileCode /= Exit.ExitSuccess || null filesOut
+ then pure ""
+ else do
+ let files = List.lines filesOut
+ fileList = List.take 3 files -- Limit to first 3 files
+ moreCount = length files - 3
+ filesText = Text.intercalate ", " (map Text.pack fileList)
+ suffix = if moreCount > 0 then " (+" <> tshow moreCount <> " more)" else ""
+ pure <| if null files then "" else " — " <> filesText <> suffix
+
-- | Check if all children of an epic are Done, and if so, transition epic to Review
checkEpicCompletion :: TaskCore.Task -> IO ()
checkEpicCompletion task =
@@ -665,6 +774,8 @@ checkEpicCompletion task =
putText ("[review] All children of epic " <> parentId <> " are Done.")
TaskCore.updateTaskStatus parentId TaskCore.Review []
putText ("[review] Epic " <> parentId <> " -> Review")
+ -- Generate summary comment for the epic
+ generateEpicSummary parentId parentTask children
where
hasParent pid t = maybe False (TaskCore.matchesId pid) (TaskCore.taskParent t)