agent-simple-english0.8.0
Technical and house-style English lint engine, CLI, and host adapters
Works on omp
1 of 1 report says this package works on omp.
1 works0 broken
Reports 1
- works
Automated load test on omp 18.12.3: `omp plugin install` succeeds and the package loads with no errors. It adds commands /ase; tools ast_edit, debug, lsp. Runtime behavior was not exercised.
Readme
agent-simple-english
agent-simple-english checks rules derived from ASD-STE100 Simplified Technical English (STE) and two enabled house-style rules.
It supplies one Engine, one CLI, a pi Adapter, and a Claude Code Adapter.
The Claude Code plugin uses CLI Hook mode to enforce the same rules.
This package reports deterministic writing problems. It does not rewrite text because an automatic rewrite can change its meaning.
What the pi Adapter does
The pi Adapter adds its active writing rules to the model prompt before each agent turn. It then applies the rules at three layers.
Write and edit gate. The extension checks prose before the
writeoredittool changes a file. A hard violation blocks the tool. A soft violation gives the model a warning but lets the tool change the file. An edit reports only new violations, so an old violation does not block an unrelated edit.Commit-message gate. The extension checks static messages in detected
git commit -mandgit commit --messagecommands. A hard violation blocks the command before the shell starts. Conventional Commit prefixes and final trailer lines do not form part of the check. A detected commit without an available static message fails closed.Reply check. Normal mode checks each final assistant reply and shows the hard and soft counts in a widget. The model receives hard violation details before its next call. Normal mode does not hide or change the reply. Strict mode makes the model send each reply through the
saytool. A strict reply stays hidden until it has no hard violations.
The pi Adapter checks Markdown prose and source comments according to the content kinds. It gives the line, column, rule ID, and suggested correction for a blocked tool call. A config, dictionary, or rule-data load error makes enabled write, edit, and commit gates fail closed.
Install the Claude Code Adapter
Install Claude Code and Bun first. The plugin needs Claude Code 2.1.296 or later. Then use the standard local plugin flow:
claude plugin marketplace add jyooi/agent-simple-english
claude plugin install simple-english@agent-simple-english
Start a new Claude Code session after installation.
Bun installs the plugin dependencies when the first hook starts.
You do not need a global simple-english package or manual hook settings.
The repository supplies the local marketplace manifest.
Marketplace publication is outside this package release.
At SessionStart, the Adapter loads the merged config for an enabled session.
It reads the config from the session working directory and adds the active writing-rule summary to context.
The summary honors hard, soft, and off rule settings plus maxSentenceWords.
The PreToolUse gate checks Write, Edit, and Bash events.
A hard write or edit violation blocks the tool and returns its location, rule ID, and suggested correction.
The Adapter checks only new edit violations, so old prose does not block an unrelated edit.
Correct the text and retry the tool.
A clean retry succeeds.
For Bash, the gate checks static messages in detected git commit commands.
It blocks a hard violation before Git starts.
It also blocks a detected commit without a static -m or --message argument.
A compliant static message passes.
Soft violations allow the event and add warning text.
Session controls
The /ase command controls the current Claude Code session.
New sessions start in enabled mode without a strict reply gate.
A change in one session does not change a parallel session.
The full command name is /simple-english:ase, and it always runs.
The short name /ase runs the same command.
If a user command or a project command has the name ase, that command takes the short name.
Then use the full name.
| Command | Result |
|---|---|
/ase on |
Enable write, edit, commit, and reply checks. |
/ase off |
Disable all checks and leave strict mode. |
/ase status |
Show the mode, rule counts, and dictionary state. |
/ase strict |
Enable the strict reply gate and all other checks. |
/ase strict off |
Disable the strict reply gate and use reply feedback. |
/ase explain |
Write the last reply again in Simplified Technical English. |
Claude Code shows argument suggestions while you type.
Type /ase, press Tab to select the command, then type the first letters of an argument.
The list shows each argument that starts with those letters, and Tab completes the first one.
After strict, the list suggests off.
Strict reply hold
Strict mode holds each reply of the main conversation until the reply check completes. Claude Code shows no reply text during the hold. A reply with no hard violation then shows unchanged. For a reply with a hard violation, Claude Code shows one notice line in place of the text. The model then gets the held text and the violation details, and it writes the reply again. The Adapter holds and checks that rewrite the same way.
The model gets a maximum of two rewrites in one turn. The sequence is: the Adapter blocks the first reply, the Adapter blocks the first rewrite, and Claude Code shows the second rewrite. The second rewrite shows as the model wrote it, also when it has a hard violation.
A step that has text and a tool call uses the same hold, and the tool runs. For such a step with a hard violation, Claude Code shows the notice line, and the Adapter keeps the text. The Adapter blocks only when the last reply of the turn has a hard violation. The model then also gets the kept text and the violation details of the earlier steps. If the last reply has no hard violation, the Adapter does not block, and the reply shows one time. The kept text of a step with a tool call does not use one of the two rewrites. The hold does not apply to subagent replies. If the reply check fails, the Adapter shows the reply and adds one warning line. After an interrupt during the hold, Claude Code shows no text for that reply.
The hold has these limits:
- Strict mode shows no streamed text, because a held reply appears only when it is complete.
- The transcript file contains the held text in the feedback message for the model.
The
stream-jsonoutput ofclaude -palso contains that message. - A
Stophook from another source does not run for a blocked reply. It runs for the reply that the user sees.
The Stop command hook stays as a second layer.
It blocks one Stop event when a reply with a hard violation showed after a failed reply check.
Reply feedback loop
In enabled non-strict mode, the Stop hook checks each finished assistant reply after Claude Code shows it.
It records only hard violation details in a state file for that Claude Code session.
The Stop hook does not block or change the reply in this mode.
At the next UserPromptSubmit event, the Adapter adds the pending feedback to the model context.
The feedback gives the line, column, rule ID, and suggested fix for each hard violation.
The Adapter then clears the pending feedback, so it adds each report only one time.
The session state retains the processed reply identity and ignores duplicate Stop events.
Clean and soft-only replies leave no pending feedback.
Each session has a separate state file under $XDG_STATE_HOME/simple-english/sessions.
The default state directory is ~/.local/state/simple-english/sessions.
The file stores the session mode, reply identity, and pending feedback.
Concurrent sessions in one project do not read or clear state from another session.
Install the pi Adapter
Install pi first. Then use the pi package mechanism:
pi install npm:agent-simple-english
Pi records the package in its user settings and loads the extension in each session. Pi packages run with your user permissions, so inspect third-party package code before installation.
Use the checkout without a persistent installation during development:
git clone https://github.com/jyooi/agent-simple-english.git
cd agent-simple-english
bun install
pi -e .
Extension commands
The mode applies to the current pi session.
The extension starts in enabled mode without strict reply gating.
Type /ase to see autocomplete suggestions for on, off, status, strict, and strict off.
The list changes to match the text that you type.
| Command | Result |
|---|---|
/ase |
Toggle all enforcement. |
/ase on |
Enable write, edit, commit, and reply checks. |
/ase off |
Disable all checks and leave strict mode. |
/ase status |
Show the mode, severity counts, and dictionary state. |
/ase strict |
Enable strict reply gating and the other checks. |
/ase strict on |
Enable strict reply gating and the other checks. |
/ase strict off |
Disable strict reply gating without changing the other checks. |
Install and use the CLI
The standalone command needs Bun.
Install it from the same npm package.
The package is agent-simple-english and the command it installs is simple-english:
bun add --global agent-simple-english
Claude Code Hook mode
The hook subcommand reads one Claude Code hook event from standard input.
It writes one hook result as JSON.
A SessionStart event returns the active rule summary as added context.
A PreToolUse event applies the write, edit, and commit gates that the plugin registers.
A Stop event records hard reply feedback in enabled non-strict mode.
In strict mode, it blocks a reply that has hard violations.
A UserPromptSubmit event adds pending feedback to context and clears that feedback.
Every hook reads the current session mode before it applies a gate.
Malformed JSON returns a non-blocking error so Claude Code can continue.
The hook also allows the event when configuration, dictionary, rule-data, tagger, transcript, state, or file processing fails.
It adds warning text for these operational failures.
Observation-write failures are silent and do not change the hook output or decision.
Claude Code reply check
The reply-check subcommand reads one JSON object from standard input.
The object has the session_id, cwd, and text fields.
The subcommand checks the text as a reply with the config of that working directory.
It writes one JSON object, which has a feedback field only when the text has a hard violation.
It also records one denied reply Observation for that text.
An input or load error causes exit code 2.
The hooks module of the plugin runs this subcommand for the strict reply hold.
Review hook observations
Enabled Hook mode logs every write, edit, static commit-message, and reply lint decision to the local XDG state directory.
Observation logging is on by default, and the log includes clean allows and soft Findings.
Plain lint runs, disabled hook sessions, skipped paths, and the pi Adapter do not write Observations.
Set SIMPLE_ENGLISH_OBSERVE=0 to stop observation logging.
Monthly Observation files use $XDG_STATE_HOME/simple-english/observations/YYYY-MM.jsonl.
The default base directory is ~/.local/state.
Each Finding stores its offending snippet for later review.
Verdicts use the separate $XDG_STATE_HOME/simple-english/verdicts.jsonl file.
These global, host-local records can contain snippets, working directories, and file paths.
New state directories use mode 0700, and new JSONL files use mode 0600.
Each record uses one append-mode write without a lock, so concurrent hooks can safely add complete lines.
Review each unjudged Finding:
simple-english observe review
Press t for a true positive or f for a false positive.
Press s to leave a Finding unjudged or q to quit.
A Verdict can include an optional note.
A false positive exists only after a human records that Verdict.
The latest Verdict for a Finding wins.
Show fire counts, judged counts, and false-positive rates for each rule:
simple-english observe stats
The report also shows total Observations and clean allows.
Lint files and standard input
Lint one or more files:
simple-english README.md
simple-english README.md src/cli/main.ts
Lint standard input when no file path is present:
printf 'Open the valve.\n' | simple-english
Lint a commit message with an explicit content kind:
git log -1 --format=%B | simple-english --kind commit-message
Request JSON output:
simple-english --json README.md
Use one explicit config file instead of discovered config files:
config_file="$(mktemp)"
printf '%s\n' '{"maxSentenceWords":25}' > "$config_file"
simple-english --config "$config_file" README.md
rm "$config_file"
The command prints each violation with its file, line, column, severity, rule ID, and message. Exit code 0 means that no hard violation exists. Exit code 1 means that at least one hard violation exists. Exit code 2 means that an argument, input, or config error occurred. Soft violations can appear with exit code 0.
CLI flags
--jsonwrites one JSON report withviolations,summary, andskippedfields. Theskippedarray is always present, and it is empty when no input was skipped. Each violation includes its offending sentence or paragraph assnippet.--config <path>uses only that config file and disables config discovery.--kind <kind>sets one content kind for all inputs. Valid values areprose-file,slash-source,hash-source,html, andcommit-message. The form--kind=<kind>also works.--helpwrites the command usage.--versionwrites the package version.The command rejects an unknown flag as an argument error.
A path of
-reads standard input. With no paths, the command also reads standard input.
Content kinds
prose-file checks all prose in a file.
It is the default for standard input, extensionless paths, and file types that have no source mapping.
slash-source checks comments in these file types:
.ts, .tsx, .js, .jsx, .mjs, .cjs, .go, .rs, .java, .c, .h, .cpp, .hpp, .cc, .cs, .swift, .kt, and .scala.
hash-source checks comments in these file types:
.sh, .bash, .zsh, .py, .rb, .yaml, .yml, .toml, and .pl.
html checks the text nodes of .html and .htm files.
It reads the page as a reader sees it, and it ignores everything else.
commit-message checks the complete input as a commit message.
A skipped path carries no check.
These extensions skip every check: .css, .scss, .less, .json, .jsonc, .svg, .xml, .typ, .csv, .tsv, and .lock.
An extensionless path still uses prose-file.
The Claude Code hook and the pi write and edit gates allow a skipped path with no lint.
The CLI prints one line that names a skipped file and exits 0.
An explicit --kind flag forces a lint on a skipped file.
The html kind
The html kind parses the page and keeps only its text nodes.
It drops tags, attribute values, comments, entity references, and doctype declarations.
It also drops the content of script, style, pre, code, and textarea.
A semicolon in a style rule or an inline script stays quiet.
A semicolon in a paragraph reports a violation at its original column.
Each prose block is one unit for the sentence rules and the paragraph rule. A sentence split across phrasing elements counts as one sentence. These phrasing elements stay inside their prose block:
a abbr b bdi bdo cite code data del dfn em i img ins kbd label mark q rp rt
ruby s samp small span strong sub sup time u var wbr
Every other element starts a new prose block. That rule separates each of these elements from its neighbours:
p li h1 h2 h3 h4 h5 h6 td th blockquote figcaption dt dd title br
Bare text directly inside div, section, or body is a prose block of its own.
An unknown element also starts a prose block.
File extension matching does not depend on letter case. Source kinds ignore comment markers inside supported string literal forms. All kinds preserve the original line and column. They ignore identifiers, YAML frontmatter, valid GFM tables, and fenced, indented, and inline Markdown code.
Inline suppression
A suppression directive names one or more registered rule IDs and applies only to the next physical line. Use this comment form in prose files and in HTML pages:
<!-- ste-disable-next-line marketing -->
The robust estimator uses this sample.
Use the matching plain comment form in slash and hash source files:
// ste-disable-next-line marketing
// The robust estimator uses this sample.
# ste-disable-next-line marketing
# The robust estimator uses this sample.
Separate multiple rule IDs with whitespace.
Commas are also accepted as separators.
A missing or unknown rule ID produces an invalid-suppression violation, which is hard by default.
Directive recognition uses lightweight per-language comment extraction rather than full language parsers.
Known accepted losses are JavaScript regular expressions inside template expressions, Ruby and Perl regular-expression literals containing directive text, Perl <<~ heredocs, adjacent multiline YAML flow scalars, and compact nested YAML block scalars with explicit indentation.
Engine fixtures pin these limitations pending per-language lexers.
Configuration
Configuration is an optional JSON object.
The project file is .simple-english.json at the repository root.
The global file is $XDG_CONFIG_HOME/simple-english/config.json.
If XDG_CONFIG_HOME is unset or not absolute, the global path is ~/.config/simple-english/config.json.
Global and project files merge key by key, including the nested rules and ruleDataExtensions objects.
Global values load first, and project values have precedence.
The pi Adapter reads the project file only when pi trusts the project.
The --config flag uses only its named file.
Existing pi config paths remain as fallbacks.
The project fallback is .pi/simple-english.json.
The global fallback is simple-english.json in the pi agent config directory.
The default pi agent config directory is ~/.pi/agent.
PI_CODING_AGENT_DIR can change that directory.
The loader reads a fallback file only when the new file at the same level is absent.
This example contains every config key and every rule:
{
"maxSentenceWords": 25,
"exemptBlockQuotes": true,
"approvedWordsPath": "./approved-words.json",
"ruleDataExtensions": {
"phrasal-verb": ["config/phrasal-verbs.json"],
"hedging": ["config/hedging.json"],
"marketing": ["config/marketing.json"],
"adjectival-participle": ["config/adjectival-participles.json"]
},
"rules": {
"contraction": "hard",
"dictionary-not-approved-word": "hard",
"hedging": "soft",
"invalid-suppression": "hard",
"marketing": "soft",
"paragraph-length": "hard",
"phrasal-verb": "hard",
"semicolon": "hard",
"sentence-length": "hard",
"verb-progressive": "hard",
"verb-passive": "soft",
"verb-perfect": "hard"
}
}
Each rule setting accepts hard, soft, or off.
A hard violation fails the CLI and blocks a gated extension action.
A soft violation produces a report or warning but does not block the action.
The off value disables that rule.
maxSentenceWords must be a positive integer and has a default value of 25.
exemptBlockQuotes must be a boolean and has a default value of false.
When it is omitted or false, block quotes receive the same checks as other prose.
When it is true, CommonMark block quote content is exempt from dictionary-not-approved-word, contraction, phrasal-verb, hedging, and marketing checks.
All other rules continue to check block quote content.
approvedWordsPath selects a user-owned approved-word list.
The loader resolves a relative path from the working directory that requested the config.
This list replaces the bundled not-approved sample and any SIMPLE_ENGLISH_DICTIONARY replacement.
A missing, unreadable, or invalid list causes lint exit code 2.
The enabled pi Adapter gates fail closed after this error.
Claude Code Hook mode reports a warning and allows the event, as it does for other load errors.
Unknown keys, unknown rule IDs, and invalid values cause a config error.
ruleDataExtensions maps each list-backed data set to more JSON data files.
The loader adds entries from these files after the bundled entries.
The loader resolves relative paths from the current working directory.
Each file must use the package dictionary data format.
An adjectival-participle extension adds exact tagged-token forms that the progressive and passive rules allow after a form of be.
A lint command reports an extension load error and continues with the bundled rule data.
The enabled pi Adapter fails closed after that error, while Claude Code Hook mode allows the event and adds a warning.
Rule reference
Rules derived from ASD-STE100
contraction
Default: hard.
Reports apostrophe contractions such as forms that end in n't, 're, 've, 'll, 'd, or 'm.
It also reports unambiguous forms that end in 's.
dictionary-not-approved-word
Default: hard. Without an approved-word list, this rule reports forms from the bundled not-approved sample and supplies approved alternatives. Part-of-speech data limits applicable sample entries when that data exists. With an approved-word list, this rule reports each prose token that the list does not contain. Matching uses exact surface forms without regard to case. See Configuration for list selection, precedence, and load errors.
paragraph-length
Default: hard.
Reports a prose paragraph that has more than six sentences.
Markdown block boundaries and list items start separate paragraphs.
An ordered list item starts a list with any start number, also directly below a paragraph line.
CommonMark keeps such a line in the paragraph when the number is not 1.
Sentence counts use the sentence-length segmentation rules.
phrasal-verb
Default: hard.
Reports listed forms and supplies their suggestion.
The bundled forms and suggestions are in src/dictionary/data/phrasal-verbs.json.
Matching does not depend on letter case. A phrase match stays on one source line.
semicolon
Default: hard. Reports each prose semicolon and asks for two sentences.
sentence-length
Default: hard.
Reports a sentence above maxSentenceWords.
The default maximum is 25 words.
Sentence segmentation recognizes only e.g., i.e., etc., vs., Fig., No., and a single capital letter followed by a period as abbreviation forms.
It keeps an abbreviation in the current sentence when lowercase prose follows it.
It preserves a sentence boundary before capitalized prose, except after a single capital letter or before a short Fig. or No. designator such as Fig. A or No. 7.
Markdown block boundaries stop abbreviation lookahead.
The start of a list item ends the sentence before it.
verb-progressive
Default: hard.
Reports a form of be followed by an -ing verb, with optional adverbs or not between them.
verb-passive
Default: soft.
Reports a form of be followed by a past participle, with optional adverbs or not between them.
Before either rule reports a finding, it checks the exact tagged token against the bundled adjectival-participle allowlist.
The ruleDataExtensions.adjectival-participle configuration extends that allowlist.
verb-perfect
Default: hard.
Reports auxiliary have followed by a past participle, with optional adverbs or not between them.
The three verb rules and applicable dictionary entries use the bundled English part-of-speech tagger.
Directive validation
invalid-suppression
Default: hard. Reports a suppression directive that has no rule ID or names an unknown rule ID.
House-style rules
These rules define package house style. They do not come from ASD-STE100. The default config enables both rules.
hedging
Default: soft.
Reports the phrases in src/dictionary/data/hedging.json.
A phrase match stays on one source line.
marketing
Default: soft.
Reports complete listed forms from src/dictionary/data/marketing.json.
A multi-word form stays on one source line.
Matching does not depend on letter case, and the rule also reports the first listed single-token component of a hyphenated token.
Dictionary and attribution
The package vendors dictionary data converted from Cameron Moore's MIT-licensed ctotheameron/pi-ste.
THIRD_PARTY_NOTICES.md records the pinned source, conversion scope, and license details.
ASD owns ASD-STE100 and limits redistribution of the official specification and dictionary. This package does not include the official specification or the complete ASD dictionary. Get the current official specification from the ASD-STE100 site. This project has no affiliation with or endorsement from ASD.
The package dictionary format and token rules are in src/dictionary/README.md.
To create an approved-word list, use your own licensed copy of the current ASD-STE100 specification. Extract the approved words into the package approved-word list format. Follow that format's exact surface-form requirements. Add source metadata that identifies your licensed revision and extraction record. Repeat the extraction and update the metadata when you adopt a new licensed revision. Keep the list private unless your license lets you distribute it. This package does not extract, include, or distribute that data. See Configuration to select the list.
Set SIMPLE_ENGLISH_DICTIONARY to replace only the bundled not-approved sample.
Hook mode resolves a relative replacement path from the session working directory.
A lint command reports a replacement dictionary load error and continues with all other rules.
The enabled pi Adapter fails closed after that error.
Development
bun install
bun run test
bun run bench:markdown
bun run lint
bun run typecheck
npm publish --dry-run
Actual npm publishing is a separate release action.