syntax + code-style · C# · .NET 10

A C# formatter for
every dotnet build.

Fast. Always on. Fully configurable.

Curb reads your .editorconfig when you have one, and works out of the box when you don't. Mechanical style fixes happen before an AI agent ever reports a diagnostic, so it edits less and finishes faster.

nuget ↗
nuget ↗
dotnet build
$ dotnet build
 
curb formatted 47 files · verified 47
  braces, wrapping, spacing, usings — fixed
 
csc  Order.cs(31,9): info IDE0007:
      Use 'var' instead of explicit type
 
✓ Build succeeded
 
# the mechanical work is gone before
# the compiler reads a line. what is
# left needed a compilation to decide.
$

Not a formatter you run.
A formatter that already ran.

Most formatters are a step you have to remember: before commit, in CI, via a pre-push hook. Curb runs before CoreCompile on every dotnet build. By the time the compiler reads a file, the style is already applied.


One package.
Then never think about it.

Curb runs before CoreCompile, so the compiler reads source that is already formatted. The mechanical offences are not reported — they are gone.

01
Use the .editorconfig you already have

No second config file. Add max_line_length when you want reflow; leave it out and Curb will never wrap a line for you.

02
Add the build package

Build-only — no library, nothing in your output. Drop it in Directory.Build.props to cover a whole solution.

03
Run dotnet build

Files are rewritten in Debug and checked in Release. A developer wants the fix; CI wants to be told. An untouched project pays nothing: no process start, no directory walk.

.editorconfig
[*.cs]
indent_style = tab
max_line_length = 120          # opt into reflow
csharp_new_line_before_open_brace = all
csharp_prefer_braces = true
csharp_style_namespace_declarations = file_scoped
Directory.Build.props
<PackageReference Include="curb"
                  Version="*" PrivateAssets="all" />

Rewriting code unattended is only reasonable if it cannot go wrong. Every file is verified in memory before anything is written — the tokens emitted must match the tokens parsed — and a file that fails is reported and left exactly as it was. Syntax Curb has not learned yet is emitted verbatim, so it is incomplete long before it is ever destructive. How it is checked →


Fast enough that
skipping it makes no sense.

Newtonsoft.Json — 945 files, no .editorconfig. Wall-clock time, best of three cold runs — first invocation, nothing cached, every file reparsed.

0.26 s
Curb
3.47 s
dotnet format whitespace
4.85 s
CSharpier

Curb ships as a native-AOT binary — about 10 ms to start, no warm-up cost. And that's still not the fastest curb gets: MSBuild's own incremental build skips curb entirely once a project is untouched — no process start, no directory walk — and even when curb does run, it reformats only the files that changed since its own cache, not the whole project. Curb is fast cold, faster warm. Full benchmark table →


Two passes.
One build.

Style rules split by what they need to know. The split is not an implementation detail — it is why Curb can run before the compiler and still leave nothing for dotnet format to do afterwards.

pass 1 · always on

Layout

Decided from the parse tree alone. No restore, no compilation, no project system — runs before CoreCompile, rewrites the file, and the compiler reads formatted source.

  • Indentation, spacing, brace placement, blank lines
  • Reflow to max_line_length
  • Braces, expression bodies, file-scoped namespaces
  • Modifier order, using directives, file headers
97 options →
pass 2 · on demand

Code-style fixes

Manual, not automatic. Run curb cleanup yourself after a build, and set EnforceCodeStyleInBuild = true (usually in Directory.Build.props) so the build actually reports the diagnostics it reads.

  • Unused usings, var, readonly fields
  • new(), string interpolation, redundant #nullable
  • Accessibility modifiers, readonly structs and members
  • Ten rules total. Everything that needs a compilation to decide.
curb cleanup →

Pass 1's output is measured as a fixed point of dotnet format whitespace in CI, on every push: 100% with reflow off, 100% with reflow on, 99.9% with csharp_keep_existing_linebreaks = true. Pass 2 is held to the same discipline against dotnet format style. Both read the same .editorconfig in your repo. How it's measured →


Style enforcement that costs no context.

Style rules written as prose in an AGENTS.md get followed inconsistently, and every correction is a round trip: the agent writes, the build complains, the agent edits, the build runs again. Every one of those edits is mechanical — there was one right answer, and it was already in your .editorconfig.

Layout needs no compilation, so it is already fixed by the time an agent's build even finishes — that half of IDE0055 never reaches it. What's left after that is the handful of rules that do need a compilation to decide: unused usings, var, readonly fields. curb cleanup fixes all of them in one pass over the diagnostics the build already produced — one command, not one round trip per offence.

AGENTS.md — the whole style section
## Style

Formatting and syntax-level code style are applied
automatically by the build. See .editorconfig.
Do not hand-format code, and do not "fix"
formatting you see in a diff.
dotnet build will do it.

After a build reports style diagnostics, run
curb cleanup --forward. It owns IDE0005, IDE0007,
IDE0034, IDE0040, IDE0044, IDE0071, IDE0090,
IDE0240, IDE0250, and IDE0251, and forwards
anything else to dotnet format style. Always safe
to add: it skips dotnet format entirely once
curb has already fixed everything.
what's left after layout, fixed in one pass
$ dotnet build
Order.cs(3,1): error IDE0005: Using directive is unnecessary
Order.cs(4,1): error IDE0005: Using directive is unnecessary
Build FAILED.

$ curb cleanup
fixed 2 of 2 diagnostics · 0 refused

$ dotnet build
Build succeeded.


Pre-commit, and anywhere
dotnet build doesn't run.

curb-cli isn't only for CI steps without the SDK. Point check at a git pre-commit hook and most runs cost a hash comparison against a cache, not a full parse — it reads the same .editorconfig your build already does. Husky.NET wires it up for the whole team in one config file.

.git/hooks/pre-commit
#!/bin/sh
curb check --cache .git/curb.cache ./src

Use as GitHub Action.

A pre-built, distroless container image on GitHub Container Registry — pulled, not built, so there is nothing to compile in your workflow. check fails the job if anything would reformat; format rewrites in place.

01
No .NET SDK install

One uses: line. No actions/setup-dotnet, no tool install, no restore.

02
Pull and run in seconds

Native-AOT on a chiseled, shell-less base image. Pulled, not built on every run.

03
Same binary, same rules

Reads the .editorconfig already in your repo — nothing new to configure.

.github/workflows/ci.yml
- uses: nullean/curb@main
  with:
    command: check          # or "format"
    path: .

One package reference.

The .editorconfig you already have. Nothing new to learn.
Nothing to remember to run. Just dotnet build.