0.0
The project is in a healthy, maintained state
Pattern rules run on LanguageTool, counting rules in Ruby. Lints Markdown and code comments.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

>= 0
~> 6.0
~> 13.0
>= 0
~> 3.0

Runtime

 Project Readme

CI Gem Version License: MIT

simple_english

Write for human readers, not for reviewers or another AI.

AI writes your docs and code comments in seconds. This linter cuts the slop it leaves behind. Every finding says what to write instead.

$ printf "The config was written by setup — don't edit it; the daemon caches rules, making the first lint slow." > note.md
$ se note.md
note.md:1:12-26: [SE_ACTIVE_VOICE] "was written by" - Use the active voice. Say who does the action.
note.md:1:73-81: [SE_ING_AFTER_COMMA] ", making" - Start a new sentence instead of the -ing phrase.
note.md:1:37-40: [SE_NO_CONTRACTIONS] "n't" - Write the words in full. No contractions.
note.md:1:33-34: [SE_NO_EMDASH] "—" - Write two sentences, or use a comma.
note.md:1:48-49: [SE_NO_SEMICOLON] ";" - Write two sentences, or name the relation.

Markdown prose plus code comments in Python, Ruby, JavaScript, TypeScript, Go, Rust, Java, C#, C++, Kotlin, bash, and YAML. Output as plain text, JSON, or SARIF.

The rules

  • Voice: say who does the action.
  • Tense: simple tenses only, no present perfect.
  • Modals: can, will, must only.
  • Punctuation: no em-dashes, no semicolons.
  • Contractions: write every word in full.
  • Sentence shape: condition before command, no -ing phrase after a comma.
  • Word choice: about 50 substitution rules, from leverage to in conclusion. make sure that keeps its "that".
  • Code comments: same pattern rules, with line and column range.
  • Counts (Markdown only): 20 words per sentence in list items, 25 in paragraphs, six sentences per paragraph at most.

The full list, with a wrong and a right example for each rule: docs/RULES.md.

Agent integrations

The linter runs as an MCP server: se mcp. It exposes one tool, lint. Any MCP client can call it.

Agents that write the prose get the rules closer to hand. One skill, simple-english-lint, ships inside every adapter. It tells the agent to lint what it writes and fix every finding.

The adapters:

Adapter Install command
pi pi install npm:pi-simple-english
Claude Code claude plugin marketplace add TonyCTHsu/simple-english
Codex codex plugin marketplace add TonyCTHsu/simple-english

Each adapter has its own README with scopes and prerequisites. See docs/DEVELOPMENT.md for the full story.

Install

Requirement: Ruby 3.3 or newer on macOS 12 or newer (arm64), or Linux x86-64 or Linux arm64 with glibc 2.35 or newer.

Homebrew (macOS)

brew install TonyCTHsu/tap/simple-english
brew services start simple-english

Homebrew installs the CLI as one gem, with the lint engine inside it. The service keeps a background daemon running. Run brew services stop simple-english to stop it.

Ruby gem (supported platforms and CI)

gem install simple_english
se README.md

Docker (no Ruby needed)

docker run --rm -v "$PWD":/work ghcr.io/tonycthsu/simple-english:latest .

The image holds the CLI and the lint engine. It lints the mounted directory, then exits. Pin the tag to a version for CI, like ghcr.io/tonycthsu/simple-english:v0.5.0. The image ships for Linux x86-64 and Linux arm64. On an Apple Silicon Mac, Docker runs the arm64 image. There is no install for Windows or Intel Macs on any channel.

Usage

Lint files, directories, or stdin:

se README.md
se docs/            # every .md, .py, .rb, .yaml, .yml, ... under docs/
se - < notes.md     # stdin (Markdown)

Lint only what changed:

git diff --name-only --diff-filter=ACM main | xargs -I{} se {}

Outputs

Pattern findings print an exclusive column range as file:line:start-end: [RULE_ID] message. A range that crosses lines ends with end-line:end-column. Columns count UTF-16 code units. Counting findings identify only the paragraph's first line. JSON and SARIF output expose the same source ranges:

se --format json docs/
se --format sarif src/ > results.sarif

Exit codes

  • 0: no findings
  • 1: findings
  • 2: input, configuration, installation, or daemon error

CI

Gate the prose in the pull request that changes it. The plain run fails the build on findings, and the SARIF report puts them inline:

name: lint-docs
on: [pull_request]
permissions:
  security-events: write
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: ruby/setup-ruby@v1
      - run: gem install simple_english
      - run: se --format sarif . > lint.sarif
      - run: se .
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: lint.sarif
        if: always()

The daemon

The first lint starts a background daemon. Later lints use it. A lint after a gem update prints a warning. Run se serve --detached once. It stops the old daemon and starts the new one.

Config and suppressions

Config file

.simple-english.yml in the working directory:

ignore:
  - vendor/**
disabled-rules:
  - SE_NO_EMDASH

ignore globs: ** crosses directories, * stays in one segment.

Inline suppressions

A line containing se: ignore suppresses findings reported on that line. Use se: ignore=RULE1,RULE2 to scope it to rules. Pattern findings cite the line of the match, so put the directive on the line the finding reports.

In Markdown:

The daemon keeps it's own lock. <!-- se: ignore=SE_NO_CONTRACTIONS -->

In a code comment:

# Don't touch this constant. se: ignore=SE_NO_CONTRACTIONS

FAQ

Have you thought about an agent skill?

Every adapter in Agent integrations bundles one: the simple-english-lint skill rides with the linter, so an agent drafts and lints with the same rules. The SimpleEnglish project ships a standalone skill for hosts without an adapter. If you like, use both: the skill helps the first draft, and the linter catches what the agent missed.

Why not Vale?

We ported all 67 rules to Vale and ran the corpus on both engines. About 60 rules behave the same. Vale has no check for the em-dash and semicolon rules: its checks see words, not punctuation. Its tagger also mislabels verbs, so the condition-first rule stays silent. The rules here include examples that CI verifies. Closing the Vale gaps needs scripts or an external tagger, and that erases Vale's main advantage: one binary with no service behind it.

Scope

The rule set comes from the Plain-mode rules of the MIT-licensed SimpleEnglish project. This tool does not check ASD-STE100 compliance. This repo holds no ASD-STE100 text. If you need full compliance, read the free standard at https://www.asd-ste100.org/.

Develop

To change the linter, add rules, or run the tests, read docs/DEVELOPMENT.md.