Skip to content

About

Keep the agent honest: a plain-English linter for Markdown prose and code comments.

Topics

Resources

Contributing

Stars

14 stars

Watchers

0 watching

Forks

Latest commit

 

History

668 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CI Gem Version License: MIT

simple_english

Write plain English for humans. Keep the agent honest.

You can teach an agent plain-English prose with a skill or a prompt. It writes clean prose in the first paragraph and slop again by the third. This linter keeps the agent honest: it reads what the agent wrote and names every broken rule. Send the findings back, and the agent corrects its own text.

Before After

Your API keys are rotated by the service every 90 days — you will need to update them if you get a 401; don't panic, simply re-run the setup command, which regenerates the credentials without any downtime.

The service rotates your API keys every 90 days. If you get a 401, update them. Do not panic. Re-run the setup command. It regenerates the credentials without any downtime.

The linter covers Markdown prose plus code comments in Python, Ruby, JavaScript, TypeScript, Go, Rust, Java, C, C++, and C#. It also reads Objective-C, Kotlin, Swift, Scala, Dart, PHP, Perl, Elixir, Lua, R, Haskell, Zig, SQL, and bash. It reads comments in config formats and markup as well: YAML, TOML, INI, Makefile, Dockerfile, HTML, and XML. Findings print as plain text, JSON, or SARIF.

If your language is not on the list, open an issue.

Agent integrations

Plug the linter into your coding agent. It writes plain English on the first pass and checks its own draft before you read the file.

Coding agent Ships as Docs
pi npm package pi-simple-english README
claude-code plugin simple-english README
codex plugin simple-english README
cursor plugin folder simple-english README
opencode plugin simple-english README
vscode-copilot config wiring README

If your agent is not on the list, open an issue.

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.

Install

Prerequisites

The gem needs Ruby 3.3 or newer. On a Mac, the system Ruby (2.6) is too old. Install one, for example with Homebrew:

brew install ruby

Homebrew's Ruby is not on the PATH by default, and neither are gem binaries. Put both in your shell profile:

echo 'export PATH="/opt/homebrew/opt/ruby/bin:$PATH"' >> ~/.zshrc
echo 'export GEM_HOME="$HOME/.gem"' >> ~/.zshrc
echo 'export PATH="$GEM_HOME/bin:$PATH"' >> ~/.zshrc

Then open a new terminal. A version manager such as mise or rbenv works too.

Ruby gem (macOS arm64, Linux x86-64 and arm64)

gem install simple_english

Homebrew (macOS arm64)

brew install TonyCTHsu/tap/simple-english

Container

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

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

MCP server

Start an MCP stdio server with one tool, lint. Any MCP client can call it.

se mcp

CI

Gate the pull request's prose in GitHub Actions. The lint fails the build on findings:

name: lint-docs
on: [pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: TonyCTHsu/simple-english@v0.13.1
        with:
          files: |
            docs/
            README.md

To see the findings inline in the pull request, lint with format: sarif and upload the report. Add permissions: {security-events: write} to the workflow:

      - uses: TonyCTHsu/simple-english@v0.13.1
        with:
          format: sarif
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: simple-english.sarif
        if: always()

Any CI can run the lint. Install the gem, run se lint ., and fail the build on exit code 1.

Git hooks

Install the gem once, then wire your hook runner. The command to run is se lint.

Pre-commit framework

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/TonyCTHsu/simple-english
    rev: v0.13.1
    hooks:
      - id: se-lint

For Husky with lint-staged, map every staged file to se lint. For Lefthook, run se lint {staged_files} in a pre-commit command.

Config and suppressions

Config file

.simple-english.yml in the working directory:

ignore:
  - vendor/**
disabled-rules:
  - SE_NO_EMDASH
rule-ignores:
  SE_MODAL_RESTRICTED:
    - .agents/skills/**

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

disabled-rules drops a rule from every file. rule-ignores drops a rule only on the listed paths. The rule still fires everywhere else. It covers prose that plays by different rules, like spec text that quotes a standard's "SHOULD":

rule-ignores:
  SE_MODAL_RESTRICTED:
    - .agents/skills/**

With that config, SE_MODAL_RESTRICTED fires in docs/ and the README, and stays quiet under .agents/skills/.

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

Why not Vale?

We ported 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. Closing the Vale gaps needs scripts or an external tagger, and that erases Vale's main advantage: one binary with no service behind it.

Compliance

This tool does not check ASD-STE100 compliance. This repo holds no ASD-STE100 text. For full compliance, read the free standard at https://www.asd-ste100.org/.

The tool checks no external style standard. The rules are this repo's own, but they draw on the GOV.UK A to Z style guide and the US plain language guide.

The rule sources are rules/simple-english.xml for pattern rules and lib/simple_english/lint/counts.rb for counting rules. The full list sits in docs/RULES.md.

Develop

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

About

Keep the agent honest: a plain-English linter for Markdown prose and code comments.

Topics

Resources

Contributing

Stars

14 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages