FsHotWatch 0.8.0-alpha.33

This is a prerelease version of FsHotWatch.
dotnet add package FsHotWatch --version 0.8.0-alpha.33
                    
NuGet\Install-Package FsHotWatch -Version 0.8.0-alpha.33
                    
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.8.0-alpha.33" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FsHotWatch" Version="0.8.0-alpha.33" />
                    
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.8.0-alpha.33
                    
#r "nuget: FsHotWatch, 0.8.0-alpha.33"
                    
#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.8.0-alpha.33
                    
#: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.8.0-alpha.33&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=FsHotWatch&version=0.8.0-alpha.33&prerelease
                    
Install as a Cake Tool

FsHotWatch

Trying to speed up the F# development feedback loop.

FsHotWatch is a background daemon that watches your source files and aims to keep the F# compiler warm, so saving a file re-checks just what changed and hands the results to your tools (linters, analyzers, test runners) — instead of each tool restarting the compiler from scratch every time.

Status: early alpha, and a lot of it is AI-written. It runs the author's own daily F# work, but behavior and APIs shift between versions, rough edges are expected, and your mileage may vary. The goal is a faster F# loop — it's still finding its shape, so issues and PRs are very welcome.

The problem

F# tools are slow because each one starts its own compiler from zero. A 15-project solution can take ~2 minutes to analyze. Every save restarts your linter, your analyzer, and your test runner — all re-parsing and type-checking the same hundreds of files again.

How it works

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

  1. You save a file — FsHotWatch notices.
  2. It re-checks just that file using the already-warm compiler — ideally milliseconds rather than minutes.
  3. Plugins get the results instantly — your linter, analyzer, and test runner see the new check results without re-parsing.
  4. You query the resultsfshw check runs everything and reports what each tool found.

Saves are debounced: if 10 files change at once (a formatter sweeping the repo, say), FsHotWatch waits for things to settle and processes them in one batch.

Quick start

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

# Run all checks. This auto-starts the daemon the first time —
# no separate "start" step needed. Verbose by default.
fshw check

# Prefer one line per plugin?
fshw check --compact   # or -q

fshw init writes a starter .fshw.json; see Configuration.

Commands

Command What it does
fshw check Run all configured checks and report findings. Auto-starts the daemon. --run-once runs without a daemon; -q/--compact for one line per plugin.
fshw status [plugin] Show the daemon's current status (optionally for one plugin).
fshw start Run the daemon in the foreground (Ctrl+C to stop). Optional — check/status start it for you.
fshw stop Stop the running daemon.
fshw format Format the code (Fantomas).
fshw test-rerun Rerun tests for an xUnit v3 --filter-class / --filter-trait slice.
fshw rerun <plugin> Force one plugin to re-run, clearing its cached state.
fshw init Generate a starter .fshw.json.
fshw config check Validate .fshw.json without starting the daemon.

Add -v for debug logging or -a for agent-friendly, parseable output. Run fshw --help for the full list.

Packages

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

Package What it does
FsHotWatch Core library — the daemon, file watcher, plugin system, IPC
FsHotWatch.Cli CLI tool — fshw check, 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.FileCommand Plugin: runs custom commands when specific files change
FsHotWatch.Coverage Plugin: checks per-file line/branch coverage thresholds after each test run

Configuration

Run fshw init to scaffold a .fshw.json in your repo root, or write one by hand. Every field is optional — sensible defaults apply when omitted.

{
  "build": {
    "command": "dotnet",
    "args": "build"
  },
  "format": true,
  "lint": true,
  "cache": "file",
  "tests": {
    "beforeRun": "dotnet build",
    "dependsOn": ["src/MyApp/Database/Migrations/**"],
    "projects": [
      {
        "project": "MyProject.Tests",
        "command": "dotnet",
        "args": "run --project tests/MyProject.Tests --no-build --",
        "filterTemplate": "--filter-class {classes}",
        "classJoin": " ",
        "group": "unit"
      }
    ]
  },
  "analyzers": {
    "paths": ["analyzers/"]
  },
  "fileCommands": [
    {
      "pattern": "*.fsx",
      "command": "dotnet",
      "args": "fsi --typecheck-only"
    }
  ],
  "coverage": {
    "configPath": "coverage-ratchet.json",
    "searchDir": "coverage"
  }
}

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 "file" Cache strategy: "none", "memory", or "file". ("jj" is a legacy alias for "file".)
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.

For memory/idle-exit, FSEvents latency, and per-task timeout keys, see Memory & tuning.

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").
dependsOn string[] [] Repo-root-relative globs (*, ?, **) naming external test inputs the symbol-diff can't see — DB migrations, generated files, schemas. Their content hash salts the test cache key, so editing a matched file forces a real test re-run even when no test source changed.
coverageDir string "coverage" Directory (repo-root-relative) for per-project Cobertura artifacts.
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.
reportVerificationFormat string "auto" How the pass/fail verdict's structured test report is obtained. The report (not the process exit code) decides green/red. auto injects --report-ctrf only for a runner detected as xUnit.v3 (else falls back to the dotnet heuristic); ctrf always injects it; off never injects it and the exit code stays authoritative.

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.

coverage fields:

Field Type Default Description
configPath string "coverage-ratchet.json" Path to the coverage-ratchet thresholds file (relative to repo root or absolute).
searchDir string "." Directory tree to search for coverage.cobertura.xml files after each test run.

Cache directory

FsHotWatch keeps its check-result cache and the TestPrune database in .fshw/ at the repository root. Add it to your .gitignore:

.fshw/

What it does and doesn't cache. The on-disk cache stores FCS check results, keyed by file content — so a fresh daemon can replay unchanged files instead of re-checking them. It does not persist the compiler's in-memory warmth: FSharpChecker and its FCS caches are rebuilt from cold on every daemon start, so the first scan after a (re)start still pays that warm-up before the cached results start landing.

Writing plugins

Plugins are declarative update functions over a shared warm compiler: you define how your state reacts to events (file checked, build completed, tests finished), and the framework manages the agent, status, caching, and IPC. See Writing a plugin.

Memory & tuning

The daemon keeps the F# compiler warm, which costs memory. FsHotWatch ships with conservative defaults (aggressive GC, optional idle-exit) so this stays in check — see Memory & tuning if you want to adjust them.

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.Analyzers

FsHotWatch plugin for F# Analyzers SDK integration

FsHotWatch.Build

FsHotWatch plugin that runs dotnet build and emits BuildCompleted events

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.8.0-alpha.33 79 6/24/2026
0.8.0-alpha.32 68 6/18/2026
0.8.0-alpha.31 95 6/17/2026
0.8.0-alpha.30 111 6/16/2026
0.8.0-alpha.29 66 6/15/2026
0.8.0-alpha.28 66 6/12/2026
0.8.0-alpha.27 69 6/11/2026
0.8.0-alpha.26 62 6/10/2026
0.8.0-alpha.25 61 6/10/2026
0.8.0-alpha.24 89 6/8/2026
0.8.0-alpha.23 62 6/7/2026
0.8.0-alpha.22 63 6/7/2026
0.8.0-alpha.21 57 6/6/2026
0.8.0-alpha.20 59 6/5/2026
0.8.0-alpha.19 60 6/4/2026
0.8.0-alpha.18 54 6/4/2026
0.8.0-alpha.17 64 6/3/2026
0.8.0-alpha.16 64 6/2/2026
0.8.0-alpha.15 92 5/28/2026
0.8.0-alpha.14 63 5/26/2026
Loading failed