Skip to main content

A markdown style linter for technical writing - flags bold/italic, em dashes, banned phrases, long sentences, label-colon openers, and more.

Project description

stylint

Voice, formatting, and code-style rules for technical write-ups, plus a mechanical checker that catches the worst offenders.

Install

uv tool install stylint

Or from a local clone:

git clone https://github.com/alexeygrigorev/stylint ~/git/stylint
uv tool install --from ~/git/stylint stylint

Then run:

stylint docs/
stylint --list-tags
stylint --ignore tables docs/
stylint --style-guide

What's here

  • style-guide/voice.md - tone, voice rules, content rules (include/leave out).
  • style-guide/formatting.md - markdown mechanics: headings, code blocks, lists, links, asides.
  • style-guide/code-style.md - educational code style inside example blocks (no defensive try/except, no chained .get(), one blank line between definitions).
  • style-guide/polish.md - judgment-level prose patterns the script cannot detect (plain words over abstractions, banned-but-context-sensitive words).
  • The same style guide docs are bundled in the Python package. Run stylint --style-guide to print their installed paths.
  • stylint/ - mechanical checker package. Edit BANNED_WORDS, BANNED_PHRASES, BANNED_OPENERS in stylint/patterns.py to extend the enforced list.
  • check_style.py - compatibility wrapper for direct script usage.

Run the checker

Scan the current directory:

stylint

Scan one file or a specific folder:

stylint path/to/post.md
stylint docs/

Suppress specific rules with --ignore:

stylint --ignore tables docs/
stylint --ignore tables,long-clause-likely docs/
stylint --list-tags

To skip files, drop a .prose-style-ignore at the scan root, one fnmatch pattern per line:

# build outputs
_site/*
node_modules/*
# generated docs
docs/api/*

Rule tags

Every finding starts with a [tag] so you know which rule fired and can pass that tag to --ignore.

Markdown mechanics

  • bold - **text** or __text__ used for emphasis.
  • italic - *text* or _text_ used for emphasis.
  • tables - GFM table row (a line starting with |).
  • hr - horizontal rule --- on its own line.
  • em-dash - the em-dash character .
  • double-hyphen - -- used as a substitute for an em dash.
  • dash-parenthetical - sentence with two - dashes inserting a parenthetical.
  • smart-quotes - curly quotes , , , .
  • backticks-in-link - link text wrapped in backticks like [`file.py`](url).
  • bare-url - raw URL in prose like see https://example.com.
  • angle-url - URL wrapped in angle brackets like <https://...>.
  • frontmatter-blank - missing blank line between YAML frontmatter closing --- and the body.

Headings

  • heading-question-word - heading starts with Why, How, What, etc.
  • heading-question-mark - heading ends with ?.
  • heading-too-deep - heading depth ### or deeper.
  • lazy-heading - heading like ## The problem, ## The RAG idea, ## The challenge.

Code blocks

  • code-no-lang - fenced code block with no language tag.
  • code-too-long - code block over 40 lines.
  • consecutive-code - two code blocks back-to-back with no prose between them.
  • lead-in - code block or list directly under a heading with no lead-in sentence.
  • lead-in-multi - the paragraph that introduces a list or code block has multiple sentences and ends with :. The lead-in sentence should be its own one-sentence paragraph.
  • chained-get - chained .get(...).get(...) in example Python.
  • double-blank - two blank lines between Python definitions.

Banned tokens (extend in stylint/patterns.py)

  • banned-word - single word on the BANNED_WORDS list (e.g. delve, leverage, crucial).
  • banned-phrase - multi-word phrase on the BANNED_PHRASES list (e.g. the power of, serves as, in action, the backbone of).
  • banned-opener - sentence-start word on the BANNED_OPENERS list (e.g. Additionally, Moreover).

Voice / fragments

  • third-person - the string Alexey appears in prose (third-person presenter reference).
  • anaphoric-no - sentence-start No X, no Y verbless fragment (e.g. No frameworks, no magic).
  • semicolon - any ; in prose.
  • cleft - pointless [This/That/It] is what X is about cleft.
  • gerund-opener - sentence opens with an -ing participial phrase (e.g. Reading through it, I noticed...).

Paragraph / sentence shape

  • paragraph-too-long - paragraph with more than 5 sentences.
  • long-sentence - sentence over 20 words, no commas.
  • long-list-likely - sentence over 20 words with commas where a colon precedes the commas or the sentence closes with and X / or X over 3+ chunks. The commas look like enumeration. Fix: convert to a bullet list.
  • long-clause-likely - sentence over 20 words with commas where the commas look like clause boundaries (subordinating conjunctions like which, that, while, because, but, however, so, when, or chains of actions with mixed subjects). Fix: split into 2-3 shorter sentences. Do NOT bullet — it would break the meaning.
  • many-commas - sentence with more than 3 commas.
  • colon-inline - colon followed by 3+ comma-separated items and a terminal and/or (likely should be a bullet list).
  • parallel-sentences - 3+ adjacent sentences sharing the same 1-2 word opener (e.g. Maybe it should... Maybe it should...).
  • choppy-rhythm - 3+ adjacent sentences of 9 words or fewer. The staccato rhythm reads worse than one or two joined sentences. Fix: combine two of them with a conjunction ("so", "because", "and", "but") or restructure as a single longer sentence.
  • repeated-and - polysyndetic chain ("A and B and C") of 3+ items joined by and instead of the oxford comma form. Fix: use commas with and only between the last two items. Idioms like "more and more" or "date and time" only fire when embedded in a longer chain ("X and more and more Y"); standalone uses don't trigger the rule.
  • meta-framing - sentence has the shape [The/A/Another] <abstract noun> [of X] is that <claim> (e.g. A big advantage of Recorder is that it keeps recording, Another limitation is that..., The key insight is that...). Writer announces the shape of the claim instead of stating it. Fix: drop the framing and lead with the claim.
  • label-colon - paragraph opens with The problem:, Goal:, What we want: followed by prose. Note: and Important: are exempt as callouts.
  • question-opener - paragraph opens with a rhetorical question (Why do we need X?).

File-level

  • now-lets-overuse - file uses more than 3 sentence-starting Now/Let's openers total. Vary openers or use the bare imperative.
  • now-lets-combo - sentence starts with the redundant pair Now let's... or Let's now.... Drop both softeners and use the bare imperative.

Reading order for a write-up

Run stylint --style-guide to locate the installed copies when you consume Stylint as a dependency.

  1. voice.md while drafting.
  2. formatting.md for any markdown syntax question.
  3. code-style.md for example code blocks.
  4. Run stylint and fix every reported finding.
  5. polish.md for a final judgment-level pass.

Hooking it into a project

The simplest setup is a pre-commit hook. From the target project root:

ln -s ~/git/stylint/check_style.py .git/hooks/post-add-style-check

Or run it from CI:

- run: stylint docs/

Notes on what's enforced vs. what's judgment

The script catches every rule listed in the "Rule tags" section above. Each error message includes the tag, a numbered list of common fixes, and (for list-conversion rules) the inline heuristic for deciding whether a bullet list is right.

Everything else (tone, voice, abstractions, "explain why", source material handling) is in the reference docs and needs a human or agent to apply judgment.

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

stylint-0.1.0.tar.gz (49.1 kB view details)

Uploaded Source

Built Distribution

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

stylint-0.1.0-py3-none-any.whl (47.2 kB view details)

Uploaded Python 3

File details

Details for the file stylint-0.1.0.tar.gz.

File metadata

  • Download URL: stylint-0.1.0.tar.gz
  • Upload date:
  • Size: 49.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: Hatch/1.16.5 cpython/3.14.3 HTTPX/0.28.1

File hashes

Hashes for stylint-0.1.0.tar.gz
Algorithm Hash digest
SHA256 56cbcecc5fe78f48eb18836d964ae30c5d686cc7e480813e53cfae7353c7741a
MD5 d8bf62b448d8fae3e2fb9e94234c1136
BLAKE2b-256 f40d6b8791dccf901d49c50389091350d090bd7820a4e7873eabae265325b4b0

See more details on using hashes here.

File details

Details for the file stylint-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: stylint-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 47.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: Hatch/1.16.5 cpython/3.14.3 HTTPX/0.28.1

File hashes

Hashes for stylint-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 429ab0332e6a4224942b9a7c422e73c394d090041c52330b5a4186e47d2f76cc
MD5 a062463f9494fac283d05f26b7b88b10
BLAKE2b-256 3f981ad1ba2599b6f694215691249dbf9f476439c2eddb2e6f91a85e763ea4c7

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