# proselint: a linter for English prose that finally got a command-line interface worth using

> The project aggregates the advice of Strunk, Orwell, Twain and a dozen others into named checks you can run in a pre-commit hook or parse as JSON. What changed in 0.16.0 is the interface around those checks, not the checks themselves.

**amperser/proselint** — [Mirror] A linter for prose.

- Repository: https://github.com/amperser/proselint
- Website: https://codeberg.org/amperser/proselint
- Stars: 4,585 · Forks: 179
- Language: JavaScript
- License: BSD-3-Clause
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/amperser-proselint

## The premise is that advice on writing is inert until it becomes executable

The README argues its own existence before describing a single feature, and the argument is worth taking seriously. Writing guidance is scattered across usage guides, dictionaries, technical manuals, essays and pamphlets. Poring over Strunk and White does not make you Strunk or White. Nobody can apply the advice in an 1100-page usage guide to everything they write. The project's position is that the traditional formats of writing knowledge are therefore essentially inert, waiting to be transformed.

The transformation offered is unglamorous: a linter is a computer program that, like a spell checker, scans a file and detects issues. The README makes the analogy explicitly by comparing it to a lint roller on a shirt. That framing sets expectations honestly. You are not getting an editor, a style engine or a language model. You are getting a program that flags sentences.

What is aggregated is specific and worth noting, because it explains the character of the output. The named sources include Chuck Palahniuk, Steve Pinker, Mary Norris, Mark Twain, Elmore Leonard, George Orwell, Matthew Butterick, William Strunk, Elwyn White, Philip Corbett and Ernest Gowers, plus the editorial staff of literary magazines and newspapers. A warning you disagree with usually traces back to a specific writer's judgement, and that is a much easier thing to argue with than a mysterious score.

The stated goal is to make that knowledge accessible in the form of a command-line utility that integrates into other tools, scripts and workflows, which is what the rest of the repository is built around.

## Install paths that anticipate how your machine is arranged

Installation is offered four ways, and the spread tells you the project expects to meet you in whatever environment you already have.

The default is pip:

```bash
pip install proselint
```

Linux distributions carry it as a system package, so Fedora users get `sudo dnf install proselint` and Debian users get `sudo apt install python3-proselint`. Ubuntu needs the universe repository enabled first, with `sudo add-apt-repository universe` before the apt install line. Packaging a project for three distributions means it is expected to sit inside a system that manages its own dependencies rather than inside a virtual environment per project.

Nix users are referred to nixpkgs, which offers both a declarative form, `environment.systemPackages = [pkgs.proselint];`, and an imperative one, `nix profile install nixpkgs#proselint`. Offering both is the right call, since the declarative form suits a machine configuration and the imperative form suits trying the tool out.

The editor integrations are extensive and split by how well they are maintained, which is an unusual and useful thing to see spelled out. Current plugins exist for Vim through ALE, Emacs through Flycheck, Neovim through none-ls.nvim, and CI through MegaLinter. A separate list is explicitly marked as archived or unmaintained and covers IntelliJ, Flymake, Sublime Text, Phabricator Arc, Visual Studio Code, Coala, Danger and Syntastic.

That second list is the more informative half. Syntastic and Flymake are both superseded within their own ecosystems, which is the likely reason their integrations stopped. A Visual Studio Code plugin exists but sits in the unmaintained group, which means anyone reaching for it should expect to run the CLI through the extension host instead.

## The check subcommand and the output format other tools can actually parse

Given a document containing one sentence:

```md
John is very unique.
```

you run `proselint check text.md`, and each suggestion prints to stdout on its own line in a fixed shape:

```bash
file:<line>:<column>: <check_name>: <message>
```

The concrete output for that sentence is:

```bash
text.md:1:9: uncomparables: Comparison of an uncomparable: 'very unique' is not comparable.
```

Read that line carefully because it is the design of the whole tool. The check name comes before the message, so a machine can group by rule and a human can look up what `uncomparables` means. A linter that emitted only prose would force every integration to parse English to find out what went wrong.

Version 0.16.0 added subcommands: `proselint check` for the original behaviour, `proselint version` to print the version, and `proselint dump-config` to view the loaded or default configuration. That last one matters more than it looks, because knowing which checks are actually active is otherwise guesswork.

The same release made the JSON output stable, reachable with `proselint check --output-format json`, with a schema documented in the repository at `docs/wire-schema.md`. The shape is a top-level `result` object keyed by file URI, each holding a `diagnostics` array where every entry carries `check_path`, `message`, a `pos` array of line and column, an absolute `span` of start and end offsets, and a `replacements` field that is null when nothing applies. Since a stable schema is what lets someone build a web front end or an editor plugin without pinning to a specific version, this is the single most consequential change in the release.

## Configuration granularity, and what 0.16.0 removed

The 0.16.0 notes describe configuration as allowing checks to be specified down to the individual level while still being controllable at the module level, with the ability to nest dictionaries to keep the configuration concise. Both halves matter. Being able to switch off every check in a module is the blunt instrument; being able to switch off one check is what lets a team adopt the tool without an argument.

The same release removed what it calls the cache system of yore, and implemented the many check streamlining suggestions from issue 1373. Removing a cache in a text linter is a statement about where the cost was. Caching parsed text is awkward when a document changes between runs, and for short prose files the parse was never the bottleneck. Stripping accumulated check complexity at the same time is consistent with that: the release is about making the tool faster and clearer, not about adding rules.

The claimed result is a five times performance improvement against the demo file over the previous release.

Version 0.14.0, published 2024-04-23, is worth a line because it fixed two things that affect correctness rather than speed: the linter now loads the default configuration if none is provided, and `lexical_illusions` gained support for hyphens. Version 0.13.0, from 2021-11-07, made memoize safe for both positional and keyword arguments and began exporting decorators that wrap meta-checks, which is the extension mechanism the current plugin ecosystem builds on.

The gap between 2021 and 2024 is real, and the two years before v0.16.0 were spent on performance and configuration rather than new rules. A project can be alive and still have a long gap between releases, so read the release dates alongside the activity rather than instead of them.

## Using it as a library, and what the repository layout says

There is a second entry point beyond the command line. To run the linter inside another Python program you use the `LintFile` class from `proselint.tools`, which requires `CheckRegistry` to have been populated first. That dependency on registry population is the thing to notice: it means the library API expects you to decide which checks exist before you lint anything, so the same discipline applies in code as in the configuration file.

The packaging metadata is modern Python. `pyproject.toml` declares `requires-python = ">=3.10"` and classifies support for 3.10 through 3.14, with `pdm-backend` as the build backend and a `[project.scripts]` entry mapping the `proselint` command to `proselint.command_line:main`. Tests run through pytest with hypothesis and the `rstr` string generator, coverage through pytest-cov, and lint and format through ruff configured at a line length of 80. Task running is handled by poethepoet rather than a Makefile, declared under `[tool.poe.tasks]`.

Only one runtime dependency is declared, and it is an unusual one: `google-re2`, with `google-re2-stubs` for types, pinned to a build from 2025-11-05 or later. RE2 is a linear-time regular expression engine that guarantees bounded runtime. For a prose linter that applies hundreds of patterns to arbitrary text, that guarantee is the whole reason to depend on something other than Python's built-in `re`, where a pathological pattern can hang the process. It is a dependency chosen for predictability, and predictability is the correct property for a tool you put in a commit hook.

The tree also shows a `corpora/` directory, a `research/` directory and `scripts/`. A linter with a research directory is a linter whose checks have citations behind them, which is consistent with the premise and explains why check names are things like `uncomparables` rather than style codes invented for the tool.

## Where proselint fits, and where it does not

The honest scope is narrow and the project does not pretend otherwise. It is a linter for English prose, packaged under BSD-3-Clause with the licence declared as a file reference, authored by Amperser Labs, and it targets documentation, essays, changelogs and any other English text where a mechanical check is meaningful.

It is not a grammar checker and not a rewriting tool. Every output is a suggestion with a name attached, and deciding whether to act on it stays with the writer. For teams that want consistent documentation language, that boundary is the feature. For anyone hoping for a tool that will make prose good, the README's own argument is the answer.

The deployment story is settled. The pre-commit configuration is short enough to read in full:

```yaml
repos:
  - repo: https://github.com/amperser/proselint
    rev: v0.16.0
    hooks:
      - id: proselint
```

with an instruction to change `rev` to whichever tag you want, and credit to Andy Airey for the hook configuration. Pointing at the tag rather than the branch means your documentation lint results do not change because someone merged a new check on a Tuesday.

Two structural facts deserve attention before you adopt it. The repository's own description reads `[Mirror] A linter for prose.` and the homepage field points to codeberg.org/amperser/proselint, so GitHub is the mirror and Codeberg is canonical; recent work may land there first. And the repository still carries a `Procfile` and a `Gemfile` in its tree alongside the Python tooling, which are leftovers from an earlier deployment arrangement and are a fair signal that the project's infrastructure has changed more than once.

The current state is a specific, well-packaged tool with a stable machine-readable output, broad editor integration and a small number of opinionated checks. Treat the opinions as configurable rather than authoritative.

## Conclusion

proselint earns its place because it refuses to guess. Every warning names the check that produced it, and the check name is a pointer to a rule you can look up, agree with, or disable, which is the difference between a linter and a style enforcer that nobody turns on. The practical entry point is `proselint check yourfile.md`, the `--output-format json` schema is stable enough to build a wrapper on, and the pre-commit configuration is a five-line block that pins a tag, so a team can adopt it in a single commit. Two things are worth knowing before you wire it into a documentation repository. Warnings about clichés, wordiness and comparables are opinionated by design, drawn from a specific set of writers and magazines, and the right response is to disable the checks you disagree with rather than to argue with the output. And the canonical home has moved to codeberg.org/amperser/proselint, with this GitHub repository described in its own description as a mirror, so point CI at Codeberg rather than assuming GitHub is where new work lands. Version 0.16.0, tagged 2025-11-14, is the current release and the last commit reached this mirror on 2026-09-04.

## FAQ

### What is prose literacy?

Prose literacy is knowing how written language works well enough to judge it: structure, clarity, word choice and the conventions a reader expects. In this project's terms it is the kind of knowledge that usage guides describe but rarely hand to you at the moment of writing. proselint exists to convert a slice of that knowledge into checks you can run against a file, which is the closest a text editor gets to being told the rules while you type.

### How do I run proselint on a document?

Install it with `pip install proselint`, or from your distribution's package manager. Then run `proselint check text.md`. Suggestions print to stdout one per line in the form `file:<line>:<column>: <check_name>: <message>`. For machine-readable output use `proselint check text.md --output-format json`, and run `proselint dump-config` to see which checks are currently active.

### Can I use proselint in a pre-commit hook?

Yes, and the configuration is short. Point the hook at the repository and pin the tag you want: a `repos` entry for `https://github.com/amperser/proselint` with `rev: v0.16.0` and `id: proselint`. The README notes you should change `rev` to whichever git tag or revision you want, which is what stops new checks from changing results on your team without review.

### Is proselint still actively maintained?

Yes, with releases spread out. The current release is v0.16.0, tagged 2025-11-14, which added subcommands, stable JSON output, a five times performance improvement and finer-grained configuration. The previous tag was 0.14.0 from 2024-04-23, and 0.13.0 predates that by about three years, so the gaps are real. The repository is not archived and the last commit arrived on 2026-09-04, and note that the canonical home has moved to codeberg.org/amperser/proselint with GitHub kept as a mirror.

## Sources

- [amperser/proselint on GitHub](https://github.com/amperser/proselint)
- [License: BSD-3-Clause](https://github.com/amperser/proselint/blob/main/LICENSE)
- [Project website](https://codeberg.org/amperser/proselint)
- [README](https://github.com/amperser/proselint/blob/main/README.md)
- [Releases](https://github.com/amperser/proselint/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/amperser-proselint
