tricorder 0.2.0.1 → 0.2.1.0
raw patch · 12 files changed
+366/−78 lines, 12 filesdep −ansi-terminaldep ~Cabaldep ~Cabal-syntaxdep ~aeson
Dependencies removed: ansi-terminal
Dependency ranges changed: Cabal, Cabal-syntax, aeson, atelier-core, brick, megaparsec, time, vty
Files
- CHANGELOG.md +14/−0
- LICENSE +1/−1
- src/Tricorder/Daemon/Core.hs +35/−8
- src/Tricorder/Daemon/Hpack.hs +81/−0
- src/Tricorder/Daemon/Hpack/Effect.hs +62/−0
- src/Tricorder/Daemon/Main.hs +5/−3
- src/Tricorder/Session.hs +4/−0
- src/Tricorder/Session/CabalFile.hs +36/−30
- src/Tricorder/Session/Config.hs +2/−0
- src/Tricorder/Session/GenerateWithHpack.hs +13/−0
- test/Unit/Tricorder/Session/CabalFileSpec.hs +85/−10
- tricorder.cabal +28/−26
CHANGELOG.md view
@@ -7,6 +7,20 @@ ## [Unreleased] +## 0.2.1.0 - 2026-08-17++### Added++- Tricorder now respects all forms of `cabal.project` files, like+ `cabal.project.local` for example. See+ [Cabal's documentation on project description files](https://cabal.readthedocs.io/en/stable/cabal-project-description-file.html). ([#73](https://github.com/tweag/tricorder/issues/73))+- With `hpack` in `PATH`, Tricorder will now run `hpack` in the directory of+ any changed `package.yaml` file in the root of the project or any+ subdirectory. Control this behavior with the `generate_with_hpack`+ configuration.+- Tricorder will now restart the GHCi session when detecting changes to a+ project's `stack.yaml`.+ ## [0.2.0.1] - 2026-08-12 ### Fixed
LICENSE view
@@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Christian Georgii+Copyright (c) 2025 Tweag Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal
src/Tricorder/Daemon/Core.hs view
@@ -9,7 +9,7 @@ import Atelier.Effects.FileWatcher (FileEvent, FileWatcher) import Atelier.Effects.Input (Input) import Atelier.Effects.Log (Log)-import Atelier.Effects.Publishing (runPubSub_)+import Atelier.Effects.Publishing (runPubSub) import Atelier.Effects.Publishing.Pub (Pub) import Atelier.Effects.Publishing.Sub (Sub) import Data.List (isSuffixOf)@@ -18,8 +18,10 @@ import Effectful.Reader.Static (Reader) import Effectful.State.Static.Shared (State) import Relude.Extra.Tuple (dup)+import System.FilePath ((</>)) import Atelier.Effects.Conc qualified as Conc+import Atelier.Effects.FileSystem qualified as FileSystem import Atelier.Effects.FileWatcher qualified as FileWatcher import Atelier.Effects.Log qualified as Log import Atelier.Effects.Publishing.Pub qualified as Pub@@ -52,12 +54,14 @@ , LoadedModule (..) , resolveKnownTargets )+import Tricorder.Daemon.Hpack.Effect (Hpack) import Tricorder.Daemon.TestRunner (TestRunner) import Tricorder.Daemon.Watch (WatchedFile) import Tricorder.Runtime (ProjectRoot (..)) import Tricorder.Session (Session (..), loadSession) import Tricorder.Session.CabalFile (CabalFile) import Tricorder.Session.Command (Command (..))+import Tricorder.Session.GenerateWithHpack (GenerateWithHpack (..)) import Tricorder.Session.TestTarget (TestTarget, renderTestTarget) import Tricorder.Session.TestTimeout (TestTimeout) import Tricorder.Waiters (Waiters)@@ -68,6 +72,7 @@ import Tricorder.Config qualified as Config import Tricorder.Daemon.Builder qualified as Builder import Tricorder.Daemon.EvalCommentRunner qualified as EvalCommentRunner+import Tricorder.Daemon.Hpack qualified as Hpack import Tricorder.Daemon.TestRunner qualified as TestRunner import Tricorder.Daemon.Watch qualified as Watch import Tricorder.Waiters qualified as Waiters@@ -90,6 +95,7 @@ , FileSystem :> es , FileWatcher :> es , GhciSession :> es+ , Hpack :> es , Input LoadedConfig :> es , Input [CabalFile] :> es , Log :> es@@ -100,16 +106,17 @@ , Waiters :> es ) => Eff es Void-main = runPubSub_ @ReloadSession- . runPubSub_ @WatchedFile- . runPubSub_ @CabalChangeDetected- . runPubSub_ @SourceChangeDetected- . runPubSub_ @RestartBuilder- . runPubSub_ @ReloadBuilder+main = runPubSub @ReloadSession+ . runPubSub @WatchedFile+ . runPubSub @CabalChangeDetected+ . runPubSub @SourceChangeDetected+ . runPubSub @RestartBuilder+ . runPubSub @ReloadBuilder $ Conc.restartableFork waitForReloadSession do root <- Reader.ask session <- loadSession Conc.fork_ $ watchConfigFile root+ conditionallyWatchStackYaml root Conc.fork_ $ Watch.files root session Conc.fork_ $ Sub.listen_ Watch.publishChange@@ -120,9 +127,13 @@ Pub.publish ReloadSession else Pub.publish RestartBuilder+ Conc.fork_ $ Sub.listen_ \(SourceChangeDetected fp event) -> Pub.publish $ ReloadBuilder fp event + when session.generateWithHpack.getGenerateWithHpack do+ void $ Conc.fork Hpack.main+ State.evalState emptyBuilderState $ withSession session where waitForReloadSession = Waiters.wait $ Sub.listenOnce_ @ReloadSession@@ -150,7 +161,23 @@ watchConfigFile root = do FileWatcher.watchFilePathsDebounced [FileWatcher.dirWhere root.getProjectRoot (Config.configFileName `isSuffixOf`)]- \_ _ -> Pub.publish $ ReloadSession+ \_ _ -> Pub.publish ReloadSession+++conditionallyWatchStackYaml+ :: ( Conc :> es+ , Debounce FilePath :> es+ , FileSystem :> es+ , FileWatcher :> es+ , Pub RestartBuilder :> es+ )+ => ProjectRoot -> Eff es ()+conditionallyWatchStackYaml root = do+ exists <- FileSystem.doesFileExist $ root.getProjectRoot </> "stack.yaml"+ when exists do+ Conc.fork_ $ FileWatcher.watchFilePathsDebounced+ [FileWatcher.dirWhere root.getProjectRoot ("stack.yaml" `isSuffixOf`)]+ \_ _ -> Pub.publish RestartBuilder -- | For a given session, handles controlling the build process itself,
+ src/Tricorder/Daemon/Hpack.hs view
@@ -0,0 +1,81 @@+module Tricorder.Daemon.Hpack (main) where++import Atelier.Effects.Chan (Chan)+import Atelier.Effects.Clock (Clock)+import Atelier.Effects.Conc (Conc)+import Atelier.Effects.Debounce (Debounce)+import Atelier.Effects.FileWatcher (FileWatcher)+import Atelier.Effects.Log (Log)+import Atelier.Effects.Publishing (runPubSub)+import Atelier.Effects.Publishing.Pub (Pub)+import Effectful.Reader.Static (Reader)++import Atelier.Effects.Conc qualified as Conc+import Atelier.Effects.FileWatcher qualified as FileWatcher+import Atelier.Effects.Log qualified as Log+import Atelier.Effects.Publishing.Pub qualified as Pub+import Atelier.Effects.Publishing.Sub qualified as Sub+import Data.List qualified as List+import Effectful.Reader.Static qualified as Reader++import Tricorder.Daemon.Hpack.Effect (Hpack)+import Tricorder.Runtime (ProjectRoot (..))++import Tricorder.Daemon.Hpack.Effect qualified as Hpack+++main+ :: ( Chan :> es+ , Clock :> es+ , Conc :> es+ , Debounce FilePath :> es+ , FileWatcher :> es+ , Hpack :> es+ , Log :> es+ , Reader ProjectRoot :> es+ )+ => Eff es ()+main = runPubSub @RePack $ Conc.scoped do+ hpackInPath <- Hpack.hpackIsInPath+ when hpackInPath do+ projectRoot <- Reader.ask+ Conc.fork_ $ Sub.listen_ \(RePack path) -> runHpack path+ vacuous $ watchPackageYaml projectRoot+++runHpack :: (Hpack :> es, Log :> es) => FilePath -> Eff es ()+runHpack path = do+ res <- Hpack.hpack path+ case res of+ Left err -> Log.err $ "Hpack error: " <> err+ Right result ->+ Log.info+ $ "Hpack: " <> case result of+ Hpack.Generated ->+ "generated " <> toText path+ Hpack.Unchanged ->+ "already up-to-date: " <> toText path+ Hpack.WasEditedManually ->+ "cabal file was edited manually: " <> toText path+ Hpack.WasGeneratedWithNewerHpack ->+ "cabal file was generated with a newer version of Hpack: " <> toText path+ Hpack.UnknownSuccess output -> "unknown successful output: " <> output+++watchPackageYaml+ :: ( Debounce FilePath :> es+ , FileWatcher :> es+ , Pub RePack :> es+ )+ => ProjectRoot -> Eff es Void+watchPackageYaml root = do+ FileWatcher.watchFilePathsDebounced+ [FileWatcher.dirWhere root.getProjectRoot (packageYaml `List.isSuffixOf`)]+ \path _ -> Pub.publish $ RePack path+++data RePack = RePack FilePath+++packageYaml :: FilePath+packageYaml = "package.yaml"
+ src/Tricorder/Daemon/Hpack/Effect.hs view
@@ -0,0 +1,62 @@+module Tricorder.Daemon.Hpack.Effect+ ( Hpack (..)+ , Result (..)+ , hpackIsInPath+ , hpack+ , run+ ) where++import Atelier.Effects.Process (Process, readProcess, runProcess, setWorkingDir, shell)+import Effectful (Effect)+import Effectful.Dispatch.Dynamic (interpret_)+import Effectful.Exception (trySync)+import Effectful.TH (makeEffect)+import System.Exit (ExitCode (..))++import Data.ByteString.Char8 qualified as B8+import System.FilePath qualified as Path+++data Hpack :: Effect where+ HpackIsInPath :: Hpack m Bool+ Hpack :: FilePath -> Hpack m (Either Text Result)+++data Result+ = Generated+ | Unchanged+ | WasGeneratedWithNewerHpack+ | WasEditedManually+ | UnknownSuccess Text+++makeEffect ''Hpack+++run :: (Process :> es) => Eff (Hpack : es) a -> Eff es a+run = interpret_ \case+ HpackIsInPath -> do+ exitCode <- runProcess $ shell "command -v hpack"+ pure $ exitCode == ExitSuccess+ Hpack path -> do+ res <-+ trySync+ $ readProcess+ $ setWorkingDir (Path.takeDirectory path)+ $ shell "hpack"+ case res of+ Left ex -> pure $ Left $ show ex+ Right (exitCode, lstdout, lstderr) -> do+ let stdout = toStrict lstdout+ stderr = toStrict lstderr+ infoMsg =+ if+ | "generated" `B8.isInfixOf` stdout -> Generated+ | "is up-to-date" `B8.isInfixOf` stdout -> Unchanged+ | "generated with a newer version" `B8.isInfixOf` stdout -> WasGeneratedWithNewerHpack+ | "was modified manually" `B8.isInfixOf` stdout -> WasEditedManually+ | otherwise -> UnknownSuccess $ decodeUtf8 stdout+ if exitCode == ExitSuccess then+ pure $ Right $ infoMsg+ else+ pure $ Left $ decodeUtf8 stderr
src/Tricorder/Daemon/Main.hs view
@@ -12,7 +12,7 @@ import Atelier.Effects.FileSystem (runFileSystemIO) import Atelier.Effects.FileWatcher (runFileWatcherIO) import Atelier.Effects.Process (runProcessIO)-import Atelier.Effects.Publishing (runPubSub_)+import Atelier.Effects.Publishing (runPubSub) import Atelier.Effects.Timeout (runTimeout) import Data.Default (def) import Effectful (runEff)@@ -40,6 +40,7 @@ import Tricorder.Daemon.Core qualified as Core import Tricorder.Daemon.DaemonInfo qualified as DaemonInfo import Tricorder.Daemon.EvalCommentRunner qualified as EvalCommentRunner+import Tricorder.Daemon.Hpack.Effect qualified as Hpack import Tricorder.Daemon.TestRunner qualified as TestRunner import Tricorder.Socket.Server qualified as Server import Tricorder.SourceLookup qualified as SourceLookup@@ -71,6 +72,7 @@ . runLogging . inputLoadedConfig . runChan+ . runEnv . inputCabalFiles . inputSession . runReader @CacheConfig.Config def@@ -78,13 +80,13 @@ . runCacheTtl @ModuleName @PackageId . runCacheTtl @(PackageId, SourceQuery) @SourceLookup.ModuleSourceResult . runProcessIO- . runEnv . runGhcPkgIO . runUnixSocketIO . runGhciSession . evalState (BuildId 1) . Input.fromState @BuildId- . runPubSub_ @BuildPhase+ . runPubSub @BuildPhase+ . Hpack.run . Hackage.run . PackageStore.run . EvalCommentRunner.run
src/Tricorder/Session.hs view
@@ -18,6 +18,7 @@ import Tricorder.Session.CabalFile (CabalFile) import Tricorder.Session.Command (Command (..), resolveCommand) import Tricorder.Session.Config (Config (..))+import Tricorder.Session.GenerateWithHpack (GenerateWithHpack (..)) import Tricorder.Session.ReplBuildDir (ReplBuildDir (..)) import Tricorder.Session.Target (Target, definesCustomPrelude, resolveTargets) import Tricorder.Session.TestTarget (TestTarget, resolveTestTargets)@@ -34,6 +35,7 @@ , watchExclusionPatterns :: WatchExclusionPatterns , replBuildDir :: ReplBuildDir , testTimeout :: TestTimeout+ , generateWithHpack :: GenerateWithHpack } deriving stock (Eq) @@ -48,6 +50,7 @@ , watchExclusionPatterns = def , replBuildDir = def , testTimeout = def+ , generateWithHpack = def } @@ -101,6 +104,7 @@ , testTargets , replBuildDir = ReplBuildDir cfgFile.replBuildDir , testTimeout = TestTimeout cfgFile.testTimeout+ , generateWithHpack = GenerateWithHpack cfgFile.generateWithHpack }
src/Tricorder/Session/CabalFile.hs view
@@ -4,6 +4,7 @@ , discoverCabalFiles ) where +import Atelier.Effects.Env (Env) import Atelier.Effects.FileSystem (FileSystem, doesFileExist, listDirectory, readFileBs) import Atelier.Effects.Input (Input, runInputEff) import Atelier.Effects.Log (Log)@@ -14,6 +15,7 @@ import Effectful.Reader.Static (Reader, ask) import System.FilePath (normalise, takeExtension, (</>)) +import Atelier.Effects.Env qualified as Env import Atelier.Effects.Log qualified as Log import Data.ByteString.Char8 qualified as BC import Data.Text qualified as T@@ -29,14 +31,14 @@ inputCabalFiles- :: ( FileSystem :> es+ :: ( Env :> es+ , FileSystem :> es , Log :> es , Reader ProjectRoot :> es ) => Eff (Input [CabalFile] : es) a -> Eff es a inputCabalFiles = runInputEff do- projectRoot <- ask- projectFilePaths <- discoverCabalFiles projectRoot+ projectFilePaths <- discoverCabalFiles (faileds, packageDescriptions) <- partitionEithers <$> for projectFilePaths \p -> do contents <- readFileBs p@@ -50,38 +52,42 @@ pure $ packageDescriptions --- | Locate every package's @.cabal@ file, logging what drove the result. In a--- multi-package project the packages live in subdirectories listed under--- @packages:@ in @cabal.project@; in a single-package project the @.cabal@--- file(s) sit in the root. Called once per session load; the result is shared--- by target and watch-dir resolution.-discoverCabalFiles :: (FileSystem :> es, Log :> es) => ProjectRoot -> Eff es [FilePath]-discoverCabalFiles (ProjectRoot projectRoot) = do- hasProject <- doesFileExist projectFile- cabalFiles <-- if hasProject then do- contents <- readFileBs projectFile- concat <$> traverse cabalFilesForEntry (projectPackageEntries contents)- else+-- | Lists all `.cabal` files for packages listed in the project root's+-- `cabal.project` (or `cabal.project.local`) file. If no `cabal.project` file+-- is found, looks for a `.cabal` file in the project root, and uses that+-- instead.+discoverCabalFiles+ :: ( Env :> es+ , FileSystem :> es+ , Reader ProjectRoot :> es+ )+ => Eff es [FilePath]+discoverCabalFiles = do+ ProjectRoot projectRoot <- ask+ homeCabalFiles <- maybe [] (one . (</> ".cabal/config")) <$> Env.lookupEnv "HOME"+ let projectFilePaths = projectCabalFiles projectRoot <> homeCabalFiles+ projectFiles <- filterM doesFileExist projectFilePaths+ case nonEmpty projectFiles of+ Nothing -> cabalFilesIn projectRoot- let listed- | null cabalFiles = "none"- | otherwise = T.intercalate ", " (map toText cabalFiles)- if hasProject then- Log.debug- $ "Found cabal.project; discovered "- <> show (length cabalFiles)- <> " package cabal file(s): "- <> listed- else- Log.debug $ "No cabal.project; using cabal file(s) in project root: " <> listed- pure cabalFiles+ Just neProjectFiles -> do+ packages <- fmap (find (not . null))+ $ for (toList neProjectFiles) \projectFile -> do+ contents <- readFileBs projectFile+ concat+ <$> traverse+ (cabalFilesForEntry projectRoot)+ (projectPackageEntries contents)+ case packages of+ Nothing -> cabalFilesIn projectRoot+ Just pkgs -> pure pkgs where- projectFile = projectRoot </> "cabal.project"+ projectCabalFiles projectRoot =+ (projectRoot </>) <$> ["cabal.project.local", "cabal.project.freeze", "cabal.project"] -- A @packages:@ entry is either a direct path to a @.cabal@ file or a -- directory to search for one.- cabalFilesForEntry entry+ cabalFilesForEntry projectRoot entry | takeExtension entry == ".cabal" = pure [projectRoot </> entry] | otherwise = cabalFilesIn (normalise (projectRoot </> entry))
src/Tricorder/Session/Config.hs view
@@ -14,6 +14,7 @@ , testTargets :: Maybe [Text] , replBuildDir :: FilePath , testTimeout :: Int+ , generateWithHpack :: Bool } deriving stock (Eq, Generic, Show) deriving (FromJSON) via WithDefaults (QuietSnake Config)@@ -29,4 +30,5 @@ , testTargets = Nothing , replBuildDir = "dist-newstyle/tricorder" , testTimeout = 10+ , generateWithHpack = True }
+ src/Tricorder/Session/GenerateWithHpack.hs view
@@ -0,0 +1,13 @@+module Tricorder.Session.GenerateWithHpack (GenerateWithHpack (..)) where++import Data.Aeson (FromJSON, ToJSON)+import Data.Default (Default (..))+++newtype GenerateWithHpack = GenerateWithHpack {getGenerateWithHpack :: Bool}+ deriving stock (Eq, Generic, Show)+ deriving (FromJSON, ToJSON) via Bool+++instance Default GenerateWithHpack where+ def = GenerateWithHpack True
test/Unit/Tricorder/Session/CabalFileSpec.hs view
@@ -1,8 +1,9 @@ module Unit.Tricorder.Session.CabalFileSpec (spec_CabalFile) where +import Atelier.Effects.Env (runEnvConst) import Atelier.Effects.FileSystem (runFileSystemState)-import Atelier.Effects.Log (runLogNoOp) import Effectful (runPureEff)+import Effectful.Reader.Static (runReader) import Effectful.State.Static.Shared (evalState) import Test.Hspec (Spec, describe, it, shouldBe) @@ -10,7 +11,7 @@ import Tricorder.Runtime (ProjectRoot (..)) import Tricorder.Session.CabalFile (discoverCabalFiles)-import Unit.Tricorder.Session.Helpers (cabalFixture, multiPackageFs)+import Unit.Tricorder.Session.Helpers (cabalFixture, libTestCabal, multiPackageFs) spec_CabalFile :: Spec@@ -18,26 +19,100 @@ describe "discoverCabalFiles" testDiscoverCabalFiles --- | Pins the discovery contract: a @cabal.project@ selects per-package--- @.cabal@ files from its @packages:@ stanza; otherwise the @.cabal@ files in--- the project root are used.+-- | Pins the discovery contract: a @cabal.project@ (or @.local@/@.freeze@+-- variant) selects per-package @.cabal@ files from its @packages:@ stanza;+-- otherwise the @.cabal@ files in the project root are used. Falls back+-- further to @$HOME/.cabal/config@'s @packages:@ stanza if none of the+-- project-root files exist. testDiscoverCabalFiles :: Spec testDiscoverCabalFiles = do describe "when there is no cabal.project" do it "finds the .cabal files in the project root" do let actual =- runDiscovery (Map.singleton "/myapp.cabal" cabalFixture)- $ discoverCabalFiles pr+ runDiscovery (Map.singleton "/myapp.cabal" cabalFixture) []+ $ discoverCabalFiles actual `shouldBe` ["/myapp.cabal"] it "returns no files when the root has no cabal file" do- let actual = runDiscovery mempty $ discoverCabalFiles pr+ let actual = runDiscovery mempty [] discoverCabalFiles actual `shouldBe` [] describe "when there is a multi-package cabal.project" do it "resolves each listed package to its .cabal (regression: was root-only)" do- let actual = runDiscovery multiPackageFs $ discoverCabalFiles pr+ let actual = runDiscovery multiPackageFs [] discoverCabalFiles actual `shouldBe` ["/pkg-a/pkg-a.cabal", "/pkg-b/pkg-b.cabal"]++ describe "priority among cabal.project.local, cabal.project.freeze, and cabal.project" do+ it "prefers cabal.project.local over cabal.project" do+ let fs =+ Map.fromList+ [ ("/cabal.project.local", "packages: pkg-a\n")+ , ("/cabal.project", "packages: pkg-b\n")+ ]+ `Map.union` multiPackageCabalFiles+ actual = runDiscovery fs [] discoverCabalFiles+ actual `shouldBe` ["/pkg-a/pkg-a.cabal"]++ it "prefers cabal.project.freeze over cabal.project" do+ let fs =+ Map.fromList+ [ ("/cabal.project.freeze", "packages: pkg-a\n")+ , ("/cabal.project", "packages: pkg-b\n")+ ]+ `Map.union` multiPackageCabalFiles+ actual = runDiscovery fs [] discoverCabalFiles+ actual `shouldBe` ["/pkg-a/pkg-a.cabal"]++ describe "when a higher-priority file lists no packages" do+ it "falls through to the next file in priority order" do+ let fs =+ Map.fromList+ [ ("/cabal.project.local", "tests: True\n")+ , ("/cabal.project", "packages: pkg-b\n")+ ]+ `Map.union` multiPackageCabalFiles+ actual = runDiscovery fs [] discoverCabalFiles+ actual `shouldBe` ["/pkg-b/pkg-b.cabal"]++ describe "packages: entry resolution" do+ it "uses a direct .cabal path entry verbatim, without scanning a directory" do+ let fs = Map.singleton "/cabal.project" "packages: sub/foo.cabal\n"+ actual = runDiscovery fs [] discoverCabalFiles+ actual `shouldBe` ["/sub/foo.cabal"]++ it "skips glob entries under packages: (not expanded)" do+ let fs = Map.singleton "/cabal.project" "packages: */*.cabal\n"+ actual = runDiscovery fs [] discoverCabalFiles+ actual `shouldBe` []++ describe "$HOME/.cabal/config fallback" do+ describe "when no cabal.project files exist" do+ it "uses $HOME/.cabal/config as a last-resort packages source" do+ let fs =+ Map.singleton "/home/user/.cabal/config" "packages: pkg-a\n"+ `Map.union` multiPackageCabalFiles+ actual = runDiscovery fs [("HOME", "/home/user")] discoverCabalFiles+ actual `shouldBe` ["/pkg-a/pkg-a.cabal"]++ describe "when $HOME/.cabal/config exists but lists no packages" do+ it "falls back to scanning the project root" do+ let fs =+ Map.fromList+ [ ("/home/user/.cabal/config", "")+ , ("/myapp.cabal", cabalFixture)+ ]+ actual = runDiscovery fs [("HOME", "/home/user")] discoverCabalFiles+ actual `shouldBe` ["/myapp.cabal"] where pr = ProjectRoot "/"- runDiscovery fs = runPureEff . evalState fs . runFileSystemState . runLogNoOp+ multiPackageCabalFiles =+ Map.fromList+ [ ("/pkg-a/pkg-a.cabal", libTestCabal "pkg-a")+ , ("/pkg-b/pkg-b.cabal", libTestCabal "pkg-b")+ ]+ runDiscovery fs env =+ runPureEff+ . runEnvConst env+ . evalState fs+ . runFileSystemState+ . runReader pr
tricorder.cabal view
@@ -4,18 +4,18 @@ -- -- see: https://github.com/sol/hpack -name: tricorder-version: 0.2.0.1-synopsis: Continuous Haskell build status, diagnostics, and tests via a shared daemon-description: tricorder rebuilds your Haskell project continuously and surfaces build status, diagnostics, test results, and documentation - for developers and LLM coding agents. Like ghcid and ghciwatch it reloads on every change, but builds run in a background daemon so multiple clients (an interactive TUI, a status CLI, an agent skill) share a single build state without triggering redundant rebuilds. It discovers components across multi-package cabal.project workspaces automatically and ships context-friendly output for agentic use via the CLI.-category: Development-homepage: https://github.com/tweag/tricorder#readme-bug-reports: https://github.com/tweag/tricorder/issues-author: Christian Georgii-maintainer: christian.georgii@tweag.io-license: MIT-license-file: LICENSE-build-type: Simple+name: tricorder+version: 0.2.1.0+synopsis: Continuous Haskell build status, diagnostics, and tests via a shared daemon+description: tricorder rebuilds your Haskell project continuously and surfaces build status, diagnostics, test results, and documentation - for developers and LLM coding agents. Like ghcid and ghciwatch it reloads on every change, but builds run in a background daemon so multiple clients (an interactive TUI, a status CLI, an agent skill) share a single build state without triggering redundant rebuilds. It discovers components across multi-package cabal.project workspaces automatically and ships context-friendly output for agentic use via the CLI.+category: Development+homepage: https://github.com/tweag/tricorder#readme+bug-reports: https://github.com/tweag/tricorder/issues+author: Victor Nascimento Bakke+maintainer: victor.bakke@tweag.io+license: MIT+license-file: LICENSE+build-type: Simple extra-doc-files: README.md CHANGELOG.md@@ -54,6 +54,8 @@ Tricorder.Daemon.GhciSession Tricorder.Daemon.GhciSession.GhciParser Tricorder.Daemon.GhciSession.GhciProcess+ Tricorder.Daemon.Hpack+ Tricorder.Daemon.Hpack.Effect Tricorder.Daemon.Main Tricorder.Daemon.TestRunner Tricorder.Daemon.Watch@@ -64,6 +66,7 @@ Tricorder.Session.CabalFile Tricorder.Session.Command Tricorder.Session.Config+ Tricorder.Session.GenerateWithHpack Tricorder.Session.ReplBuildDir Tricorder.Session.Target Tricorder.Session.TestTarget@@ -108,14 +111,13 @@ TypeFamilies ghc-options: -Weverything -Wno-unsafe -Wno-missing-safe-haskell-mode -Wno-monomorphism-restriction -Wno-missing-kind-signatures -Wno-missing-local-signatures -Wno-missing-import-lists -Wno-implicit-prelude -Wno-unticked-promoted-constructors -Wno-unused-packages -Wno-all-missed-specialisations -Wno-missed-specialisations -fplugin=Effectful.Plugin -threaded build-depends:- Cabal >=3.12 && <3.17- , Cabal-syntax >=3.12 && <3.17- , aeson ==2.2.*- , ansi-terminal ==1.1.*- , atelier-core ==0.3.*+ Cabal >=3.12 && <3.19+ , Cabal-syntax >=3.12 && <3.19+ , aeson >=2.2 && <2.4+ , atelier-core >=0.3 && <0.5 , atelier-prelude >=0.1 && <0.3 , base >=4.18 && <4.23- , brick ==2.10.*+ , brick >=2.10 && <2.14 , bytestring >=0.11 && <0.13 , casing ==0.1.* , containers >=0.6 && <0.9@@ -127,7 +129,7 @@ , effectful-th ==1.0.* , filepath >=1.4 && <1.6 , hashable ==1.5.*- , megaparsec ==9.7.*+ , megaparsec >=9.7 && <9.9 , mtl ==2.3.* , network ==3.2.* , optparse-applicative ==0.19.*@@ -139,9 +141,9 @@ , tar >=0.6 && <0.8 , template-haskell >=2.20 && <2.25 , text ==2.1.*- , time >=1.12 && <1.16+ , time >=1.12 && <1.17 , time-units ==1.0.*- , vty ==6.5.*+ , vty >=6.5 && <6.7 , vty-crossplatform ==0.5.* , yaml ==0.11.* , zlib ==0.7.*@@ -281,9 +283,9 @@ build-tool-depends: tasty-discover:tasty-discover build-depends:- Cabal-syntax >=3.12 && <3.17- , aeson ==2.2.*- , atelier-core ==0.3.*+ Cabal-syntax >=3.12 && <3.19+ , aeson >=2.2 && <2.4+ , atelier-core >=0.3 && <0.5 , atelier-prelude >=0.1 && <0.3 , base >=4.18 && <4.23 , bytestring >=0.11 && <0.13@@ -294,7 +296,7 @@ , effectful-plugin >=2.0 && <2.2 , filepath >=1.4 && <1.6 , hspec ==2.11.*- , megaparsec ==9.7.*+ , megaparsec >=9.7 && <9.9 , process ==1.6.* , regex-tdfa >=1.3.2.5 && <1.4 , stm ==2.5.*@@ -303,7 +305,7 @@ , tasty-discover ==5.2.* , tasty-hspec ==1.2.* , text ==2.1.*- , time >=1.12 && <1.16+ , time >=1.12 && <1.17 , time-units ==1.0.* , tricorder-internal , typed-process ==0.2.*