summaryrefslogtreecommitdiff
path: root/Omni/Jr.hs
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-11-30 22:03:54 -0500
committerBen Sima <ben@bensima.com>2025-11-30 22:03:54 -0500
commit725b98000aed836ac5808b3afbda4ce869956156 (patch)
treeb81d63d3a76261dc73457c88560bb8cc4fa3f13c /Omni/Jr.hs
parent9fa7697cd979eaa15a2479819463c3bdd86cc99a (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-xOmni/Jr.hs69
1 files changed, 69 insertions, 0 deletions
diff --git a/Omni/Jr.hs b/Omni/Jr.hs
index 0690970..f45ed2f 100755
--- a/Omni/Jr.hs
+++ b/Omni/Jr.hs
@@ -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 =