Skip to content
unswell

Overview

A linter for lean English, before it reaches the commit.

Unswell checks the English in documentation, comments and string literals. It measures sentence length, readability, passive density and repetition, matches a catalog of formulaic phrasings, explains what to revise, and fails the build when your editorial policy says so. It runs offline as a Go CLI, library, GitHub Action and MCP server.

Get started →Try it in the browser
brew install stokaro/unswell/unswell
docs — unswell check
$ unswell check README.md docs/guide.md
README.md:3:1: warning [filler.announced-importance] State the fact without announcing its importance.
docs/guide.md:1:1: error [scaffold.chat-preamble] Start with the subject instead of a conversational acknowledgment.
FAIL: 2 documents, 2 findings; analysis complete.
  gate: docs/guide.md: Start with the subject instead of a conversational acknowledgment.
Revision probability: unavailable (no calibrated alpha model).
$ echo $?
1

Not an authorship detector. A finding is about the wording, whoever typed it.

Two files went in. The first sentence of README.md announced its own importance before stating a fact, so filler.announced-importance reported a warning and left the gate alone. The first block of docs/guide.md opened with a chat acknowledgment, and that rule was set to gate: forbid in the project policy, so the run failed with exit code 1.

Every finding carries the original byte range and a one-based column, the rule that produced it, and a message that says what to change. Nothing left the machine: there is no service to call and no key to hold.

Wording is a policy your project can state, argue about and version, so a linter is the place to put it. Unswell ships 40 rules with weights, caps and default severities, and a configuration file decides which of them your project cares about. The alpha index is an editorial-policy index from 0 to 100. It is not a probability, and no score claims to know who wrote anything.

The 40 rules do not fire at one rate, and the difference is worth knowing before you run it. Measured over 81,690 paragraphs of documentation and comments from 259 repositories, under the strict profile:

Rule Paragraphs flagged
syntax.long-sentence 3.70%
syntax.passive-candidate-density 2.39%
readability.grade-metric 1.96%
repetition.exact-sentence 1.49%
filler.wordy-phrase 0.30%
filler.announced-importance 0.001%
every scaffold. and hype. rule 0%

On prose someone wrote, the structural rules do the work. The phrase rules match exact wordings such as a chat acknowledgment or a stock opening, and an engineer writing documentation does not produce those, so they stay silent. They earn their keep on text pasted out of an assistant’s reply, which does contain them.

That is a deliberate trade. A phrase rule that fired on ordinary documentation would be a phrase rule you turn off. Silence on clean prose is the correct behavior for a linter, and it is why unswell check on a well-edited repository reports long sentences and little else.

Each rule reports evidence you can act on, not a verdict you have to accept. filler.announced-importance matches the phrase and points at the sentence that carries it:

It is important to note that the client retries a request only after a connection failure.

The revision keeps the condition and drops the announcement:

The client retries a request only after a connection failure.

When wording has to stay, a source comment permits that exact finding and records why. The evidence stays in the report, the permission is audited, and an unused permission fails the run rather than rotting in place.

CLI

unswell check over paths or stdin, with text, JSON, SARIF, HTML and Markdown reports from one analysis.

Go library

unswell.New and Engine.Analyze return the same result the CLI prints. The library reads no configuration file of its own.

GitHub Action

A pinned release archive, verified by SHA-256, with JSON and SARIF paths as step outputs.

MCP server

unswell-mcp exposes the same engine over stdio, so an assistant can check a draft against your policy before it proposes the change.

Inputs are plain text, Markdown, and the comments and string literals of Go, JavaScript, TypeScript, TSX, Python, Rust, Java, C, C++, C#, YAML, Bash, POSIX shell, Zsh, Fish and PowerShell. Code itself is never checked. See how it works for what extraction keeps and what it leaves out.