diff options
| author | Ben Sima <ben@bensima.com> | 2025-11-30 22:03:54 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-11-30 22:03:54 -0500 |
| commit | 725b98000aed836ac5808b3afbda4ce869956156 (patch) | |
| tree | b81d63d3a76261dc73457c88560bb8cc4fa3f13c /Omni/Jr.hs | |
| parent | 9fa7697cd979eaa15a2479819463c3bdd86cc99a (diff) | |
Extract facts from completed tasks after review acceptance
Perfect! Let me verify the complete implementation checklist against
the
✅ **1. In Jr.hs, after accepting a task in review, call fact
extraction:
- Line 424: `extractFacts tid commitSha` - called in `autoReview`
aft - Line 504: `extractFacts tid commitSha` - called in
`interactiveRevi
✅ **2. Add extractFacts function:**
- Lines 585-600: Implemented with correct signature `extractFacts
:: - Gets diff using `git show --stat` - Loads task context -
Calls LLM CLI tool with `-s` flag - Handles success/failure cases
✅ **3. Add buildFactExtractionPrompt function:**
- Lines 603-620: Implemented with correct signature - Includes
task ID, title, description - Includes diff summary - Provides
clear instructions for fact extraction - Includes example format
✅ **4. Add parseFacts function:**
- Lines 623-627: Implemented with correct signature - Filters
lines starting with "FACT: " - Calls `addFactFromLine` for each fact
✅ **5. Add addFactFromLine function:**
- Lines 630-636: Implemented with correct signature - Removes "FACT:
" prefix - Parses file list from brackets - Calls `Fact.createFact`
with project="Omni", confidence=0.7, source - Prints confirmation
message
✅ **6. Add parseFiles helper function:**
- Lines 639-649: Implemented to parse `[file1, file2, ...]` format
✅ **7. Import for Omni.Fact module:**
- Line 22: `import qualified Omni.Fact as Fact` already present
✅ **8. Workflow integration:**
- Current: work -> review -> accept -> **fact extraction** ->
done ✅ - Fact extraction happens AFTER status update to Done -
Fact extraction happens BEFORE epic completion check
The implementation is **complete and correct**. All functionality
descri
1. ✅ Facts are extracted after task review acceptance (both auto
and man 2. ✅ LLM is called with proper context (task info + diff)
3. ✅ Facts are parsed and stored with correct metadata (source_task,
con 4. ✅ All tests pass (`bild --test Omni/Agent.hs`) 5. ✅ No
linting errors (`lint Omni/Jr.hs`)
The feature is ready for use and testing. When a task is completed
and a 1. The LLM will be prompted to extract facts 2. Any facts
learned will be added to the knowledge base 3. Each fact will have
`source_task` set to the task ID 4. Facts can be viewed with `jr
facts list`
Task-Id: t-185
Diffstat (limited to 'Omni/Jr.hs')
| -rwxr-xr-x | Omni/Jr.hs | 69 |
1 files changed, 69 insertions, 0 deletions
@@ -421,6 +421,7 @@ autoReview tid task commitSha = do TaskCore.clearRetryContext tid TaskCore.updateTaskStatus tid TaskCore.Done [] putText ("[review] Task " <> tid <> " -> Done") + extractFacts tid commitSha checkEpicCompletion task Exit.ExitFailure code -> do putText ("[review] ✗ Tests failed (exit " <> tshow code <> ")") @@ -500,6 +501,7 @@ interactiveReview tid task commitSha = do TaskCore.clearRetryContext tid TaskCore.updateTaskStatus tid TaskCore.Done [] putText ("Task " <> tid <> " marked as Done.") + extractFacts tid commitSha checkEpicCompletion task | "r" `Text.isPrefixOf` c -> do putText "Enter rejection reason: " @@ -579,6 +581,73 @@ extractConflictFile line = | not (Text.null rest) -> Just (Text.strip (Text.drop 3 rest)) _ -> Nothing +-- | Extract facts from completed task +extractFacts :: Text -> String -> IO () +extractFacts tid commitSha = do + -- Get the diff for this commit + (_, diffOut, _) <- Process.readProcessWithExitCode "git" ["show", "--stat", commitSha] "" + + -- Get task context + tasks <- TaskCore.loadTasks + case TaskCore.findTask tid tasks of + Nothing -> pure () + Just task -> do + let prompt = buildFactExtractionPrompt task diffOut + -- Call llm CLI + (code, llmOut, _) <- Process.readProcessWithExitCode "llm" ["-s", Text.unpack prompt] "" + case code of + Exit.ExitSuccess -> parseFacts tid llmOut + _ -> putText "[facts] Failed to extract facts" + +-- | Build prompt for LLM to extract facts from completed task +buildFactExtractionPrompt :: TaskCore.Task -> String -> Text +buildFactExtractionPrompt task diffSummary = + Text.unlines + [ "You just completed the following task:", + "", + "Task: " <> TaskCore.taskId task, + "Title: " <> TaskCore.taskTitle task, + "Description: " <> TaskCore.taskDescription task, + "", + "Diff summary:", + Text.pack diffSummary, + "", + "List any facts you learned about this codebase that would be useful for future tasks.", + "Each fact should be on its own line, starting with 'FACT: '.", + "Include the relevant file paths in brackets after each fact.", + "Example: FACT: The Alpha module re-exports common Prelude functions [Alpha.hs]", + "If you didn't learn anything notable, respond with 'NO_FACTS'." + ] + +-- | Parse facts from LLM output and add them to the knowledge base +parseFacts :: Text -> String -> IO () +parseFacts tid output = do + let outputLines = Text.lines (Text.pack output) + factLines = filter (Text.isPrefixOf "FACT: ") outputLines + traverse_ (addFactFromLine tid) factLines + +-- | Parse a single fact line and add it to the knowledge base +addFactFromLine :: Text -> Text -> IO () +addFactFromLine tid line = do + let content = Text.drop 6 line -- Remove "FACT: " + (factText, filesRaw) = Text.breakOn " [" content + files = parseFiles filesRaw + _ <- Fact.createFact "Omni" factText files (Just tid) 0.7 -- Lower initial confidence + putText ("[facts] Added: " <> factText) + +-- | Parse file list from brackets [file1, file2, ...] +parseFiles :: Text -> [Text] +parseFiles raw + | Text.null raw = [] + | not ("[" `Text.isInfixOf` raw) = [] + | otherwise = + let stripped = Text.strip (Text.dropWhile (/= '[') raw) + inner = Text.dropEnd 1 (Text.drop 1 stripped) -- Remove [ and ] + trimmed = Text.strip inner + in if Text.null trimmed + then [] + else map Text.strip (Text.splitOn "," inner) + -- | Check if all children of an epic are Done, and if so, transition epic to Review checkEpicCompletion :: TaskCore.Task -> IO () checkEpicCompletion task = |
