Skip to main content

A console prose linter — readability checks plus opt-in AI-writing / de-slop checks. Like flake8, but for English.

Project description

nabokov

A console linter for English prose. Hard sentences, adverbs, passive voice: one warning per line.

Python Built with spaCy License: MIT

Code gets review. Prose gets a shrug. nabokov catches hard sentences, adverbs, passive voice, wordy phrases, and qualifiers. Opt-in checks spot the tells of AI writing. A readability grade comes with the report. Findings print as warnings you can pipe into an editor or CI. Each check is a rule with its own code, so you switch checks on and off the way you do with flake8.

Try it

uvx nabokov draft.md          # one command, no setup; fetches the model on first run

Why

Code has linters, but prose rarely does. Style guides live in people's heads. nabokov moves them into your terminal. It points at the sentence that reads hard and says why.

The Hemingway Editor inspired the rule set. Detection runs on spaCy. Passive voice comes from a dependency parse, not a fragile regex.

Install

Requires Python 3.12 or newer.

uv tool install nabokov       # or: pipx install nabokov
nabokov download-model        # one-time: fetch the spaCy model (en_core_web_sm)
nabokov draft.md

The model is a separate download because PyPI does not allow the direct-URL dependency it ships as. nabokov fetches it on first run; the explicit nabokov download-model (or python -m spacy download en_core_web_sm) does it up front. For local development, uv sync installs everything, including the model.

Usage

nabokov draft.md                 # colored report for humans
nabokov --format=flake8 x.md     # path:line:col: CODE message
cat notes.txt | nabokov -        # read from stdin
nabokov docs/                    # walk a directory of .txt / .md / .html files
nabokov --max-grade 9 x.md       # exit non-zero if the grade goes over 9
nabokov --target essay draft.md  # judge against the ESSAY reading level
nabokov --select NB302 x.md      # run one rule
nabokov --ignore NB301 x.md      # skip a rule
nabokov --list-rules             # print every code

nabokov reads plain text, Markdown, and HTML. For .md and .html it blanks the markup: code, tags, and link URLs. It then checks only the visible prose, so findings point at real writing. For stdin, --stdin-display-name draft.md sets the type.

Exit codes follow flake8: 0 when clean, 1 on findings, 2 on a usage error.

Output formats

Choose one with --format:

  • Color (--format=color) highlights snippets and adds a grade summary. It is the terminal default.
  • Flake8 (--format=flake8) prints one finding per line, for editors and CI.
  • JSON (--format=json) returns diagnostics plus the document grade.
  • GitHub (--format=github) emits workflow annotations for GitHub Actions.

Rules

Run nabokov --list-rules to see them all. The full reference lives in docs/RULES.md.

  • NB201 / NB202: the very hard and hard reading levels.
  • NB203: a main clause buried after 20+ words of build-up (advisory).
  • NB301: adverbs.
  • NB302: passive voice.
  • NB303: qualifiers and hedges.
  • NB304: nominalizations behind light verbs — "came to an agreement" → agreed.
  • NB305: dummy subjects — "There are many resorts in Colorado" → "Colorado has…".
  • NB401: wordy phrases, with a simpler suggestion.
  • NB601: abstract, "empty prose" paragraphs, scored against the Brysbaert concreteness norms (advisory).
  • NB101: the document grade, reported with --max-grade.

Reading-level targets

One bar does not fit every text. --target sets the level nabokov holds a sentence to (case-insensitive):

  • accessible — plain language; sentences count as hard from grade 8, very hard from 12.
  • normal — the default; hard from grade 10, very hard from 14.
  • technical — docs for expert readers; hard from grade 14, very hard from 18.
  • essay — essays, blog posts, opinion pieces. The TECHNICAL thresholds, plus the loosest style budgets for a writer's voice.
  • social — short-form posts. Plain-language thresholds. Staccato fragments and repeated openers are the genre's voice, not AI tells.
  • email — business email. A high-trust audience, so the tightest style budgets of any target.
nabokov --target technical api-guide.md
nabokov --target essay draft.md

Each target also carries style budgets, counted per 1000 words. Adverbs, passive voice, qualifiers, and wordy phrases stay info within budget. Over budget, they become warnings. To make a target stick, set target in your config instead of passing the flag each run (see below).

Signs of AI writing (opt-in)

nabokov also spots common LLM tells (NB5xx). The lists come from the Wikipedia guide and community threads. It catches the it's not X, it's Y construction and puffery like delve or tapestry. Promotional phrases, chatbot filler like Great question!, and overused transitions all trip it. It also flags em-dash and emoji overuse. Rule-of-three fragments, flat sentence rhythm, and repeated openers round it out.

These checks stay off by default, because they often flag a writer's own voice. Turn them on with a flag:

nabokov --ai draft.md         # the core checks plus the AI-writing checks
nabokov --ai-only essay.md    # only the AI-writing checks

--ai is shorthand for --extend-select NB5, and --ai-only for --select NB5.

Pair it with the agent skill

The linter catches the mechanical part. Add the nabokov-editor skill to your coding agent and it also fixes the findings. Then it reads for what rules miss: empty sentences, invented detail, hollow closers. Fixes keep your meaning. Big edits wait for your approval.

# Claude Code
/plugin marketplace add viewflow/nabokov
/plugin install nabokov@viewflow

# Cursor, Codex, Gemini CLI, and other agents (via the skills CLI)
npx skills add viewflow/nabokov

Then ask your agent to lint or de-slop a file. Skill details live in skills/nabokov-editor/SKILL.md.

Configuration

Put settings under [tool.nabokov] in pyproject.toml, or in a .nabokov.toml. nabokov walks up from the current directory to find one. CLI flags win.

[tool.nabokov]
target = "NORMAL"       # ACCESSIBLE | NORMAL | TECHNICAL | ESSAY | SOCIAL | EMAIL
ignore = ["NB301"]      # e.g. stop flagging adverbs

[tool.nabokov.budgets]  # optional: per-1000-word style budgets (see docs/RULES.md)
NB301 = 20              # adverbs stay advisory (info) up to this density

Suppress one line inline:

This sentence is fine.  <!-- nabokov: ignore NB302 -->

How it works

nabokov scores readability with the Automated Readability Index (ARI). Word characters drive the grade, so nabokov counts no syllables.

The adverb list and the phrase dictionary began as classic lists for plain language. We added extra hedges and more phrase alternatives. A fuller set of irregular participles feeds the passive check.

spaCy handles the parsing. Passive voice reads the auxpass dependency. Adverbs read the part-of-speech tag plus the -ly suffix. The pipeline loads once and runs on every file.

Development

uv run pytest               # the test suite
uv run ruff check .         # lint
uv run ruff format .        # format
uv run pyright              # type-check

The nabokov-editor skill drives the linter inside an agent loop. It detects issues, rewrites while preserving meaning, and asks before any large change.

License

MIT.

Credits

Inspired by the Hemingway Editor. Parsing uses spaCy. The name is a nod to a writer who cared about sentences.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nabokov-26.7.4.tar.gz (324.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nabokov-26.7.4-py3-none-any.whl (241.1 kB view details)

Uploaded Python 3

File details

Details for the file nabokov-26.7.4.tar.gz.

File metadata

  • Download URL: nabokov-26.7.4.tar.gz
  • Upload date:
  • Size: 324.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for nabokov-26.7.4.tar.gz
Algorithm Hash digest
SHA256 1c1c50ea6cf9ab060664a32f44b53af880fd2cbff718fabfb09767d52dfcb4c8
MD5 767afb9f0947e579302af54d30a9004b
BLAKE2b-256 442f33c3a81e082b6cabe8437e5af51d22ba560bdd79a365f999f685e75f44c6

See more details on using hashes here.

File details

Details for the file nabokov-26.7.4-py3-none-any.whl.

File metadata

  • Download URL: nabokov-26.7.4-py3-none-any.whl
  • Upload date:
  • Size: 241.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for nabokov-26.7.4-py3-none-any.whl
Algorithm Hash digest
SHA256 df99a4df717f7ad8c343e88c56b1b800d7c3c62c74f7b6b6c1bdc0f96c7ffd68
MD5 98f3198a4e0edc00f4d7972f0a4507eb
BLAKE2b-256 046734db81826ef789981a5a6b5e1d6576d826550495b32024f02948ce11ea00

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page