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 |
|---|---|
|
|
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.
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.
- Voice: say who does the action.
- Tense: simple tenses only, no present perfect.
- Modals:
can,will,mustonly. - Punctuation: no em-dashes, no semicolons.
- Contractions: write every word in full.
- Sentence shape: condition before command, no
-ingphrase after a comma. - Word choice: about 50 substitution rules, from
leveragetoin conclusion.make sure thatkeeps 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.
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 rubyHomebrew'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"' >> ~/.zshrcThen open a new terminal. A version manager such as mise or rbenv
works too.
gem install simple_englishbrew install TonyCTHsu/tap/simple-englishdocker run --rm -v "$PWD":/work ghcr.io/tonycthsu/simple-english .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 {}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.sarif0: no findings1: findings2: input, configuration, installation, or daemon error
Start an MCP stdio server with one tool, lint. Any MCP
client can call it.
se mcp
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.mdTo 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.
Install the gem once, then wire your hook runner. The command to run
is se lint.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/TonyCTHsu/simple-english
rev: v0.13.1
hooks:
- id: se-lintFor Husky with lint-staged, map every staged file to se lint. For
Lefthook, run se lint {staged_files} in a pre-commit command.
.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/.
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_CONTRACTIONSWe 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.
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.
To change the linter, add rules, or run the tests, read docs/DEVELOPMENT.md.