FsHotWatch 0.3.0-alpha.1

This is a prerelease version of FsHotWatch.
dotnet add package FsHotWatch --version 0.3.0-alpha.1
                    
NuGet\Install-Package FsHotWatch -Version 0.3.0-alpha.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FsHotWatch" Version="0.3.0-alpha.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FsHotWatch" Version="0.3.0-alpha.1" />
                    
Directory.Packages.props
<PackageReference Include="FsHotWatch" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FsHotWatch --version 0.3.0-alpha.1
                    
#r "nuget: FsHotWatch, 0.3.0-alpha.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FsHotWatch@0.3.0-alpha.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FsHotWatch&version=0.3.0-alpha.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=FsHotWatch&version=0.3.0-alpha.1&prerelease
                    
Install as a Cake Tool

FsHotWatch

Speed up your F# development feedback loop.

FsHotWatch is a background daemon that watches your source files and keeps the F# compiler warm. When you save a file, it instantly re-checks it and tells your tools (linters, analyzers, test runners) what changed — without restarting the compiler from scratch each time.

The problem

F# tools are slow because they each start their own compiler from zero. A 15-project solution takes ~2 minutes to analyze. Every time you save a file, your linter restarts, your analyzer restarts, your test runner restarts — all parsing and type-checking the same 500 files again.

How FsHotWatch helps

FsHotWatch runs one compiler in the background and shares it with all your tools:

  1. You save a file — FsHotWatch notices the change
  2. It re-checks just that file using the compiler that's already warm (milliseconds, not minutes)
  3. Plugins get the results instantly — your linter, analyzer, and test runner all see the updated check results without re-parsing anything
  4. You query the resultsfs-hot-watch status shows what each tool found

Changes are debounced — if you save 10 files in quick succession (like a formatter running), FsHotWatch waits for things to settle, then processes them all in one batch.

Quick start

# Install the CLI tool
dotnet tool install -g FsHotWatch.Cli

# Start the daemon in your repo (runs in foreground, Ctrl+C to stop)
fs-hot-watch start

# From another terminal, check what's happening
fs-hot-watch status

Packages

FsHotWatch is split into small packages so you only install what you need:

Package What it does
FsHotWatch Core library — the daemon, file watcher, plugin system, IPC
FsHotWatch.Cli CLI tool — fs-hot-watch start/stop/status
FsHotWatch.TestPrune Plugin: figures out which tests to run when code changes
FsHotWatch.Analyzers Plugin: runs F# analyzers (like G-Research or your own)
FsHotWatch.Lint Plugin: runs FSharpLint using the warm compiler's results
FsHotWatch.Fantomas Plugin: checks if your files are formatted with Fantomas
FsHotWatch.Build Plugin: runs dotnet build and emits BuildCompleted events
FsHotWatch.Coverage Plugin: checks coverage thresholds after tests complete
FsHotWatch.FileCommand Plugin: runs custom commands when specific files change

Writing your own plugin

Plugins use a declarative framework: you define an update function that receives events and returns new state. The framework manages the agent, status tracking, and IPC command registration.

open FsHotWatch.Events
open FsHotWatch.PluginFramework

type MyState = { FilesChecked: int }

let myPlugin: PluginHandler<MyState, unit> =
    { Name = "my-plugin"
      Init = { FilesChecked = 0 }
      Update =
        fun ctx state event ->
            async {
                match event with
                | FileChecked result ->
                    // result.ParseResults and result.CheckResults come from
                    // the warm FSharpChecker — no re-parsing needed.
                    printfn "Checked: %s" result.File
                    ctx.ReportStatus(Completed(System.DateTime.UtcNow))
                    return { FilesChecked = state.FilesChecked + 1 }
                | _ -> return state
            }
      Commands =
        [ "my-status",
          fun state _args ->
              async { return $"checked %d{state.FilesChecked} files" } ]
      Subscriptions =
        { PluginSubscriptions.none with
            FileChecked = true }
      CacheKey = None }

// Register with the daemon:
daemon.RegisterHandler(myPlugin)

Available events (subscribe via Subscriptions):

  • FileChanged — a source file was saved (you get the file paths)
  • BuildCompleteddotnet build finished (success or failure)
  • FileChecked — a file was type-checked (you get parse + check results)
  • TestCompleted — tests finished running (you get per-project results)

What you can do in Update via ctx:

  • ctx.Checker — the warm FSharpChecker (reuse it for your own analysis)
  • ctx.RepoRoot — path to the repository root
  • ctx.ReportStatus(status) — tell the daemon your current status
  • ctx.ReportErrors(file, entries) — report diagnostics to the error ledger
  • ctx.EmitBuildCompleted(result) — emit events to other plugins
  • ctx.Post(msg) — send a custom message back to your own agent

Configuration

Create .fs-hot-watch.json in your repo root. All fields are optional — sensible defaults are used when omitted.

{
  "build": {
    "command": "dotnet",
    "args": "build"
  },
  "format": true,
  "lint": true,
  "cache": "jj",
  "tests": {
    "beforeRun": "dotnet build",
    "projects": [
      {
        "project": "MyProject.Tests",
        "command": "dotnet",
        "args": "run --project tests/MyProject.Tests --no-build --",
        "filterTemplate": "--filter-class {classes}",
        "classJoin": " ",
        "group": "unit"
      }
    ]
  },
  "coverage": {
    "directory": "./coverage",
    "thresholdsFile": "coverage-thresholds.json"
  },
  "analyzers": {
    "paths": ["analyzers/"]
  },
  "fileCommands": [
    {
      "pattern": "*.fsx",
      "command": "dotnet",
      "args": "fsi --typecheck-only"
    }
  ]
}

Configuration reference

Field Type Default Description
build object \| bool {"command": "dotnet", "args": "build"} Build command. false disables.
format bool true Enable Fantomas format-on-save preprocessor.
lint bool true Enable FSharpLint plugin. Uses fsharplint.json if found.
cache string \| bool auto ("jj" if .jj/ exists, else "file") Cache strategy: "none", "memory", "file", or "jj".
tests object Test runner config. See below.
coverage object Coverage threshold checking.
analyzers object F# Analyzers SDK integration.
fileCommands array [] Custom commands triggered by file patterns.

build fields:

Field Type Default Description
command string "dotnet" Build command.
args string "build" Arguments to the build command.
buildTemplate string Template for incremental builds. {projects} is replaced with changed project paths.

tests fields:

Field Type Default Description
beforeRun string Command to run before each test run (e.g. "dotnet build").
projects array [] List of test project configurations.

tests.projects[] fields:

Field Type Default Description
project string "unknown" Project name (used for filtering and display).
command string "dotnet" Test runner command.
args string "test --project <project>" Arguments to the test runner.
group string "default" Group name (for running subsets).
environment object {} Extra environment variables as "KEY": "VALUE" pairs.
filterTemplate string Template for class-based filtering. {classes} is replaced with affected test class names.
classJoin string " " Separator for joining class names in the filter.

coverage fields:

Field Type Default Description
directory string "./coverage" Directory containing Cobertura XML coverage reports.
thresholdsFile string Path to JSON file with per-project coverage thresholds.

analyzers fields:

Field Type Default Description
paths string[] Directories containing analyzer DLLs. Relative paths resolved from repo root.

fileCommands[] fields:

Field Type Default Description
pattern string "*.fsx" File extension pattern to match (e.g. "*.fsx", "*.sql").
command string "echo" Command to run when a matching file changes.
args string "" Arguments to the command.

Cache directory

FsHotWatch stores check result caches and the TestPrune database in .fshw/ at the repository root. Add this to your .gitignore:

.fshw/

The cache directory contains:

  • cache/ — Cached FCS check results for faster cold starts
  • test-impact.db — TestPrune dependency analysis database
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (7)

Showing the top 5 NuGet packages that depend on FsHotWatch:

Package Downloads
FsHotWatch.TestPrune

FsHotWatch plugin for TestPrune test impact analysis

FsHotWatch.Coverage

FsHotWatch plugin that checks coverage thresholds after tests complete

FsHotWatch.Analyzers

FsHotWatch plugin for F# Analyzers SDK integration

FsHotWatch.Fantomas

FsHotWatch plugin for Fantomas format checking

FsHotWatch.Lint

FsHotWatch plugin for FSharpLint integration

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0-alpha.1 15 4/8/2026
0.2.0-alpha.1 24 4/8/2026
0.1.0-alpha.1 57 4/3/2026