From 162aadc953f9133127025e55d081ee882aef25ca Mon Sep 17 00:00:00 2001 From: spatten Date: Thu, 6 Nov 2025 16:19:30 -0800 Subject: [PATCH 01/12] parse ficus summary data --- src/App/Fossa/Ficus/Analyze.hs | 53 +++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/src/App/Fossa/Ficus/Analyze.hs b/src/App/Fossa/Ficus/Analyze.hs index 41f0a50c8..081815ee7 100644 --- a/src/App/Fossa/Ficus/Analyze.hs +++ b/src/App/Fossa/Ficus/Analyze.hs @@ -29,8 +29,7 @@ import Control.Carrier.Diagnostics (Diagnostics) import Control.Concurrent.Async (async, wait) import Control.Effect.Lift (Has, Lift, sendIO) import Control.Monad (when) -import Data.Aeson (Object, decode, decodeStrictText, (.:)) -import Data.Aeson.Types (parseMaybe) +import Data.Aeson (FromJSON (parseJSON), decode, decodeStrictText, withObject, (.:)) import Data.ByteString.Lazy qualified as BL import Data.Conduit ((.|)) import Data.Conduit qualified as Conduit @@ -127,13 +126,47 @@ analyzeWithFicusMain rootDir apiOpts revision filters snippetScanRetentionDays = , ficusConfigSnippetScanRetentionDays = snippetScanRetentionDays } -findingToAnalysisId :: FicusFinding -> Maybe Int -findingToAnalysisId (FicusFinding (FicusMessageData strategy payload)) +data FicusScanStats = FicusScanStats + { skippedFiles :: Int + , processedFiles :: Int + , newFiles :: Int + , existingFiles :: Int + , matchedFiles :: Int + , unmatchedFiles :: Int + , processingTimeSeconds :: Double + } + deriving (Show, Eq) + +instance FromJSON FicusScanStats where + parseJSON = withObject "FicusScanStats" $ \obj -> + FicusScanStats + <$> obj .: "skipped_files" + <*> obj .: "processed_files" + <*> obj .: "new_files" + <*> obj .: "existing_files" + <*> obj .: "matched_files" + <*> obj .: "unmatched_files" + <*> obj .: "processing_time_seconds" + +data FicusAnalysis = FicusAnalysisResponse + { analysisId :: Int + , bucketId :: Int + , stats :: FicusScanStats + } + deriving (Show, Eq) + +instance FromJSON FicusAnalysis where + parseJSON = withObject "FicusAnalysisResponse" $ \obj -> + FicusAnalysisResponse + <$> obj .: "analysis_id" + <*> obj .: "bucket_id" + <*> obj .: "stats" + +findingToFicusAnalysis :: FicusFinding -> Maybe FicusAnalysis +findingToFicusAnalysis (FicusFinding (FicusMessageData strategy payload)) | Text.toLower strategy == "fingerprint" = - case decode (BL.fromStrict $ Text.Encoding.encodeUtf8 payload) :: Maybe Object of - Just obj -> parseMaybe (.: "analysis_id") obj - Nothing -> Nothing -findingToAnalysisId _ = Nothing + decode (BL.fromStrict $ Text.Encoding.encodeUtf8 payload) +findingToFicusAnalysis _ = Nothing runFicus :: ( Has Diagnostics sig m @@ -207,10 +240,10 @@ runFicus ficusConfig = do pure acc FicusMessageFinding finding -> do putStrLn $ "[" ++ timestamp ++ "] FINDING " <> toString (displayFicusFinding finding) - let analysisFinding = FicusSnippetScanResults <$> findingToAnalysisId finding + let analysisFinding = (FicusSnippetScanResults . analysisId) <$> findingToFicusAnalysis finding when (isJust acc && isJust analysisFinding) $ putStrLn $ - "[" ++ timestamp ++ "] ERROR " <> "Found multiple analysis ids." + "[" ++ timestamp ++ "] ERROR " <> "Found multiple ficus analysis responses." pure $ acc <|> analysisFinding ) Nothing From 675a7a1d52b7ebfe55604852c5f86e604f73047e Mon Sep 17 00:00:00 2001 From: spatten Date: Thu, 6 Nov 2025 16:39:50 -0800 Subject: [PATCH 02/12] move types into Type.hs --- src/App/Fossa/Ficus/Analyze.hs | 62 +++++++++++----------------------- src/App/Fossa/Ficus/Types.hs | 40 ++++++++++++++++++++-- 2 files changed, 57 insertions(+), 45 deletions(-) diff --git a/src/App/Fossa/Ficus/Analyze.hs b/src/App/Fossa/Ficus/Analyze.hs index 081815ee7..d266d52ba 100644 --- a/src/App/Fossa/Ficus/Analyze.hs +++ b/src/App/Fossa/Ficus/Analyze.hs @@ -21,6 +21,7 @@ import App.Fossa.Ficus.Types ( FicusMessageData (..), FicusMessages (..), FicusPerStrategyFlag (..), + FicusScanStats (..), FicusSnippetScanResults (..), ) import App.Types (ProjectRevision (..)) @@ -29,7 +30,7 @@ import Control.Carrier.Diagnostics (Diagnostics) import Control.Concurrent.Async (async, wait) import Control.Effect.Lift (Has, Lift, sendIO) import Control.Monad (when) -import Data.Aeson (FromJSON (parseJSON), decode, decodeStrictText, withObject, (.:)) +import Data.Aeson (decode, decodeStrictText) import Data.ByteString.Lazy qualified as BL import Data.Conduit ((.|)) import Data.Conduit qualified as Conduit @@ -111,7 +112,7 @@ analyzeWithFicusMain rootDir apiOpts revision filters snippetScanRetentionDays = logDebugWithTime "runFicus completed, processing results..." case ficusResults of Just results -> - logInfo $ "Ficus analysis completed successfully with analysis ID: " <> pretty (ficusSnippetScanResultsAnalysisId results) + logInfo $ pretty (formatFicusScanSummary results) Nothing -> logInfo "Ficus analysis completed but no fingerprint findings were found" pure ficusResults where @@ -126,47 +127,24 @@ analyzeWithFicusMain rootDir apiOpts revision filters snippetScanRetentionDays = , ficusConfigSnippetScanRetentionDays = snippetScanRetentionDays } -data FicusScanStats = FicusScanStats - { skippedFiles :: Int - , processedFiles :: Int - , newFiles :: Int - , existingFiles :: Int - , matchedFiles :: Int - , unmatchedFiles :: Int - , processingTimeSeconds :: Double - } - deriving (Show, Eq) - -instance FromJSON FicusScanStats where - parseJSON = withObject "FicusScanStats" $ \obj -> - FicusScanStats - <$> obj .: "skipped_files" - <*> obj .: "processed_files" - <*> obj .: "new_files" - <*> obj .: "existing_files" - <*> obj .: "matched_files" - <*> obj .: "unmatched_files" - <*> obj .: "processing_time_seconds" - -data FicusAnalysis = FicusAnalysisResponse - { analysisId :: Int - , bucketId :: Int - , stats :: FicusScanStats - } - deriving (Show, Eq) - -instance FromJSON FicusAnalysis where - parseJSON = withObject "FicusAnalysisResponse" $ \obj -> - FicusAnalysisResponse - <$> obj .: "analysis_id" - <*> obj .: "bucket_id" - <*> obj .: "stats" - -findingToFicusAnalysis :: FicusFinding -> Maybe FicusAnalysis -findingToFicusAnalysis (FicusFinding (FicusMessageData strategy payload)) +findingToSnippetScanResult :: FicusFinding -> Maybe FicusSnippetScanResults +findingToSnippetScanResult (FicusFinding (FicusMessageData strategy payload)) | Text.toLower strategy == "fingerprint" = decode (BL.fromStrict $ Text.Encoding.encodeUtf8 payload) -findingToFicusAnalysis _ = Nothing +findingToSnippetScanResult _ = Nothing + +formatFicusScanSummary :: FicusSnippetScanResults -> Text +formatFicusScanSummary results = + let stats = ficusSnippetScanResultsStats results + aid = ficusSnippetScanResultsAnalysisId results + in Text.unlines + [ "Ficus snippet scan completed successfully!" + , " Analysis ID: " <> Text.pack (show aid) + , " Files processed: " <> Text.pack (show $ ficusStatsProcessedFiles stats) + , " Files matched: " <> Text.pack (show $ ficusStatsMatchedFiles stats) + , " Files skipped: " <> Text.pack (show $ ficusStatsSkippedFiles stats) + , " Processing time: " <> Text.pack (show $ ficusStatsProcessingTimeSeconds stats) <> "s" + ] runFicus :: ( Has Diagnostics sig m @@ -240,7 +218,7 @@ runFicus ficusConfig = do pure acc FicusMessageFinding finding -> do putStrLn $ "[" ++ timestamp ++ "] FINDING " <> toString (displayFicusFinding finding) - let analysisFinding = (FicusSnippetScanResults . analysisId) <$> findingToFicusAnalysis finding + let analysisFinding = findingToSnippetScanResult finding when (isJust acc && isJust analysisFinding) $ putStrLn $ "[" ++ timestamp ++ "] ERROR " <> "Found multiple ficus analysis responses." diff --git a/src/App/Fossa/Ficus/Types.hs b/src/App/Fossa/Ficus/Types.hs index 2250b665d..04cfa7cfa 100644 --- a/src/App/Fossa/Ficus/Types.hs +++ b/src/App/Fossa/Ficus/Types.hs @@ -13,12 +13,12 @@ module App.Fossa.Ficus.Types ( FicusHashFlag (..), FicusSnippetScanFlag, FicusSnippetScanResults (..), + FicusScanStats (..), FicusPerStrategyFlag (..), ) where import App.Types (ProjectRevision) -import Data.Aeson (FromJSON, Value (Object), withText) -import Data.Aeson qualified as A +import Data.Aeson (FromJSON (parseJSON), Value (Object), withObject, withText) import Data.Aeson.Types (Parser, (.:)) import Data.Text (Text) import Fossa.API.Types @@ -27,7 +27,41 @@ import Path (Abs, Dir, Path) import Text.URI import Types (GlobFilter) -newtype FicusSnippetScanResults = FicusSnippetScanResults {ficusSnippetScanResultsAnalysisId :: Int} deriving (Eq, Ord, Show, Generic) +data FicusSnippetScanResults = FicusSnippetScanResults + { ficusSnippetScanResultsAnalysisId :: Int + , ficusSnippetScanResultsBucketId :: Int + , ficusSnippetScanResultsStats :: FicusScanStats + } + deriving (Eq, Ord, Show, Generic) + +instance FromJSON FicusSnippetScanResults where + parseJSON = withObject "FicusSnippetScanResults" $ \obj -> + FicusSnippetScanResults + <$> obj .: "analysis_id" + <*> obj .: "bucket_id" + <*> obj .: "stats" + +data FicusScanStats = FicusScanStats + { ficusStatsSkippedFiles :: Int + , ficusStatsProcessedFiles :: Int + , ficusStatsNewFiles :: Int + , ficusStatsExistingFiles :: Int + , ficusStatsMatchedFiles :: Int + , ficusStatsUnmatchedFiles :: Int + , ficusStatsProcessingTimeSeconds :: Double + } + deriving (Eq, Ord, Show, Generic) + +instance FromJSON FicusScanStats where + parseJSON = withObject "FicusScanStats" $ \obj -> + FicusScanStats + <$> obj .: "skipped_files" + <*> obj .: "processed_files" + <*> obj .: "new_files" + <*> obj .: "existing_files" + <*> obj .: "matched_files" + <*> obj .: "unmatched_files" + <*> obj .: "processing_time_seconds" data FicusMessages = FicusMessages { ficusMessageDebugs :: [FicusDebug] From ff8d6edfa5f16e8b36ca1cda08d6c8b9354cfb76 Mon Sep 17 00:00:00 2001 From: spatten Date: Fri, 7 Nov 2025 09:57:02 -0800 Subject: [PATCH 03/12] clean up the output --- src/App/Fossa/Ficus/Analyze.hs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/App/Fossa/Ficus/Analyze.hs b/src/App/Fossa/Ficus/Analyze.hs index d266d52ba..bdee5c501 100644 --- a/src/App/Fossa/Ficus/Analyze.hs +++ b/src/App/Fossa/Ficus/Analyze.hs @@ -139,11 +139,18 @@ formatFicusScanSummary results = aid = ficusSnippetScanResultsAnalysisId results in Text.unlines [ "Ficus snippet scan completed successfully!" + , "=================================================== " + , "Snippet scan summary:" , " Analysis ID: " <> Text.pack (show aid) + , " Bucket ID: " <> Text.pack (show $ ficusSnippetScanResultsBucketId results) , " Files processed: " <> Text.pack (show $ ficusStatsProcessedFiles stats) - , " Files matched: " <> Text.pack (show $ ficusStatsMatchedFiles stats) , " Files skipped: " <> Text.pack (show $ ficusStatsSkippedFiles stats) + , " Files with matches found: " <> Text.pack (show $ ficusStatsMatchedFiles stats) + , " Files with no matches found: " <> Text.pack (show $ ficusStatsUnmatchedFiles stats) + , " Files already in our knowledgebase: " <> Text.pack (show $ ficusStatsExistingFiles stats) + , " Files new to our knowledgebase: " <> Text.pack (show $ ficusStatsNewFiles stats) , " Processing time: " <> Text.pack (show $ ficusStatsProcessingTimeSeconds stats) <> "s" + , "=================================================== " ] runFicus :: From 27a2b3cbf6375dce287bb07b5d3bf9cb9a97be09 Mon Sep 17 00:00:00 2001 From: spatten Date: Fri, 7 Nov 2025 09:59:55 -0800 Subject: [PATCH 04/12] fix a test --- integration-test/Analysis/FicusSpec.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-test/Analysis/FicusSpec.hs b/integration-test/Analysis/FicusSpec.hs index 55d177a3f..1699ee8c1 100644 --- a/integration-test/Analysis/FicusSpec.hs +++ b/integration-test/Analysis/FicusSpec.hs @@ -56,8 +56,8 @@ spec = do case result of Success _warnings analysisResult -> do case analysisResult of - Just (FicusSnippetScanResults analysisId) -> do - analysisId `shouldSatisfy` (> 0) + Just results -> do + ficusSnippetScanResultsAnalysisId results `shouldSatisfy` (> 0) Nothing -> do -- No snippet scan results returned - this is acceptable for integration testing True `shouldBe` True From 4bf0efe88bd222e0ac10416fc31e72849350101c Mon Sep 17 00:00:00 2001 From: spatten Date: Fri, 7 Nov 2025 16:23:47 -0800 Subject: [PATCH 05/12] update stats struct --- src/App/Fossa/Ficus/Analyze.hs | 15 ++++++++------- src/App/Fossa/Ficus/Types.hs | 18 ++++++++++-------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/App/Fossa/Ficus/Analyze.hs b/src/App/Fossa/Ficus/Analyze.hs index bdee5c501..36728d487 100644 --- a/src/App/Fossa/Ficus/Analyze.hs +++ b/src/App/Fossa/Ficus/Analyze.hs @@ -139,18 +139,19 @@ formatFicusScanSummary results = aid = ficusSnippetScanResultsAnalysisId results in Text.unlines [ "Ficus snippet scan completed successfully!" - , "=================================================== " + , "============================================================" , "Snippet scan summary:" , " Analysis ID: " <> Text.pack (show aid) , " Bucket ID: " <> Text.pack (show $ ficusSnippetScanResultsBucketId results) - , " Files processed: " <> Text.pack (show $ ficusStatsProcessedFiles stats) , " Files skipped: " <> Text.pack (show $ ficusStatsSkippedFiles stats) - , " Files with matches found: " <> Text.pack (show $ ficusStatsMatchedFiles stats) - , " Files with no matches found: " <> Text.pack (show $ ficusStatsUnmatchedFiles stats) - , " Files already in our knowledgebase: " <> Text.pack (show $ ficusStatsExistingFiles stats) - , " Files new to our knowledgebase: " <> Text.pack (show $ ficusStatsNewFiles stats) + , " Total Files processed: " <> Text.pack (show $ ficusStatsProcessedFiles stats) + , " Unique Files processed: " <> Text.pack (show $ ficusStatsUniqueProcessedFiles stats) + , " Unique Files with matches found: " <> Text.pack (show $ ficusStatsUniqueMatchedFiles stats) + , " Unique Files with no matches found: " <> Text.pack (show $ ficusStatsUniqueUnmatchedFiles stats) + , " Unique Files already in our knowledgebase: " <> Text.pack (show $ ficusStatsUniqueExistingFiles stats) + , " Unique Files new to our knowledgebase: " <> Text.pack (show $ ficusStatsUniqueNewFiles stats) , " Processing time: " <> Text.pack (show $ ficusStatsProcessingTimeSeconds stats) <> "s" - , "=================================================== " + , "============================================================" ] runFicus :: diff --git a/src/App/Fossa/Ficus/Types.hs b/src/App/Fossa/Ficus/Types.hs index 04cfa7cfa..1db758169 100644 --- a/src/App/Fossa/Ficus/Types.hs +++ b/src/App/Fossa/Ficus/Types.hs @@ -44,10 +44,11 @@ instance FromJSON FicusSnippetScanResults where data FicusScanStats = FicusScanStats { ficusStatsSkippedFiles :: Int , ficusStatsProcessedFiles :: Int - , ficusStatsNewFiles :: Int - , ficusStatsExistingFiles :: Int - , ficusStatsMatchedFiles :: Int - , ficusStatsUnmatchedFiles :: Int + , ficusStatsUniqueProcessedFiles :: Int + , ficusStatsUniqueNewFiles :: Int + , ficusStatsUniqueExistingFiles :: Int + , ficusStatsUniqueMatchedFiles :: Int + , ficusStatsUniqueUnmatchedFiles :: Int , ficusStatsProcessingTimeSeconds :: Double } deriving (Eq, Ord, Show, Generic) @@ -57,10 +58,11 @@ instance FromJSON FicusScanStats where FicusScanStats <$> obj .: "skipped_files" <*> obj .: "processed_files" - <*> obj .: "new_files" - <*> obj .: "existing_files" - <*> obj .: "matched_files" - <*> obj .: "unmatched_files" + <*> obj .: "unique_processed_files" + <*> obj .: "unique_new_files" + <*> obj .: "unique_existing_files" + <*> obj .: "unique_matched_files" + <*> obj .: "unique_unmatched_files" <*> obj .: "processing_time_seconds" data FicusMessages = FicusMessages From 3b02a72e9aa9e6e79132e9ae1a78bb4832e40d09 Mon Sep 17 00:00:00 2001 From: spatten Date: Fri, 7 Nov 2025 16:39:51 -0800 Subject: [PATCH 06/12] add a newline --- src/App/Fossa/Ficus/Analyze.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/App/Fossa/Ficus/Analyze.hs b/src/App/Fossa/Ficus/Analyze.hs index 36728d487..3a723801c 100644 --- a/src/App/Fossa/Ficus/Analyze.hs +++ b/src/App/Fossa/Ficus/Analyze.hs @@ -139,6 +139,7 @@ formatFicusScanSummary results = aid = ficusSnippetScanResultsAnalysisId results in Text.unlines [ "Ficus snippet scan completed successfully!" + , "" , "============================================================" , "Snippet scan summary:" , " Analysis ID: " <> Text.pack (show aid) From 76c9e1f94521dd52cf5df760c6612d5676e354bb Mon Sep 17 00:00:00 2001 From: spatten Date: Thu, 13 Nov 2025 12:05:44 -0800 Subject: [PATCH 07/12] fix up summary output --- src/App/Fossa/Ficus/Analyze.hs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/App/Fossa/Ficus/Analyze.hs b/src/App/Fossa/Ficus/Analyze.hs index 3a723801c..262ff620d 100644 --- a/src/App/Fossa/Ficus/Analyze.hs +++ b/src/App/Fossa/Ficus/Analyze.hs @@ -37,6 +37,7 @@ import Data.Conduit qualified as Conduit import Data.Conduit.Combinators qualified as CC import Data.Conduit.List qualified as CCL import Data.Hashable (Hashable) +import Data.List (dropWhileEnd) import Data.Maybe (isJust) import Data.String.Conversion (ToText (toText), toString) import Data.Text (Text) @@ -47,6 +48,7 @@ import Data.Time.Format (defaultTimeLocale, formatTime) import Effect.Exec (AllowErr (Never), Command (..), ExitCode (ExitSuccess), renderCommand) import Effect.Logger (Logger, logDebug, logInfo) import Fossa.API.Types (ApiKey (..), ApiOpts (..)) +import Numeric (showFFloat) import Path (Abs, Dir, Path, toFilePath) import Prettyprinter (pretty) import Srclib.Types (Locator (..), renderLocator) @@ -149,11 +151,24 @@ formatFicusScanSummary results = , " Unique Files processed: " <> Text.pack (show $ ficusStatsUniqueProcessedFiles stats) , " Unique Files with matches found: " <> Text.pack (show $ ficusStatsUniqueMatchedFiles stats) , " Unique Files with no matches found: " <> Text.pack (show $ ficusStatsUniqueUnmatchedFiles stats) - , " Unique Files already in our knowledgebase: " <> Text.pack (show $ ficusStatsUniqueExistingFiles stats) - , " Unique Files new to our knowledgebase: " <> Text.pack (show $ ficusStatsUniqueNewFiles stats) - , " Processing time: " <> Text.pack (show $ ficusStatsProcessingTimeSeconds stats) <> "s" + , " Unique Files already in our knowledge base: " <> Text.pack (show $ ficusStatsUniqueExistingFiles stats) + , " Unique Files new to our knowledge base: " <> Text.pack (show $ ficusStatsUniqueNewFiles stats) + , " Processing time: " <> formatProcessingTime (ficusStatsProcessingTimeSeconds stats) <> "s" , "============================================================" ] + where + formatProcessingTime :: Double -> Text + formatProcessingTime seconds = + let formatted = showFFloat (Just 3) seconds "" + (whole, fractionalPart) = span (/= '.') formatted + in case fractionalPart of + [] -> Text.pack formatted + '.' : fraction -> + let trimmedFraction = dropWhileEnd (== '0') fraction + in if null trimmedFraction + then Text.pack whole + else Text.pack (whole <> "." <> trimmedFraction) + _ -> Text.pack formatted runFicus :: ( Has Diagnostics sig m From 7132551874ffef82a8a6935c9b00736c91bdeac7 Mon Sep 17 00:00:00 2001 From: spatten Date: Thu, 13 Nov 2025 14:20:45 -0800 Subject: [PATCH 08/12] use toText --- src/App/Fossa/Ficus/Analyze.hs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/App/Fossa/Ficus/Analyze.hs b/src/App/Fossa/Ficus/Analyze.hs index 262ff620d..077773314 100644 --- a/src/App/Fossa/Ficus/Analyze.hs +++ b/src/App/Fossa/Ficus/Analyze.hs @@ -144,15 +144,15 @@ formatFicusScanSummary results = , "" , "============================================================" , "Snippet scan summary:" - , " Analysis ID: " <> Text.pack (show aid) - , " Bucket ID: " <> Text.pack (show $ ficusSnippetScanResultsBucketId results) - , " Files skipped: " <> Text.pack (show $ ficusStatsSkippedFiles stats) - , " Total Files processed: " <> Text.pack (show $ ficusStatsProcessedFiles stats) - , " Unique Files processed: " <> Text.pack (show $ ficusStatsUniqueProcessedFiles stats) - , " Unique Files with matches found: " <> Text.pack (show $ ficusStatsUniqueMatchedFiles stats) - , " Unique Files with no matches found: " <> Text.pack (show $ ficusStatsUniqueUnmatchedFiles stats) - , " Unique Files already in our knowledge base: " <> Text.pack (show $ ficusStatsUniqueExistingFiles stats) - , " Unique Files new to our knowledge base: " <> Text.pack (show $ ficusStatsUniqueNewFiles stats) + , " Analysis ID: " <> toText (show aid) + , " Bucket ID: " <> toText (show $ ficusSnippetScanResultsBucketId results) + , " Files skipped: " <> toText (show $ ficusStatsSkippedFiles stats) + , " Total Files processed: " <> toText (show $ ficusStatsProcessedFiles stats) + , " Unique Files processed: " <> toText (show $ ficusStatsUniqueProcessedFiles stats) + , " Unique Files with matches found: " <> toText (show $ ficusStatsUniqueMatchedFiles stats) + , " Unique Files with no matches found: " <> toText (show $ ficusStatsUniqueUnmatchedFiles stats) + , " Unique Files already in our knowledge base: " <> toText (show $ ficusStatsUniqueExistingFiles stats) + , " Unique Files new to our knowledge base: " <> toText (show $ ficusStatsUniqueNewFiles stats) , " Processing time: " <> formatProcessingTime (ficusStatsProcessingTimeSeconds stats) <> "s" , "============================================================" ] @@ -162,13 +162,13 @@ formatFicusScanSummary results = let formatted = showFFloat (Just 3) seconds "" (whole, fractionalPart) = span (/= '.') formatted in case fractionalPart of - [] -> Text.pack formatted + [] -> toText formatted '.' : fraction -> let trimmedFraction = dropWhileEnd (== '0') fraction in if null trimmedFraction - then Text.pack whole - else Text.pack (whole <> "." <> trimmedFraction) - _ -> Text.pack formatted + then toText whole + else toText (whole <> "." <> trimmedFraction) + _ -> toText formatted runFicus :: ( Has Diagnostics sig m From b39191853edcdeed9b322a6be85ef61d05cb058d Mon Sep 17 00:00:00 2001 From: spatten Date: Thu, 13 Nov 2025 14:25:29 -0800 Subject: [PATCH 09/12] add a comment --- src/App/Fossa/Ficus/Analyze.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/App/Fossa/Ficus/Analyze.hs b/src/App/Fossa/Ficus/Analyze.hs index 077773314..ee7a42c27 100644 --- a/src/App/Fossa/Ficus/Analyze.hs +++ b/src/App/Fossa/Ficus/Analyze.hs @@ -157,6 +157,7 @@ formatFicusScanSummary results = , "============================================================" ] where + -- Format the processing time as a string with 3 decimal places formatProcessingTime :: Double -> Text formatProcessingTime seconds = let formatted = showFFloat (Just 3) seconds "" From a97faa2d366295fe1a700a3f38a7ce0b759d9431 Mon Sep 17 00:00:00 2001 From: spatten Date: Thu, 13 Nov 2025 14:57:26 -0800 Subject: [PATCH 10/12] just use printf --- src/App/Fossa/Ficus/Analyze.hs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/App/Fossa/Ficus/Analyze.hs b/src/App/Fossa/Ficus/Analyze.hs index ee7a42c27..e41207d0a 100644 --- a/src/App/Fossa/Ficus/Analyze.hs +++ b/src/App/Fossa/Ficus/Analyze.hs @@ -37,7 +37,6 @@ import Data.Conduit qualified as Conduit import Data.Conduit.Combinators qualified as CC import Data.Conduit.List qualified as CCL import Data.Hashable (Hashable) -import Data.List (dropWhileEnd) import Data.Maybe (isJust) import Data.String.Conversion (ToText (toText), toString) import Data.Text (Text) @@ -48,7 +47,6 @@ import Data.Time.Format (defaultTimeLocale, formatTime) import Effect.Exec (AllowErr (Never), Command (..), ExitCode (ExitSuccess), renderCommand) import Effect.Logger (Logger, logDebug, logInfo) import Fossa.API.Types (ApiKey (..), ApiOpts (..)) -import Numeric (showFFloat) import Path (Abs, Dir, Path, toFilePath) import Prettyprinter (pretty) import Srclib.Types (Locator (..), renderLocator) @@ -64,6 +62,7 @@ import System.Process.Typed ( waitExitCode, withProcessWait, ) +import Text.Printf (printf) import Text.URI (render) import Text.URI.Builder (PathComponent (PathComponent), TrailingSlash (TrailingSlash), setPath) import Types (GlobFilter (..), LicenseScanPathFilters (..)) @@ -159,17 +158,7 @@ formatFicusScanSummary results = where -- Format the processing time as a string with 3 decimal places formatProcessingTime :: Double -> Text - formatProcessingTime seconds = - let formatted = showFFloat (Just 3) seconds "" - (whole, fractionalPart) = span (/= '.') formatted - in case fractionalPart of - [] -> toText formatted - '.' : fraction -> - let trimmedFraction = dropWhileEnd (== '0') fraction - in if null trimmedFraction - then toText whole - else toText (whole <> "." <> trimmedFraction) - _ -> toText formatted + formatProcessingTime seconds = toText (printf "%.3f" seconds :: String) runFicus :: ( Has Diagnostics sig m From 5260a5f8ba5548a36aeebb7e5fac9cf51104d0e0 Mon Sep 17 00:00:00 2001 From: spatten Date: Fri, 14 Nov 2025 16:28:33 -0800 Subject: [PATCH 11/12] add a changelog --- Changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Changelog.md b/Changelog.md index 8f2ded91e..b8f916a72 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,4 +1,9 @@ # FOSSA CLI Changelog + +## 3.12.3 + +- Add a summary of the snippet scan when the `--x-snippet-scan` flag is used ([#1613](https://github.com/fossas/fossa-cli/pull/1613)) + ## 3.12.2 - Update the latest version of a dependency for `--x-snippet-scan`. This update will start backfilling fingerprints for ~10% of files that were previously uploaded but do not have fingerprints ([#1611](https://github.com/fossas/fossa-cli/pull/1611)) From 4cd4a6acc8c4950f98665ffe091274d43a22ed7f Mon Sep 17 00:00:00 2001 From: spatten Date: Thu, 20 Nov 2025 15:16:18 -0800 Subject: [PATCH 12/12] gitignore fossa.debug.zip --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5620378f6..1d18a1eec 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ sandbox/ fossa.debug.json fossa.debug.json.gz fossa.telemetry.json +fossa.debug.zip # Integration Tests integration-test/artifacts/ @@ -42,4 +43,4 @@ target/ # Include targets in test !test/reachability/testdata/maven-default/target -!test/reachability/testdata/maven-build-config/dist \ No newline at end of file +!test/reachability/testdata/maven-build-config/dist