packages feed

tricorder 0.7.0.0 → 1.0.0.0

raw patch · 52 files changed

+448/−519 lines, 52 filesdep ~atelier-coredep ~tricorder-typesPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: atelier-core, tricorder-types

API changes (from Hackage documentation)

- Tricorder.CLI.Arguments: parseArguments :: forall (es :: [Effect]). Arguments :> es => Eff es Command
- Tricorder.CLI.Arguments: runArguments :: forall (es :: [Effect]) a. Arguments :> es => Eff (Reader Command ': es) a -> Eff es a
- Tricorder.CLI.Operations: showTests :: forall (es :: [Effect]). (Console :> es, Exit :> es, File :> es, Reader SocketPath :> es, UnixSocket :> es) => TestOptions -> Eff es ()
- Tricorder.Session: [testMemoryLimit] :: Session -> Maybe ByteSize
- Tricorder.Session: [testTimeout] :: Session -> TestTimeout
- Tricorder.Session.Stage.Test.Config: newtype Options
- Tricorder.Session.TestTimeout: TestTimeout :: Int -> TestTimeout
- Tricorder.Session.TestTimeout: [getTestTimeout] :: TestTimeout -> Int
- Tricorder.Session.TestTimeout: instance Data.Aeson.Types.FromJSON.FromJSON Tricorder.Session.TestTimeout.TestTimeout
- Tricorder.Session.TestTimeout: instance Data.Aeson.Types.ToJSON.ToJSON Tricorder.Session.TestTimeout.TestTimeout
- Tricorder.Session.TestTimeout: instance Data.Default.Internal.Default Tricorder.Session.TestTimeout.TestTimeout
- Tricorder.Session.TestTimeout: instance GHC.Classes.Eq Tricorder.Session.TestTimeout.TestTimeout
- Tricorder.Session.TestTimeout: instance GHC.Generics.Generic Tricorder.Session.TestTimeout.TestTimeout
- Tricorder.Session.TestTimeout: instance GHC.Show.Show Tricorder.Session.TestTimeout.TestTimeout
- Tricorder.Session.TestTimeout: newtype TestTimeout
+ Tricorder.CLI.Arguments: reader :: forall (es :: [Effect]) a. Arguments :> es => Eff (Reader Command ': es) a -> Eff es a
+ Tricorder.Session.Stage.Test.Config: [memoryLimit] :: Options -> Maybe Text
+ Tricorder.Session.Stage.Test.Config: [timeoutMs] :: Options -> Maybe (AsRawUnit Millisecond)
+ Tricorder.Session.Stage.Test.Config: data Options
+ Tricorder.Session.Stage.Test.Session: [memoryLimit] :: ResolvedTestOptions -> Maybe ByteSize
+ Tricorder.Session.Stage.Test.Session: [timeout] :: ResolvedTestOptions -> Millisecond
- Tricorder.Build.Changes: CabalChangeDetected :: FilePath -> FileEvent -> CabalChangeDetected
+ Tricorder.Build.Changes: CabalChangeDetected :: FilePath -> Event -> CabalChangeDetected
- Tricorder.Build.Changes: SourceChangeDetected :: FilePath -> FileEvent -> SourceChangeDetected
+ Tricorder.Build.Changes: SourceChangeDetected :: FilePath -> Event -> SourceChangeDetected
- Tricorder.CLI.App: run :: forall (es :: [Effect]). (Brick :> es, BrickChan :> es, Clock :> es, Conc :> es, Concurrent :> es, Console :> es, Daemons :> es, Delay :> es, Exit :> es, File :> es, FileSystem :> es, GhcPkg :> es, Hackage :> es, IOE :> es, Input DaemonInfo :> es, Input Repl :> es, Input [CabalFile] :> es, Log :> es, PackageStore :> es, Process :> es, Provider_ Console Output :> es, Reader Command :> es, Reader Config :> es, Reader LogPath :> es, Reader PidFile :> es, Reader SocketPath :> es, Timeout :> es, UnixSocket :> es) => Eff es ()
+ Tricorder.CLI.App: run :: forall (es :: [Effect]). (Brick :> es, BrickChan :> es, Clock :> es, Conc :> es, Concurrent :> es, Console :> es, Daemons :> es, Delay :> es, Exit :> es, File :> es, FileSystem :> es, GhcPkg :> es, Hackage :> es, IOE :> es, Input Session :> es, Input DaemonInfo :> es, Input [CabalFile] :> es, Log :> es, PackageStore :> es, Process :> es, Provider_ Console Output :> es, Reader Command :> es, Reader Config :> es, Reader LogPath :> es, Reader PidFile :> es, Reader SocketPath :> es, Timeout :> es, UnixSocket :> es) => Eff es ()
- Tricorder.CLI.Daemon: restartDaemon :: forall (es :: [Effect]). (Daemons :> es, Delay :> es, File :> es, IOE :> es, Reader PidFile :> es, Reader SocketPath :> es, Timeout :> es, UnixSocket :> es) => Force -> Eff es (Maybe (Either [Text] Text))
+ Tricorder.CLI.Daemon: restartDaemon :: forall (es :: [Effect]). (Daemons :> es, Delay :> es, File :> es, IOE :> es, Reader PidFile :> es, Reader SocketPath :> es, Timeout :> es, UnixSocket :> es) => IfThereArePendingQueries -> Eff es (Maybe (Either [Text] Text))
- Tricorder.CLI.Daemon: stopDaemon :: forall (es :: [Effect]). (Daemons :> es, Delay :> es, File :> es, Reader PidFile :> es, Reader SocketPath :> es, Timeout :> es, UnixSocket :> es) => Force -> Eff es (Either [Text] Text)
+ Tricorder.CLI.Daemon: stopDaemon :: forall (es :: [Effect]). (Daemons :> es, Delay :> es, File :> es, Reader PidFile :> es, Reader SocketPath :> es, Timeout :> es, UnixSocket :> es) => IfThereArePendingQueries -> Eff es (Either [Text] Text)
- Tricorder.CLI.Operations: showSource :: forall (es :: [Effect]). (Console :> es, FileSystem :> es, GhcPkg :> es, Hackage :> es, Input Repl :> es, Input [CabalFile] :> es, Log :> es, PackageStore :> es) => SourceOptions -> Eff es ()
+ Tricorder.CLI.Operations: showSource :: forall (es :: [Effect]). (Console :> es, FileSystem :> es, GhcPkg :> es, Hackage :> es, Input Session :> es, Input [CabalFile] :> es, Log :> es, PackageStore :> es) => SourceOptions -> Eff es ()
- Tricorder.Daemon.Builder: [Consider] :: forall (a :: Type -> Type). FilePath -> FileEvent -> Builder a BuildConsideration
+ Tricorder.Daemon.Builder: [Consider] :: forall (a :: Type -> Type). FilePath -> Event -> Builder a BuildConsideration
- Tricorder.Daemon.Builder: consider :: forall (es :: [Effect]). (HasCallStack, Builder :> es) => FilePath -> FileEvent -> Eff es BuildConsideration
+ Tricorder.Daemon.Builder: consider :: forall (es :: [Effect]). (HasCallStack, Builder :> es) => FilePath -> Event -> Eff es BuildConsideration
- Tricorder.Daemon.Dispatch: dispatch :: KnownTargetNames -> Maybe LoadedModule -> FilePath -> FileEvent -> Maybe DispatchAction
+ Tricorder.Daemon.Dispatch: dispatch :: KnownTargetNames -> Maybe LoadedModule -> FilePath -> Event -> Maybe DispatchAction
- Tricorder.Daemon.TestRunner: [RunTestSuite] :: forall (a :: Type -> Type). Session -> (Suite -> a ()) -> TestTimeout -> RenderedTestCommand -> TestRunner a Suite
+ Tricorder.Daemon.TestRunner: [RunTestSuite] :: forall (a :: Type -> Type). Session -> (Suite -> a ()) -> RenderedTestCommand -> TestRunner a Suite
- Tricorder.Daemon.TestRunner: runTestSuite :: forall (es :: [Effect]). (HasCallStack, TestRunner :> es) => Session -> (Suite -> Eff es ()) -> TestTimeout -> RenderedTestCommand -> Eff es Suite
+ Tricorder.Daemon.TestRunner: runTestSuite :: forall (es :: [Effect]). (HasCallStack, TestRunner :> es) => Session -> (Suite -> Eff es ()) -> RenderedTestCommand -> Eff es Suite
- Tricorder.Daemon.Watch: WatchedFile :: FilePath -> FileEvent -> WatchedFile
+ Tricorder.Daemon.Watch: WatchedFile :: FilePath -> Event -> WatchedFile
- Tricorder.Daemon.Watch: [event] :: WatchedFile -> FileEvent
+ Tricorder.Daemon.Watch: [event] :: WatchedFile -> Event
- Tricorder.Session: Session :: Session -> Session -> Session -> Maybe ByteSize -> WatchDirs -> WatchExclusionPatterns -> ReplBuildDir -> TestTimeout -> GenerateWithHpack -> Hooks -> IdleTimeout -> Session
+ Tricorder.Session: Session :: Session -> Session -> Session -> WatchDirs -> WatchExclusionPatterns -> ReplBuildDir -> GenerateWithHpack -> Hooks -> IdleTimeout -> Session
- Tricorder.Session.Config: Config :: CommandConfig 'Build -> TestConfig -> CommandConfig 'Eval -> [FilePath] -> [Text] -> FilePath -> Int -> Bool -> Maybe Text -> Maybe Hooks -> Int -> Maybe Text -> [Text] -> Maybe [Text] -> Config
+ Tricorder.Session.Config: Config :: CommandConfig 'Build -> TestConfig -> CommandConfig 'Eval -> [FilePath] -> [Text] -> FilePath -> Maybe (Renamed "test_timeout" "test.timeout_ms") -> Maybe (Renamed "test_memory_limit" "test.memory_limit") -> Bool -> Maybe Hooks -> Int -> Maybe (Renamed "command" "build.command_template") -> Maybe (Renamed "targets" "build.targets") -> Maybe (Renamed "test_targets" "test.targets") -> Config
- Tricorder.Session.Config: [command] :: Config -> Maybe Text
+ Tricorder.Session.Config: [command] :: Config -> Maybe (Renamed "command" "build.command_template")
- Tricorder.Session.Config: [targets] :: Config -> [Text]
+ Tricorder.Session.Config: [targets] :: Config -> Maybe (Renamed "targets" "build.targets")
- Tricorder.Session.Config: [testMemoryLimit] :: Config -> Maybe Text
+ Tricorder.Session.Config: [testMemoryLimit] :: Config -> Maybe (Renamed "test_memory_limit" "test.memory_limit")
- Tricorder.Session.Config: [testTargets] :: Config -> Maybe [Text]
+ Tricorder.Session.Config: [testTargets] :: Config -> Maybe (Renamed "test_targets" "test.targets")
- Tricorder.Session.Config: [testTimeout] :: Config -> Int
+ Tricorder.Session.Config: [testTimeout] :: Config -> Maybe (Renamed "test_timeout" "test.timeout_ms")
- Tricorder.Session.Stage.Test.Command: render :: Session -> Maybe ByteSize -> TestTarget -> RenderedTestCommand
+ Tricorder.Session.Stage.Test.Command: render :: Session -> TestTarget -> RenderedTestCommand
- Tricorder.Session.Stage.Test.Config: Options :: Maybe OutputMode -> Options
+ Tricorder.Session.Stage.Test.Config: Options :: Maybe OutputMode -> Maybe (AsRawUnit Millisecond) -> Maybe Text -> Options
- Tricorder.Session.Stage.Test.Session: ResolvedTestOptions :: OutputMode -> ResolvedTestOptions
+ Tricorder.Session.Stage.Test.Session: ResolvedTestOptions :: OutputMode -> Millisecond -> Maybe ByteSize -> ResolvedTestOptions
- Tricorder.Session.Stage.Test.Session: resolve :: Repl -> [Target] -> Config -> Session
+ Tricorder.Session.Stage.Test.Session: resolve :: forall (es :: [Effect]). Log :> es => Repl -> [Target] -> Config -> Eff es Session
- Tricorder.Socket.Client: requestShutdown :: forall (es :: [Effect]). (File :> es, UnixSocket :> es) => Force -> FilePath -> Eff es (Either Text ())
+ Tricorder.Socket.Client: requestShutdown :: forall (es :: [Effect]). (File :> es, UnixSocket :> es) => IfThereArePendingQueries -> FilePath -> Eff es (Either Text ())
- Tricorder.SourceLookup: lookupModuleSource :: forall (es :: [Effect]). (FileSystem :> es, GhcPkg :> es, Hackage :> es, Input Repl :> es, Log :> es, PackageStore :> es) => ProjectSourceDirs -> ReexportLimits -> SourceQuery -> Eff es ModuleSourceResult
+ Tricorder.SourceLookup: lookupModuleSource :: forall (es :: [Effect]). (FileSystem :> es, GhcPkg :> es, Hackage :> es, Input Session :> es, Log :> es, PackageStore :> es) => ProjectSourceDirs -> ReexportLimits -> SourceQuery -> Eff es ModuleSourceResult

Files

CHANGELOG.md view
@@ -7,6 +7,37 @@  ## [Unreleased] +## [1.0.0.0] - 2026-10-29++### Added++- Flag `--use-current` to force Tricorder to _not_ wait for the current build,+  and use the incomplete build when reporting the status. This is the inverse+  of the previous `--wait` flag.+- Flag `--prevent-daemon` to prevent Tricorder from starting the daemon if it+  is not already running. This is the inverse of the previous `--start-daemon`+  flag.++### Changed++- Moved config option `session.test_timeout` to `session.test.timeout_ms`.+- Moved config option `session.test_memory_limit` to `session.test.memory_limit`.+- Renamed flag `--force` to `--ignore-pending-queries`.++### Removed++- Removed deprecated config option `session.command`.+- Removed deprecated config option `session.targets`.+- Removed deprecated config option `session.test_targets`.+- Removed deprecated subcommand `test-results`.+- Removed deprecated subcommand `start`.+- Removed deprecated subcommand `stop`.+- Removed deprecated subcommand `restart`.+- Removed flag `--wait`. Tricorder now waits for the current build to finish by+  default. Use `--use-current` to keep the old default behavior.+- Removed flag `--start-daemon`. Tricorder now starts the daemon by default.+  Use `--prevent-daemon` to keep the old default behavior.+ ## [0.7.0.0] - 2026-10-23  ### Added
src/Tricorder/Build/Changes.hs view
@@ -5,7 +5,7 @@     ) where -import Atelier.Effects.FileWatcher (FileEvent)+import Atelier.Effects.FileWatcher.Event qualified as FileWatcher (Event)   -- | Classifies what kind of file change triggered a dirty signal.@@ -14,9 +14,9 @@ data ChangeKind = SourceChange | CabalChange deriving stock (Eq, Ord, Show)  -data CabalChangeDetected = CabalChangeDetected FilePath FileEvent+data CabalChangeDetected = CabalChangeDetected FilePath FileWatcher.Event     deriving stock (Eq, Show)  -data SourceChangeDetected = SourceChangeDetected FilePath FileEvent+data SourceChangeDetected = SourceChangeDetected FilePath FileWatcher.Event     deriving stock (Eq, Show)
src/Tricorder/CLI/App.hs view
@@ -28,14 +28,13 @@ import Tricorder.API.IfDaemonIsStopped qualified as IfDaemonIsStopped  import Tricorder.CLI.Daemon (startDaemon, waitForDaemon)-import Tricorder.CLI.Operations (showEvalComments, showLog, showSource, showStatus, showTests)+import Tricorder.CLI.Operations (showEvalComments, showLog, showSource, showStatus) import Tricorder.CLI.UI (viewUi) import Tricorder.CLI.UI.Brick (Brick) import Tricorder.CLI.UI.BrickChan (BrickChan) import Tricorder.Daemon.DaemonInfo (DaemonInfo) import Tricorder.Runtime (LogPath (..), PidFile (..), SocketPath (..)) import Tricorder.Session.CabalFile (CabalFile)-import Tricorder.Session.Repl (Repl) import Tricorder.Socket.Client (isDaemonRunning) import Tricorder.Socket.UnixSocket (UnixSocket) import Tricorder.SourceLookup.GhcPkg (GhcPkg)@@ -45,6 +44,7 @@ import Tricorder.CLI.App.Daemon qualified as Daemon import Tricorder.CLI.App.Test qualified as Test import Tricorder.CLI.UI.Keys qualified as Keys+import Tricorder.Session.Stage.Build.Session qualified as Build (Session)   run@@ -62,8 +62,8 @@        , GhcPkg :> es        , Hackage :> es        , IOE :> es+       , Input Build.Session :> es        , Input DaemonInfo :> es-       , Input Repl :> es        , Input [CabalFile] :> es        , Log :> es        , PackageStore :> es@@ -89,11 +89,6 @@                     showStatus opts                 | not running -> Console.putStrLn "Stopped."                 | otherwise -> showStatus opts-        TestResults opts -> do-            running <- isDaemonRunning-            if not running-                then Console.putStrLn "Stopped."-                else showTests opts         Log logMode -> do             logFile <- asks @LogPath (.getLogPath)             case logMode of
src/Tricorder/CLI/App/Test.hs view
@@ -2,19 +2,21 @@  import Atelier.Effects.Console (Console) import Atelier.Effects.Delay (Delay)-import Atelier.Effects.Exit (Exit, exitFailure)+import Atelier.Effects.Exit (Exit) import Atelier.Effects.File (File) import Atelier.Effects.Posix.Daemons (Daemons, PidFile) import Effectful (IOE) import Effectful.Reader.Static (Reader)+import Tricorder.API.IfBuildIsPending (IfBuildIsPending (..)) import Tricorder.API.IfDaemonIsStopped (IfDaemonIsStopped) import Tricorder.API.OutputFormat (OutputFormat (..)) import Tricorder.API.Test (Command (..), SuitesOptions (..))-import Tricorder.API.WaitMode (WaitMode (..))  import Atelier.Effects.Console qualified as Console+import Atelier.Effects.Exit qualified as Exit import Effectful.Reader.Static qualified as Reader import Tricorder.API.FieldMask.Schema qualified as FieldMask.Schema+import Tricorder.API.IfBuildIsPending qualified as IfBuildIsPending import Tricorder.API.IfDaemonIsStopped qualified as IfDaemonIsStopped import Tricorder.API.Test.SuiteView.Schema qualified as SuiteView.Schema @@ -51,7 +53,7 @@ printResult = \case     Left err -> do         Console.putTextLn err-        exitFailure+        Exit.failure     Right res ->         Console.putTextLn res @@ -73,7 +75,7 @@             JsonOutput -> Json.argumentError err         Right (targets, selection) ->             ensureDaemonRunning opts.ifDaemonIsStopped do-                result <- awaitBuildStatus opts.wait+                result <- awaitBuildStatus opts.ifBuildIsPending                 case opts.format of                     TextOutput ->                         pure@@ -106,12 +108,12 @@        , Reader SocketPath :> es        , UnixSocket :> es        )-    => WaitMode -> Eff es (Either Text BuildState)+    => IfBuildIsPending -> Eff es (Either Text BuildState) awaitBuildStatus wait = do     SocketPath sockPath <- Reader.ask     case wait of-        WaitForBuild -> queryStatusWait sockPath-        ShowCurrent -> queryStatus sockPath+        IfBuildIsPending.WaitForBuild -> queryStatusWait sockPath+        IfBuildIsPending.UseCurrent -> queryStatus sockPath   ensureDaemonRunning
src/Tricorder/CLI/Arguments.hs view
@@ -1,4 +1,4 @@-module Tricorder.CLI.Arguments (runArguments, parseArguments) where+module Tricorder.CLI.Arguments (reader) where  import Atelier.Effects.Arguments (Arguments) import Effectful.Reader.Static (Reader)@@ -13,14 +13,10 @@ import Tricorder.Version qualified as Version  -runArguments :: (Arguments :> es) => Eff (Reader Command : es) a -> Eff es a-runArguments eff = do-    args <- parseArguments+reader :: (Arguments :> es) => Eff (Reader Command : es) a -> Eff es a+reader eff = do+    args <- Args.execParser opts     Reader.runReader args eff---parseArguments :: (Arguments :> es) => Eff es Command-parseArguments = Args.execParser opts   opts :: ParserInfo Command
src/Tricorder/CLI/Daemon.hs view
@@ -9,18 +9,19 @@ import Atelier.Effects.Delay (Delay) import Atelier.Effects.File (File) import Atelier.Effects.Posix.Daemons (Daemons)-import Atelier.Effects.Timeout (Timeout, timeout)+import Atelier.Effects.Timeout (Timeout) import Atelier.Time (Millisecond, Second) import Effectful (IOE) import Effectful.NonDet (OnEmptyPolicy (..), emptyEff, runNonDet) import Effectful.Reader.Static (Reader, ask) import Effectful.Writer.Static.Local (runWriter, tell)-import Tricorder.API.Force (Force)+import Tricorder.API.IfThereArePendingQueries (IfThereArePendingQueries) import Prelude hiding (force)  import Atelier.Effects.Delay qualified as Delay import Atelier.Effects.Posix.Daemons qualified as Daemons-import Tricorder.API.Force qualified as Force+import Atelier.Effects.Timeout qualified as Timeout+import Tricorder.API.IfThereArePendingQueries qualified as IfThereArePendingQueries  import Tricorder.Runtime (PidFile, SocketPath (..)) import Tricorder.Socket.Client (isDaemonReady, isDaemonRunning, requestShutdown)@@ -52,7 +53,7 @@        , Timeout :> es        , UnixSocket :> es        )-    => Force -> Eff es (Either [Text] Text)+    => IfThereArePendingQueries -> Eff es (Either [Text] Text) stopDaemon force = do     SocketPath sockPath <- ask     pidFile <- ask@@ -67,11 +68,11 @@         (Nothing, es) -> pure $ Left es   where     timeoutDelay :: Second = case force of-        Force.Force -> 3-        Force.NoForce -> 6+        IfThereArePendingQueries.DontWait -> 3+        IfThereArePendingQueries.Wait -> 6     requestStop sockPath pidFile = do         timeout1second (requestShutdown force sockPath) >>= \_ -> do-            didStop <- fmap isJust $ timeout timeoutDelay $ waitForStop pidFile+            didStop <- fmap isJust $ Timeout.with timeoutDelay $ waitForStop pidFile             if didStop                 then                     pure "Daemon stopped."@@ -86,7 +87,7 @@                 tell ["Daemon did not respond to SIGKILL: " <> show ex]                 emptyEff -    timeout1second = fmap (join . fmap rightToMaybe) . timeout (1 :: Second)+    timeout1second = fmap (join . fmap rightToMaybe) . Timeout.with (1 :: Second)      waitForStop :: forall es'. (Daemons :> es', Delay :> es') => PidFile -> Eff es' ()     waitForStop pidFile = fix \rec -> do@@ -113,7 +114,7 @@        , Timeout :> es        , UnixSocket :> es        )-    => Force+    => IfThereArePendingQueries     -> Eff es (Maybe (Either [Text] Text)) restartDaemon force = do     running <- isDaemonRunning
src/Tricorder/CLI/Main.hs view
@@ -1,38 +1,36 @@ module Tricorder.CLI.Main (main) where -import Atelier.Effects.Arguments (runArgumentsIO)-import Atelier.Effects.Clock (runClock)-import Atelier.Effects.Conc (runConc)-import Atelier.Effects.Console (Console, runConsole, runConsoleOut)-import Atelier.Effects.Delay (runDelay)-import Atelier.Effects.Env (runEnv)-import Atelier.Effects.Exit (exitFailure, runExit)-import Atelier.Effects.File (runFile)-import Atelier.Effects.FileSystem (runFileSystemIO)-import Atelier.Effects.Log (runLogNoOp)-import Atelier.Effects.Posix.Daemons (runDaemons)-import Atelier.Effects.Process (runProcessIO)-import Atelier.Effects.Timeout (runTimeout)+import Atelier.Effects.Console (Console) import Atelier.Signal (installTerminationHandler) import Data.Default (def) import Effectful (runEff) import Effectful.Concurrent (runConcurrent) import Effectful.Provider (provideWith_, runProvider_) +import Atelier.Effects.Arguments qualified as Arguments+import Atelier.Effects.Clock qualified as Clock+import Atelier.Effects.Conc qualified as Conc import Atelier.Effects.Console qualified as Console+import Atelier.Effects.Delay qualified as Delay+import Atelier.Effects.Env qualified as Env+import Atelier.Effects.Exit qualified as Exit+import Atelier.Effects.File qualified as File+import Atelier.Effects.FileSystem qualified as FileSystem import Atelier.Effects.FileSystem.Glob qualified as Glob import Atelier.Effects.Input qualified as Input+import Atelier.Effects.Log qualified as Log+import Atelier.Effects.Posix.Daemons qualified as Daemons+import Atelier.Effects.Process qualified as Process+import Atelier.Effects.Timeout qualified as Timeout -import Tricorder.CLI.Arguments (runArguments) import Tricorder.CLI.UI.Brick (runBrick) import Tricorder.CLI.UI.BrickChan (runBrickChan) import Tricorder.Config (asInput) import Tricorder.Runtime (runLogPath, runPidFile, runProjectRoot, runRuntimeDir, runSocketPath) import Tricorder.Session.CabalFile (inputCabalFiles)-import Tricorder.Session.CommandTemplate (CommandTemplate (..))-import Tricorder.Session.Stage.Build.Session (Session (..))  import Tricorder.CLI.App qualified as App+import Tricorder.CLI.Arguments qualified as CLI.Arguments import Tricorder.CLI.UI.Keys qualified as Keys import Tricorder.Config.LoadedConfig qualified as LoadedConfig import Tricorder.Daemon.DaemonInfo qualified as DaemonInfo@@ -47,18 +45,18 @@ main :: IO () main =     runEff-        . runTimeout+        . Timeout.run         . runConcurrent-        . runConc+        . Conc.runIO         . runBrickChan         . runBrick-        . runConsole-        . runProvider_ @Console @Console.Output runConsoleOut-        . runExit-        . runClock-        . runDelay-        . runFile-        . runFileSystemIO+        . Console.runIO+        . runProvider_ @Console @Console.Output Console.runOut+        . Exit.runIO+        . Clock.runIO+        . Delay.runIO+        . File.run+        . FileSystem.runIO         . Glob.runIO         . runProjectRoot         . runRuntimeDir@@ -69,13 +67,13 @@         . LoadedConfig.asNestedInput @"keybindings" @Keys.Config         . Input.map (either @_ @Keys.Config (const def) id)         . Input.toReader @Keys.Config-        . runDaemons-        . runProcessIO-        . runArgumentsIO-        . runArguments+        . Daemons.run+        . Process.run+        . Arguments.runIO+        . CLI.Arguments.reader         . UnixSocket.runIO-        . runEnv-        . runLogNoOp+        . Env.runIO+        . Log.runNoOp         . StackProject.inputWithCache         . inputCabalFiles         . Tricorder.Config.asInput@@ -85,13 +83,10 @@                     provideWith_ @Console Console.Stderr                         $ Console.putTextLn                         $ "Failed to load session: " <> err-                    exitFailure+                    Exit.failure                 Right session ->                     Input.withConst session-                        -- TODO: Make whatever uses this 'Repl' value use it-                        -- more sensibly. Arbitrarily using the build stage's-                        -- 'Repl' doesn't seem right.-                        . Input.withConst session.buildSession.commandTemplate.repl+                        . Input.withEff (Session.buildSession <$> Input.input)                         . GhcPkg.runGhcPkgIO                         . PackageStore.run                         . Hackage.run
src/Tricorder/CLI/Operations.hs view
@@ -2,7 +2,6 @@     ( showLog     , showSource     , showStatus-    , showTests     , showEvalComments     , showDaemonInfo     )@@ -10,7 +9,7 @@  import Atelier.Effects.Clock (Clock, currentTimeZone) import Atelier.Effects.Console (Console)-import Atelier.Effects.Exit (Exit, exitFailure)+import Atelier.Effects.Exit (Exit) import Atelier.Effects.File (File) import Atelier.Effects.FileSystem (FileSystem, doesFileExist, readFileLbs) import Atelier.Effects.Input (Input, input)@@ -24,30 +23,26 @@     ( EvalCommentsOptions (..)     , SourceOptions (..)     , StatusOptions (..)-    , TestOptions (..)     , Verbosity (..)     )+import Tricorder.API.IfBuildIsPending (IfBuildIsPending (..)) import Tricorder.API.OutputFormat (OutputFormat (..))-import Tricorder.API.WaitMode (WaitMode (..)) import Tricorder.SourceLookup.ReexportLimits (ReexportLimits (..), defaultReexportLimits)  import Atelier.Effects.Console qualified as Console+import Atelier.Effects.Exit qualified as Exit import Data.ByteString.Lazy qualified as BSL import Data.Map.Strict qualified as Map import Data.Text qualified as T+import Tricorder.API.IfBuildIsPending qualified as IfBuildIsPending  import Tricorder.Build (BuildState (..), Severity (..)) import Tricorder.Build.Duration (Duration (..)) import Tricorder.Build.Test (Suites (..))-import Tricorder.CLI.Render-    ( diagnosticLineIndexed-    , formatDuration-    , renderSourceResults-    )+import Tricorder.CLI.Render (diagnosticLineIndexed, formatDuration, renderSourceResults) import Tricorder.Daemon.DaemonInfo (DaemonInfo (..)) import Tricorder.Runtime (SocketPath (..)) import Tricorder.Session.CabalFile (CabalFile)-import Tricorder.Session.Repl (Repl) import Tricorder.Socket.Client (queryStatus, queryStatusWait) import Tricorder.Socket.UnixSocket (UnixSocket) import Tricorder.SourceLookup (lookupModuleSource, projectSourceDirs)@@ -60,6 +55,7 @@ import Tricorder.Build.EvalComment qualified as Eval import Tricorder.Build.Test qualified as Test import Tricorder.Build.Test qualified as Tests+import Tricorder.Session.Stage.Build.Session qualified as Build (Session) import Tricorder.Session.Target qualified as Target import Tricorder.Session.TestTarget qualified as TestTarget @@ -69,7 +65,7 @@ reportBuildFailed msg = do     Console.putTextLn "Build command failed:"     Console.putTextLn msg-    exitFailure+    Exit.failure   showStatus@@ -139,7 +135,7 @@                     mapM_ printDiag (zip [1 ..] result.diagnostics)                     Console.putTextLn $ buildSummary tz result                     mapM_ (uncurry (printTestRun verbosity)) $ Map.toList postBuild.testSuites.getSuites-                    when (buildHasErrors result || Test.hasFailedTests postBuild.testSuites) exitFailure+                    when (buildHasErrors result || Test.hasFailedTests postBuild.testSuites) Exit.failure      printTestRun verbosity tgt tr = do         Console.putTextLn $ case tr of@@ -200,78 +196,12 @@         else readFileLbs path >>= Console.putStr . BSL.toStrict  -showTests-    :: ( Console :> es-       , Exit :> es-       , File :> es-       , Reader SocketPath :> es-       , UnixSocket :> es-       )-    => TestOptions -> Eff es ()-showTests opts = do-    result <- awaitBuildStatus opts.wait-    case result of-        Left err -> Console.putTextLn $ "Error: " <> err-        Right state ->-            case state.phase of-                Build.Starting -> Console.putStrLn "Daemon starting, no test results yet."-                Build.Building _ _ -> Console.putStrLn "Build in progress, no test results yet."-                Build.PostBuilding _ postBuild -> renderTestRuns postBuild.testSuites.getSuites-                Build.Finished _ postBuild -> renderTestRuns postBuild.testSuites.getSuites-                Build.Failed msg -> reportBuildFailed msg-  where-    renderTestRuns suites-        | Map.null suites = Console.putStrLn "No test results."-        | Map.null filteredSuites = do-            Console.putStrLn "All passed."-            mapM_ (Console.putTextLn . ("  " <>) . TestTarget.render) $ Map.keys suites-        | otherwise = do-            mapM_ (uncurry printTestOutput) $ Map.toList filteredSuites-            when (any Test.isFailedRun filteredSuites) exitFailure-      where-        filteredSuites =-            if opts.failedOnly-                then-                    Map.filter Test.isFailedRun suites-                else-                    suites--    printTestOutput tgt tr = case tr of-        Test.SuiteRunning Nothing ->-            Console.putTextLn $ t <> "running..."-        Test.SuiteRunning (Just p) ->-            Console.putTextLn $ t <> "running... (" <> show p.compiled <> "/" <> show p.total <> ")"-        Test.SuiteErrored e ->-            Console.putTextLn $ t <> "error: " <> e.message-        Test.SuiteCompleted c -> do-            Console.putTextLn $ t <> completionSummary c-            if opts.failedOnly-                then-                    if null c.testCases-                        then do-                            Console.putTextLn "  (unrecognised test runner format — showing full output)"-                            mapM_ (Console.putTextLn . ("  " <>)) (stripGhciNoise (lines c.output))-                        else-                            mapM_ printFailedCase (filter Test.caseFailed c.testCases)-                else-                    mapM_ (Console.putTextLn . ("  " <>)) (stripGhciNoise (lines c.output))-      where-        t = TestTarget.render tgt <> "  "--    printFailedCase tc = do-        Console.putTextLn $ "  " <> tc.description-        case tc.outcome of-            Test.Failed details ->-                mapM_ (Console.putTextLn . ("    " <>)) (T.lines details)-            Test.Passed -> pure ()-- showSource     :: ( Console :> es        , FileSystem :> es        , GhcPkg :> es        , Hackage :> es-       , Input Repl :> es+       , Input Build.Session :> es        , Input [CabalFile] :> es        , Log :> es        , PackageStore :> es@@ -332,7 +262,7 @@ displayTextOutput = \case     Left err -> do         Console.putTextLn $ "Error: " <> err-        exitFailure+        Exit.failure     Right (BuildState phase _) -> case phase of         Build.Starting -> Console.putStrLn "Starting..."         Build.Building _ _ -> Console.putStrLn "Building..."@@ -340,7 +270,7 @@         Build.Finished _ postBuild -> txtEvalComments postBuild         Build.Failed err -> do             Console.putTextLn $ "Error: " <> err-            exitFailure+            Exit.failure   where     txtEvalComments postBuild =         let evalutatingComments = Eval.phasePending postBuild.evalComments@@ -387,13 +317,13 @@ displayJsonOutput = \case     Left err -> do         putJson $ Eval.Failed err-        exitFailure+        Exit.failure     Right (BuildState phase _) -> case phase of         Build.Starting -> putJson Eval.Starting         Build.Building _ _ -> putJson Eval.Building         Build.Failed msg -> do             putJson $ Eval.Failed msg-            exitFailure+            Exit.failure         Build.PostBuilding _ postBuild -> jsonEvalComments postBuild         Build.Finished _ postBuild -> jsonEvalComments postBuild   where@@ -444,9 +374,9 @@        , Reader SocketPath :> es        , UnixSocket :> es        )-    => WaitMode -> Eff es (Either Text BuildState)+    => IfBuildIsPending -> Eff es (Either Text BuildState) awaitBuildStatus wait = do     SocketPath sockPath <- ask     case wait of-        WaitForBuild -> queryStatusWait sockPath-        ShowCurrent -> queryStatus sockPath+        IfBuildIsPending.WaitForBuild -> queryStatusWait sockPath+        IfBuildIsPending.UseCurrent -> queryStatus sockPath
src/Tricorder/CLI/UI/Keys.hs view
@@ -9,7 +9,7 @@ where  import Atelier.Effects.Console (Console)-import Atelier.Effects.Exit (Exit, exitFailure)+import Atelier.Effects.Exit (Exit) import Brick     ( EventM     , Widget@@ -49,6 +49,7 @@ import Text.Casing (quietSnake)  import Atelier.Effects.Console qualified as Console+import Atelier.Effects.Exit qualified as Exit import Data.Map.Strict qualified as Map import Data.Set qualified as Set import Data.Text qualified as T@@ -154,7 +155,7 @@     unless (null errors) do         Console.putTextLn "Error(s) encountered when attempting to parse key bindings:"         traverse_ (Console.putTextLn . toText) errors-        exitFailure+        Exit.failure     pure customBindings  @@ -228,7 +229,7 @@                 $ T.intercalate "\n\n"                 $ "Your key bindings have collisions:"                     : (uncurry showCollision <$> collisions)-            exitFailure+            Exit.failure         Right dispatcher -> pure dispatcher   where     showCollision binding' handlers =
src/Tricorder/Config.hs view
@@ -6,7 +6,7 @@  import Atelier.Effects.Input (Input) import Atelier.Types.JSON.QuietSnake (QuietSnake (..))-import Atelier.Types.WithDefaults (WithDefaults (..))+import Atelier.Types.JSON.WithDefaults (WithDefaults (..)) import Data.Aeson (FromJSON, ToJSON) import Data.Default (Default (..)) 
src/Tricorder/Daemon/Builder.hs view
@@ -11,7 +11,6 @@ where  import Atelier.Effects.Clock (Clock, UTCTime)-import Atelier.Effects.FileWatcher (FileEvent) import Atelier.Effects.Log (Log) import Atelier.Effects.PubSub.Pub (Pub) import Atelier.Time (nominalDiffTime)@@ -25,6 +24,7 @@ import System.FilePath (normalise)  import Atelier.Effects.Clock qualified as Clock+import Atelier.Effects.FileWatcher.Event qualified as FileWatcher (Event) import Atelier.Effects.Log qualified as Log import Atelier.Effects.PubSub.Pub qualified as Pub import Data.Map.Strict qualified as Map@@ -58,7 +58,7 @@   data Builder :: Effect where-    Consider :: FilePath -> FileEvent -> Builder m BuildConsideration+    Consider :: FilePath -> FileWatcher.Event -> Builder m BuildConsideration     Build :: DispatchAction -> Builder m (Either BuildFailure NewLoadResult)  @@ -127,7 +127,7 @@ considerBuild     :: (State BuilderState :> es)     => FilePath-    -> FileEvent+    -> FileWatcher.Event     -> Eff es BuildConsideration considerBuild fp event = do     builderState <- get
src/Tricorder/Daemon/Core.hs view
@@ -3,7 +3,6 @@ import Atelier.Effects.Chan (Chan) import Atelier.Effects.Clock (Clock) import Atelier.Effects.Conc (Conc)-import Atelier.Effects.FileWatcher (FileEvent) import Atelier.Effects.Log (Log) import Atelier.Effects.Process (Process) import Atelier.Effects.PubSub.Pub (Pub)@@ -15,6 +14,7 @@ import Relude.Extra.Tuple (dup)  import Atelier.Effects.Conc qualified as Conc+import Atelier.Effects.FileWatcher.Event qualified as FileWatcher (Event) import Atelier.Effects.Log qualified as Log import Atelier.Effects.PubSub qualified as PubSub import Atelier.Effects.PubSub.Pub qualified as Pub@@ -24,7 +24,6 @@ import Effectful.State.Static.Shared qualified as State  import Tricorder.Build (BuildId, BuildPhase, BuildResult, PostBuild (..), Severity (..))-import Tricorder.Build.ByteSize (ByteSize) import Tricorder.Build.Changes (CabalChangeDetected (..), SourceChangeDetected (..)) import Tricorder.Daemon.Builder     ( BuildConsideration (..)@@ -40,7 +39,6 @@ import Tricorder.Runtime (ProjectRoot (..)) import Tricorder.Session (Session (..)) import Tricorder.Session.Stage.Test.Command (RenderedTestCommand (..))-import Tricorder.Session.TestTimeout (TestTimeout (..)) import Tricorder.Waiters (Waiters)  import Tricorder.Build qualified as Build@@ -66,7 +64,7 @@ data RestartBuilder = RestartBuilder  -data ReloadBuilder = ReloadBuilder FilePath FileEvent+data ReloadBuilder = ReloadBuilder FilePath FileWatcher.Event   -- | Top of the build loop. Responsible for handling changes to the Tricorder@@ -282,10 +280,7 @@     => Session -> BuildResult -> Eff es Test.Suites runTests session buildResult     | hasTargets session.testSession.targets && noErrors buildResult.diagnostics =-        runTestsForTargets-            session.testSession-            session.testMemoryLimit-            session.testTimeout+        runTestsForTargets session.testSession     | otherwise = pure mempty   where     hasTargets = not . null@@ -298,10 +293,8 @@        , TestRunner :> es        )     => Test.Session-    -> Maybe ByteSize-    -> TestTimeout     -> Eff es Test.Suites-runTestsForTargets testSession memoryLimit testTimeout = do+runTestsForTargets testSession = do     Pub.publish $ Test.Suites initial     Log.info $ "Running " <> show (length testSession.targets) <> " test suite(s)"     fmap Test.Suites . State.execState initial $ traverse_ go testSession.targets@@ -309,7 +302,7 @@     initial = Map.fromList $ (,Test.SuiteRunning Nothing) <$> testSession.targets     go target = do         Log.info $ "Running tests: " <> renderedTarget-        let testCommand = Test.Command.render testSession memoryLimit target+        let testCommand = Test.Command.render testSession target             publishProgress suite = do                 updated <- State.state $ dup . Map.insert target suite                 Pub.publish $ Test.Suites updated@@ -318,7 +311,7 @@                 <> renderedTarget                 <> " command:\n"                 <> show testCommand.command-        finishedSuite <- TestRunner.runTestSuite testSession publishProgress testTimeout testCommand+        finishedSuite <- TestRunner.runTestSuite testSession publishProgress testCommand         case finishedSuite of             Test.SuiteErrored (Test.SuiteError message) ->                 Log.warn $ "Test suite " <> renderedTarget <> "failed: " <> message
src/Tricorder/Daemon/Dispatch.hs view
@@ -12,9 +12,10 @@     ) where -import Atelier.Effects.FileWatcher (FileEvent (..)) import System.FilePath (isAbsolute, normalise, splitDirectories, takeExtension, (</>)) +import Atelier.Effects.FileWatcher.Event qualified as FileWatcher (Event)+import Atelier.Effects.FileWatcher.Event qualified as FileWatcher.Event import Data.List qualified as List import Data.Map.Strict qualified as Map import Data.Set qualified as Set@@ -126,22 +127,22 @@     :: KnownTargetNames     -> Maybe Ghci.LoadedModule     -> FilePath-    -> FileEvent+    -> FileWatcher.Event     -> Maybe DispatchAction dispatch knownTargets known fp event = case known of     Just lm -> Just $ case event of-        Added -> Reload-        Modified -> Reload-        Removed -> Unadd lm.moduleName+        FileWatcher.Event.Added -> Reload+        FileWatcher.Event.Modified -> Reload+        FileWatcher.Event.Removed -> Unadd lm.moduleName     Nothing         | fileMatchesAnyTarget knownTargets fp -> case event of-            Added -> Just Reload-            Modified -> Just Reload-            Removed -> Nothing+            FileWatcher.Event.Added -> Just Reload+            FileWatcher.Event.Modified -> Just Reload+            FileWatcher.Event.Removed -> Nothing         | otherwise -> case event of-            Added -> Just (Add fp)-            Modified -> Just (Add fp)-            Removed -> Nothing+            FileWatcher.Event.Added -> Just (Add fp)+            FileWatcher.Event.Modified -> Just (Add fp)+            FileWatcher.Event.Removed -> Nothing   -- | Keep only diagnostics whose file is under one of the watched directories.
src/Tricorder/Daemon/Ghci/Config/File.hs view
@@ -6,17 +6,13 @@  import Atelier.Time (AsRawUnit (..), Millisecond) import Atelier.Types.JSON.QuietSnake (QuietSnake (..))-import Atelier.Types.WithDefaults-    ( WithDefaults (..)-    )+import Atelier.Types.JSON.WithDefaults (WithDefaults (..)) import Data.Aeson (FromJSON, ToJSON) import Data.Default (Default (..))  import Atelier.Time qualified as Time -import Tricorder.Daemon.Ghci.Config-    ( Config (..)-    )+import Tricorder.Daemon.Ghci.Config (Config (..))   data File = File
src/Tricorder/Daemon/Ghci/Process.hs view
@@ -30,7 +30,7 @@     , setWorkingDir     , shell     )-import Atelier.Effects.Timeout (Timeout, timeout)+import Atelier.Effects.Timeout (Timeout) import Control.Concurrent.STM (TVar, modifyTVar', readTVar, retry, writeTVar) import Data.Sequence ((|>)) import Data.Time.Units (Second)@@ -43,6 +43,7 @@ import Atelier.Effects.Log qualified as Log import Atelier.Effects.Process qualified as Atelier import Atelier.Effects.Process qualified as Process+import Atelier.Effects.Timeout qualified as Timeout import Data.Text qualified as T  import Tricorder.Daemon.Ghci.Config (Config (..))@@ -289,7 +290,7 @@     void $ trySync $ do         File.hPutTextLn ghciProcess.stdin ":quit"         File.hFlush ghciProcess.stdin-    void $ timeout config.shutdownTimeout (Process.waitExitCode ghciProcess.handle)+    void $ Timeout.with config.shutdownTimeout (Process.waitExitCode ghciProcess.handle)   -- ---------------------------------------------------------------------------@@ -407,7 +408,7 @@      Conc.scoped do         stderrThread <- Conc.fork drainStderr-        result <- timeout delay $ trySync watchStdout+        result <- Timeout.with delay $ trySync watchStdout         case result of             Just (Right ()) -> pure ()             Just (Left ex) -> do@@ -416,7 +417,7 @@                 -- pipe lingers) so we surface its *complete* output — e.g.                 -- cabal's dependency-resolution error — rather than whatever                 -- the drain happened to have read so far.-                _ <- timeout (1 :: Second) (Conc.await stderrThread)+                _ <- Timeout.with (1 :: Second) (Conc.await stderrThread)                 captured <- atomically $ readTVar capturedVar                 throwIO $ StartupFailed $ fromMaybe (startupExitedMessage ex) (renderCapturedLines captured)             Nothing -> do
src/Tricorder/Daemon/HookDispatch.hs view
@@ -1,10 +1,11 @@ module Tricorder.Daemon.HookDispatch (main) where -import Atelier.Effects.FileWatcher (FileEvent (..)) import Atelier.Effects.Process (Process) import Atelier.Effects.PubSub.Sub (Sub) import Effectful.Reader.Static (Reader) +import Atelier.Effects.FileWatcher.Event qualified as FileWatcher+import Atelier.Effects.FileWatcher.Event qualified as FileWatcher.Event import Atelier.Effects.PubSub.Sub qualified as Sub import System.FilePath qualified as Path @@ -27,8 +28,9 @@             | otherwise -> pure ()  -runHsHook :: (Process :> es, Reader ProjectRoot :> es) => HaskellHooks -> FileEvent -> Eff es ()+runHsHook+    :: (Process :> es, Reader ProjectRoot :> es) => HaskellHooks -> FileWatcher.Event -> Eff es () runHsHook hooks = \case-    Added -> whenJust hooks.fileAdded Hook.run-    Modified -> whenJust hooks.fileModified Hook.run-    Removed -> whenJust hooks.fileRemoved Hook.run+    FileWatcher.Event.Added -> whenJust hooks.fileAdded Hook.run+    FileWatcher.Event.Modified -> whenJust hooks.fileModified Hook.run+    FileWatcher.Event.Removed -> whenJust hooks.fileRemoved Hook.run
src/Tricorder/Daemon/Hpack.hs view
@@ -11,6 +11,7 @@  import Atelier.Effects.Conc qualified as Conc import Atelier.Effects.FileWatcher qualified as FileWatcher+import Atelier.Effects.FileWatcher.Watch qualified as FileWatcher.Watch import Atelier.Effects.Log qualified as Log import Atelier.Effects.PubSub qualified as PubSub import Atelier.Effects.PubSub.Pub qualified as Pub@@ -73,7 +74,7 @@     => ProjectRoot -> Eff es Void watchPackageYaml root = do     FileWatcher.watchFilePathsDebounced-        [FileWatcher.dirWhere root.getProjectRoot (packageYaml `List.isSuffixOf`)]+        [FileWatcher.Watch.dirWhere root.getProjectRoot (packageYaml `List.isSuffixOf`)]         \path _ -> Pub.publish $ RePack path  
src/Tricorder/Daemon/IdleTimer.hs view
@@ -8,7 +8,7 @@ import Atelier.Effects.Clock (Clock, currentTime) import Atelier.Effects.Conc (Conc) import Atelier.Effects.Delay (Delay)-import Atelier.Effects.Exit (Exit, exitSuccess)+import Atelier.Effects.Exit (Exit) import Atelier.Effects.Input (Input, input) import Atelier.Effects.Log (Log) import Atelier.Time (Second, nominalDiffTime)@@ -22,6 +22,7 @@  import Atelier.Effects.Conc qualified as Conc import Atelier.Effects.Delay qualified as Delay+import Atelier.Effects.Exit qualified as Exit import Atelier.Effects.Log qualified as Log  import Tricorder.Session.IdleTimeout (IdleTimeout (..))@@ -78,7 +79,7 @@                             $ "Idle for "                                 <> show secs                                 <> " with no active connections, shutting down."-                        exitSuccess+                        Exit.success      interpretWith act \env -> \case         WithActivity action -> do
src/Tricorder/Daemon/Main.hs view
@@ -1,29 +1,29 @@ module Tricorder.Daemon.Main (main) where -import Atelier.Effects.Chan (runChan)-import Atelier.Effects.Clock (runClock)-import Atelier.Effects.Conc (runConc)-import Atelier.Effects.Debounce (runDebounce)-import Atelier.Effects.Delay (runDelay)-import Atelier.Effects.Env (runEnv)-import Atelier.Effects.Exit (runExit)-import Atelier.Effects.File (runFile)-import Atelier.Effects.FileSystem (runFileSystemIO)-import Atelier.Effects.FileWatcher (runFileWatcherIO)-import Atelier.Effects.Process (runProcessIO)-import Atelier.Effects.Timeout (runTimeout) import Atelier.Signal (installTerminationHandler) import Data.Default (def) import Effectful (runEff) import Effectful.Concurrent (runConcurrent) +import Atelier.Effects.Chan qualified as Chan+import Atelier.Effects.Clock qualified as Clock import Atelier.Effects.Conc qualified as Conc+import Atelier.Effects.Debounce qualified as Debounce+import Atelier.Effects.Delay qualified as Delay+import Atelier.Effects.Env qualified as Env+import Atelier.Effects.Exit qualified as Exit+import Atelier.Effects.FSNotify qualified as FSNotify+import Atelier.Effects.File qualified as File+import Atelier.Effects.FileSystem qualified as FileSystem import Atelier.Effects.FileSystem.Glob qualified as Glob+import Atelier.Effects.FileWatcher qualified as FileWatcher import Atelier.Effects.Input qualified as Input import Atelier.Effects.Log qualified as Log+import Atelier.Effects.Process qualified as Process import Atelier.Effects.PubSub qualified as PubSub import Atelier.Effects.PubSub.Pub qualified as Pub import Atelier.Effects.PubSub.Sub qualified as Sub+import Atelier.Effects.Timeout qualified as Timeout import Effectful.Reader.Static qualified as Reader import Effectful.State.Static.Shared qualified as State @@ -62,27 +62,28 @@ main =     runEff         . runConcurrent-        . runConc-        . runClock-        . runDelay-        . runTimeout-        . runDebounce @FilePath-        . runFileWatcherIO-        . runFileSystemIO+        . Conc.runIO+        . Clock.runIO+        . Delay.runIO+        . Timeout.run+        . Debounce.run @FilePath+        . FileSystem.runIO+        . FSNotify.run+        . FileWatcher.runIO         . Glob.runIO         . runProjectRoot-        . runExit-        . runFile+        . Exit.runIO+        . File.run         . runRuntimeDir         . runSocketPath         . runLogPath-        . runEnv+        . Env.runIO         . LoadedConfig.asInput         . runLogging-        . runChan+        . Chan.run         . StackProject.inputWithCache         . inputCabalFiles-        . runProcessIO+        . Process.run         . runGhcPkgIO         . UnixSocket.runIO         . Ghci.Session.run
src/Tricorder/Daemon/TestRunner.hs view
@@ -31,7 +31,7 @@     , setWorkingDir     , shell     )-import Atelier.Effects.Timeout (Timeout, timeout)+import Atelier.Effects.Timeout (Timeout) import Control.Concurrent.STM (modifyTVar') import Control.Exception (throwIO) import Data.Sequence ((|>))@@ -49,6 +49,8 @@ import Atelier.Effects.Conc qualified as Conc import Atelier.Effects.File qualified as File import Atelier.Effects.Process qualified as Process+import Atelier.Effects.Timeout qualified as Timeout+import Atelier.Time qualified as Time import Data.List qualified as List import Data.Text qualified as T @@ -59,7 +61,6 @@ import Tricorder.Session.Stage.Test.Command (RenderedTestCommand (..)) import Tricorder.Session.Stage.Test.Config (OutputMode (..)) import Tricorder.Session.Stage.Test.Session (ResolvedTestOptions (..), Session (..))-import Tricorder.Session.TestTimeout (TestTimeout (..)) import Tricorder.TestOutput (parseHspecDuration, parseHspecOutput)  import Tricorder.Build.Test qualified as Test@@ -75,7 +76,6 @@         :: Session         -> (Test.Suite -> m ())         -- ^ Handler for test run progress-        -> TestTimeout         -> RenderedTestCommand         -> TestRunner m Test.Suite @@ -98,12 +98,12 @@     => Eff (TestRunner : es) a -> Eff es a run act = do     interpretWith act \env -> \case-        RunTestSuite session progressHandler testTimeout cmd ->+        RunTestSuite session progressHandler cmd ->             localUnlift env (ConcUnlift Persistent Unlimited) \unlift -> do                 let onProgress = unlift . progressHandler . loadingToProgress                 case cmd.options.outputMode of-                    ReplOutput -> runTestSuiteWithGHCi session onProgress testTimeout cmd.command-                    StdoutOutput -> runTestSuiteWithStdout onProgress testTimeout cmd.command+                    ReplOutput -> runTestSuiteWithGHCi session onProgress cmd.command+                    StdoutOutput -> runTestSuiteWithStdout session onProgress cmd.command   runTestSuiteWithStdout@@ -114,11 +114,11 @@        , Reader ProjectRoot :> es        , Timeout :> es        )-    => (Ghci.Loading -> Eff es ())-    -> TestTimeout+    => Session+    -> (Ghci.Loading -> Eff es ())     -> RenderedCommand 'Test     -> Eff es Test.Suite-runTestSuiteWithStdout onProgress testTimeout cmd = do+runTestSuiteWithStdout session onProgress cmd = do     ProjectRoot projectRoot <- ask     -- Lines from both streams, in the order they arrived.     outputVar <- newTVarIO (mempty :: Seq Text)@@ -149,11 +149,9 @@      result <- trySync         $ Process.withProcessGroup processConfig \p ->-            case testTimeout of-                TestTimeout secs | secs <= 0 -> Right <$> runToCompletion p-                TestTimeout secs ->-                    let duration = fromIntegral secs :: Second-                    in  maybeToRight secs <$> timeout duration (runToCompletion p)+            case Time.convertUnit @_ @Second session.options.timeout of+                secs | secs <= 0 -> Right <$> runToCompletion p+                secs -> maybeToRight secs <$> Timeout.with secs (runToCompletion p)     output <- T.unlines . toList <$> readTVarIO outputVar     let completed passed =             Test.SuiteCompleted@@ -191,19 +189,17 @@        )     => Session     -> (Ghci.Loading -> Eff es ())-    -> TestTimeout     -> RenderedCommand 'Test     -> Eff es Test.Suite-runTestSuiteWithGHCi session onProgress testTimeout cmd = do+runTestSuiteWithGHCi session onProgress cmd = do     ProjectRoot projectRoot <- ask     result <- trySync         $ Ghci.Process.with session.ghci cmd projectRoot onProgress noReady \ghci _ ->-            case testTimeout of-                TestTimeout secs | secs <= 0 -> Right <$> Ghci.Process.exec ghci ":main" noProgress-                TestTimeout secs ->-                    let duration = fromIntegral secs :: Second-                    in  maybeToRight secs-                            <$> timeout duration (Ghci.Process.exec ghci ":main" noProgress)+            case Time.convertUnit @_ @Second session.options.timeout of+                secs | secs <= 0 -> Right <$> Ghci.Process.exec ghci ":main" noProgress+                secs ->+                    maybeToRight secs+                        <$> Timeout.with secs (Ghci.Process.exec ghci ":main" noProgress)     case result of         Left ex -> case fromException ex of             Just (e :: Ghci.Process.Error) -> case e of@@ -261,7 +257,7 @@ runScripted results =     reinterpret_         (evalState results)-        (\(RunTestSuite _ _ _ _) -> popResult)+        (\(RunTestSuite _ _ _) -> popResult)   where     popResult :: Eff (State [Either SomeException Test.Suite] : es) Test.Suite     popResult =
src/Tricorder/Daemon/Watch.hs view
@@ -8,21 +8,16 @@ where  import Atelier.Effects.Debounce (Debounce)-import Atelier.Effects.FileWatcher-    ( FileEvent-    , FileWatcher-    , Watch-    , containing-    , dirExt-    , dirWhere-    , excluding-    )+import Atelier.Effects.FileWatcher (FileWatcher) import Atelier.Effects.PubSub.Pub (Pub) import System.FilePath (takeExtension, takeFileName) import Text.Regex.TDFA (ExecOption (..), blankCompOpt, blankExecOpt, match) import Text.Regex.TDFA.TDFA (patternToRegex)  import Atelier.Effects.FileWatcher qualified as FileWatcher+import Atelier.Effects.FileWatcher.Event qualified as FileWatcher (Event)+import Atelier.Effects.FileWatcher.Watch qualified as FileWatcher (Watch)+import Atelier.Effects.FileWatcher.Watch qualified as Watch import Atelier.Effects.PubSub.Pub qualified as Pub  import Tricorder.Build.Changes@@ -38,7 +33,7 @@  data WatchedFile = WatchedFile     { path :: FilePath-    , event :: FileEvent+    , event :: FileWatcher.Event     }  @@ -72,18 +67,18 @@     | otherwise = SourceChange  -specs :: ProjectRoot -> WatchExclusionPatterns -> WatchDirs -> [Watch]+specs :: ProjectRoot -> WatchExclusionPatterns -> WatchDirs -> [FileWatcher.Watch] specs projectRoot watchExclusionPatterns watchDirs =     sourceWatches (coerce watchExclusionPatterns) (coerce watchDirs)         <> cabalWatches projectRoot  -sourceWatches :: [Pattern] -> [FilePath] -> [Watch]+sourceWatches :: [Pattern] -> [FilePath] -> [FileWatcher.Watch] sourceWatches exclusionPatterns =     map \d ->-        dirExt d ".hs"-            `excluding` containing "dist-newstyle"-            `excluding` exclusionMatches exclusionPatterns+        Watch.dirExt d ".hs"+            `Watch.excluding` Watch.containing "dist-newstyle"+            `Watch.excluding` exclusionMatches exclusionPatterns   exclusionMatches :: [Pattern] -> FilePath -> Bool@@ -95,9 +90,11 @@             fp  -cabalWatches :: ProjectRoot -> [Watch]+cabalWatches :: ProjectRoot -> [FileWatcher.Watch] cabalWatches (ProjectRoot projectRoot) =-    [dirWhere projectRoot isCabalFile `excluding` containing "dist-newstyle"]+    [ Watch.dirWhere projectRoot isCabalFile+        `Watch.excluding` Watch.containing "dist-newstyle"+    ]   isCabalFile :: FilePath -> Bool
src/Tricorder/Logging.hs view
@@ -3,11 +3,7 @@ import Atelier.Effects.Env (Env) import Atelier.Effects.File (BufferMode (..), File) import Atelier.Effects.Input (Input)-import Atelier.Effects.Log-    ( Log-    , Severity (..)-    , runLogToHandle-    )+import Atelier.Effects.Log (Log, Severity (..)) import Effectful (IOE) import Effectful.Reader.Static (Reader, asks) @@ -31,10 +27,10 @@     => Eff (Log : es) a -> Eff es a runLogging act = do     path <- asks @LogPath (.getLogPath)-    File.withFile path AppendMode \h -> do+    File.with path AppendMode \h -> do         File.hSetBuffering h LineBuffering         (maybeErr, sev) <- getMinimumSeverity-        runLogToHandle h sev do+        Log.runToHandle h sev do             whenJust maybeErr \err -> Log.err err             act 
src/Tricorder/Session.hs view
@@ -17,7 +17,6 @@ import Data.Text qualified as T import Effectful.Reader.Static qualified as Reader -import Tricorder.Build.ByteSize (ByteSize) import Tricorder.Runtime (ProjectRoot (..)) import Tricorder.Session.CabalFile (CabalFile) import Tricorder.Session.CommandConfig (CommandConfig (..))@@ -31,7 +30,6 @@ import Tricorder.Session.Stage.Test.Config (TestConfig (..)) import Tricorder.Session.Target (definesCustomPrelude) import Tricorder.Session.TestTarget (getTestTarget)-import Tricorder.Session.TestTimeout (TestTimeout (..)) import Tricorder.Session.Util (displayList, indent) import Tricorder.Session.WatchDirs (WatchDirs (..)) import Tricorder.Session.WatchExclusionPatterns@@ -39,7 +37,6 @@     , resolveWatchExclusionPatterns     ) -import Tricorder.Build.ByteSize qualified as ByteSize import Tricorder.Config qualified as Tricorder import Tricorder.Session.Hooks qualified as Hooks import Tricorder.Session.Stage.Build.Session qualified as Build (Session)@@ -55,11 +52,9 @@     { buildSession :: Build.Session     , testSession :: Test.Session     , evalSession :: Eval.Session-    , testMemoryLimit :: Maybe ByteSize     , watchDirs :: WatchDirs     , watchExclusionPatterns :: WatchExclusionPatterns     , replBuildDir :: ReplBuildDir-    , testTimeout :: TestTimeout     , generateWithHpack :: GenerateWithHpack     , hooks :: Hooks     , idleTimeout :: IdleTimeout@@ -73,11 +68,9 @@             { buildSession = def             , testSession = def             , evalSession = def-            , testMemoryLimit = Nothing             , watchDirs = def             , watchExclusionPatterns = def             , replBuildDir = def-            , testTimeout = def             , generateWithHpack = def             , hooks = def             , idleTimeout = def@@ -111,15 +104,6 @@     warnDeprecatedConfig cfgFile     Log.debug "Warned about deprecated config stuff" -    testMemoryLimit <- case cfgFile.testMemoryLimit of-        Nothing -> pure Nothing-        Just limit -> case ByteSize.fromText limit of-            Nothing -> do-                Log.err $ "Unable to parse test_memory_limit: " <> limit-                pure Nothing-            Just parsedLimit ->-                pure $ Just parsedLimit-     watchExclusionPatterns <-         case resolveWatchExclusionPatterns cfgFile.watchExclusionPatterns of             Left err -> do@@ -135,8 +119,8 @@      repl <- resolveRepl projectRoot     buildSession <- Build.Session.resolve cfgFile repl projectFiles-    let testSession = Test.Session.resolve repl buildSession.targets cfgFile-        evalSession = Eval.Session.resolve repl cfgFile+    testSession <- Test.Session.resolve repl buildSession.targets cfgFile+    let evalSession = Eval.Session.resolve repl cfgFile         effectiveTargets = buildSession.targets <> (getTestTarget <$> testSession.targets)         watchDirs = WatchDirs.resolve projectRoot projectFiles cfgFile effectiveTargets @@ -151,10 +135,7 @@     warnMissingTargetPlaceholder "test" cfgFile.test.commandConfig.commandTemplate     warnMissingTargetPlaceholder "eval" cfgFile.eval.commandTemplate -    warnIgnoredextraAutoArguments-        "build"-        (cfgFile.build.commandTemplate <|> cfgFile.command)-        cfgFile.build.extraAutoArguments+    warnIgnoredextraAutoArguments "build" cfgFile.build.commandTemplate cfgFile.build.extraAutoArguments     warnIgnoredextraAutoArguments         "test"         cfgFile.test.commandConfig.commandTemplate@@ -168,9 +149,7 @@             , evalSession             , watchDirs             , watchExclusionPatterns-            , testMemoryLimit             , replBuildDir = ReplBuildDir cfgFile.replBuildDir-            , testTimeout = TestTimeout cfgFile.testTimeout             , generateWithHpack = GenerateWithHpack cfgFile.generateWithHpack             , hooks = fromMaybe def cfgFile.hooks             , idleTimeout = IdleTimeout $ fromIntegral cfgFile.idleTimeoutSeconds@@ -245,9 +224,6 @@                 (toText . showPattern . fst)                 session.watchExclusionPatterns.getWatchExclusionPatterns         , "Repl build dir: " <> toText session.replBuildDir.getReplBuildDir-        , -- TODO: Remove " seconds" when TestTimeout is converted to a proper time unit.-          "Test timeout: " <> show session.testTimeout.getTestTimeout <> " seconds"-        , "Test memory limit: " <> show session.testMemoryLimit         , "Generate with hpack: " <> show session.generateWithHpack.getGenerateWithHpack         , "Hooks:"         , indent $ Hooks.display session.hooks
src/Tricorder/Session/CabalFile.hs view
@@ -128,7 +128,7 @@     => Eff es (Either Text [FilePath]) discoverCabalPackages = do     ProjectRoot projectRoot <- Reader.ask-    homeCabalFiles <- maybe [] (one . (</> ".cabal/config")) <$> Env.lookupEnv "HOME"+    homeCabalFiles <- maybe [] (one . (</> ".cabal/config")) <$> Env.lookup "HOME"     let projectFilePaths = projectCabalFiles projectRoot <> homeCabalFiles     projectFiles <- filterM doesFileExist projectFilePaths     if null projectFiles
src/Tricorder/Session/Config.hs view
@@ -1,7 +1,8 @@ module Tricorder.Session.Config (Config (..)) where  import Atelier.Types.JSON.QuietSnake (QuietSnake (..))-import Atelier.Types.WithDefaults (WithDefaults (..))+import Atelier.Types.JSON.Renamed (Renamed)+import Atelier.Types.JSON.WithDefaults (WithDefaults (..)) import Data.Aeson (FromJSON (..), ToJSON) import Data.Default (Default (..)) @@ -19,20 +20,14 @@     , watchDirs :: [FilePath]     , watchExclusionPatterns :: [Text]     , replBuildDir :: FilePath-    , testTimeout :: Int+    , testTimeout :: Maybe (Renamed "test_timeout" "test.timeout_ms")+    , testMemoryLimit :: Maybe (Renamed "test_memory_limit" "test.memory_limit")     , generateWithHpack :: Bool-    , testMemoryLimit :: Maybe Text     , hooks :: Maybe Hooks     , idleTimeoutSeconds :: Int-    , command :: Maybe Text-    -- ^ DEPRECATED: use 'build'.'commandTemplate' instead.-    -- TODO: Remove at or after version 0.8.0.0.-    , targets :: [Text]-    -- ^ DEPRECATED: use 'build'.'targets' instead.-    -- TODO: Remove at or after version 0.8.0.0.-    , testTargets :: Maybe [Text]-    -- ^ DEPRECATED: use 'test'.'targets' instead.-    -- TODO: Remove at or after version 0.8.0.0.+    , command :: Maybe (Renamed "command" "build.command_template")+    , targets :: Maybe (Renamed "targets" "build.targets")+    , testTargets :: Maybe (Renamed "test_targets" "test.targets")     }     deriving stock (Eq, Generic, Show)     deriving (ToJSON) via QuietSnake Config@@ -48,12 +43,12 @@             , watchDirs = []             , watchExclusionPatterns = []             , replBuildDir = "dist-newstyle/tricorder"-            , testTimeout = 10-            , generateWithHpack = True+            , testTimeout = Nothing             , testMemoryLimit = Nothing+            , generateWithHpack = True             , hooks = Nothing             , idleTimeoutSeconds = 300             , command = Nothing-            , targets = []+            , targets = Nothing             , testTargets = Nothing             }
src/Tricorder/Session/Hooks.hs view
@@ -9,7 +9,7 @@ where  import Atelier.Types.JSON.QuietSnake (QuietSnake (..))-import Atelier.Types.WithDefaults (WithDefaults (..))+import Atelier.Types.JSON.WithDefaults (WithDefaults (..)) import Data.Aeson (FromJSON, ToJSON) import Data.Default (Default (..)) 
src/Tricorder/Session/Stage/Build/Command.hs view
@@ -34,22 +34,20 @@     -> [Target]     -> Eff es (CommandTemplate 'Build, [Target]) resolve projectRoot cfg repl effectiveTargets = do-    template <- case customTemplate of+    template <- case cfg.build.commandTemplate of         Just tpl -> pure tpl         Nothing -> defaultBuildTemplate projectRoot repl cfg.replBuildDir     pure         ( CommandTemplate             { repl             , template-            , arguments = maybe cfg.build.extraAutoArguments (const []) customTemplate+            , arguments = maybe cfg.build.extraAutoArguments (const []) cfg.build.commandTemplate             , placeholder = targetsPlaceholder             }         , if not (null effectiveTargets)             then effectiveTargets             else [Bare "all"]         )-  where-    customTemplate = cfg.build.commandTemplate <|> cfg.command   -- | Whether the project has a @cabal.project@ or any @*.cabal@ file. Used
src/Tricorder/Session/Stage/Build/Session.hs view
@@ -57,7 +57,7 @@             , ghci = maybe def Ghci.Config.File.toConfig config.build.ghci             }   where-    rawBuildTargets = fromMaybe config.targets config.build.targets+    rawBuildTargets = fromMaybe [] config.build.targets     effectiveTargets = Target.resolve projectFiles rawBuildTargets  
src/Tricorder/Session/Stage/Test/Command.hs view
@@ -4,7 +4,6 @@     ) where -import Tricorder.Build.ByteSize (ByteSize) import Tricorder.Session.Command.RenderedCommand (RenderedCommand (..)) import Tricorder.Session.CommandTemplate (CommandTemplate (..)) import Tricorder.Session.Repl (Repl (..))@@ -22,8 +21,8 @@     }  -render :: Session -> Maybe ByteSize -> TestTarget -> RenderedTestCommand-render testSession mMemoryLimit target =+render :: Session -> TestTarget -> RenderedTestCommand+render testSession target =     RenderedTestCommand         { command =             RenderedCommand@@ -57,4 +56,4 @@                         Cabal -> cabal                         Unknown -> cabal             )-            mMemoryLimit+            testSession.options.memoryLimit
src/Tricorder/Session/Stage/Test/Config.hs view
@@ -5,6 +5,7 @@     ) where +import Atelier.Time (AsRawUnit, Millisecond) import Atelier.Types.JSON.QuietSnake (QuietSnake (..)) import Data.Aeson (FromJSON (..), ToJSON, Value (..)) import Data.Aeson.Types (ToJSON (..))@@ -41,8 +42,10 @@     parseJSON v = TestConfig <$> parseJSON v <*> parseJSON v  -newtype Options = Options+data Options = Options     { outputMode :: Maybe OutputMode+    , timeoutMs :: Maybe (AsRawUnit Millisecond)+    , memoryLimit :: Maybe Text     }     deriving stock (Eq, Generic, Show)     deriving (FromJSON, ToJSON) via QuietSnake Options@@ -52,6 +55,8 @@     def =         Options             { outputMode = Nothing+            , timeoutMs = Nothing+            , memoryLimit = Nothing             }  
src/Tricorder/Session/Stage/Test/Session.hs view
@@ -7,10 +7,15 @@     ) where +import Atelier.Effects.Log (Log)+import Atelier.Time (Millisecond) import Data.Default (Default (..)) +import Atelier.Effects.Log qualified as Log+import Atelier.Time qualified as Time import Data.Text qualified as T +import Tricorder.Build.ByteSize (ByteSize) import Tricorder.Session.CommandConfig (CommandConfig (..)) import Tricorder.Session.CommandTemplate (CommandTemplate (..), targetPlaceholder) import Tricorder.Session.Config (Config (..))@@ -20,6 +25,7 @@ import Tricorder.Session.TestTarget (TestTarget (..)) import Tricorder.Session.Util (displayList, indent) +import Tricorder.Build.ByteSize qualified as ByteSize import Tricorder.Daemon.Ghci.Config qualified as Ghci (Config) import Tricorder.Daemon.Ghci.Config qualified as Ghci.Config import Tricorder.Daemon.Ghci.Config.File qualified as Ghci.Config.File@@ -49,35 +55,54 @@  data ResolvedTestOptions = ResolvedTestOptions     { outputMode :: OutputMode+    , timeout :: Millisecond+    , memoryLimit :: Maybe ByteSize     }     deriving stock (Eq)   instance Default ResolvedTestOptions where-    def = ResolvedTestOptions ReplOutput+    def =+        ResolvedTestOptions+            { outputMode = ReplOutput+            , timeout = 10+            , memoryLimit = Nothing+            }  -resolve :: Repl -> [Target] -> Config -> Session-resolve repl buildTargets cfg =-    Session-        { commandTemplate =-            CommandTemplate-                { repl-                , template-                , arguments =-                    maybe-                        cfg.test.commandConfig.extraAutoArguments-                        (const [])-                        cfg.test.commandConfig.commandTemplate-                , placeholder = targetPlaceholder-                }-        , targets = TestTarget.resolve cfg buildTargets-        , options =-            ResolvedTestOptions-                { outputMode = fromMaybe detectedOutputMode cfg.test.options.outputMode-                }-        , ghci = maybe def Ghci.Config.File.toConfig cfg.test.commandConfig.ghci-        }+resolve :: (Log :> es) => Repl -> [Target] -> Config -> Eff es Session+resolve repl buildTargets cfg = Log.withNamespace "session.test" do+    memoryLimit <- case cfg.test.options.memoryLimit of+        Nothing -> pure Nothing+        Just limit -> case ByteSize.fromText limit of+            Nothing -> do+                Log.warn $ "Unable to parse options.memory_limit: " <> limit+                Log.info "Continuing with no memory limit."+                pure Nothing+            Just parsedLimit ->+                pure $ Just parsedLimit+    pure+        $ Session+            { commandTemplate =+                CommandTemplate+                    { repl+                    , template+                    , arguments =+                        maybe+                            cfg.test.commandConfig.extraAutoArguments+                            (const [])+                            cfg.test.commandConfig.commandTemplate+                    , placeholder = targetPlaceholder+                    }+            , targets = TestTarget.resolve cfg buildTargets+            , options =+                ResolvedTestOptions+                    { outputMode = fromMaybe detectedOutputMode cfg.test.options.outputMode+                    , timeout = maybe 10 Time.getAsRawUnit cfg.test.options.timeoutMs+                    , memoryLimit+                    }+            , ghci = maybe def Ghci.Config.File.toConfig cfg.test.commandConfig.ghci+            }   where     template = fromMaybe (defaultTestTemplate repl) cfg.test.commandConfig.commandTemplate     detectedOutputMode@@ -114,4 +139,6 @@         T.intercalate             "\n"             [ "Output mode: " <> show opts.outputMode+            , "Timeout: " <> show opts.timeout+            , "Memory limit: " <> maybe "<unlimited>" show opts.memoryLimit             ]
src/Tricorder/Session/State.hs view
@@ -27,6 +27,7 @@ import Atelier.Effects.Conc qualified as Conc import Atelier.Effects.FileSystem qualified as FileSystem import Atelier.Effects.FileWatcher qualified as FileWatcher+import Atelier.Effects.FileWatcher.Watch qualified as Watch import Atelier.Effects.Input qualified as Input import Atelier.Effects.Log qualified as Log import Atelier.Effects.PubSub qualified as PubSub@@ -136,7 +137,7 @@ watchConfigFile = do     root <- Reader.ask     FileWatcher.watchFilePathsDebounced-        [FileWatcher.dirWhere root.getProjectRoot (LoadedConfig.configFileName `List.isSuffixOf`)]+        [Watch.dirWhere root.getProjectRoot (LoadedConfig.configFileName `List.isSuffixOf`)]         \_ _ -> Pub.publish Reload  @@ -154,7 +155,7 @@     exists <- FileSystem.doesFileExist $ root.getProjectRoot </> "stack.yaml"     when exists do         Conc.fork_ $ FileWatcher.watchFilePathsDebounced-            [FileWatcher.dirWhere root.getProjectRoot ("stack.yaml" `List.isSuffixOf`)]+            [Watch.dirWhere root.getProjectRoot ("stack.yaml" `List.isSuffixOf`)]             \_ _ -> Pub.publish Reload  
src/Tricorder/Session/TestTarget.hs view
@@ -50,6 +50,6 @@ -- ever names test suites [ref:test_targets_invariant]. With neither source -- set, falls back to deriving test targets from the build 'targets'. resolve :: Config -> [Target] -> [TestTarget]-resolve cfg targets = case cfg.test.commandConfig.targets <|> cfg.testTargets of+resolve cfg targets = case cfg.test.commandConfig.targets of     Just explicit -> mapMaybe parse explicit     Nothing -> mapMaybe project targets
− src/Tricorder/Session/TestTimeout.hs
@@ -1,13 +0,0 @@-module Tricorder.Session.TestTimeout (TestTimeout (..)) where--import Data.Aeson (FromJSON, ToJSON)-import Data.Default (Default (..))---newtype TestTimeout = TestTimeout {getTestTimeout :: Int}-    deriving stock (Eq, Generic, Show)-    deriving (FromJSON, ToJSON) via Int---instance Default TestTimeout where-    def = TestTimeout 10
src/Tricorder/Socket/Client.hs view
@@ -20,14 +20,14 @@ import Effectful.Reader.Static (Reader, ask) import Effectful.State.Static.Shared (evalState, get, modify, put) import System.IO.Error (isEOFError)-import Tricorder.API.Force (Force)+import Tricorder.API.IfThereArePendingQueries (IfThereArePendingQueries) import Prelude hiding (force)  import Atelier.Effects.Delay qualified as Delay import Atelier.Effects.File qualified as File import Atelier.Effects.Posix.Daemons qualified as Daemons import Data.ByteString.Lazy qualified as BSL-import Tricorder.API.Force qualified as Force+import Tricorder.API.IfThereArePendingQueries qualified as IfThereArePendingQueries  import Tricorder.Build (BuildState, Diagnostic) import Tricorder.Runtime (PidFile)@@ -126,7 +126,7 @@  requestShutdown     :: (File :> es, UnixSocket :> es)-    => Force -> FilePath -> Eff es (Either Text ())+    => IfThereArePendingQueries -> FilePath -> Eff es (Either Text ()) requestShutdown force sockPath = withConnection sockPath \h -> do     sendQuery h $ Quit waiters     line <- File.hGetLine h@@ -137,8 +137,8 @@             pure $ Left "Failed to request shutdown"   where     waiters = case force of-        Force.Force -> IgnoreWaiters-        Force.NoForce -> WaitForWaiters+        IfThereArePendingQueries.DontWait -> IgnoreWaiters+        IfThereArePendingQueries.Wait -> WaitForWaiters   isDaemonRunning :: (Daemons :> es, Reader PidFile :> es) => Eff es Bool
src/Tricorder/Socket/Server.hs view
@@ -1,7 +1,7 @@ module Tricorder.Socket.Server (main, SocketRemoved (..)) where  import Atelier.Effects.Conc (Conc)-import Atelier.Effects.Exit (Exit, exitSuccess)+import Atelier.Effects.Exit (Exit) import Atelier.Effects.File (File) import Atelier.Effects.FileSystem (FileSystem) import Atelier.Effects.Input (Input, input)@@ -14,6 +14,7 @@ import System.IO (Handle)  import Atelier.Effects.Conc qualified as Conc+import Atelier.Effects.Exit qualified as Exit import Atelier.Effects.File qualified as File import Atelier.Effects.FileSystem qualified as FileSystem import Atelier.Effects.Log qualified as Log@@ -154,7 +155,7 @@     doQuit = do         sendJson h True         Log.info "Shutting down."-        exitSuccess+        Exit.success   respondOnce
src/Tricorder/SourceLookup.hs view
@@ -6,7 +6,7 @@ where  import Atelier.Effects.FileSystem (FileSystem, doesFileExist, readFileLbs)-import Atelier.Effects.Input (Input, input)+import Atelier.Effects.Input (Input) import Atelier.Effects.Log (Log) import System.FilePath (normalise, takeDirectory, (</>)) import Tricorder.SourceLookup.ModuleSourceResult (ModuleSourceResult (..))@@ -14,13 +14,15 @@ import Tricorder.SourceLookup.ReexportLimits (ReexportLimits (..)) import Tricorder.SourceLookup.SourceQuery (ModuleName (..), SourceQuery (..)) +import Atelier.Effects.Input qualified as Input import Atelier.Effects.Log qualified as Log import Data.Set qualified as Set import Data.Text qualified as T import Tricorder.SourceLookup.ModuleSourceResult qualified as ModuleSourceResult  import Tricorder.Session.CabalFile (CabalFile (..))-import Tricorder.Session.Repl (Repl)+import Tricorder.Session.CommandTemplate (CommandTemplate (..))+import Tricorder.Session.Stage.Build.Session (Session (..)) import Tricorder.Session.WatchDirs (allSourceDirs) import Tricorder.SourceLookup.GhcPkg (GhcPkg) import Tricorder.SourceLookup.Hackage (Hackage)@@ -34,6 +36,7 @@     , sourceExtensions     ) +import Tricorder.Session.Stage.Build.Session qualified as Build (Session) import Tricorder.SourceLookup.GhcPkg qualified as GhcPkg  @@ -83,7 +86,7 @@     :: ( FileSystem :> es        , GhcPkg :> es        , Hackage :> es-       , Input Repl :> es+       , Input Build.Session :> es        , Log :> es        , PackageStore :> es        )@@ -111,7 +114,7 @@     :: ( FileSystem :> es        , GhcPkg :> es        , Hackage :> es-       , Input Repl :> es+       , Input Build.Session :> es        , Log :> es        , PackageStore :> es        )@@ -180,7 +183,7 @@     :: ( FileSystem :> es        , GhcPkg :> es        , Hackage :> es-       , Input Repl :> es+       , Input Build.Session :> es        , Log :> es        , PackageStore :> es        )@@ -238,14 +241,14 @@ -- | Resolve a module to its package, consulting the module -> package cache first. resolvePackage     :: ( GhcPkg :> es-       , Input Repl :> es+       , Input Build.Session :> es        , Log :> es        )     => ModuleName     -> Eff es (Maybe PackageId) resolvePackage modName = do-    repl <- input-    result <- GhcPkg.findModule repl modName+    session <- Input.input @Build.Session+    result <- GhcPkg.findModule session.commandTemplate.repl modName     Log.debug $ "Source: find-module " <> unModuleName modName <> " → " <> show result     pure result 
src/Tricorder/SourceLookup/PackageStore.hs view
@@ -6,7 +6,7 @@     ) where -import Atelier.Effects.Env (Env, getEnvironment)+import Atelier.Effects.Env (Env) import Atelier.Effects.FileSystem (FileSystem) import Effectful (Effect) import Effectful.Dispatch.Dynamic (interpretWith_)@@ -15,6 +15,7 @@ import System.FilePath (takeDirectory, (</>)) import Tricorder.SourceLookup.PackageId (PackageId (..), splitPackageId) +import Atelier.Effects.Env qualified as Env import Atelier.Effects.FileSystem qualified as FileSystem import Data.Map.Strict qualified as Map @@ -66,7 +67,7 @@  findStoreBaseDir :: (Env :> es, FileSystem :> es) => Eff es FilePath findStoreBaseDir = do-    env <- Map.fromList <$> getEnvironment+    env <- Map.fromList <$> Env.all     fmap (fromMaybe tempFallback . rightToMaybe)         $ runNonDet OnEmptyKeep         $ findCabalDirCandidate env
test/Unit/Tricorder/Daemon/Ghci/ProcessSpec.hs view
@@ -1,12 +1,8 @@ module Unit.Tricorder.Daemon.Ghci.ProcessSpec (test_Process) where -import Atelier.Effects.Conc (runConc)-import Atelier.Effects.Delay (runDelay)-import Atelier.Effects.File (runFile)-import Atelier.Effects.Log (Message (..), Severity (..), runLogNoOp, runLogWriter)-import Atelier.Effects.Process (runProcessIO, terminateProcessGroup, withProcessGroup)+import Atelier.Effects.Log (Message (..), Severity (..))+import Atelier.Effects.Process (terminateProcessGroup, withProcessGroup) import Atelier.Effects.Process.Internal (RunningProcess (..))-import Atelier.Effects.Timeout (runTimeout) import Atelier.Time (Millisecond) import Control.Concurrent (threadDelay) import Control.Concurrent.STM (newTVarIO)@@ -40,7 +36,10 @@ import Atelier.Effects.Conc qualified as Conc import Atelier.Effects.Delay qualified as Delay import Atelier.Effects.File qualified as File+import Atelier.Effects.Log qualified as Log import Atelier.Effects.Process qualified as AProc+import Atelier.Effects.Process qualified as Atelier.Process+import Atelier.Effects.Timeout qualified as Timeout import Data.Text qualified as T import System.Process qualified as Process import System.Timeout qualified@@ -81,8 +80,8 @@         (result, _msgs) <-             runEff                 . runWriter @[Message]-                . runLogWriter-                . runFile+                . Log.runWriter+                . File.run                 $ do                     for_ ["line1", "line2", finishMarker 1, "line3"] (File.hPutTextLn w)                     File.hClose w@@ -94,8 +93,8 @@         (result, _msgs) <-             runEff                 . runWriter @[Message]-                . runLogWriter-                . runFile+                . Log.runWriter+                . File.run                 $ do                     for_ ["a", "b", "c", finishMarker 2] (File.hPutTextLn w)                     File.hClose w@@ -108,8 +107,8 @@         (result, _msgs) <-             runEff                 . runWriter @[Message]-                . runLogWriter-                . runFile+                . Log.runWriter+                . File.run                 $ do                     -- 'finishMarker 5' is a leftover from a prior, interrupted                     -- command; the drain waiting for 'finishMarker 9' must@@ -125,8 +124,8 @@             (outcome, _msgs) <-                 runEff                     . runWriter @[Message]-                    . runLogWriter-                    . runFile+                    . Log.runWriter+                    . File.run                     $ do                         for_ ["first", "second", "third"] (File.hPutTextLn w)                         File.hClose w -- EOF before the marker ever arrives@@ -144,8 +143,8 @@         (outcome, _msgs) <-             runEff                 . runWriter @[Message]-                . runLogWriter-                . runFile+                . Log.runWriter+                . File.run                 $ do                     File.hClose w                     trySync (Process.drainUntil r (finishMarker 1) (\_ -> pure ()))@@ -170,8 +169,8 @@             (_outcome, msgs) <-                 runEff                     . runWriter @[Message]-                    . runLogWriter-                    . runFile+                    . Log.runWriter+                    . File.run                     $ do                         File.hClose w                         trySync (Process.drainUntil r (finishMarker 3) (\_ -> pure ()))@@ -185,8 +184,8 @@         (_outcome, msgs) <-             runEff                 . runWriter @[Message]-                . runLogWriter-                . runFile+                . Log.runWriter+                . File.run                 $ do                     for_ ["oops-line-1", "oops-line-2"] (File.hPutTextLn w)                     File.hClose w@@ -235,11 +234,11 @@         result <-             runEff                 . runConcurrent-                . runTimeout-                . runDelay-                . runFile-                . runLogNoOp-                . runConc+                . Timeout.run+                . Delay.runIO+                . File.run+                . Log.runNoOp+                . Conc.runIO                 $ do                     -- A stale 'marker 5' (left by a prior interrupted reload)                     -- precedes the fresh command's real output + its 'marker 9'@@ -294,11 +293,11 @@         written <-             runEff                 . runConcurrent-                . runTimeout-                . runDelay-                . runFile-                . runLogNoOp-                . runConc+                . Timeout.run+                . Delay.runIO+                . File.run+                . Log.runNoOp+                . Conc.runIO                 $ do                     -- Pre-seed the marker on both streams so the drain returns                     -- immediately; we only care about what was written to stdin.@@ -339,10 +338,10 @@         result <-             runEff                 . runConcurrent-                . runTimeout-                . runDelay-                . runFile-                . runConc+                . Timeout.run+                . Delay.runIO+                . File.run+                . Conc.runIO                 $ do                     -- No banner will ever arrive: close the write end so the                     -- wait sees EOF at once and takes the "command exited"@@ -383,11 +382,11 @@         let scenario =                 runEff                     . runConcurrent-                    . runTimeout-                    . runDelay-                    . runFile-                    . runConc-                    . runProcessIO+                    . Timeout.run+                    . Delay.runIO+                    . File.run+                    . Conc.runIO+                    . Atelier.Process.run                     $ withProcessGroup procConfig \p -> do                         -- First stdout line is the long-lived child's pid.                         line <- File.hGetLine (AProc.getStdout p)@@ -439,7 +438,7 @@                     ignoring (stopProcess p)                     assertFailure ("could not parse child pid from: " <> show childLine)                 Just childPid -> do-                    runEff . runProcessIO $ terminateProcessGroup (RunningProcess p)+                    runEff . Atelier.Process.run $ terminateProcessGroup (RunningProcess p)                     died <- waitForProcessDeath childPid                     -- Never leak the child if the assertion fails.                     ignoring (signalProcess sigKILL (fromIntegral childPid))@@ -532,11 +531,11 @@         result <-             runEff                 . runConcurrent-                . runTimeout-                . runDelay-                . runFile-                . runLogNoOp-                . runConc+                . Timeout.run+                . Delay.runIO+                . File.run+                . Log.runNoOp+                . Conc.runIO                 $ Conc.scoped do                     -- A sibling fork in the SAME ambient scope. If the bug                     -- regresses, the drain exception will tear this scope
test/Unit/Tricorder/Daemon/Ghci/SessionSpec.hs view
@@ -149,4 +149,4 @@     :: [Either SomeException LoadResult]     -> Eff '[Session, Pub BuildProgress, IOE] a     -> IO a-runScripted results = runEff . Pub.runNoOp . Session.runScripted results+runScripted results = runEff . Pub.noOp . Session.runScripted results
test/Unit/Tricorder/Daemon/TestRunnerSpec.hs view
@@ -16,7 +16,6 @@     ) import Tricorder.Session.Command.RenderedCommand (RenderedCommand (..)) import Tricorder.Session.Stage.Test.Command (RenderedTestCommand (..))-import Tricorder.Session.TestTimeout (TestTimeout (..))  import Tricorder.Build.Test qualified as Test import Tricorder.Daemon.TestRunner qualified as TestRunner@@ -107,7 +106,7 @@     [ testCase "returns scripted TestRun" do         result <-             runScripted [Right passingRun]-                $ runTestSuite def noProgress testTimeout+                $ runTestSuite def noProgress                 $ mkResolved                 $ RenderedCommand                 $ "cabal repl test:foo"@@ -115,7 +114,7 @@     , testCase "ignores the target name argument" do         result <-             runScripted [Right failingRun]-                $ runTestSuite def noProgress testTimeout+                $ runTestSuite def noProgress                 $ mkResolved                 $ RenderedCommand                 $ "cabal repl test:anything"@@ -124,7 +123,7 @@         result <-             runScripted [Left (toException boom)]                 $ try @ErrorCall-                $ runTestSuite def noProgress testTimeout+                $ runTestSuite def noProgress                 $ mkResolved                 $ RenderedCommand                 $ "cabal repl test:foo"@@ -134,12 +133,12 @@         [ testCase "consumes results in order across multiple calls" do             (a, b) <- runScripted [Right passingRun, Right failingRun] do                 a <--                    runTestSuite def noProgress testTimeout+                    runTestSuite def noProgress                         $ mkResolved                         $ RenderedCommand                         $ "cabal repl test:foo"                 b <--                    runTestSuite def noProgress testTimeout+                    runTestSuite def noProgress                         $ mkResolved                         $ RenderedCommand                         $ "cabal repl test:bar"@@ -150,12 +149,12 @@             result <- runScripted [Left (toException boom), Right passingRun] do                 r1 <-                     try @ErrorCall-                        $ runTestSuite def noProgress testTimeout+                        $ runTestSuite def noProgress                         $ mkResolved                         $ RenderedCommand                         $ "cabal repl test:foo"                 r2 <--                    runTestSuite def noProgress testTimeout+                    runTestSuite def noProgress                         $ mkResolved                         $ RenderedCommand                         $ "cabal repl test:bar"@@ -202,10 +201,6 @@  mkResolved :: RenderedCommand 'Stage.Test -> RenderedTestCommand mkResolved cmd = RenderedTestCommand cmd def---testTimeout :: TestTimeout-testTimeout = TestTimeout (-1)   noProgress :: b -> Eff es ()
test/Unit/Tricorder/Daemon/WatchSpec.hs view
@@ -1,6 +1,7 @@ module Unit.Tricorder.Daemon.WatchSpec (test_Watch) where -import Atelier.Effects.FileWatcher (FileEvent (..), matchesAny)+import Atelier.Effects.FileWatcher (matchesAny)+import Atelier.Effects.FileWatcher.Event (Event (..)) import Effectful (runEff) import Effectful.Writer.Static.Shared (execWriter, runWriter) import Test.Tasty (TestTree, testGroup)@@ -9,10 +10,7 @@  import Atelier.Effects.PubSub.Pub qualified as Pub -import Tricorder.Build.Changes-    ( CabalChangeDetected (..)-    , SourceChangeDetected (..)-    )+import Tricorder.Build.Changes (CabalChangeDetected (..), SourceChangeDetected (..)) import Tricorder.Daemon.Watch (WatchedFile (..)) import Tricorder.Runtime (ProjectRoot (..)) import Tricorder.Session.WatchDirs (WatchDirs (..))
test/Unit/Tricorder/Session/CabalFileSpec.hs view
@@ -1,16 +1,16 @@ module Unit.Tricorder.Session.CabalFileSpec (test_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.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) +import Atelier.Effects.Env qualified as Env+import Atelier.Effects.FileSystem qualified as FileSystem import Atelier.Effects.FileSystem.Glob qualified as Glob import Atelier.Effects.Input qualified as Input+import Atelier.Effects.Log qualified as Log import Data.Map.Strict qualified as Map  import Tricorder.Runtime (ProjectRoot (..))@@ -156,9 +156,9 @@     runDiscovery fs env = runDiscoveryGlob fs env []     runDiscoveryGlob fs env script =         runPureEff-            . runEnvConst env+            . Env.runConst env             . evalState fs-            . runFileSystemState+            . FileSystem.runState             . Glob.runScripted script             . runReader pr @@ -182,7 +182,7 @@     runStack result =         runPureEff             . runReader pr-            . runLogNoOp+            . Log.runNoOp             . Input.withConst result  @@ -206,4 +206,4 @@         fmap (.projectFilePath)             . runPureEff             . evalState fs-            . runFileSystemState+            . FileSystem.runState
test/Unit/Tricorder/Session/ReplSpec.hs view
@@ -1,11 +1,12 @@ module Unit.Tricorder.Session.ReplSpec (test_Repl) where -import Atelier.Effects.FileSystem (FileSystem, runFileSystemState)+import Atelier.Effects.FileSystem (FileSystem) import Effectful (runPureEff) import Effectful.State.Static.Shared (State, evalState) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) +import Atelier.Effects.FileSystem qualified as FileSystem import Data.Map.Strict qualified as Map  import Tricorder.Runtime (ProjectRoot (..))@@ -45,7 +46,7 @@ -- @stack.yaml@, which is parsed for its @packages@ key). withFiles :: [(FilePath, ByteString)] -> Eff '[FileSystem, State (Map FilePath ByteString)] a -> a withFiles files action =-    runPureEff $ evalState (Map.fromList files) $ runFileSystemState action+    runPureEff $ evalState (Map.fromList files) $ FileSystem.runState action   pr :: ProjectRoot
test/Unit/Tricorder/Session/Stage/Build/CommandSpec.hs view
@@ -1,12 +1,13 @@ module Unit.Tricorder.Session.Stage.Build.CommandSpec (test_Command) where -import Atelier.Effects.FileSystem (FileSystem, runFileSystemState)+import Atelier.Effects.FileSystem (FileSystem) import Data.Default (def) import Effectful (runPureEff) import Effectful.State.Static.Shared (State, evalState) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) +import Atelier.Effects.FileSystem qualified as FileSystem import Data.Map.Strict qualified as Map  import Tricorder.Runtime (ProjectRoot (..))@@ -74,22 +75,6 @@ testResolveBuildCommand :: [TestTree] testResolveBuildCommand =     [ testGroup-        "deprecated top-level command"-        [ testCase "is used as the build template when build.command_template is unset" do-            renderBuildFor [] def {command = Just "foo"} [] @?= "foo"-        ]-    , testGroup-        "build.command_template"-        [ testCase "overrides the deprecated top-level command" do-            let cfg =-                    cfg0-                        { command = Just "should be ignored"-                        , build = cfg0.build {commandTemplate = Just "cabal repl {targets}"}-                        }-            renderBuildFor [("/cabal.project", "")] cfg (Target.parse <$> ["lib:foo"])-                @?= "cabal repl lib:foo"-        ]-    , testGroup         "explicit targets"         [ testCase "spell them out verbatim" do             renderBuildFor [("/cabal.project", "")] cfg0 (Target.parse <$> ["lib:foo"])@@ -145,14 +130,6 @@                         }             renderBuildFor [("/cabal.project", "")] cfg (Target.parse <$> ["lib:foo"])                 @?= "cabal repl lib:foo"-        , testCase "is ignored when the deprecated top-level command is set" do-            let cfg =-                    cfg0-                        { command = Just "cabal repl {targets}"-                        , build = cfg0.build {extraAutoArguments = ["--extra-flag"]}-                        }-            renderBuildFor [("/cabal.project", "")] cfg (Target.parse <$> ["lib:foo"])-                @?= "cabal repl lib:foo"         ]     ] @@ -171,7 +148,7 @@ -- @stack.yaml@, which is parsed for its @packages@ key). withFiles :: [(FilePath, ByteString)] -> Eff '[FileSystem, State (Map FilePath ByteString)] a -> a withFiles files action =-    runPureEff $ evalState (Map.fromList files) $ runFileSystemState action+    runPureEff $ evalState (Map.fromList files) $ FileSystem.runState action   cfg0 :: Config
test/Unit/Tricorder/Session/Stage/Test/CommandSpec.hs view
@@ -1,25 +1,26 @@ module Unit.Tricorder.Session.Stage.Test.CommandSpec (test_Command) where  import Data.Default (def)+import Effectful (runPureEff) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) +import Atelier.Effects.Log qualified as Log+ import Tricorder.Build.ByteSize (ByteSize (..), Unit (..)) import Tricorder.Session.Command.RenderedCommand (RenderedCommand (..)) import Tricorder.Session.CommandConfig (CommandConfig (..)) import Tricorder.Session.CommandTemplate (CommandTemplate (..), targetPlaceholder) import Tricorder.Session.Config (Config (..)) import Tricorder.Session.Repl (Repl (..))-import Tricorder.Session.Stage.Test.Command-    ( RenderedTestCommand (..)-    , render-    )+import Tricorder.Session.Stage.Test.Command (RenderedTestCommand (..), render) import Tricorder.Session.Stage.Test.Config (TestConfig (..))-import Tricorder.Session.Stage.Test.Session (Session (..), resolve)+import Tricorder.Session.Stage.Test.Session (ResolvedTestOptions (..), Session (..)) import Tricorder.Session.TestTarget (TestTarget (..))  import Tricorder.Session.CommandConfig qualified as CommandConfig import Tricorder.Session.Stage qualified as Stage+import Tricorder.Session.Stage.Test.Session qualified as Session import Tricorder.Session.Target qualified as Target  @@ -61,7 +62,7 @@   where     test :: CommandTemplate 'Stage.Test -> Maybe ByteSize -> TestTarget -> Text     test template mMemoryLimit target =-        (render (Session template [] def def) mMemoryLimit target).command.getRenderedCommand+        (render (Session template [] (def {memoryLimit = mMemoryLimit}) def) target).command.getRenderedCommand     testTarget = TestTarget (Target.parse "test:foo")     oneByte = ByteSize 1 B @@ -104,3 +105,7 @@                         }             (resolve Cabal [] cfg).commandTemplate.arguments @?= []         ]+++resolve :: Repl -> [Target.Target] -> Config -> Session+resolve repl target config = runPureEff . Log.runNoOp $ Session.resolve repl target config
test/Unit/Tricorder/Session/TestTargetSpec.hs view
@@ -6,6 +6,9 @@  import Tricorder.Session.Config (Config (..)) +import Tricorder.Session.CommandConfig qualified as CommandConfig+import Tricorder.Session.Config qualified as Config+import Tricorder.Session.Stage.Test.Config qualified as Test.Config import Tricorder.Session.Target qualified as Target import Tricorder.Session.TestTarget qualified as TestTarget @@ -28,12 +31,30 @@         let cfg = def :: Config         TestTarget.resolve cfg (mkTargets ["lib:mylib", "exe:myapp"])             @?= mapMaybe TestTarget.parse []-    , testCase "uses explicit testTargets list when set" do-        let cfg = def {testTargets = Just ["test:b-test"]} :: Config+    , testCase "uses explicit test.commandConfig.targets list when set" do+        let cfg =+                def+                    { Config.test =+                        def+                            { Test.Config.commandConfig =+                                def+                                    { CommandConfig.targets = Just ["test:b-test"]+                                    }+                            }+                    }         TestTarget.resolve cfg (mkTargets ["lib:a", "test:a-test", "test:b-test"])             @?= mapMaybe TestTarget.parse ["test:b-test"]     , testCase "returns empty list when testTargets is explicitly empty" do-        let cfg = def {testTargets = Just []} :: Config+        let cfg =+                def+                    { Config.test =+                        def+                            { Test.Config.commandConfig =+                                def+                                    { CommandConfig.targets = Just []+                                    }+                            }+                    }         TestTarget.resolve cfg (mkTargets ["lib:a", "test:a-test"])             @?= []     , testCase "infers multiple test: components" do
test/Unit/Tricorder/SessionSpec.hs view
@@ -1,7 +1,6 @@ module Unit.Tricorder.SessionSpec (test_Session) where -import Atelier.Effects.FileSystem (runFileSystemState)-import Atelier.Effects.Log (Message (..), Severity (..), runLogNoOp, runLogWriter)+import Atelier.Effects.Log (Message (..), Severity (..)) import Data.Aeson (object, (.=)) import Distribution.PackageDescription.Parsec (parseGenericPackageDescriptionMaybe) import Effectful (runPureEff)@@ -11,7 +10,9 @@ import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (assertFailure, testCase, (@?), (@?=)) +import Atelier.Effects.FileSystem qualified as FileSystem import Atelier.Effects.Input qualified as Input+import Atelier.Effects.Log qualified as Log import Data.Aeson qualified as Aeson import Data.Text qualified as T @@ -78,9 +79,9 @@         filter (\m -> m.severity /= DEBUG)             . runPureEff             . execWriter @[Message]-            . runLogWriter+            . Log.runWriter             . evalState @(Map FilePath ByteString) mempty-            . runFileSystemState+            . FileSystem.runState             . Input.withConst cabalFiles             . runReader (ProjectRoot "/")             . Input.withConst (LoadedConfig $ toStrict $ Aeson.encode cfg)@@ -122,9 +123,9 @@     captureLogsFor cfg =         runPureEff             . execWriter @[Message]-            . runLogWriter+            . Log.runWriter             . evalState @(Map FilePath ByteString) mempty-            . runFileSystemState+            . FileSystem.runState             . Input.withConst ([] :: [CabalFile])             . runReader (ProjectRoot "/")             . Input.withConst (LoadedConfig $ toStrict $ Aeson.encode cfg)@@ -145,9 +146,9 @@   where     loadSessionWith cfg =         runPureEff-            . runLogNoOp+            . Log.runNoOp             . evalState @(Map FilePath ByteString) mempty-            . runFileSystemState+            . FileSystem.runState             . Input.withConst ([] :: [CabalFile])             . runReader (ProjectRoot "/")             . Input.withConst (LoadedConfig $ toStrict $ Aeson.encode cfg)
test/Unit/Tricorder/SocketSpec.hs view
@@ -1,6 +1,6 @@ module Unit.Tricorder.SocketSpec (test_Socket) where -import Atelier.Effects.File (File, runFile)+import Atelier.Effects.File (File) import Atelier.Effects.FileSystem (FileSystem) import Effectful (IOE, runEff) import Effectful.Exception (trySync)@@ -8,6 +8,7 @@ import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) +import Atelier.Effects.File qualified as File import Atelier.Effects.FileSystem qualified as FileSystem  import Tricorder.Socket.Client (isDaemonReady)@@ -75,9 +76,9 @@  -- | Run scripted socket operations (no Delay needed). runScripted :: [SocketScript] -> Eff '[UnixSocket, File, IOE] a -> IO a-runScripted script = runEff . runFile . UnixSocket.runScripted script+runScripted script = runEff . File.run . UnixSocket.runScripted script   -- | Run socket operations against the real IO interpreter. runIO' :: Eff '[UnixSocket, File, FileSystem, IOE] a -> IO a-runIO' = runEff . FileSystem.runFileSystemIO . runFile . UnixSocket.runIO+runIO' = runEff . FileSystem.runIO . File.run . UnixSocket.runIO
test/Unit/Tricorder/SourceLookupSpec.hs view
@@ -1,9 +1,10 @@ module Unit.Tricorder.SourceLookupSpec (test_SourceLookup) where -import Atelier.Effects.Env (Env, runEnvConst)+import Atelier.Effects.Env (Env) import Atelier.Effects.FileSystem (FileSystem (..)) import Atelier.Effects.Input (Input)-import Atelier.Effects.Log (Log, runLogNoOp)+import Atelier.Effects.Log (Log)+import Data.Default (def) import Distribution.PackageDescription.Parsec (parseGenericPackageDescriptionMaybe) import Effectful (IOE, runEff) import Effectful.Concurrent (Concurrent, runConcurrent)@@ -16,7 +17,9 @@ import Tricorder.SourceLookup.ReexportLimits (ReexportLimits (..), defaultReexportLimits) import Tricorder.SourceLookup.SourceQuery (ModuleName, SourceQuery (..)) +import Atelier.Effects.Env qualified as Env import Atelier.Effects.Input qualified as Input+import Atelier.Effects.Log qualified as Log import Codec.Archive.Tar qualified as Tar import Codec.Archive.Tar.Entry qualified as Tar import Codec.Compression.GZip qualified as GZip@@ -26,7 +29,6 @@ import Data.Text qualified as T  import Tricorder.Session.CabalFile (CabalFile (..))-import Tricorder.Session.Repl (Repl (..)) import Tricorder.SourceLookup     ( ProjectSourceDirs (..)     , lookupModuleSource@@ -36,6 +38,7 @@ import Tricorder.SourceLookup.Hackage (Hackage (..), Result (..)) import Tricorder.SourceLookup.PackageStore (PackageStore) +import Tricorder.Session.Stage.Build.Session qualified as Build import Tricorder.SourceLookup.PackageStore qualified as PackageStore  @@ -399,7 +402,7 @@      , Env      , FileSystem      , State (Map FilePath LByteString)-     , Input Repl+     , Input Build.Session      , Log      , Concurrent      , IOE@@ -418,11 +421,11 @@ runTest pkgScript initialFs onFetch action =     runEff         . runConcurrent-        . runLogNoOp-        . Input.withConst Cabal+        . Log.runNoOp+        . Input.withConst (def :: Build.Session)         . evalState initialFs         . runFileSystemFake-        . runEnvConst [("HOME", "/h")]+        . Env.runConst [("HOME", "/h")]         . PackageStore.run         . runHackageWith onFetch         . runGhcPkgScripted pkgScript
test/Unit/Tricorder/WaitersSpec.hs view
@@ -1,6 +1,6 @@ module Unit.Tricorder.WaitersSpec (test_Waiters) where -import Atelier.Effects.Conc (Conc, runConc)+import Atelier.Effects.Conc (Conc) import Control.Exception (try) import Effectful (IOE, runEff) import Effectful.Concurrent (Concurrent, runConcurrent)@@ -30,7 +30,7 @@ runWaitersTest =     runEff         . runConcurrent-        . runConc+        . Conc.runIO         . runState @[Text] []         . Waiters.run 
tricorder.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:            tricorder-version:         0.7.0.0+version:         1.0.0.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@@ -125,7 +125,6 @@       Tricorder.Session.State.Result       Tricorder.Session.Target       Tricorder.Session.TestTarget-      Tricorder.Session.TestTimeout       Tricorder.Session.Util       Tricorder.Session.WatchDirs       Tricorder.Session.WatchExclusionPatterns@@ -172,7 +171,7 @@     , Cabal-syntax >=3.12 && <3.17     , Glob ==0.10.*     , aeson >=2.2 && <2.4-    , atelier-core ==0.8.*+    , atelier-core ==1.0.*     , atelier-prelude ==0.4.*     , base >=4.18 && <4.23     , brick ==3.0.*@@ -201,7 +200,7 @@     , text ==2.1.*     , time >=1.12 && <1.17     , time-units ==1.0.*-    , tricorder-types ==0.6.*+    , tricorder-types ==1.0.*     , vty >=6.5 && <6.7     , vty-crossplatform ==0.5.*     , yaml ==0.11.*@@ -354,7 +353,7 @@   build-depends:       Cabal-syntax >=3.12 && <3.17     , aeson >=2.2 && <2.4-    , atelier-core ==0.8.*+    , atelier-core ==1.0.*     , atelier-prelude ==0.4.*     , base >=4.18 && <4.23     , bytestring >=0.11 && <0.13@@ -376,7 +375,7 @@     , time >=1.12 && <1.17     , time-units ==1.0.*     , tricorder-internal-    , tricorder-types ==0.6.*+    , tricorder-types ==1.0.*     , typed-process ==0.2.*     , unagi-chan ==0.4.*     , unix ==2.8.*