A formatter, linter and quality gate for C#, driven by one .editorconfig.
Skala formats your code, arranges it, runs 337 analysis rules over a real compilation, and fails your build or your CI job when the result crosses a line you drew. The command line, your editor, CI and any AI agent working in the repository all read the same configuration file, so they agree about what the code should look like by construction rather than by convention.
dotnet tool install --global Rikarin.Skala.Cli
skala format .
skala check .As a .NET tool, for the command line and CI:
dotnet tool install --global Rikarin.Skala.CliPrefer a local tool manifest so everyone on the repository runs the same version:
dotnet new tool-manifest
dotnet tool install Rikarin.Skala.CliAs a package, to get the analyzers and the build integration in every dotnet build:
<PackageReference Include="Rikarin.Skala.Sdk" Version="[2.0.0]" PrivateAssets="all" />The square brackets are deliberate. All five packages ship at one version, and an exact pin is what keeps the analyzers and the tool agreeing about whether the tree is clean.
Requires the .NET 10 SDK.
skala format . # rewrite: spacing, blank lines, braces, indentation, wrapping
skala format --check . # report what would change, write nothing
skala arrange . # body styles, var, target-typed new, qualifiers, usings
skala check . # run the analyzers, print findings, apply the gate
skala fix . # apply the safe fixes, re-bind each rewritten file, re-format
skala explain SK1084 # what a rule means, why, and what it rewrites
skala explain skala_wrap_arguments_style # what an option governsskala verify . is the one an agent or a pre-commit hook should run: it is format --check plus
arrange --check plus check --gate=local, and exit 0 means there is nothing left to do.
format |
Spacing, blank lines, braces, indentation, line breaks and wrapping |
arrange |
Body styles, var, target-typed new, property patterns, qualifiers, using directives |
check |
Run the analyzers over a real compilation, report, and gate |
verify |
format --check + arrange --check + check --gate=local |
fix |
Apply fixes, re-bind each rewritten file and revert the ones that stopped compiling, re-format |
explain |
A rule's rationale and examples, or what an option does |
config |
Inspect, check, diff, distil and sync the .editorconfig |
baseline |
Accept the findings a repository has today, so the gate is about new ones |
report, trend |
Re-render a stored SARIF; show findings and metrics over time |
lsp, mcp |
Language Server Protocol and Model Context Protocol, over stdio |
hooks |
Install the pre-commit hook |
| Code | Meaning |
|---|---|
| 0 | Nothing to do |
| 1 | The gate failed |
| 2 | Formatting or arrangement is needed (--check) |
| 3 | The configuration is wrong |
| 4 | The project or compilation could not be loaded |
| 5 | The run did not complete: the formatter's own safety net tripped (SK9099), a file could not be read (SK9015), or a file is not valid in its encoding (SK9018) |
| 130 | Cancelled |
Everything is configured from .editorconfig. Standard EditorConfig and Roslyn keys work as they
are; Skala's own 436 formatting options are skala_*:
root = true
[*.cs]
indent_style = space
indent_size = 4
max_line_length = 120
skala_wrap_arguments_style = chop_if_long
skala_keep_existing_declaration_block_arrangement = false
# Add blocks around single-statement ifs and loops (the default).
csharp_prefer_braces = true:none
skala_place_simple_initializer_on_single_line = false
dotnet_diagnostic.SK1084.severity = warningskala config check . reports which options are honoured, which contradict each other, and which
keys Skala does not know. skala config explain <file> shows the effective option set for one file
with the file:line each value came from.
Gates live in skala.jsonc:
Adopting Skala in a repository that already has findings does not mean fixing them all first —
skala baseline create --apply accepts what exists today, and the gate is then about what you add.
The one exception is arrangement: an unarranged file is never accepted, because skala arrange is
its only fix and arrange --check fails on it whatever the baseline holds.
- A formatter that wraps. Not just whitespace between tokens: a line-fitting engine, so
max_line_lengthwith a wrapping style is a thing you can actually configure. - 336 rules across correctness, modernization, async, performance, security, design and
maintainability. 162 carry a fix; 107 of those are safe enough for
skala fixto apply unasked. - Real analysis. Rules run over a Roslyn compilation loaded from a binary log, an MSBuild workspace, or loose files, and each rule declares which of those it needs.
- SARIF output, so findings land in GitHub code scanning or anything else that reads it.
- Baselines,
--sincescoping, duplication detection, cognitive complexity and a recorded history you can plot. - Editor and agent integration over LSP and MCP.
Two things Skala will not do, with no flag to turn them off:
- It never writes a file whose token stream differs from the input's. If a rewrite would change the program, the file is left alone and the run reports it.
- It never formats a file it could not parse.
Both promises stand on the decode, and the token stream is only as trustworthy as the decode that
produced it. A file whose bytes are not valid in the encoding it declares — a Latin-1 é in a file
with no byte-order mark, read as UTF-8 — is refused by every verb that writes (SK9018) and left
byte-identical. Before #387 its undecodable bytes became U+FFFD on both sides of the token
comparison, the comparison agreed, and the file was written back with the original bytes gone.
The token stream is not the whole story either. A [CallerArgumentExpression] parameter hands the
program its argument's source text, whitespace and line breaks included, so Check(a < b) prints
a < b and would print a < b once formatted — with every token unchanged. skala format (and the
formatting pass of skala fix) leaves such an argument byte-identical wherever it can see that a call
captures (#432, SK-DIV-0187). It sees that from the file alone: the capturing members of the .NET
library and NUnit, and capturing methods declared in the same file. A capturing method declared in
another file is not seen.
git clone https://github.com/Rikarin/Skala.git
cd Skala
./build.sh # restore, compile, test
./build.sh Lint # Skala checks its own source with itselfSome checks need the ReSharper command line tools installed, which Skala uses as the reference formatter when regenerating its conformance corpus. Ordinary contributions do not.
Issues and pull requests are welcome at github.com/Rikarin/Skala.
The version is measured, not declared. Each release builds the previous one alongside the candidate and compares five surfaces — formatted output over a reference corpus, the rule catalogue, the exit codes, the SARIF shape and the option registry — and the bump is whatever the largest difference implies. A formatting change is a minor bump at minimum, because downstream it is a repository-wide diff.
2.0.0 is the first stable release. Every push to master publishes a 2.0.0-alpha.N prerelease
beside it, where N counts commits — those are opt-in, found only with --prerelease.
{ "gates": { "local": { "maxSeverity": "error" }, "pr": { "since": "origin/main", "newIssues": 0, "maxSeverity": "warning" }, "ci": { "baseline": ".skala/baseline.sarif", "newIssues": 0, "metrics": { "duplication": 10.0, "cognitiveComplexity": 20 } } } }