diff options
| author | Ben Sima <ben@bensima.com> | 2025-11-30 22:16:00 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-11-30 22:16:00 -0500 |
| commit | 2a39efab63ed672ba54bc0adae05922ea1222edc (patch) | |
| tree | 9abf63637561cb9cc41afa77161df58e44f263d7 /Omni/Jr.hs | |
| parent | 725b98000aed836ac5808b3afbda4ce869956156 (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-x | Omni/Jr.hs | 111 |
1 files changed, 111 insertions, 0 deletions
@@ -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) |
