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
stylint --print-style-guide voice

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, or stylint --print-style-guide voice to print one document.
  • 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. Run stylint --print-style-guide NAME to print one document directly.

  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.1.tar.gz (49.5 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.1-py3-none-any.whl (47.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: stylint-0.1.1.tar.gz
  • Upload date:
  • Size: 49.5 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.1.tar.gz
Algorithm Hash digest
SHA256 07429af279f9dc383199000fcfeece121ed3a6f84d1d44c0dcf8727319bc481c
MD5 0ce575808366e66f21f6c1e718d2db5d
BLAKE2b-256 39ce1c663b90c2c5050934cb360c44c99fea48659b4675ecc96502805de5e21e

See more details on using hashes here.

File details

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

File metadata

  • Download URL: stylint-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 47.5 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c6ba5a9f94de829c79b21349db52226576d0f109017b597b6ac3505342871eb0
MD5 4fa21097f43ed08251913c8bce5227ae
BLAKE2b-256 0b255a402b77089be075adc0970e7684ab5706ed856d928ec976394e548e486e

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